feat: add Quant OS A-share baseline

This commit is contained in:
2026-07-26 12:54:04 +08:00
commit 48c5f64bbd
98 changed files with 31874 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
import hashlib
import json
import tempfile
import unittest
from datetime import datetime
from pathlib import Path
from quant60.backtest import BacktestEngine, verify_artifact_manifest
from quant60.cli import main
from quant60.config import config_from_dict, load_config
from quant60.ledger import HashChainLedger
from quant60.synthetic import generate_synthetic_bars
PROJECT = Path(__file__).resolve().parents[1]
class BacktestCliTests(unittest.TestCase):
def test_empty_config_uses_valid_defaults(self):
config = config_from_dict({})
config.validate()
def test_non_finite_config_values_are_rejected(self):
cases = (
{"initial_cash": "NaN"},
{"fees": {"commission_rate": float("inf")}},
{"execution": {"participation_rate": float("nan")}},
{"execution": {"slippage_bps": float("inf")}},
{"strategy": {"max_weight": float("nan")}},
{"strategy": {"gross_target": float("inf")}},
{"strategy": {"cash_buffer": float("nan")}},
{"initial_cash": True},
{"execution": {"lot_size": 100.0}},
{"execution": {"cancel_open_orders_at_end": 1}},
{"strategy": {"lookback": True}},
{"strategy": {"rebalance_every": 5.0}},
{"synthetic": {"trading_days": 90.0}},
{"symbols": "600000.XSHG"},
)
for raw in cases:
with self.subTest(raw=raw):
with self.assertRaises(ValueError):
config_from_dict(raw)
def test_backtest_is_byte_deterministic(self):
config = load_config(PROJECT / "configs" / "baseline.json")
bars = generate_synthetic_bars(
config.symbols,
config.synthetic.start_date,
config.synthetic.trading_days,
config.synthetic.seed,
config.synthetic.base_volume,
)
first = BacktestEngine(config).run(bars)
second = BacktestEngine(config).run(bars)
self.assertEqual(first.run_id, second.run_id)
self.assertEqual(first.report, second.report)
with tempfile.TemporaryDirectory() as left, tempfile.TemporaryDirectory() as right:
left_paths = first.write_artifacts(left)
right_paths = second.write_artifacts(right)
for key in left_paths:
left_hash = hashlib.sha256(left_paths[key].read_bytes()).hexdigest()
right_hash = hashlib.sha256(right_paths[key].read_bytes()).hexdigest()
self.assertEqual(left_hash, right_hash, key)
self.assertTrue(HashChainLedger.read_jsonl(left_paths["events"]).verify())
report = json.loads(left_paths["report"].read_text(encoding="utf-8"))
self.assertFalse(report["investment_value_claim"])
self.assertGreater(report["fill_count"], 0)
records = [
json.loads(line)
for line in left_paths["events"].read_text(
encoding="utf-8"
).splitlines()
]
timestamps = [
datetime.fromisoformat(record["timestamp"]) for record in records
]
self.assertEqual(timestamps, sorted(timestamps))
def test_decisions_use_first_trading_session_close_of_each_week(self):
config = load_config(PROJECT / "configs" / "baseline.json")
bars = generate_synthetic_bars(
config.symbols,
config.synthetic.start_date,
config.synthetic.trading_days,
config.synthetic.seed,
config.synthetic.base_volume,
)
result = BacktestEngine(config).run(bars)
sessions = sorted({item.trading_date for item in bars})
first_by_week = {}
for session in sessions:
first_by_week.setdefault(session.isocalendar()[:2], session)
decision_dates = {
datetime.fromisoformat(item["as_of"]).date()
for item in result.targets
}
self.assertTrue(decision_dates)
for decision_date in decision_dates:
self.assertEqual(
decision_date,
first_by_week[decision_date.isocalendar()[:2]],
)
def test_incomplete_market_session_fails_closed(self):
config = load_config(PROJECT / "configs" / "baseline.json")
bars = generate_synthetic_bars(
config.symbols,
config.synthetic.start_date,
config.synthetic.trading_days,
config.synthetic.seed,
config.synthetic.base_volume,
)
removed = bars.pop(len(config.symbols) * 30)
with self.assertRaisesRegex(
ValueError,
"incomplete market-data session.*" + removed.symbol.replace(".", r"\."),
):
BacktestEngine(config).run(bars)
def test_cli_smoke_and_ledger_verify(self):
with tempfile.TemporaryDirectory() as directory:
output = Path(directory) / "run"
self.assertEqual(
main(
[
"smoke",
"--config",
str(PROJECT / "configs" / "baseline.json"),
"--output",
str(output),
]
),
0,
)
self.assertEqual(main(["verify-ledger", str(output / "events.jsonl")]), 0)
self.assertEqual(
main(["verify-manifest", str(output / "manifest.json")]),
0,
)
self.assertTrue(
verify_artifact_manifest(output / "manifest.json")["ok"]
)
def test_manifest_verifier_rejects_tampered_or_incomplete_runs(self):
config = load_config(PROJECT / "configs" / "baseline.json")
bars = generate_synthetic_bars(
config.symbols,
config.synthetic.start_date,
config.synthetic.trading_days,
config.synthetic.seed,
config.synthetic.base_volume,
)
with tempfile.TemporaryDirectory() as directory:
output = Path(directory) / "run"
BacktestEngine(config).run(bars).write_artifacts(output)
report_path = output / "report.json"
report_path.write_bytes(report_path.read_bytes() + b" ")
with self.assertRaisesRegex(ValueError, "report artifact hash mismatch"):
verify_artifact_manifest(output / "manifest.json")
(output / ".quant60-incomplete").write_text(
"interrupted\n",
encoding="ascii",
)
with self.assertRaisesRegex(ValueError, "publication is incomplete"):
verify_artifact_manifest(output / "manifest.json")
second_output = Path(directory) / "second-run"
BacktestEngine(config).run(bars).write_artifacts(second_output)
manifest_path = second_output / "manifest.json"
unexpected = second_output / "untracked-result.txt"
unexpected.write_text("not part of the run\n", encoding="utf-8")
with self.assertRaisesRegex(
ValueError,
"artifact directory is not an exact completed set",
):
verify_artifact_manifest(manifest_path)
unexpected.unlink()
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
manifest["engine_sources"]["portable_core.py"] = "0" * 64
manifest_path.write_text(
json.dumps(manifest, sort_keys=True),
encoding="utf-8",
)
with self.assertRaisesRegex(
ValueError,
"engine_sources aggregate hash mismatch",
):
verify_artifact_manifest(manifest_path)
def test_cli_backtest_alias(self):
with tempfile.TemporaryDirectory() as directory:
output = Path(directory) / "run"
self.assertEqual(
main(
[
"backtest",
"--config",
str(PROJECT / "configs" / "baseline.json"),
"--output",
str(output),
]
),
0,
)
self.assertTrue((output / "report.json").is_file())
if __name__ == "__main__":
unittest.main()