feat: preserve Quant OS target-package vertical slice
This commit is contained in:
+684
-24
@@ -6,9 +6,18 @@ tool replaces the marked import block with the exact portable core source.
|
||||
"""
|
||||
|
||||
# __PORTABLE_CORE_BUNDLE_START__
|
||||
from quant60.portable_core import build_rebalance_plan, convert_symbol
|
||||
from quant60.portable_core import (
|
||||
build_rebalance_plan,
|
||||
build_target_weight_plan,
|
||||
convert_symbol,
|
||||
lookup_target_package_exact,
|
||||
target_package_tape_sha256,
|
||||
verify_target_package,
|
||||
)
|
||||
# __PORTABLE_CORE_BUNDLE_END__
|
||||
|
||||
import base64 as _q60_base64
|
||||
import json as _q60_json
|
||||
import math
|
||||
|
||||
|
||||
@@ -26,6 +35,9 @@ class ClockStateError(RuntimeError):
|
||||
|
||||
# __BASELINE_CONFIG_START__
|
||||
CONFIG = {
|
||||
"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": "中证500",
|
||||
@@ -56,21 +68,362 @@ CONFIG = {
|
||||
}
|
||||
# __BASELINE_CONFIG_END__
|
||||
|
||||
# __TARGET_PACKAGE_TAPE_START__
|
||||
TARGET_PACKAGES = []
|
||||
# __TARGET_PACKAGE_TAPE_END__
|
||||
|
||||
|
||||
MOMENTUM_SMOKE_MODE = "portable_momentum_smoke"
|
||||
TARGET_PACKAGE_MODE = "target_package"
|
||||
EVIDENCE_PREFIX = "QUANT60_JOINQUANT_EVIDENCE_V1 "
|
||||
EVIDENCE_CHUNK_CHARACTERS = 1800
|
||||
TARGET_PRICE_SOURCE = "CURRENT_DATA_DAY_OPEN"
|
||||
|
||||
|
||||
def _evidence_value(value, depth=0):
|
||||
"""Return stable JSON data without allowing observation to stop trading."""
|
||||
if depth > 8:
|
||||
return "<max-depth>"
|
||||
if value is None or isinstance(value, (bool, int, str)):
|
||||
return value
|
||||
if isinstance(value, float):
|
||||
return value if math.isfinite(value) else None
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
str(key): _evidence_value(item, depth + 1)
|
||||
for key, item in sorted(
|
||||
value.items(),
|
||||
key=lambda pair: str(pair[0]),
|
||||
)
|
||||
}
|
||||
if isinstance(value, (list, tuple)):
|
||||
return [_evidence_value(item, depth + 1) for item in value]
|
||||
isoformat = getattr(value, "isoformat", None)
|
||||
if callable(isoformat):
|
||||
try:
|
||||
return str(isoformat())
|
||||
except Exception:
|
||||
return "<isoformat-error>"
|
||||
try:
|
||||
return str(value)
|
||||
except Exception:
|
||||
return "<string-error>"
|
||||
|
||||
|
||||
def _evidence_context_time(context):
|
||||
try:
|
||||
return _evidence_value(getattr(context, "current_dt", None))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _emit_evidence(
|
||||
event,
|
||||
payload=None,
|
||||
context=None,
|
||||
chunk_index=None,
|
||||
chunk_count=None,
|
||||
):
|
||||
"""Print one prefixed canonical JSON record and always fail soft."""
|
||||
try:
|
||||
sequence = int(
|
||||
getattr(g, "quant60_evidence_sequence", 0) or 0
|
||||
) + 1
|
||||
g.quant60_evidence_sequence = sequence
|
||||
record = {
|
||||
"context_time": _evidence_context_time(context),
|
||||
"decision_mode": getattr(
|
||||
g,
|
||||
"quant60_decision_mode",
|
||||
CONFIG.get("decision_mode"),
|
||||
),
|
||||
"event": str(event),
|
||||
"payload": _evidence_value(payload or {}),
|
||||
"schema_version": "1.0",
|
||||
"sequence": sequence,
|
||||
"source": "joinquant_hosted",
|
||||
}
|
||||
if chunk_index is not None or chunk_count is not None:
|
||||
if (
|
||||
type(chunk_index) is not int
|
||||
or type(chunk_count) is not int
|
||||
or chunk_index <= 0
|
||||
or chunk_count <= 0
|
||||
or chunk_index > chunk_count
|
||||
):
|
||||
return None
|
||||
record["chunk_index"] = chunk_index
|
||||
record["chunk_count"] = chunk_count
|
||||
encoded = _q60_json.dumps(
|
||||
record,
|
||||
ensure_ascii=True,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
)
|
||||
print(EVIDENCE_PREFIX + encoded)
|
||||
except Exception:
|
||||
# Evidence is observational. A logging/serialization failure must
|
||||
# never alter an already verified decision or hosted order path.
|
||||
return None
|
||||
return record
|
||||
|
||||
|
||||
def _emit_chunked_evidence(event, payload, context=None):
|
||||
"""Emit reconstructable base64 chunks of one canonical JSON payload."""
|
||||
try:
|
||||
canonical = _q60_json.dumps(
|
||||
_evidence_value(payload),
|
||||
ensure_ascii=True,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode("ascii")
|
||||
encoded = _q60_base64.b64encode(canonical).decode("ascii")
|
||||
chunks = [
|
||||
encoded[index : index + EVIDENCE_CHUNK_CHARACTERS]
|
||||
for index in range(0, len(encoded), EVIDENCE_CHUNK_CHARACTERS)
|
||||
] or [""]
|
||||
count = len(chunks)
|
||||
for index, content in enumerate(chunks, 1):
|
||||
_emit_evidence(
|
||||
event,
|
||||
{
|
||||
"chunk_encoding": "base64-canonical-json",
|
||||
"content": content,
|
||||
"encoded_length": len(encoded),
|
||||
},
|
||||
context,
|
||||
chunk_index=index,
|
||||
chunk_count=count,
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
return count
|
||||
|
||||
|
||||
def _observe_object(value, fields):
|
||||
if value is None:
|
||||
return None
|
||||
output = {"object_type": type(value).__name__}
|
||||
for name in fields:
|
||||
try:
|
||||
item = getattr(value, name)
|
||||
except Exception:
|
||||
continue
|
||||
if callable(item):
|
||||
continue
|
||||
output[name] = _evidence_value(item)
|
||||
return output
|
||||
|
||||
|
||||
def _observe_order(value):
|
||||
return _observe_object(
|
||||
value,
|
||||
(
|
||||
"order_id",
|
||||
"add_time",
|
||||
"security",
|
||||
"amount",
|
||||
"filled",
|
||||
"price",
|
||||
"avg_cost",
|
||||
"side",
|
||||
"action",
|
||||
"status",
|
||||
"target_quantity",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _observe_trade(value):
|
||||
return _observe_object(
|
||||
value,
|
||||
(
|
||||
"trade_id",
|
||||
"order_id",
|
||||
"time",
|
||||
"security",
|
||||
"amount",
|
||||
"price",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _observe_platform_collection(api_name, observer):
|
||||
"""Read an optional hosted collection without assuming API availability."""
|
||||
function = globals().get(api_name)
|
||||
if not callable(function):
|
||||
return {
|
||||
"available": False,
|
||||
"reason": "API_UNAVAILABLE",
|
||||
}
|
||||
try:
|
||||
raw = function()
|
||||
except Exception as exc:
|
||||
return {
|
||||
"available": False,
|
||||
"error_type": type(exc).__name__,
|
||||
"reason": "API_CALL_FAILED",
|
||||
}
|
||||
if hasattr(raw, "items"):
|
||||
items = [
|
||||
{
|
||||
"key": str(key),
|
||||
"value": observer(value),
|
||||
}
|
||||
for key, value in sorted(
|
||||
raw.items(),
|
||||
key=lambda pair: str(pair[0]),
|
||||
)
|
||||
]
|
||||
elif isinstance(raw, (list, tuple)):
|
||||
items = [observer(value) for value in raw]
|
||||
elif raw is None:
|
||||
items = []
|
||||
else:
|
||||
items = [observer(raw)]
|
||||
return {
|
||||
"available": True,
|
||||
"count": len(items),
|
||||
"items": items,
|
||||
}
|
||||
|
||||
|
||||
def _observe_portfolio(context):
|
||||
try:
|
||||
portfolio = context.portfolio
|
||||
except Exception as exc:
|
||||
return {
|
||||
"available": False,
|
||||
"error_type": type(exc).__name__,
|
||||
}
|
||||
output = {
|
||||
"available": True,
|
||||
"fields": {},
|
||||
"positions": [],
|
||||
}
|
||||
for name in (
|
||||
"total_value",
|
||||
"cash",
|
||||
"available_cash",
|
||||
"positions_value",
|
||||
"returns",
|
||||
):
|
||||
try:
|
||||
value = getattr(portfolio, name)
|
||||
except Exception:
|
||||
continue
|
||||
output["fields"][name] = _evidence_value(value)
|
||||
try:
|
||||
positions = getattr(portfolio, "positions", {})
|
||||
if hasattr(positions, "items"):
|
||||
for symbol, position in sorted(
|
||||
positions.items(),
|
||||
key=lambda pair: str(pair[0]),
|
||||
):
|
||||
item = _observe_object(
|
||||
position,
|
||||
(
|
||||
"total_amount",
|
||||
"closeable_amount",
|
||||
"price",
|
||||
"avg_cost",
|
||||
"value",
|
||||
),
|
||||
)
|
||||
item["symbol"] = str(symbol)
|
||||
output["positions"].append(item)
|
||||
except Exception as exc:
|
||||
output["positions_error_type"] = type(exc).__name__
|
||||
return output
|
||||
|
||||
|
||||
def _plan_evidence(plan):
|
||||
if not isinstance(plan, dict):
|
||||
return None
|
||||
return {
|
||||
"decision_mode": plan.get("decision_mode"),
|
||||
"execution_contract": plan.get("execution_contract"),
|
||||
"lineage": plan.get("lineage"),
|
||||
"orders": plan.get("orders"),
|
||||
"targets": plan.get("targets"),
|
||||
"universe": plan.get("universe"),
|
||||
"weights": plan.get("weights"),
|
||||
}
|
||||
|
||||
|
||||
def _validated_target_packages(config):
|
||||
mode = config.get("decision_mode")
|
||||
if mode not in (MOMENTUM_SMOKE_MODE, TARGET_PACKAGE_MODE):
|
||||
raise ValueError("unsupported decision_mode")
|
||||
if not isinstance(TARGET_PACKAGES, list):
|
||||
raise ValueError("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 ValueError(
|
||||
"portable_momentum_smoke cannot carry target packages"
|
||||
)
|
||||
return []
|
||||
if type(expected_count) is not int or expected_count <= 0:
|
||||
raise ValueError(
|
||||
"target_package mode requires a positive target_package_count"
|
||||
)
|
||||
if expected_count != len(TARGET_PACKAGES):
|
||||
raise ValueError("target package count mismatch")
|
||||
actual_sha256 = target_package_tape_sha256(TARGET_PACKAGES)
|
||||
if expected_sha256 != actual_sha256:
|
||||
raise ValueError("target package tape sha256 mismatch")
|
||||
verified = []
|
||||
package_ids = set()
|
||||
decision_ids = set()
|
||||
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"])
|
||||
if (
|
||||
package_id in package_ids
|
||||
or decision_id in decision_ids
|
||||
or clock in clocks
|
||||
):
|
||||
raise ValueError("duplicate target package decision identity")
|
||||
package_ids.add(package_id)
|
||||
decision_ids.add(decision_id)
|
||||
clocks.add(clock)
|
||||
verified.append(package)
|
||||
return verified
|
||||
|
||||
|
||||
def initialize(context):
|
||||
"""Configure a daily-open clock that executes a first-week-close signal."""
|
||||
"""Configure the selected daily-open decision mode."""
|
||||
set_option("avoid_future_data", True)
|
||||
set_option("use_real_price", True)
|
||||
g.quant60_evidence_sequence = 0
|
||||
g.quant60_order_observations = []
|
||||
g.quant60_config = dict(CONFIG)
|
||||
if g.quant60_config.get("rebalance_schedule") != "weekly_first_close":
|
||||
raise ValueError("unsupported rebalance_schedule")
|
||||
if g.quant60_config.get("universe_mode") not in ("pit_index", "fixed"):
|
||||
raise ValueError("unsupported universe_mode")
|
||||
if (
|
||||
g.quant60_config.get("universe_mode") == "pit_index"
|
||||
and not g.quant60_config.get("dedicated_account_required")
|
||||
):
|
||||
raise ValueError("pit_index mode requires a dedicated account")
|
||||
g.quant60_target_packages = _validated_target_packages(
|
||||
g.quant60_config
|
||||
)
|
||||
g.quant60_decision_mode = g.quant60_config["decision_mode"]
|
||||
if g.quant60_config["decision_mode"] == MOMENTUM_SMOKE_MODE:
|
||||
if g.quant60_config.get("rebalance_schedule") != "weekly_first_close":
|
||||
raise ValueError("unsupported rebalance_schedule")
|
||||
if g.quant60_config.get("universe_mode") not in ("pit_index", "fixed"):
|
||||
raise ValueError("unsupported universe_mode")
|
||||
if (
|
||||
g.quant60_config.get("universe_mode") == "pit_index"
|
||||
and not g.quant60_config.get("dedicated_account_required")
|
||||
):
|
||||
raise ValueError("pit_index mode requires a dedicated account")
|
||||
set_benchmark(g.quant60_config["index_symbol"])
|
||||
commission = (
|
||||
float(g.quant60_config["commission_rate"])
|
||||
@@ -108,6 +461,30 @@ def initialize(context):
|
||||
g.quant60_pending_first_session = None
|
||||
g.quant60_last_callback_date = None
|
||||
run_daily(rebalance, time="open")
|
||||
_emit_evidence(
|
||||
"INIT",
|
||||
{
|
||||
"benchmark": g.quant60_config["index_symbol"],
|
||||
"package_count": len(g.quant60_target_packages),
|
||||
"package_ids": [
|
||||
package["package_id"]
|
||||
for package in g.quant60_target_packages
|
||||
],
|
||||
"rebalance_callback": "run_daily_open",
|
||||
"target_package_tape_sha256": g.quant60_config.get(
|
||||
"target_package_tape_sha256"
|
||||
),
|
||||
},
|
||||
context,
|
||||
)
|
||||
|
||||
|
||||
def _decision_mode():
|
||||
configured = g.quant60_config.get("decision_mode")
|
||||
frozen = getattr(g, "quant60_decision_mode", configured)
|
||||
if configured != frozen:
|
||||
raise ValueError("decision_mode cannot change after initialize")
|
||||
return frozen
|
||||
|
||||
|
||||
def _close_history(symbol, count):
|
||||
@@ -262,7 +639,10 @@ def _snapshot_portfolio(context, managed):
|
||||
current = {}
|
||||
sellable = {}
|
||||
managed = set(convert_symbol(symbol, "canonical") for symbol in managed)
|
||||
dynamic = g.quant60_config.get("universe_mode") == "pit_index"
|
||||
dynamic = (
|
||||
_decision_mode() == MOMENTUM_SMOKE_MODE
|
||||
and g.quant60_config.get("universe_mode") == "pit_index"
|
||||
)
|
||||
positions = getattr(context.portfolio, "positions", {})
|
||||
for symbol, position in positions.items():
|
||||
canonical = convert_symbol(str(symbol), "canonical")
|
||||
@@ -342,8 +722,156 @@ def _order_request(symbol, delta):
|
||||
return {"style": style}
|
||||
|
||||
|
||||
def _target_package_for_open(context):
|
||||
current_date = _as_date(getattr(context, "current_dt", None), "current_dt")
|
||||
previous_date = _as_date(
|
||||
getattr(context, "previous_date", None),
|
||||
"previous_date",
|
||||
)
|
||||
signal_as_of = (
|
||||
previous_date.isoformat() + "T15:00:00+08:00"
|
||||
)
|
||||
next_session = current_date.isoformat()
|
||||
config = g.quant60_config
|
||||
packages = _validated_target_packages(config)
|
||||
matches = [
|
||||
package
|
||||
for package in packages
|
||||
if package["signal_as_of"] == signal_as_of
|
||||
and package["next_session"] == next_session
|
||||
]
|
||||
if not matches:
|
||||
_emit_evidence(
|
||||
"TARGET_PACKAGE_NOOP",
|
||||
{
|
||||
"fallback_invoked": False,
|
||||
"next_session": next_session,
|
||||
"orders_submitted": 0,
|
||||
"package_count": len(packages),
|
||||
"reason": "NO_EXACT_CLOCK_MATCH",
|
||||
"signal_as_of": signal_as_of,
|
||||
"target_package_tape_sha256": config[
|
||||
"target_package_tape_sha256"
|
||||
],
|
||||
},
|
||||
context,
|
||||
)
|
||||
return None
|
||||
if len(matches) != 1:
|
||||
raise ValueError("ambiguous target package clock")
|
||||
package = lookup_target_package_exact(
|
||||
packages,
|
||||
signal_as_of,
|
||||
next_session,
|
||||
decision_id=matches[0]["decision_id"],
|
||||
expected_tape_sha256=config["target_package_tape_sha256"],
|
||||
expected_count=config["target_package_count"],
|
||||
)
|
||||
_emit_evidence(
|
||||
"TARGET_PACKAGE_HIT",
|
||||
{
|
||||
"lineage": _target_lineage(package),
|
||||
"target_package_tape_sha256": config[
|
||||
"target_package_tape_sha256"
|
||||
],
|
||||
},
|
||||
context,
|
||||
)
|
||||
return package
|
||||
|
||||
|
||||
def _target_prices(symbols):
|
||||
try:
|
||||
current_data = get_current_data()
|
||||
except Exception as exc:
|
||||
raise PriceHistoryUnavailable(
|
||||
"current_data is unavailable: %s" % exc
|
||||
)
|
||||
prices = {}
|
||||
for canonical in sorted(symbols):
|
||||
symbol = convert_symbol(canonical, "joinquant")
|
||||
try:
|
||||
raw_price = getattr(current_data[symbol], "day_open")
|
||||
price = float(raw_price)
|
||||
except (KeyError, AttributeError, TypeError, ValueError):
|
||||
raise PriceHistoryUnavailable(
|
||||
"%s has no usable current day_open" % symbol
|
||||
)
|
||||
if not math.isfinite(price) or price <= 0.0:
|
||||
raise PriceHistoryUnavailable(
|
||||
"%s has no usable current day_open" % symbol
|
||||
)
|
||||
prices[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(context):
|
||||
package = _target_package_for_open(context)
|
||||
if package is None:
|
||||
return None
|
||||
managed = sorted(package["universe"])
|
||||
current, sellable, equity = _snapshot_portfolio(context, managed)
|
||||
if equity <= 0:
|
||||
raise ValueError("portfolio.total_value must be positive")
|
||||
prices = _target_prices(
|
||||
set(package["target_weights"]) | set(current)
|
||||
)
|
||||
_emit_evidence(
|
||||
"PLATFORM_BINDING_INPUT",
|
||||
{
|
||||
"current": current,
|
||||
"equity": equity,
|
||||
"lineage": _target_lineage(package),
|
||||
"price_source": TARGET_PRICE_SOURCE,
|
||||
"prices": prices,
|
||||
"sellable": sellable,
|
||||
},
|
||||
context,
|
||||
)
|
||||
plan = build_target_weight_plan(
|
||||
target_weights=package["target_weights"],
|
||||
prices=prices,
|
||||
current=current,
|
||||
sellable=sellable,
|
||||
equity=equity,
|
||||
cash_buffer=g.quant60_config["cash_buffer"],
|
||||
lot_size=g.quant60_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": TARGET_PRICE_SOURCE,
|
||||
"submit_trigger": "DECLARED_NEXT_SESSION_OPEN_CALLBACK",
|
||||
"declared_next_session": package["next_session"],
|
||||
"real_platform_observed": False,
|
||||
}
|
||||
_emit_evidence(
|
||||
"TARGET_PACKAGE_PLAN",
|
||||
_plan_evidence(plan),
|
||||
context,
|
||||
)
|
||||
return plan
|
||||
|
||||
|
||||
def compute_plan(context):
|
||||
"""Return the portable plan without submitting orders."""
|
||||
"""Return the selected portable plan without submitting orders."""
|
||||
if _decision_mode() == TARGET_PACKAGE_MODE:
|
||||
return _compute_target_package_plan(context)
|
||||
config = g.quant60_config
|
||||
required = int(config["lookback"]) + int(config["skip"]) + 1
|
||||
platform_universe = _decision_universe(context)
|
||||
@@ -375,13 +903,17 @@ def compute_plan(context):
|
||||
return plan
|
||||
|
||||
|
||||
def _execute_rebalance(context):
|
||||
"""Submit the portable plan's exact executable share deltas."""
|
||||
plan = compute_plan(context)
|
||||
def _submit_plan(context, plan, managed):
|
||||
"""Submit a portable plan's exact executable share deltas."""
|
||||
if plan is None:
|
||||
return None
|
||||
observe_target_package = (
|
||||
plan.get("decision_mode") == TARGET_PACKAGE_MODE
|
||||
)
|
||||
order_deltas = plan["orders"]
|
||||
current, _, unused_equity = _snapshot_portfolio(
|
||||
context,
|
||||
g.quant60_last_universe or g.quant60_config["universe"],
|
||||
managed,
|
||||
)
|
||||
del unused_equity
|
||||
all_symbols = set(current)
|
||||
@@ -401,23 +933,107 @@ def _execute_rebalance(context):
|
||||
if delta == 0:
|
||||
continue
|
||||
platform_symbol = convert_symbol(canonical, "joinquant")
|
||||
skipped_before = len(g.quant60_skipped_orders)
|
||||
request = _order_request(platform_symbol, delta)
|
||||
if request is None:
|
||||
skipped = g.quant60_skipped_orders[skipped_before:]
|
||||
if not skipped:
|
||||
skipped = [
|
||||
{
|
||||
"symbol": platform_symbol,
|
||||
"reason": "ORDER_REQUEST_NOT_BUILT",
|
||||
}
|
||||
]
|
||||
if observe_target_package:
|
||||
for item in skipped:
|
||||
_emit_evidence(
|
||||
"SKIPPED_ORDER",
|
||||
{
|
||||
"canonical_symbol": canonical,
|
||||
"delta": delta,
|
||||
"lineage": plan.get("lineage"),
|
||||
"skip": item,
|
||||
},
|
||||
context,
|
||||
)
|
||||
continue
|
||||
target = int(current.get(canonical, 0)) + delta
|
||||
if request["style"] is None:
|
||||
order_target(platform_symbol, target)
|
||||
else:
|
||||
order_target(
|
||||
platform_symbol,
|
||||
target,
|
||||
style=request["style"],
|
||||
request_evidence = {
|
||||
"canonical_symbol": canonical,
|
||||
"current_quantity": int(current.get(canonical, 0)),
|
||||
"delta": delta,
|
||||
"lineage": plan.get("lineage"),
|
||||
"platform_symbol": platform_symbol,
|
||||
"style": _observe_object(
|
||||
request["style"],
|
||||
("kind", "limit_price"),
|
||||
),
|
||||
"target_quantity": target,
|
||||
}
|
||||
if observe_target_package:
|
||||
_emit_evidence(
|
||||
"ORDER_REQUEST",
|
||||
request_evidence,
|
||||
context,
|
||||
)
|
||||
try:
|
||||
if request["style"] is None:
|
||||
order_result = order_target(platform_symbol, target)
|
||||
else:
|
||||
order_result = order_target(
|
||||
platform_symbol,
|
||||
target,
|
||||
style=request["style"],
|
||||
)
|
||||
except Exception as exc:
|
||||
if observe_target_package:
|
||||
_emit_evidence(
|
||||
"ORDER_RETURN",
|
||||
{
|
||||
"error_type": type(exc).__name__,
|
||||
"lineage": plan.get("lineage"),
|
||||
"outcome": "EXCEPTION",
|
||||
"platform_symbol": platform_symbol,
|
||||
"target_quantity": target,
|
||||
},
|
||||
context,
|
||||
)
|
||||
raise
|
||||
observation = {
|
||||
"lineage": plan.get("lineage"),
|
||||
"order": _observe_order(order_result),
|
||||
"outcome": "RETURNED",
|
||||
"platform_symbol": platform_symbol,
|
||||
"target_quantity": target,
|
||||
}
|
||||
if observe_target_package:
|
||||
g.quant60_order_observations.append(observation)
|
||||
_emit_evidence(
|
||||
"ORDER_RETURN",
|
||||
observation,
|
||||
context,
|
||||
)
|
||||
|
||||
g.quant60_last_plan = plan
|
||||
return plan
|
||||
|
||||
|
||||
def _execute_rebalance(context):
|
||||
"""Submit the selected portable plan's exact executable share deltas."""
|
||||
plan = compute_plan(context)
|
||||
if plan is None:
|
||||
return None
|
||||
managed = (
|
||||
sorted(plan["universe"])
|
||||
if "universe" in plan
|
||||
else (
|
||||
g.quant60_last_universe
|
||||
or g.quant60_config["universe"]
|
||||
)
|
||||
)
|
||||
return _submit_plan(context, plan, managed)
|
||||
|
||||
|
||||
def _as_date(value, name):
|
||||
if hasattr(value, "date"):
|
||||
value = value.date()
|
||||
@@ -439,6 +1055,16 @@ def rebalance(context):
|
||||
)
|
||||
if g.quant60_last_callback_date == current_date:
|
||||
return None
|
||||
if _decision_mode() == TARGET_PACKAGE_MODE:
|
||||
# EOD evidence is session-scoped. Clear the prior session only after
|
||||
# the duplicate-date guard so a same-day retry cannot erase evidence.
|
||||
g.quant60_last_plan = None
|
||||
g.quant60_order_observations = []
|
||||
g.quant60_skipped_orders = []
|
||||
# Consume the daily clock token before crossing the hosted order
|
||||
# boundary so callback retries cannot duplicate an accepted prefix.
|
||||
g.quant60_last_callback_date = current_date
|
||||
return _execute_rebalance(context)
|
||||
pending = g.quant60_pending_first_session
|
||||
is_first_session = (
|
||||
previous_date.isocalendar()[:2]
|
||||
@@ -463,3 +1089,37 @@ def rebalance(context):
|
||||
g.quant60_pending_first_session = current_date
|
||||
g.quant60_last_callback_date = current_date
|
||||
return None
|
||||
|
||||
|
||||
def after_trading_end(context):
|
||||
"""Emit a best-effort end-of-day observation without changing state."""
|
||||
if _decision_mode() != TARGET_PACKAGE_MODE:
|
||||
return None
|
||||
return _emit_chunked_evidence(
|
||||
"EOD_STATUS",
|
||||
{
|
||||
"last_plan": _plan_evidence(
|
||||
getattr(g, "quant60_last_plan", None)
|
||||
),
|
||||
"order_returns": getattr(
|
||||
g,
|
||||
"quant60_order_observations",
|
||||
[],
|
||||
),
|
||||
"orders_api": _observe_platform_collection(
|
||||
"get_orders",
|
||||
_observe_order,
|
||||
),
|
||||
"portfolio": _observe_portfolio(context),
|
||||
"skipped_orders": getattr(
|
||||
g,
|
||||
"quant60_skipped_orders",
|
||||
[],
|
||||
),
|
||||
"trades_api": _observe_platform_collection(
|
||||
"get_trades",
|
||||
_observe_trade,
|
||||
),
|
||||
},
|
||||
context,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user