Files
quant-os/tests/platform_joinquant_qmt_parity_test.py
T

461 lines
18 KiB
Python

import datetime
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "src"))
from platforms import joinquant_strategy, qmt_builtin_strategy
from platforms.fake_joinquant_harness import FakeJoinQuantHarness, FakePosition
from platforms.fake_qmt_harness import FakeQmtContext, FakeQmtHarness
def price_histories():
return {
"600000.XSHG": [15.0 - index * 0.02 for index in range(21)],
"000001.XSHE": [10.0 + index * 0.10 for index in range(21)],
"300750.XSHE": [100.0 + index * 1.5 for index in range(21)],
"000333.XSHE": [20.0 + index * 0.05 for index in range(21)],
}
class JoinQuantContractTest(unittest.TestCase):
def setUp(self):
self.harness = FakeJoinQuantHarness(price_histories())
self.harness.initialize(joinquant_strategy)
def test_initialize_enables_future_data_guard_and_weekly_open(self):
self.assertIs(self.harness.options["avoid_future_data"], True)
self.assertIs(self.harness.options["use_real_price"], True)
self.assertEqual(
self.harness.options["order_volume_ratio"],
0.1,
)
self.assertEqual(self.harness.benchmark, "000905.XSHG")
self.assertEqual(self.harness.order_cost["type"], "stock")
self.assertAlmostEqual(
self.harness.order_cost["cost"].open_commission,
0.00021,
)
self.assertAlmostEqual(
self.harness.order_cost["cost"].close_tax,
0.0005,
)
self.assertEqual(self.harness.slippage["type"], "stock")
self.assertAlmostEqual(
self.harness.slippage["slippage"].value,
0.0004,
)
self.assertEqual(len(self.harness.schedules), 1)
_, run_time = self.harness.schedules[0]
self.assertEqual(run_time, "open")
def test_rebalance_submits_exact_portable_executable_deltas(self):
self.assertIsNone(self.harness.run_scheduled())
self.harness.advance_session()
plan = self.harness.run_scheduled()
self.assertEqual(plan, self.harness.g.quant60_last_plan)
self.assertTrue(self.harness.orders)
ordered_symbols = {symbol for symbol, _ in self.harness.orders}
expected = {
symbol: int(delta)
for symbol, delta in plan["orders"].items()
if int(delta) != 0
}
self.assertEqual(ordered_symbols, set(expected))
self.assertEqual(
dict(self.harness.orders),
expected,
)
def test_missing_current_data_fails_closed_for_new_position(self):
best = "300750.XSHE"
del self.harness.current_data[best]
self.assertIsNone(self.harness.run_scheduled())
self.harness.advance_session()
self.harness.run_scheduled()
ordered_symbols = {symbol for symbol, _ in self.harness.orders}
self.assertNotIn(best, ordered_symbols)
self.assertEqual(
self.harness.g.quant60_skipped_orders[-1]["reason"],
"current_data_unavailable",
)
def test_star_market_buy_uses_daily_high_limit_protection(self):
histories = price_histories()
star = "688301.XSHG"
histories[star] = [20.0 + index * 0.3 for index in range(21)]
harness = FakeJoinQuantHarness(histories)
harness.current_data[star].high_limit = 31.28
harness.initialize(joinquant_strategy)
self.assertIsNone(harness.run_scheduled())
harness.advance_session()
plan = harness.run_scheduled()
self.assertGreater(plan["orders"][star], 0)
styles = dict(harness.order_styles)
self.assertEqual(styles[star].kind, "limit")
self.assertAlmostEqual(styles[star].limit_price, 31.28)
def test_star_market_sell_uses_daily_low_limit_protection(self):
histories = price_histories()
star = "688301.XSHG"
histories[star] = [20.0 + index * 0.3 for index in range(21)]
harness = FakeJoinQuantHarness(
histories,
total_value=1000.0,
positions={star: FakePosition(1000)},
)
harness.current_data[star].low_limit = 17.06
harness.initialize(joinquant_strategy)
self.assertIsNone(harness.run_scheduled())
harness.advance_session()
plan = harness.run_scheduled()
self.assertEqual(plan["orders"][star], -1000)
styles = dict(harness.order_styles)
self.assertEqual(styles[star].kind, "limit")
self.assertAlmostEqual(styles[star].limit_price, 17.06)
def test_invalid_star_protection_price_fails_closed(self):
histories = price_histories()
star = "688301.XSHG"
histories[star] = [20.0 + index * 0.3 for index in range(21)]
harness = FakeJoinQuantHarness(histories)
harness.current_data[star].high_limit = float("nan")
harness.initialize(joinquant_strategy)
self.assertIsNone(harness.run_scheduled())
harness.advance_session()
plan = harness.run_scheduled()
self.assertGreater(plan["orders"][star], 0)
self.assertNotIn(star, dict(harness.orders))
self.assertEqual(
harness.g.quant60_skipped_orders[-1]["reason"],
"star_protection_price_invalid",
)
def test_incomplete_history_aborts_entire_rebalance_without_orders(self):
histories = price_histories()
histories["300750.XSHE"] = histories["300750.XSHE"][1:]
harness = FakeJoinQuantHarness(histories)
harness.initialize(joinquant_strategy)
self.assertIsNone(harness.run_scheduled())
harness.advance_session()
with self.assertRaises(joinquant_strategy.PriceHistoryUnavailable):
harness.run_scheduled()
self.assertEqual(harness.orders, [])
def test_joinquant_unmanaged_position_aborts_without_orders(self):
harness = FakeJoinQuantHarness(
price_histories(),
positions={"601318.XSHG": FakePosition(1000)},
)
harness.initialize(joinquant_strategy)
harness.g.quant60_config["universe_mode"] = "fixed"
self.assertIsNone(harness.run_scheduled())
harness.advance_session()
with self.assertRaises(joinquant_strategy.UnmanagedPositionError):
harness.run_scheduled()
self.assertEqual(harness.orders, [])
def test_executable_sell_delta_preserves_t1_cap_and_odd_lot(self):
position = FakePosition(total_amount=1050, closeable_amount=200)
harness = FakeJoinQuantHarness(
price_histories(),
total_value=1000.0,
positions={"600000.XSHG": position},
)
harness.initialize(joinquant_strategy)
self.assertIsNone(harness.run_scheduled())
harness.advance_session()
plan = harness.run_scheduled()
self.assertEqual(plan["orders"]["600000.XSHG"], -200)
self.assertEqual(dict(harness.orders)["600000.XSHG"], 850)
def test_one_session_week_executes_on_next_available_session(self):
self.assertIsNone(self.harness.run_scheduled())
# Model a holiday week with no second session: the next available
# session is the first session of the next ISO week.
self.harness.advance_session(days=7)
plan = self.harness.run_scheduled()
self.assertIsNotNone(plan)
self.assertTrue(self.harness.orders)
self.assertEqual(
self.harness.g.quant60_pending_first_session,
self.harness.context.current_dt.date(),
)
class CrossPlatformParityTest(unittest.TestCase):
def test_same_completed_close_produces_same_portable_plan(self):
jq = FakeJoinQuantHarness(price_histories())
jq.initialize(joinquant_strategy)
jq.context.previous_date = datetime.date(2026, 7, 24)
jq.context.current_dt = datetime.datetime(2026, 7, 27, 9, 30)
jq_plan = joinquant_strategy.compute_plan(jq.context)
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
context = FakeQmtContext(
qmt_histories,
bar_time=datetime.datetime(2026, 7, 24, 15, 0),
)
qmt = FakeQmtHarness(context).install(qmt_builtin_strategy)
qmt_builtin_strategy.init(context)
self.assertEqual(context.commission["type"], 0)
self.assertEqual(
context.commission["values"],
[0.0, 0.0005, 0.00021, 0.00021, 0.0, 5.0],
)
self.assertEqual(
context.slippage,
{"type": 2, "value": 0.0002},
)
qmt_plan = qmt_builtin_strategy.compute_plan(
context, datetime.datetime(2026, 7, 24, 15, 0)
)
self.assertEqual(jq_plan, qmt_plan)
self.assertEqual(joinquant_strategy.CONFIG["skip"], 0)
self.assertEqual(qmt_builtin_strategy.CONFIG["skip"], 0)
self.assertEqual(context.last_market_request["end_time"], "20260724")
self.assertIs(context.last_market_request["fill_data"], False)
self.assertEqual(
jq.last_index_request,
{
"index_symbol": "000905.XSHG",
"date": jq.context.previous_date,
},
)
self.assertEqual(
jq.last_extras_request["info"],
"is_st",
)
self.assertEqual(
jq.last_extras_request["end_date"],
jq.context.previous_date,
)
self.assertEqual(
context.last_sector_request,
{
"sector_name": "中证500",
"timetag": context.get_bar_timetag(context.barpos),
},
)
def test_pit_st_exclusion_is_identical_across_hosted_wrappers(self):
st_canonical = "300750.XSHE"
jq = FakeJoinQuantHarness(
price_histories(),
st_symbols={st_canonical},
)
jq.initialize(joinquant_strategy)
jq.context.previous_date = datetime.date(2026, 7, 24)
jq.context.current_dt = datetime.datetime(2026, 7, 27, 9, 30)
jq_plan = joinquant_strategy.compute_plan(jq.context)
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
qmt_symbol = "300750.SZ"
context = FakeQmtContext(
qmt_histories,
bar_time=datetime.datetime(2026, 7, 24, 15, 0),
st_periods={
qmt_symbol: {
"ST": [["20260701", "20260731"]],
}
},
)
FakeQmtHarness(context).install(qmt_builtin_strategy)
qmt_builtin_strategy.init(context)
qmt_plan = qmt_builtin_strategy.compute_plan(
context,
datetime.datetime(2026, 7, 24, 15, 0),
)
self.assertEqual(jq_plan, qmt_plan)
self.assertNotIn(st_canonical, jq_plan["scores"])
self.assertNotIn(st_canonical, qmt_plan["scores"])
def test_qmt_live_orders_are_off_by_default(self):
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
context = FakeQmtContext(
qmt_histories,
trade_mode="trading",
bar_time=datetime.datetime(2026, 7, 24, 15, 0),
)
harness = FakeQmtHarness(context)
self.assertIsNone(harness.run(qmt_builtin_strategy))
context._bar_time += datetime.timedelta(days=7)
plan = qmt_builtin_strategy.handlebar(context)
self.assertIsNotNone(plan)
self.assertEqual(harness.orders, [])
def test_qmt_builtin_live_mutation_cannot_be_enabled_by_parameters(self):
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
context = FakeQmtContext(
qmt_histories,
trade_mode="trading",
bar_time=datetime.datetime(2026, 7, 24, 15, 0),
)
harness = FakeQmtHarness(context).install(qmt_builtin_strategy)
qmt_builtin_strategy.init(context)
context.do_back_test = "False"
self.assertFalse(qmt_builtin_strategy._is_backtest(context))
self.assertFalse(qmt_builtin_strategy._orders_allowed(context))
# Even an injected ContextInfo knob cannot enable broker mutation.
context.q60_config = {
"enable_live_orders": True,
"live_confirmation": "Q60_LIVE_ACK",
}
self.assertFalse(qmt_builtin_strategy._orders_allowed(context))
def test_qmt_module_state_survives_contextinfo_rollback_between_bars(self):
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
first_context = FakeQmtContext(
qmt_histories,
trade_mode="backtest",
bar_time=datetime.datetime(2026, 7, 20, 15, 0),
)
harness = FakeQmtHarness(first_context).install(qmt_builtin_strategy)
qmt_builtin_strategy.init(first_context)
self.assertFalse(
any(name.startswith("q60_") for name in vars(first_context))
)
self.assertIsNone(qmt_builtin_strategy.handlebar(first_context))
# QMT documents that ContextInfo user attributes may roll back between
# handlebar calls. Rebuild a clean platform context to model that
# boundary; the module-global clock must still recognize a new week.
rebuilt_context = FakeQmtContext(
qmt_histories,
trade_mode="backtest",
bar_time=datetime.datetime(2026, 7, 27, 15, 0),
)
self.assertFalse(
any(name.startswith("q60_") for name in vars(rebuilt_context))
)
plan = qmt_builtin_strategy.handlebar(rebuilt_context)
self.assertIsNotNone(plan)
self.assertGreater(len(harness.orders), 0)
self.assertIs(qmt_builtin_strategy.g.last_plan, plan)
def test_qmt_backtest_submits_once_per_iso_week(self):
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
context = FakeQmtContext(qmt_histories, trade_mode="backtest")
harness = FakeQmtHarness(context)
self.assertIsNone(harness.run(qmt_builtin_strategy))
context._bar_time += datetime.timedelta(days=7)
first = qmt_builtin_strategy.handlebar(context)
first_count = len(harness.orders)
second = qmt_builtin_strategy.handlebar(context)
self.assertIsNotNone(first)
self.assertGreater(first_count, 0)
self.assertIsNone(second)
self.assertEqual(len(harness.orders), first_count)
first_ids = {item["user_order_id"] for item in harness.orders}
context._bar_time += datetime.timedelta(days=7)
third = qmt_builtin_strategy.handlebar(context)
second_ids = {
item["user_order_id"] for item in harness.orders[first_count:]
}
self.assertIsNotNone(third)
self.assertTrue(first_ids.isdisjoint(second_ids))
self.assertTrue(all(len(value.encode("ascii")) <= 24 for value in second_ids))
def test_qmt_incomplete_history_aborts_without_passorder(self):
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
qmt_histories["300750.SZ"] = qmt_histories["300750.SZ"][1:]
context = FakeQmtContext(qmt_histories, trade_mode="backtest")
harness = FakeQmtHarness(context)
self.assertIsNone(harness.run(qmt_builtin_strategy))
context._bar_time += datetime.timedelta(days=7)
with self.assertRaises(qmt_builtin_strategy.PriceHistoryUnavailable):
qmt_builtin_strategy.handlebar(context)
self.assertEqual(harness.orders, [])
def test_qmt_unmanaged_position_aborts_without_passorder(self):
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
position = type(
"Position",
(),
{
"stock_code": "601318.SH",
"volume": 1000,
"can_use_volume": 1000,
},
)()
context = FakeQmtContext(
qmt_histories,
positions=[position],
trade_mode="backtest",
)
harness = FakeQmtHarness(context)
self.assertIsNone(harness.run(qmt_builtin_strategy))
qmt_builtin_strategy.g.config["universe_mode"] = "fixed"
context._bar_time += datetime.timedelta(days=7)
with self.assertRaises(qmt_builtin_strategy.UnmanagedPositionError):
qmt_builtin_strategy.handlebar(context)
self.assertEqual(harness.orders, [])
def test_qmt_minute_driver_is_rejected_before_orders(self):
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
context = FakeQmtContext(
qmt_histories,
trade_mode="backtest",
params={"period": "1m"},
)
harness = FakeQmtHarness(context)
with self.assertRaises(qmt_builtin_strategy.UnsupportedStrategyPeriod):
harness.run(qmt_builtin_strategy)
self.assertEqual(harness.orders, [])
def test_qmt_builtin_do_back_test_flag_enables_backtest_orders(self):
qmt_histories = {
symbol.replace(".XSHE", ".SZ").replace(".XSHG", ".SH"): values
for symbol, values in price_histories().items()
}
context = FakeQmtContext(qmt_histories, trade_mode="backtest")
del context.trade_mode
context._param.pop("trade_mode")
context.do_back_test = True
harness = FakeQmtHarness(context)
self.assertIsNone(harness.run(qmt_builtin_strategy))
context._bar_time += datetime.timedelta(days=7)
plan = qmt_builtin_strategy.handlebar(context)
self.assertIsNotNone(plan)
self.assertGreater(len(harness.orders), 0)
def test_qmt_source_is_ascii_despite_gbk_header(self):
payload = (ROOT / "platforms/qmt_builtin_strategy.py").read_bytes()
payload.decode("ascii")
self.assertTrue(payload.startswith(b"# coding: gbk"))
if __name__ == "__main__":
unittest.main()