hamuna-quant-cli 0.1.10__tar.gz → 0.1.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/PKG-INFO +1 -1
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/__init__.py +1 -1
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/__main__.py +51 -3
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/runtime/server_client.py +23 -4
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli.egg-info/PKG-INFO +1 -1
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/pyproject.toml +1 -1
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/MANIFEST.in +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/README.md +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/README.md +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/_market_fallback.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/_metrics_15.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/_test_akquant_parity.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/akquant_data_adapter.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/akquant_runner.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/akquant_schema_adapter.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/base_strategy.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/cross_sectional_helpers.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/live/__init__.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/live/loader.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/live/qmt_broker.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/live/qmt_market.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/live/runner.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/prebuilt_downloader.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/prebuilt_resolver.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/qmt_translator.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/runtime/__init__.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/runtime/backtest.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/runtime/cache.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/runtime/discipline.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/runtime/http_client.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/runtime/s3client.py +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/scripts/server.json +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli.egg-info/SOURCES.txt +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli.egg-info/dependency_links.txt +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli.egg-info/entry_points.txt +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli.egg-info/requires.txt +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli.egg-info/top_level.txt +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/setup.cfg +0 -0
- {hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/tests/test_live_safety.py +0 -0
|
@@ -335,10 +335,18 @@ def cmd_commit(args) -> int:
|
|
|
335
335
|
Round 18.62 P0-1: 新增 step 1.5 replay diff gate — `--replay-result` 必填,
|
|
336
336
|
与 `--result` 算 sharpe diff, 超阈值 (HAMUNA_REPLAY_GATE_PCT 缺省 0.20) → exit 5.
|
|
337
337
|
server `UpdateStrategyResult` handler 二次校验防绕过.
|
|
338
|
+
|
|
339
|
+
Round 18.62 P1-1: 新增 step 1.4 version bump — `GET /strategies/{id}` 拿
|
|
340
|
+
`latest_version` (无 → "0.0.0"); 默认 patch 自动 bump (`0.0.0 → 0.0.1`),
|
|
341
|
+
`--major` → `X+1.0.0`, `--minor` → `X.Y+1.0`. `--major + --minor` 互斥.
|
|
342
|
+
server handler 强校验 semver 格式 + 严格递增; PUT body 必含 `version`.
|
|
338
343
|
"""
|
|
339
344
|
from . import runtime
|
|
345
|
+
from .runtime.server_client import ServerError
|
|
346
|
+
import io
|
|
340
347
|
import json
|
|
341
348
|
import os
|
|
349
|
+
import re
|
|
342
350
|
import tarfile
|
|
343
351
|
|
|
344
352
|
sid = args.strategy_id
|
|
@@ -370,10 +378,36 @@ def cmd_commit(args) -> int:
|
|
|
370
378
|
print(f"replay_result.json 不存在: {args.replay_result}", file=sys.stderr)
|
|
371
379
|
return 2
|
|
372
380
|
|
|
381
|
+
# ----- Round 18.62 P1-1 step 1.4: version bump -----
|
|
382
|
+
# cli 先 GET /strategies/{id} 拿 latest_version (无 → "0.0.0"); 默认 patch 自动 bump,
|
|
383
|
+
# --major → X+1.0.0, --minor → X.Y+1.0. 互斥校验防呆. server 二次校验 (semver 格式 +
|
|
384
|
+
# 严格递增) 防绕过. PUT body 必含 version (P1-1 server 强校验).
|
|
385
|
+
if args.major and args.minor:
|
|
386
|
+
print("ERROR: --major 与 --minor 互斥, 选一个或不传 (默认 patch)", file=sys.stderr)
|
|
387
|
+
return 2
|
|
388
|
+
try:
|
|
389
|
+
strat = runtime.server_client.get_strategy(sid)
|
|
390
|
+
latest = (strat or {}).get("latest_version") or "0.0.0"
|
|
391
|
+
except ServerError:
|
|
392
|
+
latest = "0.0.0" # 404 / 401 等都 fallback, 新策略首次 commit
|
|
393
|
+
if not re.fullmatch(r"\d+\.\d+\.\d+", latest):
|
|
394
|
+
print(f"ERROR: server latest_version '{latest}' 不是合法 semver (X.Y.Z), "
|
|
395
|
+
f"请手动清理 strategies.latest_version 后重试", file=sys.stderr)
|
|
396
|
+
return 2
|
|
397
|
+
major, minor, patch = (int(x) for x in latest.split("."))
|
|
398
|
+
if args.major:
|
|
399
|
+
new_version = f"{major + 1}.0.0"
|
|
400
|
+
elif args.minor:
|
|
401
|
+
new_version = f"{major}.{minor + 1}.0"
|
|
402
|
+
else:
|
|
403
|
+
new_version = f"{major}.{minor}.{patch + 1}"
|
|
404
|
+
print(f"=== Step 1.4/3: P1-1 version bump ({latest} → {new_version}) ===",
|
|
405
|
+
file=sys.stderr)
|
|
406
|
+
|
|
373
407
|
bundle_out = Path(args.bundle_out) if args.bundle_out else Path(f"runs/{sid}/bundle.tar.gz")
|
|
374
408
|
bundle_out.parent.mkdir(parents=True, exist_ok=True)
|
|
375
409
|
|
|
376
|
-
print("=== Step 1/3: 打包 bundle (strategy + result + config + params) ===",
|
|
410
|
+
print("=== Step 1/3: 打包 bundle (strategy + result + config + params + replay + version) ===",
|
|
377
411
|
file=sys.stderr)
|
|
378
412
|
with tarfile.open(bundle_out, "w:gz") as tar:
|
|
379
413
|
tar.add(strategy_path, arcname=strategy_path.name)
|
|
@@ -385,6 +419,11 @@ def cmd_commit(args) -> int:
|
|
|
385
419
|
# Round 18.62 P0-1: replay_result.json 也打进去, 方便回溯 / 审计.
|
|
386
420
|
if replay_path:
|
|
387
421
|
tar.add(replay_path, arcname=replay_path.name)
|
|
422
|
+
# Round 18.62 P1-1: version.json 内存写, audit 用 (server 不读, 仅本地回溯).
|
|
423
|
+
ver_bytes = json.dumps({"version": new_version}, ensure_ascii=False).encode("utf-8")
|
|
424
|
+
ver_info = tarfile.TarInfo(name="version.json")
|
|
425
|
+
ver_info.size = len(ver_bytes)
|
|
426
|
+
tar.addfile(ver_info, io.BytesIO(ver_bytes))
|
|
388
427
|
print(f" ✓ bundle = {bundle_out} ({bundle_out.stat().st_size} bytes)", file=sys.stderr)
|
|
389
428
|
|
|
390
429
|
# ----- Round 18.62 P0-1 step 1.5: replay diff gate -----
|
|
@@ -420,14 +459,17 @@ def cmd_commit(args) -> int:
|
|
|
420
459
|
print(f" ✓ replay gate pass (diff={diff_pct:.2%} <= {threshold:.0%}, "
|
|
421
460
|
f"replay.sharpe={rp_sharpe:.4f})", file=sys.stderr)
|
|
422
461
|
|
|
423
|
-
print("=== Step 2/3: PUT /strategies/:id/result (metrics + replay_metrics) ===",
|
|
462
|
+
print("=== Step 2/3: PUT /strategies/:id/result (metrics + replay_metrics + version) ===",
|
|
463
|
+
file=sys.stderr)
|
|
424
464
|
try:
|
|
425
465
|
result = json.loads(result_path.read_text(encoding="utf-8"))
|
|
426
466
|
except json.JSONDecodeError as e:
|
|
427
467
|
print(f" ✗ result.json 解析失败: {e}", file=sys.stderr)
|
|
428
468
|
return 2
|
|
429
469
|
try:
|
|
430
|
-
resp = runtime.server_client.upload_backtest_result(
|
|
470
|
+
resp = runtime.server_client.upload_backtest_result(
|
|
471
|
+
sid, result, replay_metrics=replay_metrics, version=new_version,
|
|
472
|
+
)
|
|
431
473
|
except runtime.server_client.ServerError as e:
|
|
432
474
|
print(f" ✗ 上传 metrics 失败: {e}", file=sys.stderr)
|
|
433
475
|
return 4
|
|
@@ -1000,6 +1042,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1000
1042
|
"或 --skip-replay-gate 跳过)")
|
|
1001
1043
|
cm.add_argument("--skip-replay-gate", action="store_true",
|
|
1002
1044
|
help="[qa 临时] 跳过 replay diff gate (非 dev 默认)")
|
|
1045
|
+
# Round 18.62 P1-1: version bump. 默认 patch 自动 (从 server latest_version +0.0.1),
|
|
1046
|
+
# 显式 --major / --minor opt-in. 互斥.
|
|
1047
|
+
cm.add_argument("--major", action="store_true",
|
|
1048
|
+
help="[P1-1] major bump (X+1.0.0); 跟 --minor 互斥")
|
|
1049
|
+
cm.add_argument("--minor", action="store_true",
|
|
1050
|
+
help="[P1-1] minor bump (X.Y+1.0); 跟 --major 互斥")
|
|
1003
1051
|
cm.add_argument("--config", help="config.json 路径")
|
|
1004
1052
|
cm.add_argument("--params", default=None,
|
|
1005
1053
|
help="params.json 路径 (固定 schema; 整个内容作 multipart params 字段, desktop 实盘配置源)")
|
{hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/runtime/server_client.py
RENAMED
|
@@ -72,19 +72,38 @@ def _api_call(method: str, path: str, body: dict | None = None) -> dict:
|
|
|
72
72
|
return envelope
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def upload_backtest_result(strategy_id: str, result: dict, replay_metrics: dict | None = None) -> dict:
|
|
76
|
-
"""PUT /api/v1/strategies/{id}/result → 上传 BacktestResult-shape dict + 可选 replay_metrics.
|
|
75
|
+
def upload_backtest_result(strategy_id: str, result: dict, replay_metrics: dict | None = None, version: str | None = None) -> dict:
|
|
76
|
+
"""PUT /api/v1/strategies/{id}/result → 上传 BacktestResult-shape dict + 可选 replay_metrics + version.
|
|
77
77
|
|
|
78
78
|
兼容 akquant_runner.run_akquant_backtest() 输出 (13 顶层 key + metrics 子 dict 15 key + ...),
|
|
79
79
|
先做轻清洗: NaN/Inf → None (json 标准不允许), 后传给 server.
|
|
80
80
|
|
|
81
81
|
Round 18.62 P0-1: replay_metrics (Sharpe/TotalReturn/WinRate/Trades/DiffPct/RunAt) —
|
|
82
82
|
跟 result 平级, server handler 二次校验 diff 防绕过. None 时不传该 key (老调用方不破).
|
|
83
|
+
Round 18.62 P1-1: version (X.Y.Z semver) — cli 默认 patch 自动 bump (cmd_commit step 1.4
|
|
84
|
+
拿 server latest_version 算新 version), server handler 二次校验 semver 格式 + 严格递增.
|
|
85
|
+
老调用方 (无 version kwarg) → 不传该 key → server 422 (P1-1 强校验).
|
|
83
86
|
"""
|
|
84
87
|
payload = _sanitize_for_json(result)
|
|
88
|
+
# P1-1 P0-1.1 ponytail: 无条件 wrap `{"result": ...}` 让 server 解析形状一致
|
|
89
|
+
# (即使 replay_metrics=None / version=None 也要 wrap, 防止 server handler 422 invalid shape).
|
|
90
|
+
# 此改动在 P1-1 同步实施, P0-1 Case F 已知问题修复.
|
|
91
|
+
body: dict = {"result": payload}
|
|
85
92
|
if replay_metrics is not None:
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
body["replay_metrics"] = _sanitize_for_json(replay_metrics)
|
|
94
|
+
if version is not None:
|
|
95
|
+
body["version"] = version
|
|
96
|
+
return _api_call('PUT', f'/api/v1/strategies/{strategy_id}/result', body)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_strategy(strategy_id: str) -> dict:
|
|
100
|
+
"""GET /api/v1/strategies/{id} → server DataValue (含 latest_version / result / versions[]).
|
|
101
|
+
|
|
102
|
+
Round 18.62 P1-1: cli 端 version auto-bump 需要 latest_version. 404 raise ServerError,
|
|
103
|
+
caller (cmd_commit) fallback "0.0.0" + patch bump. 返 dict (含 latest_version 字段);
|
|
104
|
+
若 server 返回 DataValue 是 dict 直接返, 是 None 也直接返让 caller 用 .get() 容错.
|
|
105
|
+
"""
|
|
106
|
+
return _api_call('GET', f'/api/v1/strategies/{strategy_id}')
|
|
88
107
|
|
|
89
108
|
|
|
90
109
|
def create_strategy(name: str, code: str = '', params: dict | None = None) -> dict:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hamuna-quant-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.11"
|
|
8
8
|
description = "Hamuna A 股回测 + 实盘统一 CLI (akquant 0.3.x). 替代 v1 自建 driver + QMT-style 策略规范."
|
|
9
9
|
readme = "hamuna_quant_cli/README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/_test_akquant_parity.py
RENAMED
|
File without changes
|
{hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/akquant_data_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/akquant_schema_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
{hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli/cross_sectional_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{hamuna_quant_cli-0.1.10 → hamuna_quant_cli-0.1.11}/hamuna_quant_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|