diff --git a/.gitignore b/.gitignore index 4e0ec16..883256b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ data/ .cert .cert/ CLAUDE.md -brag-output/ +brag-output*/ # --- DATABASE --- *.sqlite diff --git a/backend/main.py b/backend/main.py index 7d2ff89..12e426c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -63,7 +63,7 @@ async def lifespan(app: FastAPI): enable_docs = os.getenv("ENABLE_DOCS", "0") == "1" app = FastAPI( title="ApexAlgo Engine API", - version="2.2.0", + version="2.2.1", swagger_ui_init_oauth={"clientId": "test"}, lifespan=lifespan, docs_url="/docs" if enable_docs else None, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b03f88c..ae797f9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "2.2.0", + "version": "2.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "2.2.0", + "version": "2.2.1", "dependencies": { "@fontsource-variable/inter": "^5.3.0", "@fontsource/jetbrains-mono": "^5.3.0", diff --git a/frontend/package.json b/frontend/package.json index e7afa9e..91e65a7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.2.0", + "version": "2.2.1", "license": "AGPL-3.0-only", "type": "module", "scripts": { diff --git a/frontend/src/components/BotManagerUI.jsx b/frontend/src/components/BotManagerUI.jsx index f7f8f97..4f350b1 100644 --- a/frontend/src/components/BotManagerUI.jsx +++ b/frontend/src/components/BotManagerUI.jsx @@ -155,81 +155,89 @@ const fmtDay = (iso) => { return Number.isNaN(d.getTime()) ? '?' : d.toLocaleDateString(undefined, { day: '2-digit', month: 'short', year: '2-digit' }); }; -/* One slim line: how many configurations of this strategy have been +/* Two slim rows: (1) how many configurations of this strategy have been backtested (on this slice of data / ever), the range the last one walked, - whether that range is pinned, and a jump to Analytics — the performance - numbers themselves live there, not on the card */ + whether it is locked, and a jump to Analytics — the performance numbers + themselves live there, not on the card; (2) whether that range has been + verified against the exchange, with the verify/lock actions */ function BacktestResult({ bot, updateBotConfig, verifyData, verifying }) { const sm = bot.last_backtest_summary ?? bot.settings?.last_backtest_summary; if (!sm || typeof sm.trades !== 'number') return null; - const restated = Number(sm.restated_candles) || 0; - const verifiedAt = sm.verified_at ? new Date(sm.verified_at) : null; - const canVerify = !!sm.data_from && !!sm.data_to && !verifying; const total = Number(sm.variants) || 0; const onSlice = Number(sm.variants_on_slice) || 0; - const pinned = !!(bot.settings?.backtest_from && bot.settings?.backtest_to); - const canPin = !bot.is_active && !!sm.data_from && !!sm.data_to; + const locked = !!(bot.settings?.backtest_from && bot.settings?.backtest_to); + const verified = !!sm.verified_at; + const restated = Number(sm.restated_candles) || 0; + const hasRange = !!sm.data_from && !!sm.data_to; + // Lock only after a verify: the snapshot you freeze should be one you have + // compared with the exchange. Unlocking is always allowed. + const canLock = !bot.is_active && hasRange && (locked || verified); const counterTitle = onSlice > 0 ? `Variant #${onSlice} on this slice of data (same pairs, timeframe and range) — ${total} distinct configuration${total === 1 ? '' : 's'} of this strategy backtested in total. Switching pairs or range starts a new slice; the total keeps counting. Reset the bot to start over.` : (total > 0 ? `${total} distinct configuration${total === 1 ? '' : 's'} of this strategy have been backtested. Reset the bot to start counting again.` : undefined); - const pin = () => updateBotConfig(bot.id, bot, { settings: { backtest_from: sm.data_from, backtest_to: sm.data_to } }); - const unpin = () => updateBotConfig(bot.id, bot, { settings: { backtest_from: null, backtest_to: null } }); - const action = "text-3xs font-bold uppercase tracking-wider transition-colors"; + const lock = () => updateBotConfig(bot.id, bot, { settings: { backtest_from: sm.data_from, backtest_to: sm.data_to } }); + const unlock = () => updateBotConfig(bot.id, bot, { settings: { backtest_from: null, backtest_to: null } }); + const action = "text-3xs font-bold uppercase tracking-wider transition-colors shrink-0"; + const exchange = (bot.settings?.data_exchange || 'exchange').toString(); + const verifiedTime = verified ? new Date(sm.verified_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''; return ( -
+
Data changed historical candles differ from the previous run on this slice
)} - {verifiedAt && (restated > 0 ? ( -- Exchange restated {restated} candle{restated === 1 ? '' : 's'} - local snapshot kept · verified {fmtDay(sm.verified_at)} {verifiedAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} -
- ) : ( -- Matches exchange · verified {fmtDay(sm.verified_at)} {verifiedAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} -
- ))}