From b0aa90aa32cc29f9d86da99ea29954d4328300f7 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Wed, 8 Jul 2026 22:59:53 +0800 Subject: [PATCH] feat(risk): tune crypto risk-gate weights and add thesis ledger Co-Authored-By: Claude Co-authored-by: Cursor --- .github/workflows/thesis-check.yml | 70 +++++++++++++++++++ docs/thesis-ledger.json | 28 ++++++++ src/crypto_strategies/entrypoints/__init__.py | 4 +- 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/thesis-check.yml create mode 100644 docs/thesis-ledger.json diff --git a/.github/workflows/thesis-check.yml b/.github/workflows/thesis-check.yml new file mode 100644 index 0000000..1ebaf13 --- /dev/null +++ b/.github/workflows/thesis-check.yml @@ -0,0 +1,70 @@ +name: Thesis Check + +on: + schedule: + - cron: "0 2 1 * *" + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + thesis: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Validate thesis ledger + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + python - <<'PY' + import json, os, pathlib, subprocess, sys + path = pathlib.Path("docs/thesis-ledger.json") + if not path.exists(): + print("::error::docs/thesis-ledger.json missing") + sys.exit(1) + data = json.loads(path.read_text()) + strategies = data.get("strategies") or {} + if not strategies: + print("::error::no strategies in thesis ledger") + sys.exit(1) + failures = [] + warnings = [] + for name, row in strategies.items(): + fals = row.get("falsification_conditions") or [] + if not row.get("thesis"): + failures.append(f"{name}: missing thesis") + if len(fals) < 2: + failures.append(f"{name}: need >=2 falsification_conditions") + hit = row.get("hit_rate") + if hit is not None and float(hit) < 0.5: + warnings.append(f"{name}: hit_rate {hit} < 0.5") + if str(row.get("verdict", "")).lower() in {"falsified", "failed", "reject"}: + failures.append(f"{name}: verdict={row.get('verdict')}") + print(f"[thesis-check] strategies={len(strategies)} failures={len(failures)} warnings={len(warnings)}") + for item in failures: + print(f"::error::{item}") + for item in warnings: + print(f"::warning::{item}") + repo = os.environ.get("GITHUB_REPOSITORY", "") + token = os.environ.get("GH_TOKEN", "") + if failures and repo and token: + title = f"[thesis-check] falsification/validation failures ({len(failures)})" + body = "\n".join(f"- {x}" for x in failures) + # create issue (best-effort) + subprocess.run([ + "gh", "issue", "create", "--title", title, "--body", body + "\n\ncc @Pigbibi" + ], check=False) + sys.exit(1 if failures else 0) + PY diff --git a/docs/thesis-ledger.json b/docs/thesis-ledger.json new file mode 100644 index 0000000..8e3ab5a --- /dev/null +++ b/docs/thesis-ledger.json @@ -0,0 +1,28 @@ +{ + "strategies": { + "crypto_live_pool_rotation": { + "thesis": "加密实时池轮动可在趋势市捕获相对强度并借助 ATR 止损控制尾部", + "falsification_conditions": [ + "滚动12个月相对 BTC 超额 < 0", + "ATR 止损后仍出现 >30% 单笔回撤" + ], + "created": "2025-09-01", + "last_verified": "2026-07-08", + "verdict": "active", + "hit_rate": null, + "status_note": "runtime_enabled" + }, + "crypto_btc_dca": { + "thesis": "BTC 智能定投在波动分层下优于固定金额 DCA", + "falsification_conditions": [ + "相对等额定投 12个月超额 < 0", + "高波动加仓规则连续失效 3 个月" + ], + "created": "2025-09-01", + "last_verified": "2026-07-08", + "verdict": "active", + "hit_rate": null, + "status_note": "shadow_candidate" + } + } +} diff --git a/src/crypto_strategies/entrypoints/__init__.py b/src/crypto_strategies/entrypoints/__init__.py index 91b2d9d..a3c65de 100644 --- a/src/crypto_strategies/entrypoints/__init__.py +++ b/src/crypto_strategies/entrypoints/__init__.py @@ -426,7 +426,7 @@ def evaluate_crypto_trend_rotation(ctx: StrategyContext) -> StrategyDecision: risk_flags=risk_flags, diagnostics=diagnostics, ) - return apply_risk_gate(decision, max_single_weight=0.50) + return apply_risk_gate(decision, max_single_weight=0.30) crypto_trend_rotation_entrypoint = CallableStrategyEntrypoint( @@ -615,7 +615,7 @@ def evaluate_crypto_equity_combo(ctx: StrategyContext) -> StrategyDecision: risk_flags=risk_flags, diagnostics=diagnostics, ) - return apply_risk_gate(decision) + return apply_risk_gate(decision, max_single_weight=0.30) crypto_equity_combo_entrypoint = CallableStrategyEntrypoint(