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
+1
View File
@@ -0,0 +1 @@
"""Build and environment inspection tools for quant60."""
+164
View File
@@ -0,0 +1,164 @@
"""Build a tiny synthetic Qlib binary provider with only the standard library."""
from __future__ import annotations
import argparse
import datetime as dt
import json
import math
import struct
from pathlib import Path
from typing import Dict, Iterable, Mapping, Optional, Sequence
def _qlib_name(symbol: str) -> str:
value = str(symbol).strip().upper()
if len(value) != 8 or value[:2] not in {"SH", "SZ", "BJ"}:
raise ValueError("fixture symbols must use Qlib SH600000 form")
if not value[2:].isdigit():
raise ValueError("invalid Qlib symbol")
return value
def _write_lines(path: Path, lines: Iterable[str]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def _write_feature(path: Path, start_index: int, values: Sequence[float]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
numeric = [float(start_index)] + [float(value) for value in values]
path.write_bytes(struct.pack("<" + "f" * len(numeric), *numeric))
def write_qlib_fixture(
root: Path,
*,
calendar: Sequence[str],
features: Mapping[str, Mapping[str, Sequence[float]]],
markets: Optional[Mapping[str, Sequence[str]]] = None,
) -> Dict[str, object]:
"""Write calendar/instruments/features in Qlib's documented bin layout."""
output = root.expanduser().resolve()
if not calendar:
raise ValueError("calendar must not be empty")
if len(set(calendar)) != len(calendar):
raise ValueError("calendar contains duplicates")
ordered_calendar = sorted(str(value) for value in calendar)
if ordered_calendar != list(calendar):
raise ValueError("calendar must be sorted")
_write_lines(output / "calendars/day.txt", ordered_calendar)
normalized: Dict[str, Mapping[str, Sequence[float]]] = {}
for raw_symbol, fields in features.items():
symbol = _qlib_name(raw_symbol)
if not fields:
raise ValueError(f"{symbol} has no fields")
for field, values in fields.items():
if len(values) != len(calendar):
raise ValueError(
f"{symbol}/{field} has {len(values)} values; "
f"expected {len(calendar)}"
)
for value in values:
if not math.isfinite(float(value)):
raise ValueError(f"{symbol}/{field} contains non-finite data")
_write_feature(
output
/ "features"
/ symbol.lower()
/ (str(field).lower().lstrip("$") + ".day.bin"),
0,
values,
)
normalized[symbol] = fields
start, end = calendar[0], calendar[-1]
all_lines = [
f"{symbol}\t{start}\t{end}" for symbol in sorted(normalized)
]
_write_lines(output / "instruments/all.txt", all_lines)
for market, symbols in (markets or {}).items():
selected = [_qlib_name(symbol) for symbol in symbols]
unknown = set(selected).difference(normalized)
if unknown:
raise ValueError(f"{market} contains missing symbols: {sorted(unknown)}")
_write_lines(
output / f"instruments/{market.lower()}.txt",
[f"{symbol}\t{start}\t{end}" for symbol in sorted(selected)],
)
return {
"provider_uri": str(output),
"calendar_count": len(calendar),
"instruments": sorted(normalized),
"markets": sorted((markets or {}).keys()),
"format_note": (
"each *.day.bin is little-endian float32; element 0 is calendar "
"start index, followed by field values"
),
}
def _business_days(start: dt.date, count: int) -> list[str]:
output = []
current = start
while len(output) < count:
if current.weekday() < 5:
output.append(current.isoformat())
current += dt.timedelta(days=1)
return output
def _fields(closes: Sequence[float], volume: float) -> Dict[str, Sequence[float]]:
change = [0.0]
for previous, current in zip(closes, closes[1:]):
change.append(float(current) / float(previous) - 1.0)
return {
"open": [value * 0.998 for value in closes],
"high": [value * 1.01 for value in closes],
"low": [value * 0.99 for value in closes],
"close": list(closes),
"volume": [volume] * len(closes),
"factor": [1.0] * len(closes),
"change": change,
}
def build_default_fixture(root: Path, days: int = 80) -> Dict[str, object]:
"""Create a deterministic two-stock market and one benchmark."""
if int(days) < 30:
raise ValueError("default fixture needs at least 30 business days")
calendar = _business_days(dt.date(2024, 1, 2), int(days))
closes_up = [10.0 * (1.004 ** index) for index in range(len(calendar))]
closes_down = [20.0 * (0.999 ** index) for index in range(len(calendar))]
closes_bench = [100.0 * (1.001 ** index) for index in range(len(calendar))]
return write_qlib_fixture(
root,
calendar=calendar,
features={
"SH600000": _fields(closes_up, 2_000_000.0),
"SZ000001": _fields(closes_down, 1_500_000.0),
"SH000300": _fields(closes_bench, 100_000_000.0),
},
markets={"csi300": ["SH600000", "SZ000001"]},
)
def _main(argv: Optional[list[str]] = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("output_dir", type=Path)
parser.add_argument("--days", type=int, default=80)
args = parser.parse_args(argv)
print(
json.dumps(
build_default_fixture(args.output_dir, args.days),
ensure_ascii=False,
indent=2,
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(_main())
+276
View File
@@ -0,0 +1,276 @@
"""Inline one portable core into JoinQuant and QMT single-file artifacts."""
from __future__ import annotations
import argparse
import ast
import hashlib
import json
import pprint
from pathlib import Path
from typing import Any, Dict, Optional
START = "# __PORTABLE_CORE_BUNDLE_START__"
END = "# __PORTABLE_CORE_BUNDLE_END__"
CONFIG_START = "# __BASELINE_CONFIG_START__"
CONFIG_END = "# __BASELINE_CONFIG_END__"
def sha256_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def _portable_body(source: str) -> str:
"""Remove the core module docstring and future imports before inlining."""
tree = ast.parse(source)
lines = source.splitlines()
first_line = 0
if (
tree.body
and isinstance(tree.body[0], ast.Expr)
and isinstance(tree.body[0].value, ast.Constant)
and isinstance(tree.body[0].value.value, str)
):
first_line = int(tree.body[0].end_lineno or 0)
body = lines[first_line:]
body = [
line
for line in body
if not line.lstrip().startswith("from __future__ import ")
]
return "\n".join(body).strip() + "\n"
def inline_core(wrapper_source: str, core_source: str) -> str:
if wrapper_source.count(START) != 1 or wrapper_source.count(END) != 1:
raise ValueError("wrapper must contain exactly one portable-core marker pair")
start = wrapper_source.index(START)
end = wrapper_source.index(END, start) + len(END)
replacement = (
START
+ "\n# Inlined by tools/bundle_platforms.py; do not edit this region.\n"
+ _portable_body(core_source)
+ END
)
return wrapper_source[:start] + replacement + wrapper_source[end:]
def _qmt_symbol(symbol: str) -> str:
suffixes = {
".XSHG": ".SH",
".XSHE": ".SZ",
".XBSE": ".BJ",
}
for suffix, replacement in suffixes.items():
if symbol.endswith(suffix):
return symbol[: -len(suffix)] + replacement
raise ValueError(f"unsupported canonical symbol in baseline config: {symbol}")
def effective_platform_config(
baseline: Dict[str, Any],
platform: str,
) -> Dict[str, Any]:
strategy = baseline["strategy"]
execution = baseline["execution"]
fees = baseline["fees"]
universe_config = baseline["universe"]
symbols = [str(symbol) for symbol in baseline["symbols"]]
if platform == "qmt_builtin":
universe = [_qmt_symbol(symbol) for symbol in symbols]
elif platform == "joinquant":
universe = symbols
else:
raise ValueError(f"unsupported platform config: {platform}")
common: Dict[str, Any] = {
"universe_mode": str(universe_config["mode"]),
"index_symbol": str(universe_config["index_symbol"]),
"qmt_sector_name": str(universe_config["qmt_sector_name"]),
"dedicated_account_required": bool(
universe_config["dedicated_account_required"]
),
"exclude_st": bool(universe_config["exclude_st"]),
"universe": universe,
"lookback": int(strategy["lookback"]),
"skip": int(strategy["skip"]),
"top_n": int(strategy["top_n"]),
"max_weight": float(strategy["max_weight"]),
"gross_target": float(strategy["gross_target"]),
"cash_buffer": float(strategy["cash_buffer"]),
"lot_size": int(execution["lot_size"]),
"participation_rate": float(execution["participation_rate"]),
"slippage_bps": float(execution["slippage_bps"]),
"commission_rate": float(fees["commission_rate"]),
"minimum_commission": float(fees["minimum_commission"]),
"stamp_duty_rate": float(fees["stamp_duty_rate"]),
"transfer_fee_rate": float(fees["transfer_fee_rate"]),
"rebalance_schedule": str(strategy["rebalance_schedule"]),
}
if platform == "qmt_builtin":
return {
"account_id": "test",
**common,
}
return common
def inline_config(wrapper_source: str, config: Dict[str, Any]) -> str:
if (
wrapper_source.count(CONFIG_START) != 1
or wrapper_source.count(CONFIG_END) != 1
):
raise ValueError("wrapper must contain exactly one baseline-config marker pair")
start = wrapper_source.index(CONFIG_START)
end = wrapper_source.index(CONFIG_END, start) + len(CONFIG_END)
body = pprint.pformat(config, width=88, sort_dicts=False)
# QMT's built-in strategy is shipped with a GBK coding declaration but
# kept byte-for-byte ASCII for portability. Preserve non-ASCII config
# values (for example a Chinese sector name) as Python unicode escapes.
body = body.encode("ascii", "backslashreplace").decode("ascii")
replacement = (
CONFIG_START
+ "\n# Generated from configs/baseline.json; do not edit this region.\n"
+ "CONFIG = "
+ body
+ "\n"
+ CONFIG_END
)
return wrapper_source[:start] + replacement + wrapper_source[end:]
def bundle_one(
*,
core_path: Path,
wrapper_path: Path,
output_path: Path,
effective_config: Dict[str, Any],
require_ascii: bool = False,
) -> Dict[str, str]:
core_bytes = core_path.read_bytes()
wrapper_bytes = wrapper_path.read_bytes()
core_source = core_bytes.decode("utf-8")
wrapper_source = wrapper_bytes.decode("utf-8")
bundled = inline_config(
inline_core(wrapper_source, core_source),
effective_config,
)
output_bytes = bundled.encode("utf-8")
if require_ascii:
try:
bundled.encode("ascii")
except UnicodeEncodeError as exc:
raise ValueError(
"QMT #coding:gbk bundle must remain ASCII-only"
) from exc
# The built-in QMT runtime is documented as Python 3.6. Parse against
# that grammar explicitly; compiling only on the build host would miss
# accidental modern syntax.
ast.parse(bundled, filename=str(output_path), feature_version=(3, 6))
compile(bundled, str(output_path), "exec")
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_bytes(output_bytes)
return {
"core": str(core_path),
"core_sha256": sha256_bytes(core_bytes),
"wrapper": str(wrapper_path),
"wrapper_sha256": sha256_bytes(wrapper_bytes),
"effective_config_sha256": sha256_bytes(
(
json.dumps(
effective_config,
ensure_ascii=True,
separators=(",", ":"),
sort_keys=True,
)
+ "\n"
).encode("ascii")
),
"output": str(output_path),
"output_sha256": sha256_bytes(output_bytes),
}
def bundle_all(
project_root: Path,
output_dir: Optional[Path] = None,
) -> Dict[str, object]:
root = project_root.expanduser().resolve()
output = (
output_dir.expanduser().resolve()
if output_dir
else root / "dist"
)
core = root / "src" / "quant60" / "portable_core.py"
if not core.is_file():
raise FileNotFoundError(core)
config_path = root / "configs" / "baseline.json"
baseline_bytes = config_path.read_bytes()
baseline = json.loads(baseline_bytes.decode("utf-8"))
if not isinstance(baseline, dict):
raise ValueError("configs/baseline.json must contain an object")
entries = {
"joinquant": bundle_one(
core_path=core,
wrapper_path=root / "platforms" / "joinquant_strategy.py",
output_path=output / "joinquant_strategy.py",
effective_config=effective_platform_config(baseline, "joinquant"),
),
"qmt_builtin": bundle_one(
core_path=core,
wrapper_path=root / "platforms" / "qmt_builtin_strategy.py",
output_path=output / "qmt_builtin_strategy.py",
effective_config=effective_platform_config(baseline, "qmt_builtin"),
require_ascii=True,
),
}
# Paths in a committed manifest must be portable and must not expose the
# local account/home directory. Hashes remain byte-for-byte identities.
logical_outputs = {
"joinquant": "dist/joinquant_strategy.py",
"qmt_builtin": "dist/qmt_builtin_strategy.py",
}
for platform_name, entry in entries.items():
entry["core"] = "src/quant60/portable_core.py"
entry["wrapper"] = (
"platforms/joinquant_strategy.py"
if "joinquant" in entry["wrapper"]
else "platforms/qmt_builtin_strategy.py"
)
# The manifest describes the committed logical artifact, not the
# caller's temporary build directory. This keeps clean-room builds
# byte-for-byte comparable with committed ``dist``.
entry["output"] = logical_outputs[platform_name]
manifest = {
"schema_version": 1,
"baseline_config": "configs/baseline.json",
"baseline_config_sha256": sha256_bytes(baseline_bytes),
"portable_core_sha256": entries["joinquant"]["core_sha256"],
"artifacts": entries,
}
manifest_path = output / "bundle_manifest.json"
encoded = (
json.dumps(manifest, ensure_ascii=True, indent=2, sort_keys=True) + "\n"
).encode("ascii")
manifest_path.write_bytes(encoded)
try:
manifest["manifest"] = str(manifest_path.relative_to(root))
except ValueError:
manifest["manifest"] = manifest_path.name
manifest["manifest_sha256"] = sha256_bytes(encoded)
return manifest
def _main(argv: Optional[list[str]] = None) -> int:
default_root = Path(__file__).resolve().parents[1]
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--project-root", type=Path, default=default_root)
parser.add_argument("--output-dir", type=Path)
args = parser.parse_args(argv)
manifest = bundle_all(args.project_root, args.output_dir)
print(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(_main())
+209
View File
@@ -0,0 +1,209 @@
"""Report static artifacts and local optional-runtime availability."""
from __future__ import annotations
import argparse
import importlib
import importlib.metadata
import importlib.util
import json
import platform
import sys
from pathlib import Path
from typing import Any, Dict, Optional
CAPABILITY_MATRIX: Dict[str, Dict[str, Any]] = {
"quant_os_research": {
"backtest": True,
"hosted": False,
"live_orders": False,
"safe_default": True,
"market_scope": (
"synthetic research plus verified authorized-provider "
"PIT snapshot decisions/backtests"
),
},
"portable_local": {
"backtest": True,
"hosted": False,
"live_orders": False,
"safe_default": True,
"market_scope": "SH/SZ/BJ core math",
},
"joinquant_hosted": {
"backtest": True,
"hosted": True,
"live_orders": False,
"safe_default": True,
"market_scope": "configured SH/SZ wrapper universe",
},
"qmt_builtin": {
"backtest": True,
"hosted": True,
"live_orders": False,
"safe_default": True,
"market_scope": "configured SH/SZ wrapper universe; backtest-only",
},
"qmt_research": {
"backtest": True,
"hosted": False,
"live_orders": False,
"safe_default": True,
"market_scope": "QMT entitlement; runner is backtest/history only",
},
"qmt_shadow": {
"backtest": False,
"hosted": False,
"live_orders": False,
"safe_default": True,
"market_scope": (
"licensed XtTrader SH/SZ read-only account queries; "
"account-bound inert target diff only"
),
},
"xttrader": {
"backtest": False,
"hosted": False,
"live_orders": "explicit opt-in plus fresh structured policy guard",
"safe_default": True,
"market_scope": "SH/SZ; adapter rejects BJ explicitly",
},
"qlib_0_9_7": {
"backtest": True,
"hosted": False,
"live_orders": False,
"safe_default": True,
"market_scope": (
"provider-defined; portable signal research bridge, "
"not target/order parity"
),
},
}
def _module_probe(name: str, deep: bool) -> Dict[str, Any]:
try:
available = importlib.util.find_spec(name) is not None
except (ImportError, ModuleNotFoundError, ValueError):
available = False
result: Dict[str, Any] = {
"available": available,
"import_checked": bool(deep),
"import_ok": None,
"error": None,
}
top_level = name.split(".", 1)[0]
try:
result["version"] = importlib.metadata.version(
"pyqlib" if top_level == "qlib" else top_level
)
except importlib.metadata.PackageNotFoundError:
result["version"] = None
if available and deep:
try:
importlib.import_module(name)
result["import_ok"] = True
except Exception as exc:
result["import_ok"] = False
result["error"] = f"{type(exc).__name__}: {exc}"
return result
def probe_capabilities(
project_root: Optional[Path] = None,
*,
deep: bool = False,
) -> Dict[str, Any]:
root = (
project_root.expanduser().resolve()
if project_root
else Path(__file__).resolve().parents[1]
)
generated = root / "dist"
return {
"schema_version": 1,
"python": {
"version": platform.python_version(),
"implementation": platform.python_implementation(),
"executable": sys.executable,
"platform": platform.platform(),
},
"artifacts": {
"portable_core": (root / "src/quant60/portable_core.py").is_file(),
"joinquant_wrapper": (root / "platforms/joinquant_strategy.py").is_file(),
"qmt_builtin_wrapper": (
root / "platforms/qmt_builtin_strategy.py"
).is_file(),
"joinquant_bundle": (
generated / "joinquant_strategy.py"
).is_file(),
"qmt_builtin_bundle": (
generated / "qmt_builtin_strategy.py"
).is_file(),
"bundle_manifest": (
generated / "bundle_manifest.json"
).is_file(),
"colab_notebook": (
root / "notebooks/quant_os_colab.ipynb"
).is_file(),
"jqdata_snapshot_adapter": (
root / "adapters/jqdata_local.py"
).is_file(),
"snapshot_decision_pipeline": (
root / "src/quant60/snapshot_pipeline.py"
).is_file(),
"snapshot_backtest": (
root / "src/quant60/snapshot_backtest.py"
).is_file(),
"mock_parity_exporter": (
root / "tools/export_mock_parity.py"
).is_file(),
"qlib_momentum_and_alpha158_cli": (
root / "platforms/qlib_runner.py"
).is_file(),
"qmt_shadow_planner": (
root / "src/quant60/qmt_shadow.py"
).is_file(),
"qmt_shadow_cli": (
root / "tools/qmt_shadow_plan.py"
).is_file(),
},
"optional_runtimes": {
"jqdatasdk": _module_probe("jqdatasdk", deep),
"cvxpy": _module_probe("cvxpy", deep),
"qlib": _module_probe("qlib", deep),
"xtquant": _module_probe("xtquant", deep),
"xtquant.qmttools": _module_probe("xtquant.qmttools", deep),
"xtquant.xttrader": _module_probe("xtquant.xttrader", deep),
},
"matrix": CAPABILITY_MATRIX,
"verification_boundary": (
"artifact presence and optional import probes only; no external "
"platform backtest or broker entitlement is claimed"
),
}
def _main(argv: Optional[list[str]] = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--project-root", type=Path)
parser.add_argument(
"--deep",
action="store_true",
help="Import optional runtimes; still performs no broker mutation.",
)
args = parser.parse_args(argv)
print(
json.dumps(
probe_capabilities(args.project_root, deep=args.deep),
ensure_ascii=False,
indent=2,
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(_main())
+208
View File
@@ -0,0 +1,208 @@
"""Export and verify the deterministic JoinQuant/QMT wrapper parity fixture.
This evidence proves only the local wrapper contract. It never represents a
real hosted-platform, licensed QMT, broker, or investment-performance pass.
"""
from __future__ import annotations
import argparse
import datetime as dt
import hashlib
import json
import os
import tempfile
from pathlib import Path
from typing import Any
from platforms import joinquant_strategy, qmt_builtin_strategy
from platforms.fake_joinquant_harness import FakeJoinQuantHarness
from platforms.fake_qmt_harness import FakeQmtContext, FakeQmtHarness
PROJECT = Path(__file__).resolve().parents[1]
def _canonical_json(payload: Any) -> str:
return json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
def _sha256_bytes(payload: bytes) -> str:
return hashlib.sha256(payload).hexdigest()
def _sha256_payload(payload: Any) -> str:
return _sha256_bytes(_canonical_json(payload).encode("utf-8"))
def _price_histories() -> dict[str, list[float]]:
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)],
}
def _source_hashes() -> dict[str, str]:
paths = (
PROJECT / "platforms/joinquant_strategy.py",
PROJECT / "platforms/qmt_builtin_strategy.py",
PROJECT / "src/quant60/portable_core.py",
PROJECT / "configs/baseline.json",
)
return {
str(path.relative_to(PROJECT)): _sha256_bytes(path.read_bytes())
for path in paths
}
def build_report() -> dict[str, Any]:
histories = _price_histories()
jq = FakeJoinQuantHarness(histories)
jq.initialize(joinquant_strategy)
jq.context.previous_date = dt.date(2026, 7, 24)
jq.context.current_dt = dt.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 histories.items()
}
qmt_context = FakeQmtContext(
qmt_histories,
bar_time=dt.datetime(2026, 7, 24, 15, 0),
)
FakeQmtHarness(qmt_context).install(qmt_builtin_strategy)
qmt_builtin_strategy.init(qmt_context)
qmt_plan = qmt_builtin_strategy.compute_plan(
qmt_context,
dt.datetime(2026, 7, 24, 15, 0),
)
sources = _source_hashes()
body = {
"schema_version": "1.0",
"evidence_class": "mock_contract_only",
"real_platform_pass": False,
"investment_value_claim": False,
"fixture": {
"as_of": "2026-07-24T15:00:00+08:00",
"history_length": 21,
"symbols": sorted(histories),
"price_history_sha256": _sha256_payload(histories),
},
"tolerances": {
"score_absolute": 1e-12,
"target_weight_absolute": 1e-10,
"target_quantity": "exact_integer",
"order_delta": "exact_integer",
},
"joinquant_plan": jq_plan,
"qmt_plan": qmt_plan,
"comparisons": {
"whole_plan_exact": jq_plan == qmt_plan,
"score_exact": jq_plan["scores"] == qmt_plan["scores"],
"target_weight_exact": (
jq_plan["weights"] == qmt_plan["weights"]
),
"target_quantity_exact": (
jq_plan["targets"] == qmt_plan["targets"]
),
"order_delta_exact": jq_plan["orders"] == qmt_plan["orders"],
},
"source_sha256": sources,
"source_aggregate_sha256": _sha256_payload(sources),
"gate_credit": [],
"required_next_evidence": [
"JoinQuant hosted export",
"licensed QMT backtest export",
"canonical clock/data/fee/fill difference report",
],
}
if not all(body["comparisons"].values()):
raise RuntimeError("local JoinQuant/QMT wrapper parity failed")
body["report_sha256"] = _sha256_payload(body)
return body
def write_report(path: Path) -> dict[str, Any]:
report = build_report()
output = path.expanduser().resolve()
output.parent.mkdir(parents=True, exist_ok=True)
encoded = (
json.dumps(
report,
ensure_ascii=False,
indent=2,
sort_keys=True,
allow_nan=False,
)
+ "\n"
).encode("utf-8")
descriptor, temporary = tempfile.mkstemp(
prefix=f".{output.name}.",
suffix=".tmp",
dir=str(output.parent),
)
try:
with os.fdopen(descriptor, "wb") as handle:
handle.write(encoded)
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, output)
except BaseException:
try:
os.unlink(temporary)
except FileNotFoundError:
pass
raise
return report
def verify_report(path: Path) -> dict[str, Any]:
report = json.loads(path.read_text(encoding="utf-8"))
saved_hash = report.pop("report_sha256", None)
if not isinstance(saved_hash, str) or _sha256_payload(report) != saved_hash:
raise ValueError("mock parity report hash mismatch")
expected = build_report()
if saved_hash != expected["report_sha256"]:
raise ValueError("mock parity report does not match current sources")
return {
"ok": True,
"evidence_class": report["evidence_class"],
"report_sha256": saved_hash,
"whole_plan_exact": report["comparisons"]["whole_plan_exact"],
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
export_parser = subparsers.add_parser("export")
export_parser.add_argument("--output", required=True, type=Path)
verify_parser = subparsers.add_parser("verify")
verify_parser.add_argument("report", type=Path)
args = parser.parse_args(argv)
if args.command == "export":
result = write_report(args.output)
summary = {
"ok": True,
"output": str(args.output.expanduser().resolve()),
"evidence_class": result["evidence_class"],
"report_sha256": result["report_sha256"],
}
else:
summary = verify_report(args.report)
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+37
View File
@@ -0,0 +1,37 @@
"""Authenticate interactively and build a canonical JQData index snapshot."""
from __future__ import annotations
import argparse
import json
from datetime import date
from adapters.jqdata_local import (
authenticate,
fetch_index_daily_snapshot,
load_jqdata,
)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--index", default="000905.XSHG")
parser.add_argument("--start", required=True, type=date.fromisoformat)
parser.add_argument("--end", required=True, type=date.fromisoformat)
parser.add_argument("--output", required=True)
args = parser.parse_args()
sdk = load_jqdata()
authenticate(sdk)
paths = fetch_index_daily_snapshot(
sdk,
index_symbol=args.index,
start_date=args.start,
end_date=args.end,
output_dir=args.output,
)
print(json.dumps({"ok": True, "artifacts": paths}, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+255
View File
@@ -0,0 +1,255 @@
"""Run or verify the strictly read-only QMT/XtTrader shadow preflight.
Environment fallbacks:
QMT_USERDATA_PATH QMT_ACCOUNT_ID QMT_SESSION_ID
QMT_ACCOUNT_HASH_KEY_FILE
The runtime adapter is always constructed with ``allow_live_orders=False``.
No command in this tool submits or cancels an order.
"""
from __future__ import annotations
import argparse
import json
import os
import secrets
import stat
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
for import_root in (PROJECT_ROOT, PROJECT_ROOT / "src"):
if str(import_root) not in sys.path:
sys.path.insert(0, str(import_root))
from adapters.xttrader_live import XtTraderLiveAdapter
from quant60.qmt_shadow import (
MAX_BROKER_ASSET_ABSOLUTE_TOLERANCE,
MAX_SHADOW_QUERY_DURATION_SECONDS,
build_qmt_shadow_plan,
verify_qmt_shadow_plan,
write_qmt_shadow_plan,
)
def _required(value: str | None, name: str) -> str:
if value is None or not value.strip():
raise ValueError(f"{name} is required")
return value.strip()
def _bounded_non_negative_float(
*,
name: str,
maximum: float,
):
def parse(value: str) -> float:
try:
parsed = float(value)
except ValueError as exc:
raise argparse.ArgumentTypeError(
f"{name} must be a number"
) from exc
if not 0 <= parsed <= maximum:
raise argparse.ArgumentTypeError(
f"{name} must be between 0 and {maximum:g}"
)
return parsed
return parse
def _load_or_create_account_hash_key(path: str | Path) -> bytes:
"""Load a stable local HMAC key without ever exposing it in artifacts."""
key_path = Path(path).expanduser()
parent_existed = key_path.parent.exists()
key_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
if not parent_existed:
try:
key_path.parent.chmod(0o700)
except OSError:
pass
create_flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
create_flags |= getattr(os, "O_CLOEXEC", 0)
try:
descriptor = os.open(str(key_path), create_flags, 0o600)
except FileExistsError:
descriptor = None
if descriptor is not None:
key = secrets.token_bytes(32)
with os.fdopen(descriptor, "wb") as handle:
handle.write(key)
handle.flush()
os.fsync(handle.fileno())
return _load_existing_account_hash_key(key_path)
def _load_existing_account_hash_key(path: str | Path) -> bytes:
key_path = Path(path).expanduser()
if not key_path.is_file():
raise ValueError(
"QMT account hash key file does not exist; verification never "
"creates a replacement key"
)
read_flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0)
read_flags |= getattr(os, "O_NOFOLLOW", 0)
descriptor = os.open(str(key_path), read_flags)
with os.fdopen(descriptor, "rb") as handle:
key = handle.read()
if len(key) < 32:
raise ValueError("QMT account hash key must contain at least 32 bytes")
try:
mode = stat.S_IMODE(key_path.stat().st_mode)
if os.name != "nt" and mode & 0o077:
raise ValueError(
"QMT account hash key file must not be group/world accessible"
)
except OSError as exc:
raise ValueError("cannot validate QMT account hash key file") from exc
return key
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
plan = subparsers.add_parser("plan", help="query QMT and write a shadow plan")
plan.add_argument("--decision", required=True)
plan.add_argument("--output", required=True)
plan.add_argument(
"--userdata-path", default=os.environ.get("QMT_USERDATA_PATH")
)
plan.add_argument("--account-id", default=os.environ.get("QMT_ACCOUNT_ID"))
plan.add_argument(
"--session-id",
type=int,
default=(
int(os.environ["QMT_SESSION_ID"])
if os.environ.get("QMT_SESSION_ID")
else None
),
)
plan.add_argument(
"--max-query-duration-seconds",
type=_bounded_non_negative_float(
name="max-query-duration-seconds",
maximum=MAX_SHADOW_QUERY_DURATION_SECONDS,
),
default=MAX_SHADOW_QUERY_DURATION_SECONDS,
help=(
"query-window ceiling; may be tightened but cannot exceed "
f"{MAX_SHADOW_QUERY_DURATION_SECONDS:g}s"
),
)
plan.add_argument(
"--market-value-tolerance",
type=_bounded_non_negative_float(
name="market-value-tolerance",
maximum=MAX_BROKER_ASSET_ABSOLUTE_TOLERANCE,
),
default=MAX_BROKER_ASSET_ABSOLUTE_TOLERANCE,
help=(
"asset identity tolerance; may be tightened but cannot exceed "
f"{MAX_BROKER_ASSET_ABSOLUTE_TOLERANCE:g} CNY"
),
)
plan.add_argument(
"--account-hash-key-file",
default=os.environ.get(
"QMT_ACCOUNT_HASH_KEY_FILE",
str(Path.home() / ".config" / "quant-os" / "account-hash.key"),
),
help=(
"local 0600 HMAC key file; generated once when absent and never "
"written to artifacts"
),
)
verify = subparsers.add_parser(
"verify", help="verify a completed shadow-plan artifact set"
)
verify.add_argument("--manifest", required=True)
verify.add_argument("--decision", required=True)
verify.add_argument(
"--account-hash-key-file",
default=os.environ.get(
"QMT_ACCOUNT_HASH_KEY_FILE",
str(Path.home() / ".config" / "quant-os" / "account-hash.key"),
),
help=(
"existing local 0600 HMAC key used to authenticate the broker "
"observation; verify never creates a key"
),
)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
if args.command == "verify":
account_hash_key = _load_existing_account_hash_key(
args.account_hash_key_file
)
payload = verify_qmt_shadow_plan(
args.manifest,
decision_manifest=args.decision,
account_hash_key=account_hash_key,
)
print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
return 0
userdata_path = _required(args.userdata_path, "QMT userdata path")
account_id = _required(args.account_id, "QMT account id")
session_id = (
args.session_id
if args.session_id is not None
else secrets.randbelow(2_000_000_000) + 1
)
account_hash_key = _load_or_create_account_hash_key(
args.account_hash_key_file
)
adapter = XtTraderLiveAdapter.from_xtquant(
userdata_path=userdata_path,
session_id=session_id,
account_id=account_id,
allow_live_orders=False,
strategy_name="quant-os-shadow",
)
try:
adapter.connect()
result = build_qmt_shadow_plan(
decision_manifest=args.decision,
adapter=adapter,
max_query_duration_seconds=args.max_query_duration_seconds,
market_value_tolerance=args.market_value_tolerance,
account_hash_key=account_hash_key,
)
paths = write_qmt_shadow_plan(result, args.output)
verification = verify_qmt_shadow_plan(
paths["manifest"],
decision_manifest=args.decision,
account_hash_key=account_hash_key,
)
ready = result["plan"]["status"] == "SHADOW_READY"
payload = {
"ok": ready,
"artifact_written": True,
"artifact_verified": verification["ok"],
"plan_id": result["plan"]["plan_id"],
"status": result["plan"]["status"],
"blockers": result["plan"]["blockers"],
"artifacts": paths,
"read_only": True,
}
finally:
adapter.close()
print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
return 0 if payload["ok"] else 2
if __name__ == "__main__":
raise SystemExit(main())
+74
View File
@@ -0,0 +1,74 @@
"""Validate or execute the plain-Python cells in the Quant60 Colab notebook."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
def load_notebook(path: Path) -> dict[str, Any]:
payload = json.loads(path.read_text(encoding="utf-8"))
if payload.get("nbformat") != 4 or not isinstance(payload.get("cells"), list):
raise ValueError("expected a valid nbformat 4 notebook")
return payload
def code_cells(
payload: dict[str, Any],
*,
include_optional: bool,
) -> list[tuple[int, str]]:
output: list[tuple[int, str]] = []
for index, cell in enumerate(payload["cells"]):
if cell.get("cell_type") != "code":
continue
tags = cell.get("metadata", {}).get("tags", [])
if "optional" in tags and not include_optional:
continue
source = cell.get("source")
if not isinstance(source, list) or any(
not isinstance(line, str) for line in source
):
raise ValueError(f"cell {index} source must be a string array")
output.append((index, "".join(source)))
return output
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("path", type=Path)
parser.add_argument("--execute", action="store_true")
parser.add_argument("--include-optional", action="store_true")
args = parser.parse_args(argv)
payload = load_notebook(args.path.resolve())
cells = code_cells(payload, include_optional=args.include_optional)
for index, source in cells:
compile(source, f"{args.path}:cell-{index}", "exec")
if args.execute:
namespace: dict[str, Any] = {"__name__": "__quant60_notebook__"}
for index, source in cells:
print(f"[quant60-notebook] executing cell {index}")
exec(
compile(source, f"{args.path}:cell-{index}", "exec"),
namespace,
namespace,
)
print(
json.dumps(
{
"ok": True,
"code_cells": len(cells),
"executed": bool(args.execute),
"optional_included": bool(args.include_optional),
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())