feat: add full dashboard monitoring + FEATURES.md documentation

- Create docs/FEATURES.md with complete feature reference (14 entry
  filters, 12 exit conditions, backtest history, risk modes, session
  rules, auto-trainer, active components table, architecture diagram)

- Extend main_live.py _write_dashboard_status() with 10 new data
  sections: entryFilters, riskMode, cooldown, timeFilter,
  sessionMultiplier, positionDetails, autoTrainer, performance,
  marketClose, h1BiasDetails. Add filter tracking at each checkpoint
  in _trading_iteration() and 7 helper methods.

- Add 9 TypeScript interfaces and extend TradingStatus in trading.ts

- Create BotStatusCard (risk mode, cooldown bar, AUC, uptime, market
  close) and EntryFilterCard (14 filters with pass/block/skip icons)

- Enhance SessionCard (lot multiplier badge + time filter status),
  RiskCard (risk mode badge + total loss progress bar), PositionsCard
  (expandable per-position details with momentum, TP probability)

- Update page.tsx layout: BotStatusCard replaces SettingsCard in Row 2,
  EntryFilterCard added to Row 3 sidebar

- Add API defaults for all new fields

Dashboard now monitors 100% of bot features. Verified: Next.js build
0 errors, bot + API + dashboard all run clean, Docker rebuilt OK.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
GifariKemal
2026-02-08 13:45:31 +07:00
co-authored by Claude Opus 4.6
parent cb41bfe5ba
commit 61877480b3
11 changed files with 1056 additions and 47 deletions
+10
View File
@@ -54,6 +54,16 @@ DEFAULT_STATUS = {
"regime": {"name": "", "volatility": 0.0, "confidence": 0.0},
"positions": [],
"logs": [],
"entryFilters": [],
"riskMode": {"mode": "unknown", "reason": "", "recommendedLot": 0, "maxAllowedLot": 0, "totalLoss": 0, "maxTotalLoss": 0, "remainingDailyRisk": 0},
"cooldown": {"active": False, "secondsRemaining": 0, "totalSeconds": 150},
"timeFilter": {"wibHour": 0, "isBlocked": False, "blockedHours": [9, 21]},
"sessionMultiplier": 1.0,
"positionDetails": [],
"autoTrainer": {"lastRetrain": None, "currentAuc": None, "minAucThreshold": 0.65, "hoursSinceRetrain": 0, "nextRetrainHour": 5, "modelsFitted": False},
"performance": {"loopCount": 0, "avgExecutionMs": 0, "uptimeHours": 0, "totalSessionTrades": 0, "totalSessionProfit": 0},
"marketClose": {"hoursToDailyClose": 0, "hoursToWeekendClose": 0, "nearWeekend": False, "marketOpen": False},
"h1BiasDetails": {"bias": "NEUTRAL", "ema20": 0, "price": 0},
}
+20 -8
View File
@@ -12,7 +12,8 @@ import {
PositionsCard,
LogCard,
PriceChart,
SettingsCard,
BotStatusCard,
EntryFilterCard,
} from "@/components/dashboard";
import { Skeleton } from "@/components/ui/skeleton";
@@ -121,6 +122,8 @@ export default function Dashboard() {
session={data.session}
isGoldenTime={data.isGoldenTime}
canTrade={data.canTrade}
sessionMultiplier={data.sessionMultiplier}
timeFilter={data.timeFilter}
/>
</div>
<div className="min-w-0 overflow-hidden">
@@ -129,11 +132,12 @@ export default function Dashboard() {
dailyProfit={data.dailyProfit}
consecutiveLosses={data.consecutiveLosses}
riskPercent={data.riskPercent}
riskMode={data.riskMode}
/>
</div>
</div>
{/* ── Row 2: Signals ── */}
{/* ── Row 2: Signals + Bot Status ── */}
<div
className="grid gap-1.5 overflow-hidden"
style={{ gridTemplateColumns: 'repeat(4, minmax(0, 1fr))' }}
@@ -171,11 +175,13 @@ export default function Dashboard() {
/>
</div>
<div className="min-w-0 overflow-hidden">
{data.settings ? (
<SettingsCard settings={data.settings} />
) : (
<div className="glass rounded-lg h-full" />
)}
<BotStatusCard
riskMode={data.riskMode}
cooldown={data.cooldown}
autoTrainer={data.autoTrainer}
performance={data.performance}
marketClose={data.marketClose}
/>
</div>
</div>
@@ -188,8 +194,14 @@ export default function Dashboard() {
<PriceChart data={data.priceHistory} />
</div>
<div className="min-w-0 min-h-0 overflow-hidden flex flex-col gap-1.5">
<div className="min-h-0" style={{ flex: '0 0 auto', maxHeight: '40%' }}>
<EntryFilterCard filters={data.entryFilters || []} />
</div>
<div className="flex-1 min-h-0">
<PositionsCard positions={data.positions} />
<PositionsCard
positions={data.positions}
positionDetails={data.positionDetails}
/>
</div>
<div className="flex-1 min-h-0">
<LogCard logs={data.logs} />
@@ -0,0 +1,114 @@
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Activity, Timer, Brain, Gauge, Clock } from "lucide-react";
import { cn } from "@/lib/utils";
import type { RiskMode, CooldownStatus, AutoTrainerStatus, PerformanceStatus, MarketCloseStatus } from "@/types/trading";
interface BotStatusCardProps {
riskMode?: RiskMode;
cooldown?: CooldownStatus;
autoTrainer?: AutoTrainerStatus;
performance?: PerformanceStatus;
marketClose?: MarketCloseStatus;
}
function getRiskModeVariant(mode: string) {
switch (mode) {
case "normal": return "success";
case "recovery": return "warning";
case "protected": return "danger";
case "stopped": return "danger";
default: return "secondary";
}
}
export function BotStatusCard({ riskMode, cooldown, autoTrainer, performance, marketClose }: BotStatusCardProps) {
const mode = riskMode?.mode || "unknown";
const aucColor = (autoTrainer?.currentAuc ?? 0) >= 0.7 ? "text-success" : (autoTrainer?.currentAuc ?? 0) >= 0.65 ? "text-warning" : "text-danger";
return (
<Card className="glass">
<CardHeader>
<CardTitle className="text-[11px] font-medium text-muted-foreground flex items-center gap-1.5 uppercase tracking-wider">
<Activity className="h-3.5 w-3.5" />
Bot Status
</CardTitle>
</CardHeader>
<CardContent className="space-y-1.5">
{/* Risk Mode */}
<div className="flex items-center justify-between">
<span className="text-[10px] text-muted-foreground">Risk Mode</span>
<Badge
variant={getRiskModeVariant(mode) as "success" | "warning" | "danger" | "secondary"}
className={cn("text-[10px] h-4 px-1.5 uppercase", mode === "stopped" && "animate-pulse")}
>
{mode}
</Badge>
</div>
{/* Cooldown */}
<div className="space-y-0.5">
<div className="flex items-center justify-between">
<span className="text-[10px] text-muted-foreground flex items-center gap-1">
<Timer className="h-2.5 w-2.5" />
Cooldown
</span>
<span className={cn("text-[10px] font-number", cooldown?.active ? "text-warning" : "text-muted-foreground/60")}>
{cooldown?.active ? `${cooldown.secondsRemaining}s` : "Ready"}
</span>
</div>
{cooldown?.active && (
<div className="h-1 w-full bg-surface-light rounded-full overflow-hidden">
<div
className="h-full rounded-full bg-warning transition-all duration-1000"
style={{ width: `${cooldown.totalSeconds > 0 ? ((cooldown.totalSeconds - cooldown.secondsRemaining) / cooldown.totalSeconds) * 100 : 0}%` }}
/>
</div>
)}
</div>
{/* Auto Trainer */}
<div className="flex items-center justify-between">
<span className="text-[10px] text-muted-foreground flex items-center gap-1">
<Brain className="h-2.5 w-2.5" />
Model AUC
</span>
<span className={cn("text-[10px] font-bold font-number", aucColor)}>
{autoTrainer?.currentAuc != null ? autoTrainer.currentAuc.toFixed(3) : "N/A"}
</span>
</div>
{/* Performance */}
<div className="flex items-center justify-between">
<span className="text-[10px] text-muted-foreground flex items-center gap-1">
<Gauge className="h-2.5 w-2.5" />
Uptime
</span>
<span className="text-[10px] font-number text-foreground">
{performance ? `${performance.uptimeHours}h | ${performance.loopCount} loops` : "—"}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-[10px] text-muted-foreground">Exec Speed</span>
<span className={cn("text-[10px] font-number", (performance?.avgExecutionMs ?? 0) > 50 ? "text-warning" : "text-success")}>
{performance ? `${performance.avgExecutionMs}ms` : "—"}
</span>
</div>
{/* Market Close */}
<div className="pt-0.5 border-t border-border flex items-center justify-between">
<span className="text-[10px] text-muted-foreground flex items-center gap-1">
<Clock className="h-2.5 w-2.5" />
Close
</span>
<span className={cn("text-[10px] font-number", marketClose?.nearWeekend ? "text-warning font-bold" : "text-muted-foreground")}>
{marketClose ? `D:${marketClose.hoursToDailyClose}h W:${marketClose.hoursToWeekendClose}h` : "—"}
</span>
</div>
</CardContent>
</Card>
);
}
@@ -0,0 +1,86 @@
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Filter, Check, X, Minus } from "lucide-react";
import { cn } from "@/lib/utils";
import type { EntryFilter } from "@/types/trading";
interface EntryFilterCardProps {
filters: EntryFilter[];
}
export function EntryFilterCard({ filters }: EntryFilterCardProps) {
const passedCount = filters.filter((f) => f.passed).length;
const totalCount = filters.length;
const hasBlocker = filters.some((f) => !f.passed);
// Find the first blocker index — filters after it were not evaluated
const firstBlockerIdx = filters.findIndex((f) => !f.passed);
return (
<Card className="glass h-full flex flex-col">
<CardHeader>
<CardTitle className="text-[11px] font-medium text-muted-foreground flex items-center gap-1.5 uppercase tracking-wider">
<Filter className="h-3.5 w-3.5" />
Entry Filters
{totalCount > 0 && (
<Badge
variant={hasBlocker ? "danger" : "success"}
className="ml-auto text-[10px] h-4 px-1.5"
>
{passedCount}/{totalCount}
</Badge>
)}
</CardTitle>
</CardHeader>
<CardContent className="flex-1 min-h-0 overflow-auto">
{totalCount === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center">
<Minus className="h-4 w-4 text-muted-foreground/30 mb-1" />
<p className="text-[10px] text-muted-foreground/60">Waiting for candle...</p>
</div>
) : (
<div className="space-y-0.5">
{filters.map((filter, idx) => {
// Determine status: passed, blocked, or not evaluated
const isNotEvaluated = firstBlockerIdx >= 0 && idx > firstBlockerIdx;
const isBlocker = !filter.passed && idx === firstBlockerIdx;
return (
<div
key={`${filter.name}-${idx}`}
className={cn(
"flex items-center gap-1.5 px-1.5 py-0.5 rounded text-[10px]",
isBlocker && "bg-danger/10",
isNotEvaluated && "opacity-40"
)}
>
{isNotEvaluated ? (
<Minus className="h-2.5 w-2.5 text-muted-foreground/40 flex-shrink-0" />
) : filter.passed ? (
<Check className="h-2.5 w-2.5 text-success flex-shrink-0" />
) : (
<X className="h-2.5 w-2.5 text-danger flex-shrink-0" />
)}
<span className={cn(
"truncate flex-1",
isBlocker ? "text-danger font-semibold" : "text-muted-foreground"
)}>
{filter.name}
</span>
<span className={cn(
"text-[9px] truncate max-w-[80px]",
isBlocker ? "text-danger" : "text-muted-foreground/60"
)}>
{filter.detail}
</span>
</div>
);
})}
</div>
)}
</CardContent>
</Card>
);
}
@@ -11,3 +11,5 @@ export { EquityChart } from "./equity-chart";
export { Header } from "./header";
export { Sparkline } from "./sparkline";
export { SettingsCard } from "./settings-card";
export { BotStatusCard } from "./bot-status-card";
export { EntryFilterCard } from "./entry-filter-card";
@@ -1,16 +1,23 @@
"use client";
import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Layers, Inbox } from "lucide-react";
import { Layers, Inbox, ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@/lib/utils";
import type { Position } from "@/types/trading";
import type { Position, PositionDetail } from "@/types/trading";
interface PositionsCardProps {
positions: Position[];
positionDetails?: PositionDetail[];
}
export function PositionsCard({ positions }: PositionsCardProps) {
export function PositionsCard({ positions, positionDetails }: PositionsCardProps) {
const [expandedTicket, setExpandedTicket] = useState<number | null>(null);
const getDetail = (ticket: number) =>
positionDetails?.find((d) => d.ticket === ticket);
return (
<Card className="glass h-full flex flex-col">
<CardHeader>
@@ -32,33 +39,91 @@ export function PositionsCard({ positions }: PositionsCardProps) {
</div>
) : (
<div className="space-y-1">
{positions.map((pos) => (
<div
key={pos.ticket}
className={cn(
"flex items-center justify-between p-1.5 rounded-md bg-surface-light/50",
pos.type === "BUY" ? "border-l-2 border-l-success" : "border-l-2 border-l-danger"
)}
>
<div className="flex items-center gap-1.5">
<Badge
variant={pos.type === "BUY" ? "success" : "danger"}
className="text-[10px] h-4 px-1"
{positions.map((pos) => {
const detail = getDetail(pos.ticket);
const isExpanded = expandedTicket === pos.ticket;
const hasDetail = !!detail;
return (
<div key={pos.ticket}>
<div
className={cn(
"flex items-center justify-between p-1.5 rounded-md bg-surface-light/50",
pos.type === "BUY" ? "border-l-2 border-l-success" : "border-l-2 border-l-danger",
hasDetail && "cursor-pointer hover:bg-surface-light/80"
)}
onClick={() => hasDetail && setExpandedTicket(isExpanded ? null : pos.ticket)}
>
{pos.type}
</Badge>
<span className="text-[11px] font-number">
{pos.volume} @ {pos.priceOpen.toFixed(2)}
</span>
<div className="flex items-center gap-1.5">
<Badge
variant={pos.type === "BUY" ? "success" : "danger"}
className="text-[10px] h-4 px-1"
>
{pos.type}
</Badge>
<span className="text-[11px] font-number">
{pos.volume} @ {pos.priceOpen.toFixed(2)}
</span>
</div>
<div className="flex items-center gap-1">
<span className={cn(
"text-[11px] font-bold font-number",
pos.profit >= 0 ? "text-success" : "text-danger"
)}>
{pos.profit >= 0 ? "+" : ""}${pos.profit.toFixed(2)}
</span>
{hasDetail && (
isExpanded
? <ChevronUp className="h-3 w-3 text-muted-foreground/40" />
: <ChevronDown className="h-3 w-3 text-muted-foreground/40" />
)}
</div>
</div>
{/* Expandable Details */}
{isExpanded && detail && (
<div className="ml-2 mt-0.5 p-1.5 rounded bg-surface-light/30 space-y-0.5 text-[10px]">
<div className="flex justify-between">
<span className="text-muted-foreground">Peak Profit</span>
<span className="font-number text-success">${detail.peakProfit.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">DD from Peak</span>
<span className={cn("font-number", detail.drawdownFromPeak > 30 ? "text-danger" : "text-muted-foreground")}>
{detail.drawdownFromPeak.toFixed(1)}%
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Momentum</span>
<span className={cn("font-number", detail.momentum > 0 ? "text-success" : detail.momentum < 0 ? "text-danger" : "text-muted-foreground")}>
{detail.momentum > 0 ? "+" : ""}{detail.momentum}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">TP Probability</span>
<span className={cn("font-number", detail.tpProbability >= 50 ? "text-success" : "text-warning")}>
{detail.tpProbability}%
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Duration</span>
<span className="font-number text-muted-foreground">{detail.tradeHours}h</span>
</div>
{(detail.reversalWarnings > 0 || detail.stalls > 0) && (
<div className="flex gap-2 pt-0.5 border-t border-border/50">
{detail.reversalWarnings > 0 && (
<span className="text-warning">Rev: {detail.reversalWarnings}</span>
)}
{detail.stalls > 0 && (
<span className="text-muted-foreground">Stalls: {detail.stalls}</span>
)}
</div>
)}
</div>
)}
</div>
<span className={cn(
"text-[11px] font-bold font-number",
pos.profit >= 0 ? "text-success" : "text-danger"
)}>
{pos.profit >= 0 ? "+" : ""}${pos.profit.toFixed(2)}
</span>
</div>
))}
);
})}
</div>
)}
</CardContent>
@@ -1,17 +1,30 @@
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { ShieldAlert, AlertTriangle } from "lucide-react";
import { cn, formatUSD } from "@/lib/utils";
import type { RiskMode } from "@/types/trading";
interface RiskCardProps {
dailyLoss: number;
dailyProfit: number;
consecutiveLosses: number;
riskPercent: number;
riskMode?: RiskMode;
}
export function RiskCard({ dailyLoss, dailyProfit, consecutiveLosses, riskPercent }: RiskCardProps) {
function getRiskModeVariant(mode: string): "success" | "warning" | "danger" | "secondary" {
switch (mode) {
case "normal": return "success";
case "recovery": return "warning";
case "protected": return "danger";
case "stopped": return "danger";
default: return "secondary";
}
}
export function RiskCard({ dailyLoss, dailyProfit, consecutiveLosses, riskPercent, riskMode }: RiskCardProps) {
const isCritical = riskPercent >= 100;
const isHigh = riskPercent >= 80;
const isMedium = riskPercent >= 50;
@@ -28,6 +41,8 @@ export function RiskCard({ dailyLoss, dailyProfit, consecutiveLosses, riskPercen
return "bg-success";
};
const mode = riskMode?.mode || "unknown";
return (
<Card className={cn(
"glass",
@@ -41,8 +56,15 @@ export function RiskCard({ dailyLoss, dailyProfit, consecutiveLosses, riskPercen
)}>
<ShieldAlert className="h-3.5 w-3.5" />
Risk
{/* Risk Mode Badge */}
<Badge
variant={getRiskModeVariant(mode)}
className={cn("ml-auto text-[9px] h-4 px-1 uppercase", mode === "stopped" && "animate-pulse")}
>
{mode}
</Badge>
{isCritical && (
<span className="ml-auto flex items-center gap-1 text-[10px] bg-danger text-white px-1.5 py-0.5 rounded-full animate-pulse">
<span className="flex items-center gap-1 text-[10px] bg-danger text-white px-1.5 py-0.5 rounded-full animate-pulse">
<AlertTriangle className="h-2.5 w-2.5" />
BREACHED
</span>
@@ -81,7 +103,37 @@ export function RiskCard({ dailyLoss, dailyProfit, consecutiveLosses, riskPercen
style={{ width: `${Math.min(riskPercent, 100)}%` }}
/>
</div>
{/* Remaining daily risk */}
{riskMode && riskMode.remainingDailyRisk > 0 && (
<div className="flex justify-between items-center mt-0.5">
<span className="text-[9px] text-muted-foreground/60">Remaining</span>
<span className="text-[9px] font-number text-muted-foreground/60">
{formatUSD(riskMode.remainingDailyRisk)}
</span>
</div>
)}
</div>
{/* Total Loss Progress */}
{riskMode && riskMode.maxTotalLoss > 0 && (
<div className="pt-0.5">
<div className="flex justify-between items-center mb-0.5">
<span className="text-[10px] text-muted-foreground">Total Loss</span>
<span className="text-[10px] font-number text-muted-foreground">
{formatUSD(riskMode.totalLoss)} / {formatUSD(riskMode.maxTotalLoss)}
</span>
</div>
<div className="h-1 w-full bg-surface-light rounded-full overflow-hidden">
<div
className={cn(
"h-full rounded-full transition-all duration-500",
(riskMode.totalLoss / riskMode.maxTotalLoss) >= 0.8 ? "bg-danger" : "bg-warning/60"
)}
style={{ width: `${riskMode.maxTotalLoss > 0 ? Math.min((riskMode.totalLoss / riskMode.maxTotalLoss) * 100, 100) : 0}%` }}
/>
</div>
</div>
)}
</CardContent>
</Card>
);
@@ -2,16 +2,19 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Clock, Sparkles, CheckCircle2, XCircle } from "lucide-react";
import { Clock, Sparkles, CheckCircle2, XCircle, Ban } from "lucide-react";
import { cn } from "@/lib/utils";
import type { TimeFilter } from "@/types/trading";
interface SessionCardProps {
session: string;
isGoldenTime: boolean;
canTrade: boolean;
sessionMultiplier?: number;
timeFilter?: TimeFilter;
}
export function SessionCard({ session, isGoldenTime, canTrade }: SessionCardProps) {
export function SessionCard({ session, isGoldenTime, canTrade, sessionMultiplier, timeFilter }: SessionCardProps) {
const getSessionColor = (s: string) => {
const lower = s.toLowerCase();
if (lower.includes("london")) return "text-info";
@@ -20,12 +23,21 @@ export function SessionCard({ session, isGoldenTime, canTrade }: SessionCardProp
return "text-warning";
};
const mult = sessionMultiplier ?? 1.0;
const multLabel = `${mult}x`;
const multVariant = mult < 1 ? "warning" : mult > 1 ? "success" : "secondary";
return (
<Card className="glass">
<CardHeader>
<CardTitle className="text-[11px] font-medium text-muted-foreground flex items-center gap-1.5 uppercase tracking-wider">
<Clock className="h-3.5 w-3.5" />
Session
{sessionMultiplier != null && (
<Badge variant={multVariant as "warning" | "success" | "secondary"} className="ml-auto text-[10px] h-4 px-1">
{multLabel}
</Badge>
)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-1.5">
@@ -56,6 +68,23 @@ export function SessionCard({ session, isGoldenTime, canTrade }: SessionCardProp
{canTrade ? "CAN TRADE" : "NO TRADE"}
</Badge>
</div>
{/* Time Filter Status */}
{timeFilter && (
<div className="flex items-center gap-1.5 pt-0.5 border-t border-border">
{timeFilter.isBlocked ? (
<Ban className="h-3 w-3 text-danger" />
) : (
<Clock className="h-3 w-3 text-muted-foreground/40" />
)}
<span className={cn(
"text-[10px]",
timeFilter.isBlocked ? "text-danger font-semibold" : "text-muted-foreground"
)}>
WIB {timeFilter.wibHour}:00{timeFilter.isBlocked ? " BLOCKED" : ""}
</span>
</div>
)}
</CardContent>
</Card>
);
+81
View File
@@ -1,5 +1,74 @@
// Trading data types
export interface EntryFilter {
name: string;
passed: boolean;
detail: string;
}
export interface RiskMode {
mode: string;
reason: string;
recommendedLot: number;
maxAllowedLot: number;
totalLoss: number;
maxTotalLoss: number;
remainingDailyRisk: number;
}
export interface CooldownStatus {
active: boolean;
secondsRemaining: number;
totalSeconds: number;
}
export interface TimeFilter {
wibHour: number;
isBlocked: boolean;
blockedHours: number[];
}
export interface PositionDetail {
ticket: number;
peakProfit: number;
drawdownFromPeak: number;
momentum: number;
tpProbability: number;
reversalWarnings: number;
stalls: number;
tradeHours: number;
}
export interface AutoTrainerStatus {
lastRetrain: string | null;
currentAuc: number | null;
minAucThreshold: number;
hoursSinceRetrain: number;
nextRetrainHour: number;
modelsFitted: boolean;
}
export interface PerformanceStatus {
loopCount: number;
avgExecutionMs: number;
uptimeHours: number;
totalSessionTrades: number;
totalSessionProfit: number;
}
export interface MarketCloseStatus {
hoursToDailyClose: number;
hoursToWeekendClose: number;
nearWeekend: boolean;
marketOpen: boolean;
}
export interface H1BiasDetails {
bias: string;
ema20: number;
price: number;
}
export interface TradingStatus {
timestamp: string;
connected: boolean;
@@ -63,6 +132,18 @@ export interface TradingStatus {
dynamicThreshold?: number;
marketQuality?: string;
marketScore?: number;
// === NEW: Extended monitoring ===
entryFilters?: EntryFilter[];
riskMode?: RiskMode;
cooldown?: CooldownStatus;
timeFilter?: TimeFilter;
sessionMultiplier?: number;
positionDetails?: PositionDetail[];
autoTrainer?: AutoTrainerStatus;
performance?: PerformanceStatus;
marketClose?: MarketCloseStatus;
h1BiasDetails?: H1BiasDetails;
}
export interface BotSettings {