feat: preserve Quant OS target-package vertical slice

This commit is contained in:
2026-07-30 21:27:44 +08:00
parent 00efeb7ec2
commit 919c64c679
42 changed files with 15454 additions and 404 deletions
+35 -10
View File
@@ -2,25 +2,50 @@
"artifacts": {
"joinquant": {
"core": "src/quant60/portable_core.py",
"core_sha256": "af29e4e5e10705cf6aff89874394c1e80d3780f093c19d90ed53ef9d0188a934",
"effective_config_sha256": "3ef9b9efda9ec6cc5f57a5daca57cab3b68242e0368f18fdaaf9e249bdaa7873",
"core_sha256": "f9de9f18e4008bb0fc80edb0b21cae6ea06a61828d89e9b7c11057f98225fc19",
"effective_config_sha256": "d4ae2882e9b5d8d673e7c62c0dc54def7c20221c358ac091ee6529bf0413df0b",
"output": "dist/joinquant_strategy.py",
"output_sha256": "22ddb0546beb3bd4e4bbedecb271452111324c3903e45be1f4ac3d39f38cce4f",
"output_sha256": "be4ca1d5764416b7c8e6c206121c0f0bd8219e7317679d79bc72a95492286b1a",
"wrapper": "platforms/joinquant_strategy.py",
"wrapper_sha256": "b5a8e3b0a774a0f45f52f6bcccbe786e234913a1a4cf5b68b85e2bd989ee9294"
"wrapper_sha256": "0c672b64665b7c1d4409ba288b00d1608e976800af3a4f9a6467767ce4def592"
},
"qmt_builtin": {
"core": "src/quant60/portable_core.py",
"core_sha256": "af29e4e5e10705cf6aff89874394c1e80d3780f093c19d90ed53ef9d0188a934",
"effective_config_sha256": "11f189ea5c9d4eaddeb6394dc342abc871941cb3395d7ff79aae1275ce2ad98f",
"core_sha256": "f9de9f18e4008bb0fc80edb0b21cae6ea06a61828d89e9b7c11057f98225fc19",
"effective_config_sha256": "7e4739937e4e99e5b58154f53271d8d3bfaed47d8d1d5adc747101babde827f5",
"output": "dist/qmt_builtin_strategy.py",
"output_sha256": "ad503e5ebde60a5deb0ec86334c04d4be2d77ab91d9607d4fe88e2119f22d5dc",
"output_sha256": "483877b7ea55c8e0f5e4e47b48a156b3add6cf0a8085011414c5c600913939b5",
"wrapper": "platforms/qmt_builtin_strategy.py",
"wrapper_sha256": "efb931bbd04d0abf0a576f2422a13403fd4d659dccf59f8170e8b13b1bafa640"
"wrapper_sha256": "a1e53dc22ebd9bb01be43675786151606bbed273d8ab883fb933461e379d2727"
}
},
"baseline_config": "configs/baseline.json",
"baseline_config_sha256": "939a7df386b06bc82869940572c35544e9361913b94d9e03027411e51b1006e1",
"portable_core_sha256": "af29e4e5e10705cf6aff89874394c1e80d3780f093c19d90ed53ef9d0188a934",
"schema_version": 1
"decision_mode": "portable_momentum_smoke",
"joinquant_observation": {
"eod_api_policy": "best_effort_get_orders_get_trades",
"eod_chunk_characters": 1800,
"eod_chunk_encoding": "base64-canonical-json",
"events": [
"INIT",
"TARGET_PACKAGE_NOOP",
"TARGET_PACKAGE_HIT",
"PLATFORM_BINDING_INPUT",
"TARGET_PACKAGE_PLAN",
"SKIPPED_ORDER",
"ORDER_REQUEST",
"ORDER_RETURN",
"EOD_STATUS"
],
"fail_soft": true,
"prefix": "QUANT60_JOINQUANT_EVIDENCE_V1 ",
"schema_version": "1.0",
"target_price_source": "CURRENT_DATA_DAY_OPEN"
},
"portable_core_sha256": "f9de9f18e4008bb0fc80edb0b21cae6ea06a61828d89e9b7c11057f98225fc19",
"schema_version": 1,
"target_package_count": 0,
"target_package_ids": [],
"target_package_sha256": [],
"target_package_tape_sha256": null
}
+1225 -24
View File
File diff suppressed because it is too large Load Diff
+851 -17
View File
@@ -3,6 +3,8 @@
# __PORTABLE_CORE_BUNDLE_START__
# Inlined by tools/bundle_platforms.py; do not edit this region.
import hashlib
import json
import math
import re
@@ -26,6 +28,45 @@ _SHORT_EXCHANGES = {
"XBSE": "BJ",
}
_TARGET_PACKAGE_FIELDS = {
"schema_version",
"package_type",
"package_id",
"decision_id",
"release",
"evidence_class",
"model_id",
"signal_as_of",
"next_session",
"model_sha256",
"data_sha256",
"feature_sha256",
"risk_sha256",
"cost_sha256",
"optimizer_sha256",
"config_sha256",
"source_sha256",
"target_weights",
"universe",
"constraints",
"package_sha256",
}
_TARGET_PACKAGE_HASH_FIELDS = (
"model_sha256",
"data_sha256",
"feature_sha256",
"risk_sha256",
"cost_sha256",
"optimizer_sha256",
"config_sha256",
"source_sha256",
)
class TargetPackageLookupMiss(ValueError, LookupError):
"""An already verified tape has no exact requested decision."""
def _sum_numeric(values):
"""Sum numerics without trusting a hosted runtime's global ``sum`` name."""
@@ -36,6 +77,15 @@ def _sum_numeric(values):
return total
def _any_true(values):
"""Return built-in ``any`` semantics without trusting hosted globals."""
for value in values:
if value:
return True
return False
def _infer_exchange(code):
if code.startswith(("4", "8")):
return "XBSE"
@@ -88,6 +138,396 @@ def convert_symbol(symbol, target="canonical"):
return normalize_symbol(symbol, target)
def _canonical_json(value):
"""Match ``quant60.ledger.canonical_json`` for plain JSON values."""
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
def _json_copy(value, name):
try:
return json.loads(_canonical_json(value))
except (TypeError, ValueError) as error:
raise ValueError("%s must contain finite JSON values only" % name) from error
def _canonical_mapping(value, name, value_validator):
if not hasattr(value, "items"):
raise TypeError("%s must be a mapping" % name)
output = {}
for raw_symbol, raw_value in value.items():
symbol = normalize_symbol(raw_symbol)
if symbol in output:
raise ValueError(
"%s contains duplicate normalized symbol %s" % (name, symbol)
)
output[symbol] = value_validator(raw_value, "%s.%s" % (name, symbol))
return dict((symbol, output[symbol]) for symbol in sorted(output))
def _finite_non_negative(value, name):
if isinstance(value, bool):
raise ValueError("%s must be a finite non-negative number" % name)
try:
numeric = float(value)
except (TypeError, ValueError):
raise ValueError("%s must be a finite non-negative number" % name)
if not math.isfinite(numeric) or numeric < 0.0:
raise ValueError("%s must be a finite non-negative number" % name)
return numeric
def _non_negative_quantity(value, name):
numeric = _finite_non_negative(value, name)
if numeric != math.floor(numeric):
raise ValueError("%s must be a whole-share quantity" % name)
return int(numeric)
def _positive_price(value, name):
numeric = _finite_non_negative(value, name)
if numeric <= 0.0:
raise ValueError("%s must be positive" % name)
return numeric
def _optional_non_empty_string(value, name):
if value is None:
return None
if not isinstance(value, str) or not value.strip():
raise ValueError("%s must be a non-empty string when provided" % name)
return value
def _sha256_hex(value, name, allow_none=False):
if value is None and allow_none:
return None
if (
not isinstance(value, str)
or len(value) != 64
or _any_true(
character not in "0123456789abcdef"
for character in value
)
):
raise ValueError(
"%s must be a 64-character lowercase SHA-256 digest" % name
)
if value == "0" * 64:
raise ValueError("%s cannot be an all-zero placeholder" % name)
return value
def _reject_quantity_fields(value, path="$"):
if hasattr(value, "items"):
for raw_key, child in value.items():
key = str(raw_key)
normalized = key.lower().replace("-", "_")
if "quantity" in normalized or "quantities" in normalized:
raise ValueError(
"%s.%s: quantities do not belong in TargetPackageV1"
% (path, key)
)
_reject_quantity_fields(child, "%s.%s" % (path, key))
elif isinstance(value, list):
for index, child in enumerate(value):
_reject_quantity_fields(child, "%s[%d]" % (path, index))
def _verify_identifier(value, name):
if (
not isinstance(value, str)
or re.match(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$", value) is None
):
raise ValueError("%s is not a canonical identifier" % name)
return value
def _verify_target_package_clock(signal_as_of, next_session):
if (
not isinstance(signal_as_of, str)
or re.match(
r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T15:00:00\+08:00$",
signal_as_of,
)
is None
):
raise ValueError("signal_as_of must be canonical China-market close")
if (
not isinstance(next_session, str)
or re.match(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$", next_session) is None
):
raise ValueError("next_session must use canonical YYYY-MM-DD")
if next_session <= signal_as_of[:10]:
raise ValueError("next_session must follow signal_as_of")
def _target_package_content_sha256(package):
body = dict(package)
body.pop("package_sha256", None)
_reject_quantity_fields(body)
try:
payload = _canonical_json(body).encode("utf-8")
except (TypeError, ValueError) as error:
raise ValueError(
"target package must contain finite JSON values only"
) from error
return hashlib.sha256(payload).hexdigest()
def verify_target_package(
package,
expected_signal_as_of=None,
expected_next_session=None,
expected_decision_id=None,
expected_package_sha256=None,
):
"""Verify the platform-critical subset of a sealed ``TargetPackageV1``.
This intentionally duplicates a small, Python-3.6-compatible safety
boundary from the richer local validator. Hosted runtimes must verify the
embedded package again instead of trusting the bundling process.
"""
if not hasattr(package, "items"):
raise TypeError("target package must be a mapping")
candidate = _json_copy(package, "target package")
_reject_quantity_fields(candidate)
fields = set(candidate)
if fields != _TARGET_PACKAGE_FIELDS:
missing = sorted(_TARGET_PACKAGE_FIELDS - fields)
unknown = sorted(fields - _TARGET_PACKAGE_FIELDS)
raise ValueError(
"target package fields mismatch; missing=%s, unknown=%s"
% (missing, unknown)
)
if candidate["schema_version"] != "1.0":
raise ValueError("unsupported target package schema_version")
if candidate["package_type"] != "TargetPackageV1":
raise ValueError("unsupported target package type")
for name in (
"package_id",
"decision_id",
"release",
"evidence_class",
"model_id",
):
_verify_identifier(candidate[name], name)
_verify_target_package_clock(
candidate["signal_as_of"], candidate["next_session"]
)
for name in _TARGET_PACKAGE_HASH_FIELDS:
_sha256_hex(candidate[name], name)
_sha256_hex(candidate["package_sha256"], "package_sha256")
raw_weights = candidate["target_weights"]
if not hasattr(raw_weights, "items") or not raw_weights:
raise ValueError("target_weights must be a non-empty mapping")
weights = {}
for raw_symbol, raw_weight in raw_weights.items():
symbol = normalize_symbol(raw_symbol)
if symbol != raw_symbol:
raise ValueError(
"target_weights symbol must be canonical: %s" % raw_symbol
)
if symbol in weights:
raise ValueError("duplicate target weight for %s" % symbol)
weight = _finite_non_negative(
raw_weight, "target_weights.%s" % symbol
)
if weight > 1.0:
raise ValueError("target weight exceeds one for %s" % symbol)
weights[symbol] = weight
gross = math.fsum(weights.values())
if gross > 1.0 + 1e-12:
raise ValueError("target_weights gross exceeds one")
universe = candidate["universe"]
if not hasattr(universe, "items") or not universe:
raise ValueError("universe must be a non-empty mapping")
canonical_universe = {}
for raw_symbol, raw_state in universe.items():
symbol = normalize_symbol(raw_symbol)
if symbol != raw_symbol:
raise ValueError("universe symbol must be canonical: %s" % raw_symbol)
if symbol in canonical_universe:
raise ValueError("duplicate universe symbol %s" % symbol)
if not hasattr(raw_state, "items"):
raise ValueError("universe state must be a mapping for %s" % symbol)
state = raw_state.get("state")
if state not in (
"openable",
"hold_only",
"sell_only",
"frozen",
"excluded",
):
raise ValueError("unsupported universe state for %s" % symbol)
canonical_universe[symbol] = state
missing_universe = sorted(set(weights) - set(canonical_universe))
if missing_universe:
raise ValueError(
"target symbols are absent from universe: %s" % missing_universe
)
for symbol, weight in weights.items():
if canonical_universe[symbol] == "excluded" and weight > 1e-12:
raise ValueError("excluded symbol has positive weight: %s" % symbol)
constraints = candidate["constraints"]
if not hasattr(constraints, "items"):
raise ValueError("constraints must be a mapping")
if constraints.get("long_only") is not True:
raise ValueError("TargetPackageV1 must be long-only")
declared_gross = _finite_non_negative(
constraints.get("actual_gross_weight"),
"constraints.actual_gross_weight",
)
max_gross = _finite_non_negative(
constraints.get("max_gross_weight"),
"constraints.max_gross_weight",
)
max_single = _finite_non_negative(
constraints.get("max_single_weight"),
"constraints.max_single_weight",
)
if abs(declared_gross - gross) > 1e-12:
raise ValueError("declared gross does not equal target_weights gross")
if gross > max_gross + 1e-12 or max_gross > 1.0:
raise ValueError("target_weights exceed max_gross_weight")
if max_single <= 0.0 or max_single > 1.0:
raise ValueError("invalid max_single_weight")
if _any_true(
weight > max_single + 1e-12 for weight in weights.values()
):
raise ValueError("target_weights exceed max_single_weight")
model_claim = " ".join(
(
candidate["release"],
candidate["evidence_class"],
)
).lower()
if (
"baseline" in model_claim
and "portable-momentum" in candidate["model_id"].lower()
):
raise ValueError(
"portable-momentum cannot claim a baseline target package"
)
expected_hash = _target_package_content_sha256(candidate)
if candidate["package_sha256"] != expected_hash:
raise ValueError("target package content hash mismatch")
if (
expected_signal_as_of is not None
and candidate["signal_as_of"] != expected_signal_as_of
):
raise ValueError("target package signal_as_of does not match request")
if (
expected_next_session is not None
and candidate["next_session"] != expected_next_session
):
raise ValueError("target package next_session does not match request")
if (
expected_decision_id is not None
and candidate["decision_id"] != expected_decision_id
):
raise ValueError("target package decision_id does not match request")
if expected_package_sha256 is not None:
_sha256_hex(
expected_package_sha256,
"expected_package_sha256",
)
if candidate["package_sha256"] != expected_package_sha256:
raise ValueError("target package digest does not match request")
return candidate
def target_package_tape_sha256(packages):
"""Hash the exact ordered canonical JSON package array."""
if not isinstance(packages, (list, tuple)):
raise TypeError("target package tape must be a list or tuple")
try:
payload = _canonical_json(list(packages)).encode("utf-8")
except (TypeError, ValueError) as error:
raise ValueError(
"target package tape must contain finite JSON values only"
) from error
return hashlib.sha256(payload).hexdigest()
def lookup_target_package_exact(
packages,
signal_as_of,
next_session,
decision_id=None,
expected_tape_sha256=None,
expected_count=None,
):
"""Verify a complete tape, then return one exact dual-clock decision."""
if not isinstance(packages, (list, tuple)) or not packages:
raise ValueError("target package tape must be a non-empty list or tuple")
_verify_target_package_clock(signal_as_of, next_session)
decision_id = _optional_non_empty_string(decision_id, "decision_id")
if expected_count is not None:
if (
isinstance(expected_count, bool)
or not isinstance(expected_count, int)
or expected_count < 0
):
raise ValueError("expected_count must be a non-negative integer")
if len(packages) != expected_count:
raise ValueError("target package tape count mismatch")
if expected_tape_sha256 is not None:
_sha256_hex(expected_tape_sha256, "expected_tape_sha256")
if target_package_tape_sha256(packages) != expected_tape_sha256:
raise ValueError("target package tape digest mismatch")
verified = []
package_ids = set()
decision_ids = set()
clock_keys = set()
for raw_package in packages:
package = verify_target_package(raw_package)
clock_key = (package["signal_as_of"], package["next_session"])
if package["package_id"] in package_ids:
raise ValueError("duplicate target package_id")
if package["decision_id"] in decision_ids:
raise ValueError("duplicate target decision_id")
if clock_key in clock_keys:
raise ValueError("duplicate target package clock")
package_ids.add(package["package_id"])
decision_ids.add(package["decision_id"])
clock_keys.add(clock_key)
verified.append(package)
matches = [
package
for package in verified
if package["signal_as_of"] == signal_as_of
and package["next_session"] == next_session
]
if not matches:
raise TargetPackageLookupMiss(
"no target package for exact signal_as_of/next_session clock"
)
if len(matches) != 1:
raise ValueError("ambiguous exact target package decision")
if (
decision_id is not None
and matches[0]["decision_id"] != decision_id
):
raise ValueError("exact target package decision_id mismatch")
return _json_copy(matches[0], "target package")
def momentum_score(prices, lookback=20, skip=0):
"""Return trailing simple momentum, or ``None`` if history is insufficient.
@@ -314,6 +754,108 @@ def order_deltas(
return result
def build_target_weight_plan(
target_weights,
prices,
current,
sellable,
equity,
cash_buffer=0.02,
lot_size=100,
decision_id=None,
package_sha256=None,
):
"""Bind canonical post-risk weights to one account without recomputing Alpha.
``prices`` must cover exactly every target or currently managed symbol.
Missing prices fail closed even for a held symbol whose desired weight is
zero. A missing ``sellable`` entry is safe and means zero sellable shares.
"""
weights = _canonical_mapping(
target_weights,
"target_weights",
_finite_non_negative,
)
gross = math.fsum(weights.values())
if _any_true(weight > 1.0 for weight in weights.values()):
raise ValueError("target weight must not exceed one")
if gross > 1.0 + 1e-12:
raise ValueError("target weight gross must not exceed one")
canonical_current = _canonical_mapping(
current,
"current",
_non_negative_quantity,
)
canonical_sellable = _canonical_mapping(
sellable,
"sellable",
_non_negative_quantity,
)
unknown_sellable = sorted(set(canonical_sellable) - set(canonical_current))
if unknown_sellable:
raise ValueError(
"sellable contains symbols absent from current: %s"
% unknown_sellable
)
for symbol, quantity in canonical_sellable.items():
if quantity > canonical_current[symbol]:
raise ValueError(
"sellable exceeds current quantity for %s" % symbol
)
canonical_prices = _canonical_mapping(
prices,
"prices",
_positive_price,
)
managed_symbols = set(weights) | set(canonical_current)
price_symbols = set(canonical_prices)
missing_prices = sorted(managed_symbols - price_symbols)
extra_prices = sorted(price_symbols - managed_symbols)
if missing_prices or extra_prices:
if missing_prices:
raise KeyError(
"prices do not cover managed symbols: missing=%s"
% missing_prices
)
raise ValueError(
"prices contain unmanaged symbols: extra=%s" % extra_prices
)
complete_weights = dict(
(symbol, weights.get(symbol, 0.0))
for symbol in sorted(managed_symbols)
)
targets = target_quantities(
complete_weights,
canonical_prices,
equity,
lot_size,
cash_buffer,
)
orders = order_deltas(
targets,
canonical_current,
canonical_sellable,
lot_size,
)
decision_id = _optional_non_empty_string(decision_id, "decision_id")
package_sha256 = _sha256_hex(
package_sha256,
"package_sha256",
allow_none=True,
)
return {
"decision_id": decision_id,
"package_sha256": package_sha256,
"weights": complete_weights,
"targets": targets,
"orders": orders,
}
def build_rebalance_plan(
price_history,
current,
@@ -359,14 +901,19 @@ def build_rebalance_plan(
__all__ = [
"build_rebalance_plan",
"build_target_weight_plan",
"capped_target_weights",
"convert_symbol",
"lookup_target_package_exact",
"momentum_score",
"normalize_symbol",
"order_deltas",
"rank_momentum",
"round_board_lot",
"TargetPackageLookupMiss",
"target_package_tape_sha256",
"target_quantities",
"verify_target_package",
]
# __PORTABLE_CORE_BUNDLE_END__
@@ -399,6 +946,9 @@ g = G()
# __BASELINE_CONFIG_START__
# Generated from configs/baseline.json; do not edit this region.
CONFIG = {'account_id': 'test',
'decision_mode': 'portable_momentum_smoke',
'target_package_count': 0,
'target_package_tape_sha256': None,
'universe_mode': 'pit_index',
'index_symbol': '000905.XSHG',
'qmt_sector_name': '\u4e2d\u8bc1500',
@@ -421,6 +971,77 @@ CONFIG = {'account_id': 'test',
'rebalance_schedule': 'weekly_first_close'}
# __BASELINE_CONFIG_END__
# __TARGET_PACKAGE_TAPE_START__
# Generated by tools/bundle_platforms.py; do not edit this region.
TARGET_PACKAGES = []
# __TARGET_PACKAGE_TAPE_END__
MOMENTUM_SMOKE_MODE = "portable_momentum_smoke"
TARGET_PACKAGE_MODE = "target_package"
def _validated_target_packages(config):
mode = config.get("decision_mode")
if mode not in (MOMENTUM_SMOKE_MODE, TARGET_PACKAGE_MODE):
raise UnsupportedStrategyPeriod("unsupported decision_mode")
if not isinstance(TARGET_PACKAGES, list):
raise UnsupportedStrategyPeriod(
"TARGET_PACKAGES must be a list"
)
expected_count = config.get("target_package_count")
expected_sha256 = config.get("target_package_tape_sha256")
if mode == MOMENTUM_SMOKE_MODE:
if (
type(expected_count) is not int
or expected_count != 0
or expected_sha256 is not None
or TARGET_PACKAGES
):
raise UnsupportedStrategyPeriod(
"portable_momentum_smoke cannot carry target packages"
)
return []
if type(expected_count) is not int or expected_count <= 0:
raise UnsupportedStrategyPeriod(
"target_package mode requires a positive target_package_count"
)
if expected_count != len(TARGET_PACKAGES):
raise UnsupportedStrategyPeriod(
"target package count mismatch"
)
actual_sha256 = target_package_tape_sha256(TARGET_PACKAGES)
if expected_sha256 != actual_sha256:
raise UnsupportedStrategyPeriod(
"target package tape sha256 mismatch"
)
verified = []
package_ids = set()
decision_ids = set()
clocks = set()
signal_clocks = set()
for raw_package in TARGET_PACKAGES:
package = verify_target_package(raw_package)
package_id = package["package_id"]
decision_id = package["decision_id"]
clock = (package["signal_as_of"], package["next_session"])
signal_as_of = package["signal_as_of"]
if (
package_id in package_ids
or decision_id in decision_ids
or clock in clocks
or signal_as_of in signal_clocks
):
raise UnsupportedStrategyPeriod(
"duplicate or ambiguous target package decision identity"
)
package_ids.add(package_id)
decision_ids.add(decision_id)
clocks.add(clock)
signal_clocks.add(signal_as_of)
verified.append(package)
return verified
def _param(ContextInfo, name, default=None):
params = getattr(ContextInfo, "_param", {})
@@ -442,22 +1063,25 @@ def init(ContextInfo):
override = _param(ContextInfo, "q60_" + key, None)
if override is not None:
g.config[key] = override
if (
g.config.get("rebalance_schedule")
!= "weekly_first_close"
):
raise UnsupportedStrategyPeriod(
"unsupported rebalance_schedule"
)
if g.config.get("universe_mode") not in ("pit_index", "fixed"):
raise UnsupportedStrategyPeriod("unsupported universe_mode")
if (
g.config.get("universe_mode") == "pit_index"
and not g.config.get("dedicated_account_required")
):
raise UnsupportedStrategyPeriod(
"pit_index mode requires a dedicated account"
)
g.target_packages = _validated_target_packages(g.config)
g.decision_mode = g.config["decision_mode"]
if g.config["decision_mode"] == MOMENTUM_SMOKE_MODE:
if (
g.config.get("rebalance_schedule")
!= "weekly_first_close"
):
raise UnsupportedStrategyPeriod(
"unsupported rebalance_schedule"
)
if g.config.get("universe_mode") not in ("pit_index", "fixed"):
raise UnsupportedStrategyPeriod("unsupported universe_mode")
if (
g.config.get("universe_mode") == "pit_index"
and not g.config.get("dedicated_account_required")
):
raise UnsupportedStrategyPeriod(
"pit_index mode requires a dedicated account"
)
set_commission = getattr(ContextInfo, "set_commission", None)
set_slippage = getattr(ContextInfo, "set_slippage", None)
if not callable(set_commission) or not callable(set_slippage):
@@ -487,6 +1111,7 @@ def init(ContextInfo):
g.last_plan = None
g.last_universe = None
g.last_universe_as_of = None
g.last_target_signal_as_of = None
if (
g.config.get("universe_mode") == "fixed"
and hasattr(ContextInfo, "set_universe")
@@ -494,6 +1119,16 @@ def init(ContextInfo):
ContextInfo.set_universe(g.config["universe"])
def _decision_mode():
configured = g.config.get("decision_mode")
frozen = getattr(g, "decision_mode", configured)
if configured != frozen:
raise UnsupportedStrategyPeriod(
"decision_mode cannot change after init"
)
return frozen
def _bar_datetime(ContextInfo):
timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
return datetime.datetime.fromtimestamp(float(timetag) / 1000.0)
@@ -670,7 +1305,10 @@ def _portfolio(ContextInfo, managed):
convert_symbol(symbol, "canonical")
for symbol in managed
)
dynamic = g.config.get("universe_mode") == "pit_index"
dynamic = (
_decision_mode() == MOMENTUM_SMOKE_MODE
and g.config.get("universe_mode") == "pit_index"
)
for position in _trade_details(ContextInfo, "position") or []:
code = str(
_get_attr(position, ["stock_code", "m_strInstrumentID"], "")
@@ -716,6 +1354,8 @@ def _portfolio(ContextInfo, managed):
def compute_plan(ContextInfo, bar_time=None):
if _decision_mode() == TARGET_PACKAGE_MODE:
return _compute_target_package_plan(ContextInfo, bar_time)
if bar_time is None:
bar_time = _bar_datetime(ContextInfo)
membership_timetag = int(
@@ -748,6 +1388,136 @@ def compute_plan(ContextInfo, bar_time=None):
)
def _signal_as_of(bar_time):
if (
int(bar_time.hour) != 15
or int(bar_time.minute) != 0
or int(bar_time.second) != 0
or int(bar_time.microsecond) != 0
):
raise UnsupportedStrategyPeriod(
"target_package mode requires the completed 15:00 daily close"
)
return bar_time.strftime("%Y-%m-%dT15:00:00+08:00")
def _target_package_for_close(bar_time):
signal_as_of = _signal_as_of(bar_time)
config = g.config
packages = _validated_target_packages(config)
matches = [
package
for package in packages
if package["signal_as_of"] == signal_as_of
]
if not matches:
return None
if len(matches) != 1:
raise UnsupportedStrategyPeriod(
"ambiguous target package signal clock"
)
package = matches[0]
return lookup_target_package_exact(
packages,
signal_as_of,
package["next_session"],
decision_id=package["decision_id"],
expected_tape_sha256=config["target_package_tape_sha256"],
expected_count=config["target_package_count"],
)
def _target_close_prices(ContextInfo, bar_time, symbols):
platform_symbols = [
convert_symbol(symbol, "qmt")
for symbol in sorted(symbols)
]
raw = ContextInfo.get_market_data_ex(
fields=["close"],
stock_code=platform_symbols,
period="1d",
start_time="",
end_time=bar_time.strftime("%Y%m%d"),
count=1,
dividend_type="front_ratio",
fill_data=False,
subscribe=False,
)
if not hasattr(raw, "get"):
raise PriceHistoryUnavailable(
"target close query returned an invalid payload"
)
prices = {}
for symbol in platform_symbols:
values = _series_close(raw.get(symbol))
if len(values) != 1:
raise PriceHistoryUnavailable(
"%s needs one completed daily close" % symbol
)
try:
price = float(values[0])
except (TypeError, ValueError):
raise PriceHistoryUnavailable(
"%s contains a missing/non-numeric close" % symbol
)
if not math.isfinite(price) or price <= 0.0:
raise PriceHistoryUnavailable(
"%s contains a missing/non-positive close" % symbol
)
prices[convert_symbol(symbol, "canonical")] = price
return prices
def _target_lineage(package):
return {
"package_id": package["package_id"],
"package_sha256": package["package_sha256"],
"decision_id": package["decision_id"],
"signal_as_of": package["signal_as_of"],
"next_session": package["next_session"],
"release": package["release"],
"model_id": package["model_id"],
}
def _compute_target_package_plan(ContextInfo, bar_time=None):
if bar_time is None:
bar_time = _bar_datetime(ContextInfo)
package = _target_package_for_close(bar_time)
if package is None:
return None
managed = sorted(package["universe"])
current, sellable, equity = _portfolio(ContextInfo, managed)
if equity <= 0:
raise ValueError("QMT account equity must be positive")
plan = build_target_weight_plan(
target_weights=package["target_weights"],
prices=_target_close_prices(
ContextInfo,
bar_time,
set(package["target_weights"]) | set(current),
),
current=current,
sellable=sellable,
equity=equity,
cash_buffer=g.config["cash_buffer"],
lot_size=g.config["lot_size"],
decision_id=package["decision_id"],
package_sha256=package["package_sha256"],
)
plan["decision_mode"] = TARGET_PACKAGE_MODE
plan["lineage"] = _target_lineage(package)
plan["universe"] = package["universe"]
plan["execution_contract"] = {
"signal_price_source": "COMPLETED_DAILY_CLOSE",
"submit_trigger": "NEXT_BAR_FIRST_TICK",
"quick_trade": 0,
"declared_next_session": package["next_session"],
"real_platform_observed": False,
}
return plan
def _orders_allowed(ContextInfo):
# This hosted wrapper is deliberately backtest-only. Its cached account
# query and in-memory weekly marker cannot provide crash-safe live
@@ -810,12 +1580,76 @@ def _submit_delta(ContextInfo, canonical, delta, week_key):
)
def _submit_target_delta(ContextInfo, canonical, delta, package_sha256):
config = g.config
order_code = convert_symbol(canonical, "qmt")
operation = 23 if delta > 0 else 24
volume = abs(int(delta))
user_order_id = "q60t-%s-%s" % (
str(package_sha256)[:8],
order_code.split(".")[0],
)
function = globals()["passorder"]
arg_count = getattr(getattr(function, "__code__", None), "co_argcount", 11)
if arg_count <= 8:
function(
operation,
1101,
config["account_id"],
order_code,
5,
-1,
volume,
ContextInfo,
)
else:
function(
operation,
1101,
config["account_id"],
order_code,
5,
-1,
volume,
"quant60",
0,
user_order_id,
ContextInfo,
)
def handlebar(ContextInfo):
if not _is_backtest(ContextInfo) and hasattr(ContextInfo, "is_last_bar"):
if not ContextInfo.is_last_bar():
return None
bar_time = _bar_datetime(ContextInfo)
if _decision_mode() == TARGET_PACKAGE_MODE:
signal_as_of = _signal_as_of(bar_time)
if g.last_target_signal_as_of == signal_as_of:
return None
plan = compute_plan(ContextInfo, bar_time)
# Consume the clock before crossing passorder so a callback retry
# cannot duplicate an already accepted order prefix.
g.last_target_signal_as_of = signal_as_of
if plan is None:
return None
g.last_plan = plan
if not _orders_allowed(ContextInfo):
return plan
ordered = sorted(
plan["orders"].items(),
key=lambda item: item[1],
)
for canonical, delta in ordered:
if int(delta) != 0:
_submit_target_delta(
ContextInfo,
canonical,
delta,
plan["lineage"]["package_sha256"],
)
return plan
year, week, unused = bar_time.isocalendar()
del unused
week_key = (year, week)
+55
View File
@@ -0,0 +1,55 @@
{
"artifacts": {
"joinquant": {
"core": "src/quant60/portable_core.py",
"core_sha256": "f9de9f18e4008bb0fc80edb0b21cae6ea06a61828d89e9b7c11057f98225fc19",
"effective_config_sha256": "640980026f397df2e6d0f9e3884b58c7e2343e1be70ca1b629f6111b889ec7cb",
"output": "dist/target-package/joinquant_strategy.py",
"output_sha256": "0586930ddc483f4489f09b562f4d4232b458b703e6a1194af809b707211be8c4",
"wrapper": "platforms/joinquant_strategy.py",
"wrapper_sha256": "0c672b64665b7c1d4409ba288b00d1608e976800af3a4f9a6467767ce4def592"
},
"qmt_builtin": {
"core": "src/quant60/portable_core.py",
"core_sha256": "f9de9f18e4008bb0fc80edb0b21cae6ea06a61828d89e9b7c11057f98225fc19",
"effective_config_sha256": "ace13da661a2921e6bf75e48a574fd605fb6eef87c0025b89460082326a39d36",
"output": "dist/target-package/qmt_builtin_strategy.py",
"output_sha256": "21f634a9831341fef2d86a6df612d01c79121794a48e525da76df226dc0729ed",
"wrapper": "platforms/qmt_builtin_strategy.py",
"wrapper_sha256": "a1e53dc22ebd9bb01be43675786151606bbed273d8ab883fb933461e379d2727"
}
},
"baseline_config": "configs/baseline.json",
"baseline_config_sha256": "939a7df386b06bc82869940572c35544e9361913b94d9e03027411e51b1006e1",
"decision_mode": "target_package",
"joinquant_observation": {
"eod_api_policy": "best_effort_get_orders_get_trades",
"eod_chunk_characters": 1800,
"eod_chunk_encoding": "base64-canonical-json",
"events": [
"INIT",
"TARGET_PACKAGE_NOOP",
"TARGET_PACKAGE_HIT",
"PLATFORM_BINDING_INPUT",
"TARGET_PACKAGE_PLAN",
"SKIPPED_ORDER",
"ORDER_REQUEST",
"ORDER_RETURN",
"EOD_STATUS"
],
"fail_soft": true,
"prefix": "QUANT60_JOINQUANT_EVIDENCE_V1 ",
"schema_version": "1.0",
"target_price_source": "CURRENT_DATA_DAY_OPEN"
},
"portable_core_sha256": "f9de9f18e4008bb0fc80edb0b21cae6ea06a61828d89e9b7c11057f98225fc19",
"schema_version": 1,
"target_package_count": 1,
"target_package_ids": [
"TP-20240311-795dcfba"
],
"target_package_sha256": [
"baa15c8156aa6cb54d3775d0cd562613a9d0a4f15b0c39f2a60e0f8b8c6f1f3a"
],
"target_package_tape_sha256": "5383c376951637b8d4b1ca42fc6db80347abda74c0181c0f70d6c411fb97ea64"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff