import unittest from quant60.portable_core import ( build_rebalance_plan, capped_target_weights, momentum_score, normalize_symbol, order_deltas, target_quantities, ) class PortableCoreTests(unittest.TestCase): def test_symbol_round_trip(self): inputs = [ ("600000.XSHG", "600000.SH", "SH600000"), ("000001.XSHE", "000001.SZ", "SZ000001"), ("830799.XBSE", "830799.BJ", "BJ830799"), ] for canonical, qmt, qlib in inputs: self.assertEqual(normalize_symbol(qmt), canonical) self.assertEqual(normalize_symbol(qlib), canonical) self.assertEqual(normalize_symbol(canonical, "qmt"), qmt) self.assertEqual(normalize_symbol(canonical, "qlib"), qlib) def test_momentum_uses_latest_completed_bar(self): self.assertAlmostEqual(momentum_score([10, 11, 12], 2, 0), 0.2) self.assertAlmostEqual(momentum_score([10, 11, 12], 1, 1), 0.1) def test_caps_and_cash_buffer_are_not_double_counted(self): weights = capped_target_weights( {"600000.XSHG": 3, "000001.XSHE": 2, "300750.XSHE": 1}, max_weight=0.4, gross_target=0.95, ) self.assertAlmostEqual(sum(weights.values()), 0.95) self.assertLessEqual(max(weights.values()), 0.4 + 1e-12) quantities = target_quantities( weights, {symbol: 10 for symbol in weights}, equity=1_000_000, lot_size=100, cash_buffer=0.02, ) invested = sum(quantity * 10 for quantity in quantities.values()) self.assertGreater(invested, 930_000) self.assertLessEqual(invested, 980_000) def test_non_finite_or_out_of_range_portfolio_inputs_are_rejected(self): for value in (float("nan"), float("inf"), float("-inf")): with self.subTest(kind="score", value=value): with self.assertRaises(ValueError): capped_target_weights({"600000.SH": value}) with self.subTest(kind="weight", value=value): with self.assertRaises(ValueError): target_quantities( {"600000.SH": value}, {"600000.SH": 10.0}, 100_000, ) with self.subTest(kind="equity", value=value): with self.assertRaises(ValueError): target_quantities( {"600000.SH": 0.5}, {"600000.SH": 10.0}, value, ) for max_weight, gross_target in ((1.1, 0.9), (0.2, 1.1)): with self.subTest(max_weight=max_weight, gross_target=gross_target): with self.assertRaises(ValueError): capped_target_weights( {"600000.SH": 1.0}, max_weight=max_weight, gross_target=gross_target, ) def test_sell_delta_respects_sellable(self): deltas = order_deltas( {"600000.XSHG": 0}, {"600000.XSHG": 1000}, {"600000.XSHG": 300}, lot_size=100, ) self.assertEqual(deltas, {"600000.XSHG": -300}) def test_star_targets_and_partial_orders_respect_200_share_minimum(self): self.assertEqual( target_quantities( {"688301.XSHG": 1.0}, {"688301.XSHG": 300.0}, equity=50_000, lot_size=100, cash_buffer=0.02, ), {"688301.XSHG": 0}, ) self.assertEqual( target_quantities( {"688301.XSHG": 1.0}, {"688301.XSHG": 200.0}, equity=50_000, lot_size=100, cash_buffer=0.02, ), {"688301.XSHG": 200}, ) self.assertEqual( order_deltas( {"688301.XSHG": 300}, {"688301.XSHG": 200}, {"688301.XSHG": 200}, lot_size=100, ), {}, ) self.assertEqual( order_deltas( {"688301.XSHG": 1300}, {"688301.XSHG": 1400}, {"688301.XSHG": 1400}, lot_size=100, ), {}, ) def test_complete_odd_lot_liquidation_is_preserved(self): self.assertEqual( order_deltas( {"600000.XSHG": 0}, {"600000.XSHG": 1050}, {"600000.XSHG": 1050}, lot_size=100, ), {"600000.XSHG": -1050}, ) self.assertEqual( order_deltas( {"688301.XSHG": 0}, {"688301.XSHG": 150}, {"688301.XSHG": 150}, lot_size=100, ), {"688301.XSHG": -150}, ) def test_complete_plan_is_deterministic(self): histories = { "600000.SH": [10 + index * 0.1 for index in range(25)], "000001.SZ": [10 + index * 0.05 for index in range(25)], } arguments = dict( price_history=histories, current={}, sellable={}, equity=100_000, lookback=20, skip=0, top_n=2, max_weight=0.5, gross_target=0.95, cash_buffer=0.02, lot_size=100, ) self.assertEqual( build_rebalance_plan(**arguments), build_rebalance_plan(**arguments), ) if __name__ == "__main__": unittest.main()