feat: named live books; start selected_2026_09 (26 strategies)
A live book is a cohort of strategies, each with its own account, started on one date with one capital and a flat fee per order (trader.LIVE_BOOKS). The original book keeps its state in data/; a named book in data/books/<book>/. The monitor runs every book; auto/status/log/compare/ replay take --book. A fresh account opens on the book's first session. selected_2026_09: US $10,000 and $1 per order from 2026-09-25, CN ¥50,000 and ¥5 per order from 2026-09-28 (the first CN session after the choice). Strategies beat their benchmark in the Apr-Sep 2026 point-in-time replay and passed a 10-year PIT backtest (US Sharpe >= 0.6, MaxDD > -50%; CN Sharpe >= 0.2, CAGR > 0), no daily variants, at most two frequencies per factor signal, plus the three 2026-09 research strategies. bridge.py live --book and bridge.py books feed a book selector on the live page (/api/strategies/live?book=, /api/strategies/live/books). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ order sizing, and how results are reported."""
|
||||
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@@ -61,6 +63,47 @@ class PanelRefreshTests(unittest.TestCase):
|
||||
self.assertEqual(len(cleaned), 4)
|
||||
|
||||
|
||||
class LiveBookTests(unittest.TestCase):
|
||||
BOOK = {"label": "test", "start": {"us": "2026-09-16"}, "capital": {"us": 5_000.0},
|
||||
"fee_per_order": {"us": 1.0}, "strategies": {"us": ["momentum"]}}
|
||||
|
||||
def _run(self, state: dict):
|
||||
close = _panel({"AAA": [10, 11, 12, 13, 14], "BBB": [20, 20, 21, 21, 22],
|
||||
"SPY": [1, 1, 1, 1, 1]})
|
||||
args = SimpleNamespace(market="us", strategy="momentum", book="test", capital=None,
|
||||
tx_cost=0.001, fixed_fee=2.0, integer_shares=True)
|
||||
equal = pd.DataFrame(0.5, index=close.index, columns=["AAA", "BBB"])
|
||||
with mock.patch.dict(trader.LIVE_BOOKS, {"test": self.BOOK}), \
|
||||
mock.patch.object(trader, "strategy_universe", lambda m, n: (["AAA", "BBB"], "SPY")), \
|
||||
mock.patch.object(trader, "pit_candidates", lambda m, n, s: []), \
|
||||
mock.patch.object(trader, "load_strategy_prices", lambda *a, **k: (close, close, close)), \
|
||||
mock.patch.object(trader, "session_signals", lambda m, n, t, days, c, o: equal.loc[days]):
|
||||
return trader._process_sessions(args, state, refresh=False)
|
||||
|
||||
def test_a_new_account_starts_on_the_book_date_with_its_capital_and_flat_fee(self):
|
||||
state = self._run({})
|
||||
self.assertEqual(min(state["daily_equity"]), "2026-09-16")
|
||||
self.assertEqual(state["initial_capital"], 5_000.0)
|
||||
self.assertTrue(state["trade_log"])
|
||||
self.assertTrue(all(t["commission"] == 1.0 for t in state["trade_log"]))
|
||||
|
||||
def test_no_account_is_opened_before_the_book_starts(self):
|
||||
with mock.patch.dict(self.BOOK, {"start": {"us": "2026-10-01"}}):
|
||||
self.assertIsNone(self._run({}))
|
||||
|
||||
def test_book_strategies_are_live_strategies_of_their_market(self):
|
||||
for book, cfg in trader.LIVE_BOOKS.items():
|
||||
for market, names in cfg["strategies"].items():
|
||||
for name in names:
|
||||
self.assertIn(name, trader.STRATEGY_REGISTRY, book)
|
||||
self.assertNotIn(name, trader.LIVE_EXCLUDED_STRATEGIES, book)
|
||||
self.assertTrue(trader.strategy_supports_market(name, market), (book, name))
|
||||
self.assertEqual(len(names), len(set(names)), book)
|
||||
pd.Timestamp(cfg["start"][market])
|
||||
self.assertGreater(cfg["capital"][market], 0)
|
||||
self.assertGreaterEqual(cfg["fee_per_order"][market], 0)
|
||||
|
||||
|
||||
class SessionAndOrderTests(unittest.TestCase):
|
||||
def test_todays_bar_is_not_a_close_until_the_session_settles(self):
|
||||
raw = _panel({"SPY": [1, 1, 1]}, start="2026-09-22")
|
||||
|
||||
Reference in New Issue
Block a user