Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tests/fe8a_runtime_today_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,24 @@ test("a bound account without today's record is missing, not a known zero-run da
});
assert.equal(summary.accounts[0].status, "no_data");
assert.equal(summary.accounts[0].unavailable_reason, "today_record_missing");
assert.equal(summary.market_session, "trading_day");
assert.equal(summary.counts.observed, 0);
assert.equal(summary.counts.runs, null);
assert.equal(summary.counts.by_state.no_data, 1);
});

test("a weekend without records is a non-trading day, not missing data", async () => {
const saturday = Date.parse("2026-10-24T12:00:00Z");
const { summary } = await summarize({ selected: [accounts[2]],
at: saturday,
read: account => Response.json(dailyBody(account, { date: "2026-10-24", record: null, data_status: "unavailable" },
{ business_date: "2026-10-24" })),
});
assert.equal(summary.market_session, "weekend");
assert.equal(summary.accounts[0].status, "no_data");
assert.equal(summary.accounts[0].unavailable_reason, "non_trading_weekend");
});

test("ambiguous ownership never reads or merges reports across bindings", async () => {
const binding = bindingFor(accounts[0]);
const { summary, reads } = await summarize({ selected: [accounts[0]],
Expand Down
4 changes: 3 additions & 1 deletion tests/runtime_today_request_reuse_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ const response=await get('/api/runtime/today');
assert.equal(response.status,200);
const body=await response.json();
assert.equal(body.accounts.length,10);
assert.ok(body.accounts.every(row=>row.status==='no_data' && row.unavailable_reason==='today_record_missing'));
const expectedNoDataReason=body.market_session==='weekend'?'non_trading_weekend':'today_record_missing';
assert.equal(body.market_session,[0,6].includes(new Date(`${body.date}T00:00:00Z`).getUTCDay())?'weekend':'trading_day');
assert.ok(body.accounts.every(row=>row.status==='no_data' && row.unavailable_reason===expectedNoDataReason));
console.log('synthetic shared reads', JSON.stringify(Object.fromEntries(['auth_config','account_options','runtime_daily_bindings','account_facts_bindings'].map(key => [key, reads.get(key)]))));
assert.equal(reads.get('runtime_daily_bindings'),1,'one registry read for the request');
assert.equal(reads.get('account_options'),1,'one account options read for the request');
Expand Down
15 changes: 13 additions & 2 deletions web/strategy-switch-console/application/console_api/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ export function businessDate(now = Date.now()) {
timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit",
}).format(now);
}
// No market holiday source exists in this Worker; only weekends are provable.
function nonTradingWeekend(date) {
const weekday = new Date(`${date}T00:00:00Z`).getUTCDay();
return weekday === 0 || weekday === 6;
}
export async function todaySummary(request, env, deps, accounts, now = Date.now()) {
// Daily records, their reader, and this summary must share one date domain.
const date = runtimeDailyBusinessDate(now);
const weekend = nonTradingWeekend(date);
let bindings;
try { bindings = await deps.dailyBindings(env); }
catch { bindings = null; }
Expand All @@ -37,7 +43,11 @@ export async function todaySummary(request, env, deps, accounts, now = Date.now(
const body = await response.json();
if (body.account_key !== account.key || body.platform !== account.platform || body.date !== date)
throw Error("daily_binding_mismatch");
if (!body.record) return { ...base, status: "no_data", unavailable_reason: "today_record_missing" };
if (!body.record) {
const weekendClosed = weekend && account.platform !== "binance";
return { ...base, status: "no_data",
unavailable_reason: weekendClosed ? "non_trading_weekend" : "today_record_missing" };
}
const record = body.record;
if (record.business_date !== date || record.target_key !== matches[0].target_key)
throw Error("daily_record_mismatch");
Expand Down Expand Up @@ -72,7 +82,8 @@ export async function todaySummary(request, env, deps, accounts, now = Date.now(
}
return { status: rows.some(r => r.status === "read_failed") ? "read_failed"
: rows.some(r => r.status !== "ok") ? "partial" : "ok",
date, timezone: RUNTIME_DAILY_TIMEZONE, as_of: rows.map(r => r.as_of).filter(Boolean).sort()[0] || null,
date, timezone: RUNTIME_DAILY_TIMEZONE, market_session: weekend ? "weekend" : "trading_day",
as_of: rows.map(r => r.as_of).filter(Boolean).sort()[0] || null,
accounts: rows, counts: { configured: rows.length, by_state: runStateCounts(rows),
observed: rows.filter(r => r.run_count !== null).length,
runs: rows.some(r => r.run_count === null || r.completeness !== "complete") ? null : rows.reduce((n, r) => n + r.run_count, 0) },
Expand Down
6 changes: 6 additions & 0 deletions web/strategy-switch-console/contracts/console-api.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2361,6 +2361,12 @@
"timezone": {
"const": "America/New_York"
},
"market_session": {
"enum": [
"weekend",
"trading_day"
]
},
"as_of": {
"type": [
"string",
Expand Down
1 change: 1 addition & 0 deletions web/strategy-switch-console/frontend/src/api/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ export interface TodayRunSummary extends ReadOnlyProjection {
status: "ok" | "partial" | "read_failed";
date: string;
timezone: "America/New_York";
market_session?: "weekend" | "trading_day";
as_of: string | null;
coverage: string;
counts: { configured: number; observed: number; runs: number | null; by_state?: Record<RunState, number> };
Expand Down
2 changes: 2 additions & 0 deletions web/strategy-switch-console/frontend/src/i18n/pages/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const zh: Record<string, string> = {
"health.kpi.credentials_unit": "项需关注",
"health.kpi.recent_failure": "今日失败账户",
"health.business_date": "纽约业务日 {value}",
"health.non_trading_day": "非交易日:周末无计划运行;加密货币账户可能仍有记录。",
"health.grid_read_failed_note": "部分日期读取失败,无法确认当天是否运行。",
"health.global_scope_note": "本页展示全部平台与账户的运行状态。",
"health.grid.incomplete": "未完成",
Expand Down Expand Up @@ -50,6 +51,7 @@ export const en: Record<string, string> = {
"health.kpi.credentials_unit": "need attention",
"health.kpi.recent_failure": "Today's failed accounts",
"health.business_date": "New York business day {value}",
"health.non_trading_day": "Non-trading day: no scheduled weekend runs; crypto accounts may still report.",
"health.grid_read_failed_note": "Some dates could not be read, so whether a run occurred is unknown.",
"health.global_scope_note": "This page shows runtime status for all platforms and accounts.",
"health.grid.incomplete": "Incomplete",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ export default function HealthPage() {
? <span className="health-business-day" data-state="read_failed">{t("state.read_failed")}</span>
: <span className="health-business-day">{t("health.business_date", { value: `${summary.data.date} · ${summary.data.timezone}` })}</span>}
{summary.phase === "ready" && summary.data.as_of && <AsOfStamp value={summary.data.as_of} />}
{summary.phase === "ready" && summary.data.market_session === "weekend" && (
<span className="health-business-day" data-state="non_trading">{t("health.non_trading_day")}</span>
)}
</div>
<StatStrip summary={summary} credential={credential} />

Expand Down
34 changes: 17 additions & 17 deletions web/strategy-switch-console/v2_asset_map.js

Large diffs are not rendered by default.

Loading