From ae3ed78b101d7fa19a397726f2e64d1c392b015b Mon Sep 17 00:00:00 2001 From: fanziqi <403508380@qq.com> Date: Fri, 13 Mar 2026 16:00:18 +0800 Subject: [PATCH] Refactor V5 engine: split state/scoring and wire strategy factory --- V52_FRONTEND_TASK.md | 63 - V52_TASK.md | 224 --- backend/backtest.py | 11 +- backend/main.py | 4 +- backend/paper_monitor.py | 18 +- backend/paper_trading.py | 202 +++ backend/signal_engine.py | 1200 +-------------- backend/signal_state.py | 298 ++++ backend/strategy_loader.py | 190 +++ backend/strategy_scoring.py | 554 +++++++ docs/AB_TEST_CHECKLIST.md | 36 - docs/AI_HANDBOOK.md | 203 +++ docs/API_CONTRACTS.md | 1364 +++++++++++++++++ docs/BACKEND_RUNTIME.md | 219 +++ docs/DETAILED_CODE_REVIEW.md | 379 ----- docs/FRONTEND_PAGES.md | 1249 +++++++++++++++ docs/PROBLEM_REPORT.md | 145 -- docs/PROJECT.md | 425 ----- docs/STRATEGY_FACTORY_VALIDATION.md | 497 ++++++ docs/V52-TODO.md | 61 - docs/ai/00-system-overview.md | 117 -- docs/ai/01-architecture-map.md | 137 -- docs/ai/02-module-cheatsheet.md | 218 --- docs/ai/03-api-contracts.md | 242 --- docs/ai/04-data-model.md | 301 ---- docs/ai/05-build-run-test.md | 251 --- docs/ai/06-decision-log.md | 162 -- docs/ai/07-glossary.md | 100 -- docs/ai/99-open-questions.md | 141 -- docs/ai/INDEX.md | 52 - docs/arbitrage-engine-full-spec.md | 17 +- .../execution-state-machine.md | 251 --- .../funding-rate-arbitrage-plan.md | 205 --- docs/arbitrage-engine/index.mdx | 20 - docs/arbitrage-engine/phase0-progress.md | 138 -- docs/arbitrage-engine/requirements-v1.3.md | 540 ------- .../strategy-plaza-data-contract.md | 203 --- docs/arbitrage-engine/v2-v4-plan.mdx | 417 ----- docs/arbitrage-engine/v5-signal-system.mdx | 239 --- .../arbitrage-engine/v51-optimization-plan.md | 146 -- .../v51-performance-analysis.md | 163 -- .../v51-signal-enhancement.md | 250 --- docs/arbitrage-engine/v51-signal-system.md | 132 -- docs/arbitrage-engine/v51_gemini_analysis.md | 175 --- docs/arbitrage-engine/v52-development.md | 383 ----- .../arbitrage-engine/v52-evolution-roadmap.md | 269 ---- .../v52-performance-analysis.md | 193 --- docs/arbitrage-engine/v52-signal-system.md | 151 -- docs/arbitrage-engine/v53-design.md | 304 ---- .../v53-implementation-checklist.md | 108 -- docs/arbitrage-engine/v54-requirements.md | 265 ---- signal-engine.log | 196 --- 52 files changed, 4876 insertions(+), 8952 deletions(-) delete mode 100644 V52_FRONTEND_TASK.md delete mode 100644 V52_TASK.md create mode 100644 backend/paper_trading.py create mode 100644 backend/signal_state.py create mode 100644 backend/strategy_loader.py create mode 100644 backend/strategy_scoring.py delete mode 100644 docs/AB_TEST_CHECKLIST.md create mode 100644 docs/AI_HANDBOOK.md create mode 100644 docs/API_CONTRACTS.md create mode 100644 docs/BACKEND_RUNTIME.md delete mode 100644 docs/DETAILED_CODE_REVIEW.md create mode 100644 docs/FRONTEND_PAGES.md delete mode 100644 docs/PROBLEM_REPORT.md delete mode 100644 docs/PROJECT.md create mode 100644 docs/STRATEGY_FACTORY_VALIDATION.md delete mode 100644 docs/V52-TODO.md delete mode 100644 docs/ai/00-system-overview.md delete mode 100644 docs/ai/01-architecture-map.md delete mode 100644 docs/ai/02-module-cheatsheet.md delete mode 100644 docs/ai/03-api-contracts.md delete mode 100644 docs/ai/04-data-model.md delete mode 100644 docs/ai/05-build-run-test.md delete mode 100644 docs/ai/06-decision-log.md delete mode 100644 docs/ai/07-glossary.md delete mode 100644 docs/ai/99-open-questions.md delete mode 100644 docs/ai/INDEX.md delete mode 100644 docs/arbitrage-engine/execution-state-machine.md delete mode 100644 docs/arbitrage-engine/funding-rate-arbitrage-plan.md delete mode 100644 docs/arbitrage-engine/index.mdx delete mode 100644 docs/arbitrage-engine/phase0-progress.md delete mode 100644 docs/arbitrage-engine/requirements-v1.3.md delete mode 100644 docs/arbitrage-engine/strategy-plaza-data-contract.md delete mode 100644 docs/arbitrage-engine/v2-v4-plan.mdx delete mode 100644 docs/arbitrage-engine/v5-signal-system.mdx delete mode 100644 docs/arbitrage-engine/v51-optimization-plan.md delete mode 100644 docs/arbitrage-engine/v51-performance-analysis.md delete mode 100644 docs/arbitrage-engine/v51-signal-enhancement.md delete mode 100644 docs/arbitrage-engine/v51-signal-system.md delete mode 100644 docs/arbitrage-engine/v51_gemini_analysis.md delete mode 100644 docs/arbitrage-engine/v52-development.md delete mode 100644 docs/arbitrage-engine/v52-evolution-roadmap.md delete mode 100644 docs/arbitrage-engine/v52-performance-analysis.md delete mode 100644 docs/arbitrage-engine/v52-signal-system.md delete mode 100644 docs/arbitrage-engine/v53-design.md delete mode 100644 docs/arbitrage-engine/v53-implementation-checklist.md delete mode 100644 docs/arbitrage-engine/v54-requirements.md delete mode 100644 signal-engine.log diff --git a/V52_FRONTEND_TASK.md b/V52_FRONTEND_TASK.md deleted file mode 100644 index 64254b9..0000000 --- a/V52_FRONTEND_TASK.md +++ /dev/null @@ -1,63 +0,0 @@ -# V5.2 Frontend Differentiation Task - -## Problem -V5.1 and V5.2 currently share the same pages. Boss wants clear visual separation. - -## Requirements - -### 1. Signals Page (/signals) - Side-by-side comparison -Currently shows one set of scores per coin. Change to show BOTH V5.1 and V5.2 scores side by side. - -For the "Latest Signal" cards at the top, each coin should show: -``` -BTC SHORT V5.1: 80分 | V5.2: 85分 5m前 -``` - -The V5.2 score should show FR and Liquidation subscores that V5.1 doesn't have. - -To get V5.2 scores, add a new API endpoint `/api/signals/latest-v52` that returns the V5.2 evaluation alongside V5.1. Or modify the existing `/api/signals/latest` to include both strategy scores. - -### 2. Paper Trading Page (/paper) - Strategy Tabs at TOP -Add prominent tabs at the very top of the page: - -``` -[全部] [V5.1 模拟盘] [V5.2 模拟盘] -``` - -When selecting a strategy tab: -- Current positions: only show positions for that strategy -- Trade history: only show trades for that strategy -- Stats: only show stats for that strategy -- Equity curve: only show curve for that strategy -- The "全部" tab shows everything combined (current behavior) - -### 3. Visual Differentiation -- V5.1 trades/positions: use a subtle blue-gray badge -- V5.2 trades/positions: use a green badge with ✨ icon -- V5.2 positions should show extra info: FR score and Liquidation score prominently - -### 4. Backend API Changes Needed - -#### Modify `/api/signals/latest` endpoint in main.py -Return both V5.1 and V5.2 evaluations. The signal_engine already evaluates both strategies per cycle and saves the primary one. We need to also save V5.2 evaluations or compute them on-the-fly. - -Simplest approach: Add a field to the signal_indicators table or return strategy-specific data. - -Actually, the simplest approach for NOW: In the latest signal cards, just show the score that's already there (from primary strategy), and add a note showing which strategy it's from. The real differentiation happens in paper trades where the strategy column exists. - -#### `/api/paper/trades` already supports `?strategy=` filter (Codex added this) -#### `/api/paper/stats-by-strategy` already exists - -### 5. Key Files to Modify -- `frontend/app/paper/page.tsx` - Add strategy tabs at top, filter everything by selected strategy -- `frontend/app/signals/page.tsx` - Show V5.2 specific info (FR/Liq scores) in latest signal cards -- Backend: may need minor API tweaks - -### 6. Important -- Don't break existing functionality -- The strategy tabs should be very prominent (not small buttons buried in a section) -- Use consistent styling: slate-800 bg for active tab, slate-100 for inactive -- Test with `npm run build` - -When completely finished, run: -openclaw system event --text "Done: V5.2 frontend differentiation - strategy tabs, visual badges, FR/Liq display" --mode now diff --git a/V52_TASK.md b/V52_TASK.md deleted file mode 100644 index 0e79d4a..0000000 --- a/V52_TASK.md +++ /dev/null @@ -1,224 +0,0 @@ -# V5.2 Development Task - -## Context -You are working on the `dev` branch of the ArbitrageEngine project. -This is a quantitative trading signal system with: -- Backend: Python (FastAPI + PostgreSQL) -- Frontend: Next.js + shadcn/ui + Tailwind - -## Database Connection -- Host: 34.85.117.248 (Cloud SQL) -- Port: 5432, DB: arb_engine, User: arb, Password: arb_engine_2026 - -## What to Build (V5.2) - -### 1. Strategy Configuration Framework -Create `backend/strategies/` directory with JSON configs: - -**backend/strategies/v51_baseline.json:** -```json -{ - "name": "v51_baseline", - "version": "5.1", - "threshold": 75, - "weights": { - "direction": 45, - "crowding": 20, - "environment": 15, - "confirmation": 15, - "auxiliary": 5 - }, - "accel_bonus": 5, - "tp_sl": { - "sl_multiplier": 2.0, - "tp1_multiplier": 1.5, - "tp2_multiplier": 3.0 - }, - "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium"] -} -``` - -**backend/strategies/v52_8signals.json:** -```json -{ - "name": "v52_8signals", - "version": "5.2", - "threshold": 75, - "weights": { - "direction": 40, - "crowding": 25, - "environment": 15, - "confirmation": 20, - "auxiliary": 5 - }, - "accel_bonus": 5, - "tp_sl": { - "sl_multiplier": 2.0, - "tp1_multiplier": 1.5, - "tp2_multiplier": 3.0 - }, - "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium", "funding_rate", "liquidation"] -} -``` - -### 2. Signal Engine Changes (signal_engine.py) - -#### 2a. Add FR scoring to evaluate_signal() -After the crowding section, add funding_rate scoring: - -```python -# Funding Rate scoring (拥挤层加分) -# Read from market_indicators table -funding_rate = to_float(self.market_indicators.get("funding_rate")) -fr_score = 0 -if funding_rate is not None: - fr_abs = abs(funding_rate) - if fr_abs >= 0.001: # extreme ±0.1% - # Extreme: penalize if going WITH the crowd - if (direction == "LONG" and funding_rate > 0.001) or \ - (direction == "SHORT" and funding_rate < -0.001): - fr_score = -5 - else: - fr_score = 5 - elif fr_abs >= 0.0003: # moderate ±0.03% - # Moderate: reward going AGAINST the crowd - if (direction == "LONG" and funding_rate < -0.0003) or \ - (direction == "SHORT" and funding_rate > 0.0003): - fr_score = 5 - else: - fr_score = 0 -``` - -#### 2b. Add liquidation scoring -```python -# Liquidation scoring (确认层加分) -liq_score = 0 -liq_data = self.fetch_recent_liquidations() # new method -if liq_data: - liq_long_usd = liq_data.get("long_usd", 0) - liq_short_usd = liq_data.get("short_usd", 0) - # Thresholds by symbol - thresholds = {"BTCUSDT": 500000, "ETHUSDT": 200000, "XRPUSDT": 100000, "SOLUSDT": 100000} - threshold = thresholds.get(self.symbol, 100000) - total = liq_long_usd + liq_short_usd - if total >= threshold: - if liq_short_usd > 0 and liq_long_usd > 0: - ratio = liq_short_usd / liq_long_usd - elif liq_short_usd > 0: - ratio = float('inf') - else: - ratio = 0 - if ratio >= 2.0 and direction == "LONG": - liq_score = 5 # shorts getting liquidated, price going up - elif ratio <= 0.5 and direction == "SHORT": - liq_score = 5 # longs getting liquidated, price going down -``` - -#### 2c. Add fetch_recent_liquidations method to SymbolState -```python -def fetch_recent_liquidations(self, window_ms=300000): - """Fetch last 5min liquidation totals from liquidations table""" - now_ms = int(time.time() * 1000) - cutoff = now_ms - window_ms - with get_sync_conn() as conn: - with conn.cursor() as cur: - cur.execute(""" - SELECT - COALESCE(SUM(CASE WHEN side='SELL' THEN usd_value ELSE 0 END), 0) as long_liq, - COALESCE(SUM(CASE WHEN side='BUY' THEN usd_value ELSE 0 END), 0) as short_liq - FROM liquidations - WHERE symbol=%s AND trade_time >= %s - """, (self.symbol, cutoff)) - row = cur.fetchone() - if row: - return {"long_usd": row[0], "short_usd": row[1]} - return None -``` - -#### 2d. Add funding_rate to fetch_market_indicators -Add "funding_rate" to the indicator types: -```python -for ind_type in ["long_short_ratio", "top_trader_position", "open_interest_hist", "coinbase_premium", "funding_rate"]: -``` -And the extraction: -```python -elif ind_type == "funding_rate": - indicators[ind_type] = float(val.get("lastFundingRate", 0)) -``` - -#### 2e. Update total_score calculation -Currently: -```python -total_score = direction_score + accel_bonus + crowding_score + environment_score + confirmation_score + aux_score -``` -Change to: -```python -total_score = direction_score + accel_bonus + crowding_score + fr_score + environment_score + confirmation_score + liq_score + aux_score -``` - -#### 2f. Update factors dict -Add fr_score and liq_score to the factors: -```python -result["factors"] = { - ...existing factors..., - "funding_rate": {"score": fr_score, "value": funding_rate}, - "liquidation": {"score": liq_score, "long_usd": liq_data.get("long_usd", 0) if liq_data else 0, "short_usd": liq_data.get("short_usd", 0) if liq_data else 0}, -} -``` - -#### 2g. Change threshold from 60 to 75 -In evaluate_signal, change: -```python -# OLD -elif total_score >= 60 and not no_direction and not in_cooldown: - result["signal"] = direction - result["tier"] = "light" -# NEW: remove the 60 tier entirely, minimum is 75 -``` - -Also update reverse signal threshold from 60 to 75: -In main() loop: -```python -# OLD -if existing_dir and eval_dir and existing_dir != eval_dir and result["score"] >= 60: -# NEW -if existing_dir and eval_dir and existing_dir != eval_dir and result["score"] >= 75: -``` - -### 3. Strategy field in paper_trades -Add SQL migration at top of init_schema() or in a migration: -```sql -ALTER TABLE paper_trades ADD COLUMN IF NOT EXISTS strategy VARCHAR(32) DEFAULT 'v51_baseline'; -``` - -### 4. AB Test: Both strategies evaluate each cycle -In the main loop, evaluate signal twice (once per strategy config) and potentially open trades for both. Each trade records which strategy triggered it. - -### 5. Frontend: Update paper/page.tsx -- Show strategy column in trade history table -- Show FR and liquidation scores in signal details -- Add strategy filter/tab (v51 vs v52) - -### 6. API: Add strategy stats endpoint -In main.py, add `/api/paper/stats-by-strategy` that groups stats by strategy field. - -## Important Notes -- Keep ALL existing functionality working -- Don't break the existing V5.1 scoring - it should still work as strategy "v51_baseline" -- The FR data is already in market_indicators table (collected every 5min) -- The liquidation data is already in liquidations table -- Test with: `cd frontend && npm run build` to verify no frontend errors -- Test backend: `python3 -c "from signal_engine import *; print('OK')"` to verify imports -- Port for dev testing: API=8100, Frontend=3300 -- Total score CAN exceed 100 (that's by design) - -## Files to modify: -1. `backend/signal_engine.py` - core scoring changes -2. `backend/main.py` - new API endpoints -3. `backend/db.py` - add strategy column migration -4. `frontend/app/paper/page.tsx` - UI updates -5. NEW: `backend/strategies/v51_baseline.json` -6. NEW: `backend/strategies/v52_8signals.json` - -When completely finished, run this command to notify me: -openclaw system event --text "Done: V5.2 core implementation complete - FR+liquidation scoring, threshold 75, strategy configs, AB test framework" --mode now diff --git a/backend/backtest.py b/backend/backtest.py index b19063d..37b4578 100644 --- a/backend/backtest.py +++ b/backend/backtest.py @@ -24,9 +24,10 @@ from typing import Optional import psycopg2 -# 复用signal_engine的核心类 +# 复用 signal_engine/signal_state 的核心类与评分逻辑 sys.path.insert(0, os.path.dirname(__file__)) from signal_engine import SymbolState, WINDOW_MID +from strategy_scoring import evaluate_signal as score_strategy logging.basicConfig( level=logging.INFO, @@ -220,7 +221,13 @@ class BacktestEngine: if len(self.positions) > 0: return - result = self.state.evaluate_signal(time_ms) + # 使用统一评分入口(V5.1 baseline 配置) + strategy_cfg = { + "name": "v51_baseline", + "threshold": 75, + "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium"], + } + result = score_strategy(self.state, time_ms, strategy_cfg=strategy_cfg) signal = result.get("signal") if not signal: return diff --git a/backend/main.py b/backend/main.py index 1df003b..d39c7de 100644 --- a/backend/main.py +++ b/backend/main.py @@ -448,7 +448,7 @@ async def get_signal_indicators( @app.get("/api/signals/latest") -async def get_signal_latest(user: dict = Depends(get_current_user), strategy: str = "v52_8signals"): +async def get_signal_latest(user: dict = Depends(get_current_user), strategy: str = "v53"): result = {} for sym in SYMBOLS: row = await async_fetchrow( @@ -601,7 +601,7 @@ async def get_market_indicators(user: dict = Depends(get_current_user)): async def get_signal_history( symbol: str = "BTC", limit: int = 50, - strategy: str = "v52_8signals", + strategy: str = "v53", user: dict = Depends(get_current_user), ): """返回最近的信号历史(只返回有信号的记录),含各层分数""" diff --git a/backend/paper_monitor.py b/backend/paper_monitor.py index 898d13b..6ef9793 100644 --- a/backend/paper_monitor.py +++ b/backend/paper_monitor.py @@ -47,16 +47,19 @@ def check_and_close(symbol_upper: str, price: float): with get_sync_conn() as conn: with conn.cursor() as cur: + # 关联 strategies 表获取 timeout_minutes(每个策略可独立配置超时时间) cur.execute( - "SELECT id, direction, entry_price, tp1_price, tp2_price, sl_price, " - "tp1_hit, entry_ts, atr_at_entry, risk_distance " - "FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit')", - (symbol_upper,) + "SELECT p.id, p.direction, p.entry_price, p.tp1_price, p.tp2_price, p.sl_price, " + "p.tp1_hit, p.entry_ts, p.atr_at_entry, p.risk_distance, s.timeout_minutes " + "FROM paper_trades AS p " + "LEFT JOIN strategies AS s ON p.strategy_id = s.strategy_id " + "WHERE p.symbol=%s AND p.status IN ('active','tp1_hit')", + (symbol_upper,), ) positions = cur.fetchall() for pos in positions: - pid, direction, entry_price, tp1, tp2, sl, tp1_hit, entry_ts, atr_entry, rd_db = pos + pid, direction, entry_price, tp1, tp2, sl, tp1_hit, entry_ts, atr_entry, rd_db, timeout_db = pos closed = False new_status = None pnl_r = 0.0 @@ -112,8 +115,9 @@ def check_and_close(symbol_upper: str, price: float): tp2_r = (entry_price - tp2) / risk_distance if risk_distance > 0 else 0 pnl_r = 0.5 * tp1_r + 0.5 * tp2_r - # 时间止损:60分钟(市价平仓,用当前价) - if not closed and (now_ms - entry_ts > 60 * 60 * 1000): + # 时间止损:按 strategies.timeout_minutes 配置(缺省 240 分钟) + timeout_minutes = timeout_db if timeout_db and timeout_db > 0 else 240 + if not closed and (now_ms - entry_ts > timeout_minutes * 60 * 1000): closed = True exit_price = price # 超时是市价平仓 new_status = "timeout" diff --git a/backend/paper_trading.py b/backend/paper_trading.py new file mode 100644 index 0000000..e3cc884 --- /dev/null +++ b/backend/paper_trading.py @@ -0,0 +1,202 @@ +""" +paper_trading.py — 模拟盘开仓/平仓辅助函数 + +从原来的 signal_engine.py 拆分出的 paper_trades 辅助逻辑: + - paper_open_trade(): 写入 paper_trades 开仓记录; + - paper_has_active_position() / paper_get_active_direction(); + - paper_close_by_signal() / paper_active_count()。 + +行为保持与原实现完全一致,供 signal_engine 调用。 +""" + +from typing import Optional + +from db import get_sync_conn +from signal_engine import PAPER_FEE_RATE # 复用全局配置 + + +def paper_open_trade( + symbol: str, + direction: str, + price: float, + score: int, + tier: str, + atr: float, + now_ms: int, + factors: dict = None, + strategy: str = "v51_baseline", + tp_sl: Optional[dict] = None, + strategy_id: Optional[str] = None, + strategy_name_snapshot: Optional[str] = None, + logger=None, +): + """模拟开仓:写入 paper_trades""" + import json as _json3 + + if atr <= 0: + return + tp_sl_cfg = tp_sl or {} + sl_multiplier = float(tp_sl_cfg.get("sl_multiplier", 2.0)) + + # 支持两种配置方式: + # - 新版 v5.4 strategies 表:tp1_ratio / tp2_ratio = 以 R 计的目标(× risk_distance) + # - 旧版 v5.2/v5.3 JSON 策略:tp1_multiplier / tp2_multiplier = 以 ATR 计的目标 + tp1_ratio = tp_sl_cfg.get("tp1_ratio") + tp2_ratio = tp_sl_cfg.get("tp2_ratio") + use_r_based = tp1_ratio is not None and tp2_ratio is not None + if use_r_based: + tp1_ratio = float(tp1_ratio) + tp2_ratio = float(tp2_ratio) + else: + tp1_multiplier = float(tp_sl_cfg.get("tp1_multiplier", 1.5)) + tp2_multiplier = float(tp_sl_cfg.get("tp2_multiplier", 3.0)) + + # 统一定义:1R = SL 距离 = sl_multiplier × ATR + risk_distance = sl_multiplier * atr + if direction == "LONG": + sl = price - risk_distance + if use_r_based: + tp1 = price + tp1_ratio * risk_distance + tp2 = price + tp2_ratio * risk_distance + else: + tp1 = price + tp1_multiplier * atr + tp2 = price + tp2_multiplier * atr + else: + sl = price + risk_distance + if use_r_based: + tp1 = price - tp1_ratio * risk_distance + tp2 = price - tp2_ratio * risk_distance + else: + tp1 = price - tp1_multiplier * atr + tp2 = price - tp2_multiplier * atr + + # SL 合理性校验:实际距离必须在 risk_distance 的 80%~120% 范围内 + actual_sl_dist = abs(sl - price) + if actual_sl_dist < risk_distance * 0.8 or actual_sl_dist > risk_distance * 1.2: + if logger: + logger.error( + f"[{symbol}] ⚠️ SL校验失败,拒绝开仓: direction={direction} price={price:.4f} " + f"sl={sl:.4f} actual_dist={actual_sl_dist:.4f} expected={risk_distance:.4f} atr={atr:.4f}" + ) + return + + with get_sync_conn() as conn: + with conn.cursor() as cur: + cur.execute( + "INSERT INTO paper_trades (symbol,direction,score,tier,entry_price,entry_ts,tp1_price,tp2_price,sl_price,atr_at_entry,score_factors,strategy,risk_distance,strategy_id,strategy_name_snapshot) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", + ( + symbol, + direction, + score, + tier, + price, + now_ms, + tp1, + tp2, + sl, + atr, + _json3.dumps(factors) if factors else None, + strategy, + risk_distance, + strategy_id, + strategy_name_snapshot, + ), + ) + conn.commit() + if logger: + logger.info( + f"[{symbol}] 📝 模拟开仓: {direction} @ {price:.2f} score={score} tier={tier} strategy={strategy} " + f"TP1={tp1:.2f} TP2={tp2:.2f} SL={sl:.2f}" + ) + + +def paper_has_active_position(symbol: str, strategy: Optional[str] = None) -> bool: + """检查该币种是否有活跃持仓""" + with get_sync_conn() as conn: + with conn.cursor() as cur: + if strategy: + cur.execute( + "SELECT COUNT(*) FROM paper_trades WHERE symbol=%s AND strategy=%s AND status IN ('active','tp1_hit')", + (symbol, strategy), + ) + else: + cur.execute( + "SELECT COUNT(*) FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit')", + (symbol,), + ) + return cur.fetchone()[0] > 0 + + +def paper_get_active_direction(symbol: str, strategy: Optional[str] = None) -> str | None: + """获取该币种活跃持仓的方向,无持仓返回 None""" + with get_sync_conn() as conn: + with conn.cursor() as cur: + if strategy: + cur.execute( + "SELECT direction FROM paper_trades WHERE symbol=%s AND strategy=%s AND status IN ('active','tp1_hit') LIMIT 1", + (symbol, strategy), + ) + else: + cur.execute( + "SELECT direction FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit') LIMIT 1", + (symbol,), + ) + row = cur.fetchone() + return row[0] if row else None + + +def paper_close_by_signal(symbol: str, current_price: float, now_ms: int, strategy: Optional[str] = None, logger=None): + """反向信号平仓:按当前价平掉该币种所有活跃仓位""" + with get_sync_conn() as conn: + with conn.cursor() as cur: + if strategy: + cur.execute( + "SELECT id, direction, entry_price, tp1_hit, atr_at_entry, risk_distance " + "FROM paper_trades WHERE symbol=%s AND strategy=%s AND status IN ('active','tp1_hit')", + (symbol, strategy), + ) + else: + cur.execute( + "SELECT id, direction, entry_price, tp1_hit, atr_at_entry, risk_distance " + "FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit')", + (symbol,), + ) + positions = cur.fetchall() + for pos in positions: + pid, direction, entry_price, tp1_hit, atr_entry, rd_db = pos + risk_distance = rd_db if rd_db and rd_db > 0 else abs(entry_price * 0.01) + if direction == "LONG": + pnl_r = (current_price - entry_price) / risk_distance if risk_distance > 0 else 0 + else: + pnl_r = (entry_price - current_price) / risk_distance if risk_distance > 0 else 0 + # 扣手续费 + fee_r = (2 * PAPER_FEE_RATE * entry_price) / risk_distance if risk_distance > 0 else 0 + pnl_r -= fee_r + cur.execute( + "UPDATE paper_trades SET status='signal_flip', exit_price=%s, exit_ts=%s, pnl_r=%s WHERE id=%s", + (current_price, now_ms, round(pnl_r, 4), pid), + ) + if logger: + logger.info( + f"[{symbol}] 📝 反向信号平仓: {direction} @ {current_price:.2f} pnl={pnl_r:+.2f}R" + f"{f' strategy={strategy}' if strategy else ''}" + ) + conn.commit() + + +def paper_active_count(strategy: Optional[str] = None) -> int: + """当前活跃持仓总数(按策略独立计数)""" + with get_sync_conn() as conn: + with conn.cursor() as cur: + if strategy: + cur.execute( + "SELECT COUNT(*) FROM paper_trades WHERE strategy=%s AND status IN ('active','tp1_hit')", + (strategy,), + ) + else: + cur.execute( + "SELECT COUNT(*) FROM paper_trades WHERE status IN ('active','tp1_hit')" + ) + return cur.fetchone()[0] + diff --git a/backend/signal_engine.py b/backend/signal_engine.py index cfd640a..848aed7 100644 --- a/backend/signal_engine.py +++ b/backend/signal_engine.py @@ -18,16 +18,28 @@ signal_engine.py — V5 短线交易信号引擎(PostgreSQL版) import logging import os import time -import json import threading import asyncio -from collections import deque from datetime import datetime, timezone -from typing import Any, Optional +from typing import Optional +import json import websockets from db import get_sync_conn, init_schema +from signal_state import SymbolState as BaseSymbolState +from strategy_scoring import evaluate_signal as score_strategy +from strategy_loader import ( + load_strategy_configs, + load_strategy_configs_from_db, +) +from paper_trading import ( + paper_open_trade, + paper_has_active_position, + paper_get_active_direction, + paper_close_by_signal, + paper_active_count, +) logging.basicConfig( level=logging.INFO, @@ -42,131 +54,6 @@ logger = logging.getLogger("signal-engine") SYMBOLS = ["BTCUSDT", "ETHUSDT", "XRPUSDT", "SOLUSDT"] LOOP_INTERVAL = 15 # 秒(从5改15,CPU降60%,信号质量无影响) STRATEGY_DIR = os.path.join(os.path.dirname(__file__), "strategies") -DEFAULT_STRATEGY_FILES = ["v51_baseline.json", "v52_8signals.json", "v53.json", "v53_fast.json", "v53_middle.json"] - - -def load_strategy_configs() -> list[dict]: - configs = [] - for filename in DEFAULT_STRATEGY_FILES: - path = os.path.join(STRATEGY_DIR, filename) - try: - with open(path, "r", encoding="utf-8") as f: - cfg = json.load(f) - if isinstance(cfg, dict) and cfg.get("name"): - configs.append(cfg) - except FileNotFoundError: - logger.warning(f"策略配置缺失: {path}") - except Exception as e: - logger.error(f"策略配置加载失败 {path}: {e}") - if not configs: - logger.warning("未加载到策略配置,回退到v51_baseline默认配置") - configs.append( - { - "name": "v51_baseline", - "threshold": 75, - "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium"], - "tp_sl": {"sl_multiplier": 2.0, "tp1_multiplier": 1.5, "tp2_multiplier": 3.0}, - } - ) - return configs - - -def load_strategy_configs_from_db() -> list[dict]: - """ - V5.4: 从 strategies 表读取 running 状态的策略配置。 - 把 DB 字段映射成现有 JSON 格式(保持与 JSON 文件完全兼容)。 - 失败时返回空列表,调用方应 fallback 到 JSON。 - 内存安全:每次读取只返回配置列表,无缓存,无大对象。 - """ - try: - with get_sync_conn() as conn: - with conn.cursor() as cur: - cur.execute(""" - SELECT - strategy_id::text, display_name, symbol, - cvd_fast_window, cvd_slow_window, - weight_direction, weight_env, weight_aux, weight_momentum, - entry_score, - gate_obi_enabled, obi_threshold, - gate_whale_enabled, whale_usd_threshold, whale_flow_pct, - gate_vol_enabled, vol_atr_pct_min, - gate_cvd_enabled, - gate_spot_perp_enabled, spot_perp_threshold, - sl_atr_multiplier, tp1_ratio, tp2_ratio, - timeout_minutes, flip_threshold, direction - FROM strategies - WHERE status = 'running' - ORDER BY created_at ASC - """) - rows = cur.fetchall() - - configs = [] - for row in rows: - (sid, display_name, symbol, - cvd_fast, cvd_slow, - w_dir, w_env, w_aux, w_mom, - entry_score, - gate_obi, obi_thr, - gate_whale, whale_usd_thr, whale_flow_pct_val, - gate_vol, vol_atr_pct, - gate_cvd, - gate_spot, spot_thr, - sl_mult, tp1_r, tp2_r, - timeout_min, flip_thr, direction) = row - - # 把 display_name 映射回 legacy strategy name(用于兼容评分逻辑) - # legacy 策略用固定 UUID 识别 - LEGACY_UUID_MAP = { - "00000000-0000-0000-0000-000000000053": "v53", - "00000000-0000-0000-0000-000000000054": "v53_middle", - "00000000-0000-0000-0000-000000000055": "v53_fast", - } - strategy_name = LEGACY_UUID_MAP.get(sid, f"custom_{sid[:8]}") - - # 构造与 JSON 文件格式兼容的配置 dict - cfg = { - "name": strategy_name, - "strategy_id": sid, # V5.4 新增:用于写 strategy_id 到 DB - "strategy_name_snapshot": display_name, # V5.4 新增:写入时快照名称 - "symbol": symbol, - "direction": direction, - "cvd_fast_window": cvd_fast, - "cvd_slow_window": cvd_slow, - "threshold": entry_score, - "weights": { - "direction": w_dir, - "env": w_env, - "aux": w_aux, - "momentum": w_mom, - }, - "gates": { - # 门1 波动率 - "vol": {"enabled": gate_vol, "vol_atr_pct_min": float(vol_atr_pct or 0.002)}, - # 门2 CVD共振 - "cvd": {"enabled": gate_cvd}, - # 门3 鲸鱼否决 - "whale": {"enabled": gate_whale, "whale_usd_threshold": float(whale_usd_thr or 50000), "whale_flow_pct": float(whale_flow_pct_val or 0.5)}, - # 门4 OBI否决 - "obi": {"enabled": gate_obi, "threshold": float(obi_thr or 0.35)}, - # 门5 期现背离 - "spot_perp": {"enabled": gate_spot, "threshold": float(spot_thr or 0.005)}, - }, - "tp_sl": { - "sl_multiplier": sl_mult, - "tp1_multiplier": tp1_r, - "tp2_multiplier": tp2_r, - }, - "timeout_minutes": timeout_min, - "flip_threshold": flip_thr, - } - configs.append(cfg) - - logger.info(f"[DB] 已加载 {len(configs)} 个策略配置: {[c['name'] for c in configs]}") - return configs - - except Exception as e: - logger.warning(f"[DB] load_strategy_configs_from_db 失败,将 fallback 到 JSON: {e}") - return [] # ─── 模拟盘配置 ─────────────────────────────────────────────────── PAPER_TRADING_ENABLED = False # 总开关(兼容旧逻辑) @@ -218,9 +105,6 @@ WINDOW_VWAP = 30 * 60 * 1000 # 30分钟 ATR_PERIOD_MS = 5 * 60 * 1000 ATR_LENGTH = 14 -# 信号冷却 -COOLDOWN_MS = 10 * 60 * 1000 - def fetch_market_indicators(symbol: str) -> dict: """从PG读取最新的market_indicators数据,解析JSONB提取关键数值""" @@ -272,852 +156,27 @@ def fetch_market_indicators(symbol: str) -> dict: return indicators -def to_float(value: Any) -> Optional[float]: - try: - return float(value) if value is not None else None - except (TypeError, ValueError): - return None +class SymbolState(BaseSymbolState): + """ + 兼容旧接口的 SymbolState 包装: + - 旧代码只传入 symbol,这里补全窗口与 ATR 配置。 + - 评分逻辑统一在 strategy_scoring.evaluate_signal 中实现。 + """ - -# ─── 滚动窗口 ─────────────────────────────────────────────────── - -class TradeWindow: - def __init__(self, window_ms: int): - self.window_ms = window_ms - self.trades: deque = deque() - self.buy_vol = 0.0 - self.sell_vol = 0.0 - self.pq_sum = 0.0 - self.q_sum = 0.0 - - def add(self, time_ms: int, qty: float, price: float, is_buyer_maker: int): - self.trades.append((time_ms, qty, price, is_buyer_maker)) - pq = price * qty - self.pq_sum += pq - self.q_sum += qty - if is_buyer_maker == 0: - self.buy_vol += qty - else: - self.sell_vol += qty - - def trim(self, now_ms: int): - cutoff = now_ms - self.window_ms - while self.trades and self.trades[0][0] < cutoff: - t_ms, qty, price, ibm = self.trades.popleft() - self.pq_sum -= price * qty - self.q_sum -= qty - if ibm == 0: - self.buy_vol -= qty - else: - self.sell_vol -= qty - - @property - def cvd(self) -> float: - return self.buy_vol - self.sell_vol - - @property - def vwap(self) -> float: - return self.pq_sum / self.q_sum if self.q_sum > 0 else 0.0 - - -class ATRCalculator: - def __init__(self, period_ms: int = ATR_PERIOD_MS, length: int = ATR_LENGTH): - self.period_ms = period_ms - self.length = length - self.candles: deque = deque(maxlen=length + 1) - self.current_candle: Optional[dict] = None - self.atr_history: deque = deque(maxlen=288) - - def update(self, time_ms: int, price: float): - bar_ms = (time_ms // self.period_ms) * self.period_ms - if self.current_candle is None or self.current_candle["bar"] != bar_ms: - if self.current_candle is not None: - self.candles.append(self.current_candle) - self.current_candle = {"bar": bar_ms, "open": price, "high": price, "low": price, "close": price} - else: - c = self.current_candle - c["high"] = max(c["high"], price) - c["low"] = min(c["low"], price) - c["close"] = price - - @property - def atr(self) -> float: - if len(self.candles) < 2: - return 0.0 - trs = [] - candles_list = list(self.candles) - for i in range(1, len(candles_list)): - prev_close = candles_list[i-1]["close"] - c = candles_list[i] - tr = max(c["high"] - c["low"], abs(c["high"] - prev_close), abs(c["low"] - prev_close)) - trs.append(tr) - if not trs: - return 0.0 - atr_val = trs[0] - for tr in trs[1:]: - atr_val = (atr_val * (self.length - 1) + tr) / self.length - return atr_val - - @property - def atr_percentile(self) -> float: - current = self.atr - if current == 0: - return 50.0 - self.atr_history.append(current) - if len(self.atr_history) < 10: - return 50.0 - sorted_hist = sorted(self.atr_history) - rank = sum(1 for x in sorted_hist if x <= current) - return (rank / len(sorted_hist)) * 100 - - -# ─── FR历史最大值缓存(每小时更新)─────────────────────────────── -_max_fr_cache: dict = {} # {symbol: max_abs_fr} -_max_fr_updated: float = 0 - -def get_max_fr(symbol: str) -> float: - """获取该币种历史最大|FR|,每小时刷新一次""" - global _max_fr_cache, _max_fr_updated - now = time.time() - if now - _max_fr_updated > 3600 or symbol not in _max_fr_cache: - try: - with get_sync_conn() as conn: - with conn.cursor() as cur: - cur.execute( - "SELECT symbol, MAX(ABS((value->>'fundingRate')::float)) as max_fr " - "FROM market_indicators WHERE indicator_type='funding_rate' " - "GROUP BY symbol" - ) - for row in cur.fetchall(): - _max_fr_cache[row[0]] = row[1] if row[1] else 0.0001 - _max_fr_updated = now - except Exception as e: - logger.warning(f"get_max_fr error: {e}") - return _max_fr_cache.get(symbol, 0.0001) # 默认0.01%防除零 - - -class SymbolState: def __init__(self, symbol: str): - self.symbol = symbol - self.win_fast = TradeWindow(WINDOW_FAST) - self.win_mid = TradeWindow(WINDOW_MID) - self.win_day = TradeWindow(WINDOW_DAY) - self.win_vwap = TradeWindow(WINDOW_VWAP) - self.atr_calc = ATRCalculator() - self.last_processed_id = 0 - self.last_trade_price = 0.0 - self.warmup = True - self.prev_cvd_fast = 0.0 - self.prev_cvd_fast_slope = 0.0 - self.prev_oi_value = 0.0 - self.market_indicators = fetch_market_indicators(symbol) - self.last_signal_ts: dict[str, int] = {} - self.last_signal_dir: dict[str, str] = {} - self.recent_large_trades: deque = deque() - # ── Phase 2 实时内存字段(由后台WebSocket协程更新)────────── - self.rt_obi: float = 0.0 # 订单簿失衡[-1,1](实时WebSocket,所有symbol) - self.rt_spot_perp_div: float = 0.0 # 期现背离(spot-mark)/mark(实时WebSocket,所有symbol) - # tiered_cvd_whale:按成交额分档,实时累计(最近15分钟窗口) - self._whale_trades: deque = deque() # (time_ms, usd_val, is_sell) - self.WHALE_WINDOW_MS: int = 15 * 60 * 1000 # 15分钟 - - def process_trade(self, agg_id: int, time_ms: int, price: float, qty: float, is_buyer_maker: int): - now_ms = time_ms - self.win_fast.add(time_ms, qty, price, is_buyer_maker) - self.win_mid.add(time_ms, qty, price, is_buyer_maker) - self.win_day.add(time_ms, qty, price, is_buyer_maker) - self.win_vwap.add(time_ms, qty, price, is_buyer_maker) - self.atr_calc.update(time_ms, price) - self.win_fast.trim(now_ms) - self.win_mid.trim(now_ms) - self.win_day.trim(now_ms) - self.win_vwap.trim(now_ms) - self.last_processed_id = agg_id - self.last_trade_price = price # 最新成交价,用于entry_price - - # tiered_cvd_whale 实时累计(>$100k 为巨鲸) - usd_val = price * qty - if usd_val >= 100_000: - self._whale_trades.append((time_ms, usd_val, bool(is_buyer_maker))) - # 修剪15分钟窗口 - cutoff = now_ms - self.WHALE_WINDOW_MS - while self._whale_trades and self._whale_trades[0][0] < cutoff: - self._whale_trades.popleft() - - @property - def whale_cvd_ratio(self) -> float: - """巨鲸净CVD比率[-1,1],基于最近15分钟>$100k成交""" - buy_usd = sum(t[1] for t in self._whale_trades if not t[2]) - sell_usd = sum(t[1] for t in self._whale_trades if t[2]) - total = buy_usd + sell_usd - return (buy_usd - sell_usd) / total if total > 0 else 0.0 - - def compute_p95_p99(self) -> tuple: - if len(self.win_day.trades) < 100: - return 5.0, 10.0 - qtys = sorted([t[1] for t in self.win_day.trades]) - n = len(qtys) - p95 = qtys[int(n * 0.95)] - p99 = qtys[int(n * 0.99)] - if "BTC" in self.symbol: - p95 = max(p95, 5.0); p99 = max(p99, 10.0) - else: - p95 = max(p95, 50.0); p99 = max(p99, 100.0) - return p95, p99 - - def update_large_trades(self, now_ms: int, p99: float): - cutoff = now_ms - 15 * 60 * 1000 - while self.recent_large_trades and self.recent_large_trades[0][0] < cutoff: - self.recent_large_trades.popleft() - # 只检查新trade(避免重复添加) - seen = set(t[0] for t in self.recent_large_trades) # time_ms作为去重key - for t in self.win_fast.trades: - if t[1] >= p99 and t[0] > cutoff and t[0] not in seen: - self.recent_large_trades.append((t[0], t[1], t[3])) - seen.add(t[0]) - - def build_evaluation_snapshot(self, now_ms: int) -> dict: - cvd_fast = self.win_fast.cvd - cvd_mid = self.win_mid.cvd - cvd_day = self.win_day.cvd - vwap = self.win_vwap.vwap - atr = self.atr_calc.atr - atr_pct = self.atr_calc.atr_percentile - p95, p99 = self.compute_p95_p99() - self.update_large_trades(now_ms, p99) - price = self.last_trade_price if self.last_trade_price > 0 else vwap # 用最新成交价,非VWAP - cvd_fast_slope = cvd_fast - self.prev_cvd_fast - cvd_fast_accel = cvd_fast_slope - self.prev_cvd_fast_slope - self.prev_cvd_fast = cvd_fast - self.prev_cvd_fast_slope = cvd_fast_slope - - oi_value = to_float(self.market_indicators.get("open_interest_hist")) - if oi_value is None or self.prev_oi_value == 0: - oi_change = 0.0 - environment_score = 10 - else: - oi_change = (oi_value - self.prev_oi_value) / self.prev_oi_value if self.prev_oi_value > 0 else 0.0 - if oi_change >= 0.03: - environment_score = 15 - elif oi_change > 0: - environment_score = 10 - else: - environment_score = 5 - if oi_value is not None and oi_value > 0: - self.prev_oi_value = oi_value - - return { - "cvd_fast": cvd_fast, - "cvd_mid": cvd_mid, - "cvd_day": cvd_day, - "vwap": vwap, - "atr": atr, - "atr_value": atr, # V5.3: ATR绝对值快照(用于feature_events落库) - "atr_pct": atr_pct, - "p95": p95, - "p99": p99, - "price": price, - "cvd_fast_slope": cvd_fast_slope, - "cvd_fast_accel": cvd_fast_accel, - "oi_change": oi_change, - "environment_score": environment_score, - "oi_value": oi_value, - } - - def fetch_recent_liquidations(self, window_ms: int = 300000): - """Fetch last 5min liquidation totals from liquidations table""" - now_ms = int(time.time() * 1000) - cutoff = now_ms - window_ms - with get_sync_conn() as conn: - with conn.cursor() as cur: - cur.execute( - """ - SELECT - COALESCE(SUM(CASE WHEN side='LONG' THEN usd_value ELSE 0 END), 0) as long_liq, - COALESCE(SUM(CASE WHEN side='SHORT' THEN usd_value ELSE 0 END), 0) as short_liq - FROM liquidations - WHERE symbol=%s AND trade_time >= %s - """, - (self.symbol, cutoff), - ) - row = cur.fetchone() - if row: - return {"long_usd": row[0], "short_usd": row[1]} - return None - - def evaluate_signal(self, now_ms: int, strategy_cfg: Optional[dict] = None, snapshot: Optional[dict] = None) -> dict: - strategy_cfg = strategy_cfg or {} - strategy_name = strategy_cfg.get("name", "v51_baseline") - track = strategy_cfg.get("track", "ALT") - - # ─── Track Router ─────────────────────────────────────────── - # v53 → 统一评分(BTC/ETH/XRP/SOL) - # v53_alt / v53_btc → 兼容旧策略名,转发到 _evaluate_v53() - # v51/v52 → 原有代码路径(兼容,不修改) - if strategy_name.startswith("v53") or strategy_name.startswith("custom_"): - allowed_symbols = strategy_cfg.get("symbols", []) - if allowed_symbols and self.symbol not in allowed_symbols: - snap = snapshot or self.build_evaluation_snapshot(now_ms) - return self._empty_result(strategy_name, snap) - return self._evaluate_v53(now_ms, strategy_cfg, snapshot) - # ─── 原有V5.1/V5.2评分逻辑(保持不变)──────────────────────── - strategy_cfg = strategy_cfg or {} - strategy_name = strategy_cfg.get("name", "v51_baseline") - strategy_threshold = int(strategy_cfg.get("threshold", 75)) - enabled_signals = set(strategy_cfg.get("signals", [])) - - snap = snapshot or self.build_evaluation_snapshot(now_ms) - cvd_fast = snap["cvd_fast"] - cvd_mid = snap["cvd_mid"] - vwap = snap["vwap"] - atr = snap["atr"] - atr_pct = snap["atr_pct"] - p95 = snap["p95"] - p99 = snap["p99"] - price = snap["price"] - cvd_fast_slope = snap["cvd_fast_slope"] - cvd_fast_accel = snap["cvd_fast_accel"] - oi_change = snap["oi_change"] - environment_score = snap["environment_score"] - - result = { - "strategy": strategy_name, - "cvd_fast": cvd_fast, - "cvd_mid": cvd_mid, - "cvd_day": snap["cvd_day"], - "cvd_fast_slope": cvd_fast_slope, - "atr": atr, - "atr_pct": atr_pct, - "vwap": vwap, - "price": price, - "p95": p95, - "p99": p99, - "signal": None, - "direction": None, - "score": 0, - "tier": None, - "factors": {}, - } - - if self.warmup or price == 0 or atr == 0: - return result - - # 判断倾向方向(用于评分展示,即使冷却或方向不一致也计算) - no_direction = False - last_signal_ts = self.last_signal_ts.get(strategy_name, 0) - in_cooldown = now_ms - last_signal_ts < COOLDOWN_MS - - if cvd_fast > 0 and cvd_mid > 0: - direction = "LONG" - elif cvd_fast < 0 and cvd_mid < 0: - direction = "SHORT" - else: - direction = "LONG" if cvd_fast > 0 else "SHORT" - no_direction = True - - # V5.1 五层评分体系(总分100,方向层可因加速额外+5) - # 1) 方向层(45分 + 5加速分) - direction_score = 0 - if (direction == "LONG" and cvd_fast > 0) or (direction == "SHORT" and cvd_fast < 0): - direction_score += 15 - if (direction == "LONG" and cvd_mid > 0) or (direction == "SHORT" and cvd_mid < 0): - direction_score += 15 - has_adverse_p99 = any( - (direction == "LONG" and lt[2] == 1) or (direction == "SHORT" and lt[2] == 0) - for lt in self.recent_large_trades + super().__init__( + symbol, + WINDOW_FAST, + WINDOW_MID, + WINDOW_DAY, + WINDOW_VWAP, + ATR_PERIOD_MS, + ATR_LENGTH, + fetch_market_indicators, ) - has_aligned_p99 = any( - (direction == "LONG" and lt[2] == 0) or (direction == "SHORT" and lt[2] == 1) - for lt in self.recent_large_trades - ) - if has_aligned_p99: - direction_score += 15 - elif not has_adverse_p99: - direction_score += 10 - accel_bonus = 0 - if "accel" in enabled_signals and ( - (direction == "LONG" and cvd_fast_accel > 0) or (direction == "SHORT" and cvd_fast_accel < 0) - ): - accel_bonus = int(strategy_cfg.get("accel_bonus", 5)) - # 2) 拥挤层(20分)- market_indicators缺失时给中间分 - long_short_ratio = to_float(self.market_indicators.get("long_short_ratio")) - if long_short_ratio is None: - ls_score = 5 - elif (direction == "SHORT" and long_short_ratio > 2.0) or (direction == "LONG" and long_short_ratio < 0.5): - ls_score = 10 - else: - ls_score = 5 - - top_trader_position = to_float(self.market_indicators.get("top_trader_position")) - if top_trader_position is None: - top_trader_score = 5 - else: - if direction == "LONG": - if top_trader_position >= 0.55: - top_trader_score = 10 - elif top_trader_position <= 0.45: - top_trader_score = 0 - else: - top_trader_score = 5 - else: - if top_trader_position <= 0.45: - top_trader_score = 10 - elif top_trader_position >= 0.55: - top_trader_score = 0 - else: - top_trader_score = 5 - crowding_score = ls_score + top_trader_score - - # Funding Rate scoring (拥挤层加分) - # Read from market_indicators table - funding_rate = to_float(self.market_indicators.get("funding_rate")) - fr_score = 0.0 - if "funding_rate" in enabled_signals and funding_rate is not None and funding_rate != 0: - max_fr = get_max_fr(self.symbol) - fr_abs = abs(funding_rate) - # 线性映射: raw = (|当前FR| / 历史最大FR) × 5, clamp到5 - raw_score = min(fr_abs / max_fr * 5.0, 5.0) - # FR评估"资金费率对当前方向有多有利",0~5分,不扣分 - # 做多+FR负(空头付费给多头)=有利 → 给分 - # 做空+FR正(多头付费给空头)=有利 → 给分 - # 其他情况(FR方向不利) → 0分 - if (direction == "LONG" and funding_rate < 0) or (direction == "SHORT" and funding_rate > 0): - fr_score = round(raw_score, 2) # 有利,给分 - else: - fr_score = 0.0 # 不利,不给分也不扣分 - - # 4) 确认层(15分) - confirmation_score = 15 if ( - (direction == "LONG" and cvd_fast > 0 and cvd_mid > 0) - or (direction == "SHORT" and cvd_fast < 0 and cvd_mid < 0) - ) else 0 - - # Liquidation scoring (确认层加分) - liq_score = 0 - liq_data = None - if "liquidation" in enabled_signals: - liq_data = self.fetch_recent_liquidations() - if liq_data: - liq_long_usd = liq_data.get("long_usd", 0) - liq_short_usd = liq_data.get("short_usd", 0) - total = liq_long_usd + liq_short_usd - if total > 0: - # ratio = 空头清算 / 多头清算 - # ratio高 = 空头爆仓多 = 有利于做多 - if liq_long_usd > 0: - ratio = liq_short_usd / liq_long_usd - else: - ratio = float("inf") if liq_short_usd > 0 else 1.0 - - # 梯度评分:ratio越极端分越高 - def liq_ratio_to_score(r: float) -> int: - if r >= 2.0: return 5 - if r >= 1.5: return 3 - if r >= 1.2: return 1 - return 0 - - if direction == "LONG": - # 做多:空头爆仓多(ratio高)有利 - liq_score = liq_ratio_to_score(ratio) - else: - # 做空:多头爆仓多(ratio低=1/ratio高)有利 - inv_ratio = (1.0 / ratio) if ratio > 0 else float("inf") - liq_score = liq_ratio_to_score(inv_ratio) - - # 5) 辅助层(5分) - coinbase_premium = to_float(self.market_indicators.get("coinbase_premium")) - if coinbase_premium is None: - aux_score = 2 - elif (direction == "LONG" and coinbase_premium > 0.0005) or (direction == "SHORT" and coinbase_premium < -0.0005): - aux_score = 5 - elif abs(coinbase_premium) <= 0.0005: - aux_score = 2 - else: - aux_score = 0 - - # 按策略配置的权重缩放各层分数 - weights = strategy_cfg.get("weights", {}) - w_direction = weights.get("direction", 45) - w_crowding = weights.get("crowding", 20) - w_fr = weights.get("funding_rate", 0) - w_environment = weights.get("environment", 15) - w_confirmation = weights.get("confirmation", 15) - w_liq = weights.get("liquidation", 0) - w_auxiliary = weights.get("auxiliary", 5) - - # 原始评分范围: direction 0~45, crowding 0~20, environment 0~15, confirmation 0~15, auxiliary 0~5 - # FR 0~5 (or -5~5), Liq 0~5 - # 缩放到配置权重 - scaled_direction = min(round(direction_score / 45 * w_direction) + accel_bonus, w_direction) - scaled_crowding = round(crowding_score / 20 * w_crowding) - scaled_fr = fr_score if w_fr > 0 else 0 # FR already 0~5 range, matches w_fr=5 - scaled_environment = round(environment_score / 15 * w_environment) - scaled_confirmation = round(confirmation_score / 15 * w_confirmation) - scaled_liq = liq_score if w_liq > 0 else 0 # Liq already 0~5 range, matches w_liq=5 - scaled_auxiliary = round(aux_score / 5 * w_auxiliary) - - total_score = scaled_direction + scaled_crowding + scaled_fr + scaled_environment + scaled_confirmation + scaled_liq + scaled_auxiliary - total_score = max(0, min(round(total_score, 1), 100)) # clamp 0~100, 保留1位小数 - - result["score"] = total_score - result["direction"] = direction - result["factors"] = { - "direction": { - "score": scaled_direction, - "max": w_direction, - "cvd_fast": 15 if ((direction == "LONG" and cvd_fast > 0) or (direction == "SHORT" and cvd_fast < 0)) else 0, - "cvd_mid": 15 if ((direction == "LONG" and cvd_mid > 0) or (direction == "SHORT" and cvd_mid < 0)) else 0, - "p99_flow": 15 if has_aligned_p99 else (10 if not has_adverse_p99 else 0), - "accel_bonus": accel_bonus, - }, - "crowding": {"score": scaled_crowding, "max": w_crowding, "long_short_ratio": ls_score, "top_trader_position": top_trader_score}, - "environment": {"score": scaled_environment, "max": w_environment, "open_interest_hist": oi_change}, - "confirmation": {"score": scaled_confirmation, "max": w_confirmation}, - "auxiliary": {"score": scaled_auxiliary, "max": w_auxiliary, "coinbase_premium": coinbase_premium}, - "funding_rate": {"score": round(scaled_fr, 2), "max": w_fr, "value": funding_rate, "max_fr_hist": get_max_fr(self.symbol)}, - "liquidation": { - "score": scaled_liq, - "max": w_liq, - "long_usd": liq_data.get("long_usd", 0) if liq_data else 0, - "short_usd": liq_data.get("short_usd", 0) if liq_data else 0, - }, - } - - # 始终输出direction供反向平仓判断(不受冷却限制) - result["direction"] = direction if not no_direction else None - - heavy_threshold = max(strategy_threshold + 10, 85) - if total_score >= heavy_threshold and not no_direction and not in_cooldown: - result["signal"] = direction - result["tier"] = "heavy" - elif total_score >= strategy_threshold and not no_direction and not in_cooldown: - result["signal"] = direction - result["tier"] = "standard" - else: - result["signal"] = None - result["tier"] = None - - if result["signal"]: - self.last_signal_ts[strategy_name] = now_ms - self.last_signal_dir[strategy_name] = direction - return result - - def _empty_result(self, strategy_name: str, snap: dict) -> dict: - """返回空评分结果(symbol不匹配track时使用)""" - return { - "strategy": strategy_name, - "cvd_fast": snap["cvd_fast"], "cvd_mid": snap["cvd_mid"], - "cvd_day": snap["cvd_day"], "cvd_fast_slope": snap["cvd_fast_slope"], - "atr": snap["atr"], "atr_value": snap.get("atr_value", snap["atr"]), - "atr_pct": snap["atr_pct"], "vwap": snap["vwap"], "price": snap["price"], - "p95": snap["p95"], "p99": snap["p99"], - "signal": None, "direction": None, "score": 0, "tier": None, "factors": {}, - } - - -def _window_ms(window_str: str) -> int: - """把CVD窗口字符串转换为毫秒,如 '5m'->300000, '1h'->3600000, '4h'->14400000""" - window_str = (window_str or "30m").strip().lower() - if window_str.endswith("h"): - return int(window_str[:-1]) * 3600 * 1000 - elif window_str.endswith("m"): - return int(window_str[:-1]) * 60 * 1000 - return 30 * 60 * 1000 # fallback 30min - - def _evaluate_v53(self, now_ms: int, strategy_cfg: dict, snapshot: Optional[dict] = None) -> dict: - """ - V5.3 统一评分(BTC/ETH/XRP/SOL) - 架构:四层评分 55/25/15/5 + per-symbol 四门控制 - - 门1:波动率下限(atr/price) - - 门2:CVD共振(fast+mid同向) - - 门3:OBI否决(实时WebSocket,fallback DB) - - 门4:期现背离否决(实时WebSocket,fallback DB) - BTC额外:whale_cvd_ratio(>$100k巨鲸CVD) - """ - strategy_name = strategy_cfg.get("name", "v53") - strategy_threshold = int(strategy_cfg.get("threshold", 75)) - flip_threshold = int(strategy_cfg.get("flip_threshold", 85)) - - snap = snapshot or self.build_evaluation_snapshot(now_ms) - - # 按策略配置的 cvd_fast_window / cvd_slow_window 动态切片重算CVD - # 支持 5m/15m/30m/1h/4h 所有组合 - cvd_fast_window = strategy_cfg.get("cvd_fast_window", "30m") - cvd_slow_window = strategy_cfg.get("cvd_slow_window", "4h") - fast_ms = _window_ms(cvd_fast_window) - slow_ms = _window_ms(cvd_slow_window) - # 默认窗口 (30m/4h) 直接用快照,否则从 trades 列表切片重算 - if cvd_fast_window == "30m" and cvd_slow_window == "4h": - cvd_fast = snap["cvd_fast"] - cvd_mid = snap["cvd_mid"] - else: - cutoff_fast = now_ms - fast_ms - cutoff_slow = now_ms - slow_ms - buy_f = sell_f = buy_m = sell_m = 0.0 - # fast: 从 win_fast (30min) 或 win_mid (4h) 中切片 - src_fast = self.win_mid if fast_ms > WINDOW_FAST else self.win_fast - for t_ms, qty, _price, ibm in src_fast.trades: - if t_ms >= cutoff_fast: - if ibm == 0: buy_f += qty - else: sell_f += qty - # slow: 从 win_mid (4h) 中切片 - for t_ms, qty, _price, ibm in self.win_mid.trades: - if t_ms >= cutoff_slow: - if ibm == 0: buy_m += qty - else: sell_m += qty - cvd_fast = buy_f - sell_f - cvd_mid = buy_m - sell_m - - price = snap["price"] - atr = snap["atr"] - atr_value = snap.get("atr_value", atr) - cvd_fast_accel = snap["cvd_fast_accel"] - environment_score_raw = snap["environment_score"] - - result = self._empty_result(strategy_name, snap) - if self.warmup or price == 0 or atr == 0: - return result - - last_signal_ts = self.last_signal_ts.get(strategy_name, 0) - in_cooldown = now_ms - last_signal_ts < COOLDOWN_MS - - # ── 五门参数:优先读 DB config(V5.4),fallback 到 JSON symbol_gates ──── - # DB config 来自 load_strategy_configs_from_db(),已映射到 strategy_cfg["gates"] - db_gates = strategy_cfg.get("gates") or {} - symbol_gates = (strategy_cfg.get("symbol_gates") or {}).get(self.symbol, {}) - - # 门1 波动率:vol_atr_pct_min(ATR%价格,如0.002=价格的0.2%) - gate_vol_enabled = db_gates.get("vol", {}).get("enabled", True) - min_vol = float(db_gates.get("vol", {}).get("vol_atr_pct_min") or - symbol_gates.get("min_vol_threshold", 0.002)) - - # 门2 CVD共振:gate_cvd_enabled(快慢CVD必须同向) - gate_cvd_enabled = db_gates.get("cvd", {}).get("enabled", True) - - # 门3 鲸鱼否决:whale_usd_threshold + whale_flow_pct(BTC) - gate_whale_enabled = db_gates.get("whale", {}).get("enabled", True) - whale_usd = float(db_gates.get("whale", {}).get("whale_usd_threshold") or - symbol_gates.get("whale_threshold_usd", 50000)) - whale_flow_pct = float(db_gates.get("whale", {}).get("whale_flow_pct") or - symbol_gates.get("whale_flow_threshold_pct", 0.5)) / 100 - - # 门4 OBI否决:obi_threshold - gate_obi_enabled = db_gates.get("obi", {}).get("enabled", True) - obi_veto = float(db_gates.get("obi", {}).get("threshold") or - symbol_gates.get("obi_veto_threshold", 0.35)) - - # 门5 期现背离:spot_perp_threshold - gate_spd_enabled = db_gates.get("spot_perp", {}).get("enabled", False) - spd_veto = float(db_gates.get("spot_perp", {}).get("threshold") or - symbol_gates.get("spot_perp_divergence_veto", 0.005)) - - gate_block = None - - # 门1:波动率下限(可关闭) - atr_pct_price = atr / price if price > 0 else 0 - if gate_vol_enabled and atr_pct_price < min_vol: - gate_block = f"low_vol({atr_pct_price:.4f}<{min_vol})" - - # 门2:CVD共振(方向门,可关闭) - if cvd_fast > 0 and cvd_mid > 0: - direction = "LONG" - cvd_resonance = 30 - no_direction = False - elif cvd_fast < 0 and cvd_mid < 0: - direction = "SHORT" - cvd_resonance = 30 - no_direction = False - else: - direction = "LONG" if cvd_fast > 0 else "SHORT" - cvd_resonance = 0 - if gate_cvd_enabled: - no_direction = True - if not gate_block: - gate_block = "no_direction_consensus" - else: - no_direction = False # 门2关闭时,允许单线CVD方向 - - # 门3:鲸鱼否决(BTC用whale_cvd_ratio,ALT用大单对立,可关闭) - if gate_whale_enabled and not gate_block and not no_direction: - if self.symbol == "BTCUSDT": - # BTC:巨鲸CVD净方向与信号方向冲突时否决 - whale_cvd = self.whale_cvd_ratio if self._whale_trades else to_float(self.market_indicators.get("tiered_cvd_whale")) or 0.0 - if (direction == "LONG" and whale_cvd < -whale_flow_pct) or \ - (direction == "SHORT" and whale_cvd > whale_flow_pct): - gate_block = f"whale_cvd_veto({whale_cvd:.3f})" - else: - # ALT:recent_large_trades 里有对立大单则否决 - whale_adverse = any( - (direction == "LONG" and lt[2] == 1 and lt[1] * price >= whale_usd) or - (direction == "SHORT" and lt[2] == 0 and lt[1] * price >= whale_usd) - for lt in self.recent_large_trades - ) - whale_aligned = any( - (direction == "LONG" and lt[2] == 0 and lt[1] * price >= whale_usd) or - (direction == "SHORT" and lt[2] == 1 and lt[1] * price >= whale_usd) - for lt in self.recent_large_trades - ) - if whale_adverse and not whale_aligned: - gate_block = f"whale_adverse(>${whale_usd/1000:.0f}k)" - - # 门4:OBI否决(实时WS优先,fallback DB,可关闭) - obi_raw = self.rt_obi if self.rt_obi != 0.0 else to_float(self.market_indicators.get("obi_depth_10")) - if gate_obi_enabled and not gate_block and not no_direction and obi_raw is not None: - if direction == "LONG" and obi_raw < -obi_veto: - gate_block = f"obi_veto({obi_raw:.3f}<-{obi_veto})" - elif direction == "SHORT" and obi_raw > obi_veto: - gate_block = f"obi_veto({obi_raw:.3f}>{obi_veto})" - - # 门5:期现背离否决(实时WS优先,fallback DB,可关闭) - spot_perp_div = self.rt_spot_perp_div if self.rt_spot_perp_div != 0.0 else to_float(self.market_indicators.get("spot_perp_divergence")) - if gate_spd_enabled and not gate_block and not no_direction and spot_perp_div is not None: - if (direction == "LONG" and spot_perp_div < -spd_veto) or \ - (direction == "SHORT" and spot_perp_div > spd_veto): - gate_block = f"spd_veto({spot_perp_div:.4f})" - - gate_passed = gate_block is None - - # ── Direction Layer(55分)───────────────────────────────── - has_adverse_p99 = any( - (direction == "LONG" and lt[2] == 1) or (direction == "SHORT" and lt[2] == 0) - for lt in self.recent_large_trades - ) - has_aligned_p99 = any( - (direction == "LONG" and lt[2] == 0) or (direction == "SHORT" and lt[2] == 1) - for lt in self.recent_large_trades - ) - p99_flow = 20 if has_aligned_p99 else (10 if not has_adverse_p99 else 0) - accel_bonus = 5 if ( - (direction == "LONG" and cvd_fast_accel > 0) or - (direction == "SHORT" and cvd_fast_accel < 0) - ) else 0 - - # v53_fast:accel 独立触发路径(不要求 cvd 双线同向) - accel_independent_score = 0 - if is_fast and not no_direction: - accel_cfg = strategy_cfg.get("accel_independent", {}) - if accel_cfg.get("enabled", False): - # accel 足够大 + p99 大单同向 → 独立给分 - accel_strong = ( - (direction == "LONG" and cvd_fast_accel > 0 and has_aligned_p99) or - (direction == "SHORT" and cvd_fast_accel < 0 and has_aligned_p99) - ) - if accel_strong: - accel_independent_score = int(accel_cfg.get("min_direction_score", 35)) - - direction_score = max(min(cvd_resonance + p99_flow + accel_bonus, 55), accel_independent_score) - - # ── Crowding Layer(25分)───────────────────────────────── - long_short_ratio = to_float(self.market_indicators.get("long_short_ratio")) - if long_short_ratio is None: - ls_score = 7 - elif (direction == "SHORT" and long_short_ratio > 2.0) or (direction == "LONG" and long_short_ratio < 0.5): - ls_score = 15 - elif (direction == "SHORT" and long_short_ratio > 1.5) or (direction == "LONG" and long_short_ratio < 0.7): - ls_score = 10 - elif (direction == "SHORT" and long_short_ratio > 1.0) or (direction == "LONG" and long_short_ratio < 1.0): - ls_score = 7 - else: - ls_score = 0 - - top_trader_position = to_float(self.market_indicators.get("top_trader_position")) - if top_trader_position is None: - top_trader_score = 5 - else: - if direction == "LONG": - top_trader_score = 10 if top_trader_position >= 0.55 else (0 if top_trader_position <= 0.45 else 5) - else: - top_trader_score = 10 if top_trader_position <= 0.45 else (0 if top_trader_position >= 0.55 else 5) - crowding_score = min(ls_score + top_trader_score, 25) - - # ── Environment Layer(15分)────────────────────────────── - # OI变化率基础分(v53: 0~15) - oi_base_score = round(environment_score_raw / 15 * 10) # 缩到10分 - - # v53_fast:OBI 正向加分(5分,放入 environment 层) - obi_bonus = 0 - if is_fast and obi_raw is not None: - obi_cfg = strategy_cfg.get("obi_scoring", {}) - strong_thr = float(obi_cfg.get("strong_threshold", 0.30)) - weak_thr = float(obi_cfg.get("weak_threshold", 0.15)) - strong_sc = int(obi_cfg.get("strong_score", 5)) - weak_sc = int(obi_cfg.get("weak_score", 3)) - obi_aligned = (direction == "LONG" and obi_raw > 0) or (direction == "SHORT" and obi_raw < 0) - obi_abs = abs(obi_raw) - if obi_aligned: - if obi_abs >= strong_thr: - obi_bonus = strong_sc - elif obi_abs >= weak_thr: - obi_bonus = weak_sc - - environment_score = min(oi_base_score + obi_bonus, 15) if is_fast else round(environment_score_raw / 15 * 15) - - # ── Auxiliary Layer(5分)──────────────────────────────── - coinbase_premium = to_float(self.market_indicators.get("coinbase_premium")) - if coinbase_premium is None: - aux_score = 2 - elif (direction == "LONG" and coinbase_premium > 0.0005) or (direction == "SHORT" and coinbase_premium < -0.0005): - aux_score = 5 - elif abs(coinbase_premium) <= 0.0005: - aux_score = 2 - else: - aux_score = 0 - - total_score = min(direction_score + crowding_score + environment_score + aux_score, 100) - total_score = max(0, round(total_score, 1)) - if not gate_passed: - total_score = 0 - - # whale_cvd for BTC display - whale_cvd_display = (self.whale_cvd_ratio if self._whale_trades else to_float(self.market_indicators.get("tiered_cvd_whale"))) if self.symbol == "BTCUSDT" else None - - result.update({ - "score": total_score, - "direction": direction if (not no_direction and gate_passed) else None, - "atr_value": atr_value, - "cvd_fast_5m": cvd_fast if is_fast else None, # v53_fast: 存5m实算CVD - "factors": { - "track": "BTC" if self.symbol == "BTCUSDT" else "ALT", - "gate_passed": gate_passed, - "gate_block": gate_block, - "atr_pct_price": round(atr_pct_price, 5), - "obi_raw": obi_raw, - "spot_perp_div": spot_perp_div, - "whale_cvd_ratio": whale_cvd_display, - "direction": { - "score": direction_score, "max": 55, - "cvd_resonance": cvd_resonance, "p99_flow": p99_flow, "accel_bonus": accel_bonus, - }, - "crowding": { - "score": crowding_score, "max": 25, - "lsr_contrarian": ls_score, "top_trader_position": top_trader_score, - }, - "environment": {"score": environment_score, "max": 15, "oi_base": oi_base_score if is_fast else environment_score, "obi_bonus": obi_bonus}, - "auxiliary": {"score": aux_score, "max": 5, "coinbase_premium": coinbase_premium}, - }, - }) - - if not no_direction and gate_passed and not in_cooldown: - if total_score >= flip_threshold: - result["signal"] = direction - result["tier"] = "heavy" - elif total_score >= strategy_threshold: - result["signal"] = direction - result["tier"] = "standard" - - if result["signal"]: - self.last_signal_ts[strategy_name] = now_ms - self.last_signal_dir[strategy_name] = direction - return result - - def _evaluate_v53_alt(self, now_ms: int, strategy_cfg: dict, snapshot: Optional[dict] = None) -> dict: - """已废弃,由 _evaluate_v53() 统一处理,保留供兼容""" - return self._evaluate_v53(now_ms, strategy_cfg, snapshot) - - def _evaluate_v53_btc(self, now_ms: int, strategy_cfg: dict, snapshot: Optional[dict] = None) -> dict: - """已废弃,由 _evaluate_v53() 统一处理,保留供兼容""" - return self._evaluate_v53(now_ms, strategy_cfg, snapshot) + # 评分相关方法已抽离到 backend/strategy_scoring.py, + # 这里保留一个纯状态容器封装,不再实现打分逻辑。 # ─── PG DB操作 ─────────────────────────────────────────────────── # ─── PG DB操作 ─────────────────────────────────────────────────── @@ -1256,155 +315,6 @@ def save_indicator_1m(ts: int, symbol: str, result: dict): # ─── 模拟盘 ────────────────────────────────────────────────────── -def paper_open_trade( - symbol: str, - direction: str, - price: float, - score: int, - tier: str, - atr: float, - now_ms: int, - factors: dict = None, - strategy: str = "v51_baseline", - tp_sl: Optional[dict] = None, - strategy_id: Optional[str] = None, - strategy_name_snapshot: Optional[str] = None, -): - """模拟开仓""" - import json as _json3 - if atr <= 0: - return - sl_multiplier = float((tp_sl or {}).get("sl_multiplier", 2.0)) - tp1_multiplier = float((tp_sl or {}).get("tp1_multiplier", 1.5)) - tp2_multiplier = float((tp_sl or {}).get("tp2_multiplier", 3.0)) - risk_distance = sl_multiplier * atr # 1R = SL距离 = sl_multiplier × ATR - if direction == "LONG": - sl = price - sl_multiplier * atr - tp1 = price + tp1_multiplier * atr - tp2 = price + tp2_multiplier * atr - else: - sl = price + sl_multiplier * atr - tp1 = price - tp1_multiplier * atr - tp2 = price - tp2_multiplier * atr - - # SL 合理性校验:实际距离必须在 risk_distance 的 80%~120% 范围内 - actual_sl_dist = abs(sl - price) - if actual_sl_dist < risk_distance * 0.8 or actual_sl_dist > risk_distance * 1.2: - logger.error( - f"[{symbol}] ⚠️ SL校验失败,拒绝开仓: direction={direction} price={price:.4f} " - f"sl={sl:.4f} actual_dist={actual_sl_dist:.4f} expected={risk_distance:.4f} atr={atr:.4f}" - ) - return - - with get_sync_conn() as conn: - with conn.cursor() as cur: - cur.execute( - "INSERT INTO paper_trades (symbol,direction,score,tier,entry_price,entry_ts,tp1_price,tp2_price,sl_price,atr_at_entry,score_factors,strategy,risk_distance,strategy_id,strategy_name_snapshot) " - "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", - ( - symbol, - direction, - score, - tier, - price, - now_ms, - tp1, - tp2, - sl, - atr, - _json3.dumps(factors) if factors else None, - strategy, - risk_distance, - strategy_id, - strategy_name_snapshot, - ), - ) - conn.commit() - logger.info( - f"[{symbol}] 📝 模拟开仓: {direction} @ {price:.2f} score={score} tier={tier} strategy={strategy} " - f"TP1={tp1:.2f} TP2={tp2:.2f} SL={sl:.2f}" - ) - - -def paper_has_active_position(symbol: str, strategy: Optional[str] = None) -> bool: - """检查该币种是否有活跃持仓""" - with get_sync_conn() as conn: - with conn.cursor() as cur: - if strategy: - cur.execute( - "SELECT COUNT(*) FROM paper_trades WHERE symbol=%s AND strategy=%s AND status IN ('active','tp1_hit')", - (symbol, strategy), - ) - else: - cur.execute("SELECT COUNT(*) FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit')", (symbol,)) - return cur.fetchone()[0] > 0 - - -def paper_get_active_direction(symbol: str, strategy: Optional[str] = None) -> str | None: - """获取该币种活跃持仓的方向,无持仓返回None""" - with get_sync_conn() as conn: - with conn.cursor() as cur: - if strategy: - cur.execute( - "SELECT direction FROM paper_trades WHERE symbol=%s AND strategy=%s AND status IN ('active','tp1_hit') LIMIT 1", - (symbol, strategy), - ) - else: - cur.execute( - "SELECT direction FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit') LIMIT 1", - (symbol,), - ) - row = cur.fetchone() - return row[0] if row else None - - -def paper_close_by_signal(symbol: str, current_price: float, now_ms: int, strategy: Optional[str] = None): - """反向信号平仓:按当前价平掉该币种所有活跃仓位""" - with get_sync_conn() as conn: - with conn.cursor() as cur: - if strategy: - cur.execute( - "SELECT id, direction, entry_price, tp1_hit, atr_at_entry, risk_distance " - "FROM paper_trades WHERE symbol=%s AND strategy=%s AND status IN ('active','tp1_hit')", - (symbol, strategy), - ) - else: - cur.execute( - "SELECT id, direction, entry_price, tp1_hit, atr_at_entry, risk_distance " - "FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit')", - (symbol,), - ) - positions = cur.fetchall() - for pos in positions: - pid, direction, entry_price, tp1_hit, atr_entry, rd_db = pos - risk_distance = rd_db if rd_db and rd_db > 0 else abs(entry_price * 0.01) - if direction == "LONG": - pnl_r = (current_price - entry_price) / risk_distance if risk_distance > 0 else 0 - else: - pnl_r = (entry_price - current_price) / risk_distance if risk_distance > 0 else 0 - # 扣手续费 - fee_r = (2 * PAPER_FEE_RATE * entry_price) / risk_distance if risk_distance > 0 else 0 - pnl_r -= fee_r - cur.execute( - "UPDATE paper_trades SET status='signal_flip', exit_price=%s, exit_ts=%s, pnl_r=%s WHERE id=%s", - (current_price, now_ms, round(pnl_r, 4), pid) - ) - logger.info( - f"[{symbol}] 📝 反向信号平仓: {direction} @ {current_price:.2f} pnl={pnl_r:+.2f}R" - f"{f' strategy={strategy}' if strategy else ''}" - ) - conn.commit() - - -def paper_active_count(strategy: Optional[str] = None) -> int: - """当前活跃持仓总数(按策略独立计数)""" - with get_sync_conn() as conn: - with conn.cursor() as cur: - if strategy: - cur.execute("SELECT COUNT(*) FROM paper_trades WHERE strategy=%s AND status IN ('active','tp1_hit')", (strategy,)) - else: - cur.execute("SELECT COUNT(*) FROM paper_trades WHERE status IN ('active','tp1_hit')") - return cur.fetchone()[0] # ─── 实时 WebSocket 数据(OBI + 期现背离)──────────────────────── @@ -1520,7 +430,15 @@ def main(): strategy_configs = load_strategy_configs() strategy_names = [cfg.get("name", "unknown") for cfg in strategy_configs] logger.info(f"已加载策略配置: {', '.join(strategy_names)}") - primary_strategy_name = "v52_8signals" if any(cfg.get("name") == "v52_8signals" for cfg in strategy_configs) else strategy_names[0] + # 选取 primary 策略用于 1m 指标表:优先 v53 系列,其次第一个配置 + primary_candidates = ["v53", "v53_middle", "v53_fast"] + primary_strategy_name = None + for cand in primary_candidates: + if any(cfg.get("name") == cand for cfg in strategy_configs): + primary_strategy_name = cand + break + if not primary_strategy_name: + primary_strategy_name = strategy_names[0] if strategy_names else "v53" states = {sym: SymbolState(sym) for sym in SYMBOLS} @@ -1550,12 +468,14 @@ def main(): snapshot = state.build_evaluation_snapshot(now_ms) strategy_results: list[tuple[dict, dict]] = [] for strategy_cfg in strategy_configs: - strategy_result = state.evaluate_signal(now_ms, strategy_cfg=strategy_cfg, snapshot=snapshot) + strategy_result = score_strategy( + state, now_ms, strategy_cfg=strategy_cfg, snapshot=snapshot + ) strategy_results.append((strategy_cfg, strategy_result)) # 每个策略独立存储indicator for strategy_cfg, strategy_result in strategy_results: - sname = strategy_cfg.get("name", "v51_baseline") + sname = strategy_cfg.get("name") or "v53" sid = strategy_cfg.get("strategy_id") ssnap = strategy_cfg.get("strategy_name_snapshot") save_indicator(now_ms, sym, strategy_result, strategy=sname, @@ -1574,27 +494,38 @@ def main(): save_indicator_1m(now_ms, sym, primary_result) last_1m_save[sym] = bar_1m - # 反向信号平仓:按策略独立判断,score>=75才触发 + # 反向信号平仓:按策略独立判断,score>=每策略配置的 flip_threshold 才触发 if warmup_cycles <= 0: for strategy_cfg, result in strategy_results: - strategy_name = strategy_cfg.get("name", "v51_baseline") + strategy_name = strategy_cfg.get("name") or "v53" if not is_strategy_enabled(strategy_name): continue # V5.4: custom策略只处理自己配置的symbol strategy_symbol = strategy_cfg.get("symbol", "") if strategy_symbol and strategy_symbol != sym: continue + # per-strategy 方向约束:只接受与策略方向一致的反向信号 + dir_cfg_raw = (strategy_cfg.get("direction") or "both").lower() + if dir_cfg_raw not in ("long", "short", "both"): + dir_cfg_raw = "both" eval_dir = result.get("direction") existing_dir = paper_get_active_direction(sym, strategy_name) - if existing_dir and eval_dir and existing_dir != eval_dir and result["score"] >= 75: + # 反向平仓分数门槛:优先使用每个策略自己的 flip_threshold,fallback 到 entry_threshold/75 + flip_thr = int(strategy_cfg.get("flip_threshold", strategy_cfg.get("threshold", 75))) + # 如果策略配置为只多/只空,则忽略与配置相反方向的评估结果 + if dir_cfg_raw == "long" and eval_dir == "SHORT": + continue + if dir_cfg_raw == "short" and eval_dir == "LONG": + continue + if existing_dir and eval_dir and existing_dir != eval_dir and result["score"] >= flip_thr: paper_close_by_signal(sym, result["price"], now_ms, strategy_name) logger.info( f"[{sym}] 📝 反向信号平仓[{strategy_name}]: {existing_dir} → {eval_dir} " - f"(score={result['score']})" + f"(score={result['score']}, flip_thr={flip_thr})" ) for strategy_cfg, result in strategy_results: - strategy_name = strategy_cfg.get("name", "v51_baseline") + strategy_name = strategy_cfg.get("name") or "v53" if result.get("signal"): logger.info( f"[{sym}] 🚨 信号[{strategy_name}]: {result['signal']} " @@ -1643,7 +574,14 @@ def main(): else: strategy_configs = load_strategy_configs() # A1: 热重载权重/阈值/TP/SL strategy_names = [cfg.get("name", "unknown") for cfg in strategy_configs] - primary_strategy_name = "v52_8signals" if any(cfg.get("name") == "v52_8signals" for cfg in strategy_configs) else strategy_names[0] + primary_candidates = ["v53", "v53_middle", "v53_fast"] + primary_strategy_name = None + for cand in primary_candidates: + if any(cfg.get("name") == cand for cfg in strategy_configs): + primary_strategy_name = cand + break + if not primary_strategy_name: + primary_strategy_name = strategy_names[0] if strategy_names else "v53" if list(PAPER_ENABLED_STRATEGIES) != old_strategies: logger.info(f"📋 配置热加载: enabled_strategies={PAPER_ENABLED_STRATEGIES}") for sym, state in states.items(): diff --git a/backend/signal_state.py b/backend/signal_state.py new file mode 100644 index 0000000..db816ed --- /dev/null +++ b/backend/signal_state.py @@ -0,0 +1,298 @@ +""" +signal_state.py — CVD/ATR 滚动窗口与 SymbolState 抽象 + +从原来的 signal_engine.py 中拆分出的纯数据结构与计算逻辑: + - TradeWindow:逐笔成交滚动窗口,负责 CVD/VWAP 计算; + - ATRCalculator:按固定周期聚合 K 线并计算 ATR/百分位; + - get_max_fr:资金费率历史最大值缓存; + - SymbolState:单币种的内存状态(窗口、指标快照、巨鲸数据等)。 + +这些类保持与原实现完全一致,只是搬迁到独立模块,便于维护与测试。 +""" + +import time +from collections import deque +from typing import Any, Optional + +from db import get_sync_conn + + +def to_float(value: Any) -> Optional[float]: + try: + return float(value) if value is not None else None + except (TypeError, ValueError): + return None + + +class TradeWindow: + def __init__(self, window_ms: int): + self.window_ms = window_ms + self.trades: deque = deque() + self.buy_vol = 0.0 + self.sell_vol = 0.0 + self.pq_sum = 0.0 + self.q_sum = 0.0 + + def add(self, time_ms: int, qty: float, price: float, is_buyer_maker: int): + self.trades.append((time_ms, qty, price, is_buyer_maker)) + pq = price * qty + self.pq_sum += pq + self.q_sum += qty + if is_buyer_maker == 0: + self.buy_vol += qty + else: + self.sell_vol += qty + + def trim(self, now_ms: int): + cutoff = now_ms - self.window_ms + while self.trades and self.trades[0][0] < cutoff: + t_ms, qty, price, ibm = self.trades.popleft() + self.pq_sum -= price * qty + self.q_sum -= qty + if ibm == 0: + self.buy_vol -= qty + else: + self.sell_vol -= qty + + @property + def cvd(self) -> float: + return self.buy_vol - self.sell_vol + + @property + def vwap(self) -> float: + return self.pq_sum / self.q_sum if self.q_sum > 0 else 0.0 + + +class ATRCalculator: + def __init__(self, period_ms: int, length: int): + self.period_ms = period_ms + self.length = length + self.candles: deque = deque(maxlen=length + 1) + self.current_candle: Optional[dict] = None + self.atr_history: deque = deque(maxlen=288) + + def update(self, time_ms: int, price: float): + bar_ms = (time_ms // self.period_ms) * self.period_ms + if self.current_candle is None or self.current_candle["bar"] != bar_ms: + if self.current_candle is not None: + self.candles.append(self.current_candle) + self.current_candle = { + "bar": bar_ms, + "open": price, + "high": price, + "low": price, + "close": price, + } + else: + c = self.current_candle + c["high"] = max(c["high"], price) + c["low"] = min(c["low"], price) + c["close"] = price + + @property + def atr(self) -> float: + if len(self.candles) < 2: + return 0.0 + trs = [] + candles_list = list(self.candles) + for i in range(1, len(candles_list)): + prev_close = candles_list[i - 1]["close"] + c = candles_list[i] + tr = max( + c["high"] - c["low"], + abs(c["high"] - prev_close), + abs(c["low"] - prev_close), + ) + trs.append(tr) + if not trs: + return 0.0 + atr_val = trs[0] + for tr in trs[1:]: + atr_val = (atr_val * (self.length - 1) + tr) / self.length + return atr_val + + @property + def atr_percentile(self) -> float: + current = self.atr + if current == 0: + return 50.0 + self.atr_history.append(current) + if len(self.atr_history) < 10: + return 50.0 + sorted_hist = sorted(self.atr_history) + rank = sum(1 for x in sorted_hist if x <= current) + return (rank / len(sorted_hist)) * 100 + + +# ─── FR 历史最大值缓存(每小时更新)─────────────────────────────── +_max_fr_cache: dict = {} # {symbol: max_abs_fr} +_max_fr_updated: float = 0 + + +def get_max_fr(symbol: str) -> float: + """获取该币种历史最大|FR|,每小时刷新一次""" + global _max_fr_cache, _max_fr_updated + now = time.time() + if now - _max_fr_updated > 3600 or symbol not in _max_fr_cache: + try: + with get_sync_conn() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT symbol, MAX(ABS((value->>'fundingRate')::float)) as max_fr " + "FROM market_indicators WHERE indicator_type='funding_rate' " + "GROUP BY symbol" + ) + for row in cur.fetchall(): + _max_fr_cache[row[0]] = row[1] if row[1] else 0.0001 + _max_fr_updated = now + except Exception: + # 读取失败时保持旧缓存,返回一个小的默认值防除零 + pass + return _max_fr_cache.get(symbol, 0.0001) # 默认0.01%防除零 + + +class SymbolState: + def __init__( + self, + symbol: str, + window_fast_ms: int, + window_mid_ms: int, + window_day_ms: int, + window_vwap_ms: int, + atr_period_ms: int, + atr_length: int, + fetch_market_indicators_fn, + ): + self.symbol = symbol + self.win_fast = TradeWindow(window_fast_ms) + self.win_mid = TradeWindow(window_mid_ms) + self.win_day = TradeWindow(window_day_ms) + self.win_vwap = TradeWindow(window_vwap_ms) + self.atr_calc = ATRCalculator(atr_period_ms, atr_length) + self.last_processed_id = 0 + self.last_trade_price = 0.0 + self.warmup = True + self.prev_cvd_fast = 0.0 + self.prev_cvd_fast_slope = 0.0 + self.prev_oi_value = 0.0 + # 从外部函数获取最新 market_indicators + self.market_indicators = fetch_market_indicators_fn(symbol) + self.last_signal_ts: dict[str, int] = {} + self.last_signal_dir: dict[str, str] = {} + self.recent_large_trades: deque = deque() + # ── Phase 2 实时内存字段(由后台 WebSocket 协程更新)────────── + self.rt_obi: float = 0.0 + self.rt_spot_perp_div: float = 0.0 + # tiered_cvd_whale:按成交额分档,实时累计(最近15分钟窗口) + self._whale_trades: deque = deque() # (time_ms, usd_val, is_sell) + self.WHALE_WINDOW_MS: int = 15 * 60 * 1000 # 15 分钟 + + def process_trade(self, agg_id: int, time_ms: int, price: float, qty: float, is_buyer_maker: int): + now_ms = time_ms + self.win_fast.add(time_ms, qty, price, is_buyer_maker) + self.win_mid.add(time_ms, qty, price, is_buyer_maker) + self.win_day.add(time_ms, qty, price, is_buyer_maker) + self.win_vwap.add(time_ms, qty, price, is_buyer_maker) + self.atr_calc.update(time_ms, price) + self.win_fast.trim(now_ms) + self.win_mid.trim(now_ms) + self.win_day.trim(now_ms) + self.win_vwap.trim(now_ms) + self.last_processed_id = agg_id + self.last_trade_price = price # 最新成交价,用于 entry_price + + # tiered_cvd_whale 实时累计(>$100k 为巨鲸) + usd_val = price * qty + if usd_val >= 100_000: + self._whale_trades.append((time_ms, usd_val, bool(is_buyer_maker))) + # 修剪 15 分钟窗口 + cutoff = now_ms - self.WHALE_WINDOW_MS + while self._whale_trades and self._whale_trades[0][0] < cutoff: + self._whale_trades.popleft() + + @property + def whale_cvd_ratio(self) -> float: + """巨鲸净 CVD 比率[-1,1],基于最近 15 分钟 >$100k 成交""" + buy_usd = sum(t[1] for t in self._whale_trades if not t[2]) + sell_usd = sum(t[1] for t in self._whale_trades if t[2]) + total = buy_usd + sell_usd + return (buy_usd - sell_usd) / total if total > 0 else 0.0 + + def compute_p95_p99(self) -> tuple: + if len(self.win_day.trades) < 100: + return 5.0, 10.0 + qtys = sorted([t[1] for t in self.win_day.trades]) + n = len(qtys) + p95 = qtys[int(n * 0.95)] + p99 = qtys[int(n * 0.99)] + if "BTC" in self.symbol: + p95 = max(p95, 5.0) + p99 = max(p99, 10.0) + else: + p95 = max(p95, 50.0) + p99 = max(p99, 100.0) + return p95, p99 + + def update_large_trades(self, now_ms: int, p99: float): + cutoff = now_ms - 15 * 60 * 1000 + while self.recent_large_trades and self.recent_large_trades[0][0] < cutoff: + self.recent_large_trades.popleft() + # 只检查新 trade(避免重复添加) + seen = set(t[0] for t in self.recent_large_trades) # time_ms 作为去重 key + for t in self.win_fast.trades: + if t[1] >= p99 and t[0] > cutoff and t[0] not in seen: + self.recent_large_trades.append((t[0], t[1], t[3])) + seen.add(t[0]) + + def build_evaluation_snapshot(self, now_ms: int) -> dict: + cvd_fast = self.win_fast.cvd + cvd_mid = self.win_mid.cvd + cvd_day = self.win_day.cvd + vwap = self.win_vwap.vwap + atr = self.atr_calc.atr + atr_pct = self.atr_calc.atr_percentile + p95, p99 = self.compute_p95_p99() + self.update_large_trades(now_ms, p99) + price = self.last_trade_price if self.last_trade_price > 0 else vwap # 用最新成交价,非 VWAP + cvd_fast_slope = cvd_fast - self.prev_cvd_fast + cvd_fast_accel = cvd_fast_slope - self.prev_cvd_fast_slope + self.prev_cvd_fast = cvd_fast + self.prev_cvd_fast_slope = cvd_fast_slope + + oi_value = to_float(self.market_indicators.get("open_interest_hist")) + if oi_value is None or self.prev_oi_value == 0: + oi_change = 0.0 + environment_score = 10 + else: + oi_change = ( + (oi_value - self.prev_oi_value) / self.prev_oi_value + if self.prev_oi_value > 0 + else 0.0 + ) + if oi_change >= 0.03: + environment_score = 15 + elif oi_change > 0: + environment_score = 10 + else: + environment_score = 5 + if oi_value is not None and oi_value > 0: + self.prev_oi_value = oi_value + + return { + "cvd_fast": cvd_fast, + "cvd_mid": cvd_mid, + "cvd_day": cvd_day, + "vwap": vwap, + "atr": atr, + "atr_value": atr, + "atr_pct": atr_pct, + "p95": p95, + "p99": p99, + "price": price, + "cvd_fast_slope": cvd_fast_slope, + "cvd_fast_accel": cvd_fast_accel, + "oi_change": oi_change, + "environment_score": environment_score, + "oi_value": oi_value, + } + diff --git a/backend/strategy_loader.py b/backend/strategy_loader.py new file mode 100644 index 0000000..e5a1810 --- /dev/null +++ b/backend/strategy_loader.py @@ -0,0 +1,190 @@ +""" +strategy_loader.py — 从 JSON 文件 / DB 加载策略配置 + +从原来的 signal_engine.py 拆分出的策略加载逻辑: + - load_strategy_configs(): 从 backend/strategies/*.json 读取配置; + - load_strategy_configs_from_db(): 从 strategies 表读取 running 策略并映射到 cfg dict。 + +行为保持与原实现完全一致,用于给 signal_engine 等调用方复用。 +""" + +import json +import logging +import os +from typing import Any + +from db import get_sync_conn + +logger = logging.getLogger("strategy-loader") + +STRATEGY_DIR = os.path.join(os.path.dirname(__file__), "strategies") +DEFAULT_STRATEGY_FILES = [ + # 仅保留 V5.3 系列作为本地默认策略 + "v53.json", + "v53_fast.json", + "v53_middle.json", +] + + +def load_strategy_configs() -> list[dict]: + """从本地 JSON 文件加载默认策略配置""" + configs: list[dict[str, Any]] = [] + for filename in DEFAULT_STRATEGY_FILES: + path = os.path.join(STRATEGY_DIR, filename) + try: + with open(path, "r", encoding="utf-8") as f: + cfg = json.load(f) + if isinstance(cfg, dict) and cfg.get("name"): + configs.append(cfg) + except FileNotFoundError: + logger.warning(f"策略配置缺失: {path}") + except Exception as e: + logger.error(f"策略配置加载失败 {path}: {e}") + if not configs: + logger.warning("未加载到策略配置,回退到 v53 默认配置") + configs.append( + { + "name": "v53", + "threshold": 75, + "flip_threshold": 85, + "tp_sl": { + "sl_multiplier": 2.0, + "tp1_multiplier": 1.5, + "tp2_multiplier": 3.0, + }, + # 默认支持四个主交易对,其他细节(gates/symbol_gates) + # 在 evaluate_v53 内部有安全的默认值。 + "symbols": ["BTCUSDT", "ETHUSDT", "XRPUSDT", "SOLUSDT"], + } + ) + return configs + + +def load_strategy_configs_from_db() -> list[dict]: + """ + V5.4: 从 strategies 表读取 running 状态的策略配置。 + 把 DB 字段映射成现有 JSON 格式(保持与 JSON 文件完全兼容)。 + 失败时返回空列表,调用方应 fallback 到 JSON。 + 内存安全:每次读取只返回配置列表,无缓存,无大对象。 + """ + try: + with get_sync_conn() as conn: + with conn.cursor() as cur: + cur.execute( + """ + SELECT + strategy_id::text, display_name, symbol, + cvd_fast_window, cvd_slow_window, + weight_direction, weight_env, weight_aux, weight_momentum, + entry_score, + gate_obi_enabled, obi_threshold, + gate_whale_enabled, whale_usd_threshold, whale_flow_pct, + gate_vol_enabled, vol_atr_pct_min, + gate_cvd_enabled, + gate_spot_perp_enabled, spot_perp_threshold, + sl_atr_multiplier, tp1_ratio, tp2_ratio, + timeout_minutes, flip_threshold, direction + FROM strategies + WHERE status = 'running' + ORDER BY created_at ASC + """ + ) + rows = cur.fetchall() + + configs: list[dict[str, Any]] = [] + for row in rows: + ( + sid, + display_name, + symbol, + cvd_fast, + cvd_slow, + w_dir, + w_env, + w_aux, + w_mom, + entry_score, + gate_obi, + obi_thr, + gate_whale, + whale_usd_thr, + whale_flow_pct_val, + gate_vol, + vol_atr_pct, + gate_cvd, + gate_spot, + spot_thr, + sl_mult, + tp1_r, + tp2_r, + timeout_min, + flip_thr, + direction, + ) = row + + # 把 display_name 映射回 legacy strategy name(用于兼容评分逻辑) + # legacy 策略用固定 UUID 识别 + LEGACY_UUID_MAP = { + "00000000-0000-0000-0000-000000000053": "v53", + "00000000-0000-0000-0000-000000000054": "v53_middle", + "00000000-0000-0000-0000-000000000055": "v53_fast", + } + strategy_name = LEGACY_UUID_MAP.get(sid, f"custom_{sid[:8]}") + + cfg: dict[str, Any] = { + "name": strategy_name, + "strategy_id": sid, # V5.4 新增:用于写 strategy_id 到 DB + "strategy_name_snapshot": display_name, + "symbol": symbol, + "direction": direction, + "cvd_fast_window": cvd_fast, + "cvd_slow_window": cvd_slow, + "threshold": entry_score, + "weights": { + "direction": w_dir, + "env": w_env, + "aux": w_aux, + "momentum": w_mom, + }, + "gates": { + "vol": { + "enabled": gate_vol, + "vol_atr_pct_min": float(vol_atr_pct or 0.002), + }, + "cvd": {"enabled": gate_cvd}, + "whale": { + "enabled": gate_whale, + "whale_usd_threshold": float(whale_usd_thr or 50000), + "whale_flow_pct": float(whale_flow_pct_val or 0.5), + }, + "obi": { + "enabled": gate_obi, + "threshold": float(obi_thr or 0.35), + }, + "spot_perp": { + "enabled": gate_spot, + "threshold": float(spot_thr or 0.005), + }, + }, + "tp_sl": { + # V5.4: 统一采用“以 R 计”的配置: + # risk_distance = sl_atr_multiplier × ATR = 1R + # TP1 = entry ± tp1_ratio × risk_distance + # TP2 = entry ± tp2_ratio × risk_distance + "sl_multiplier": sl_mult, + "tp1_ratio": tp1_r, + "tp2_ratio": tp2_r, + }, + "timeout_minutes": timeout_min, + "flip_threshold": flip_thr, + } + configs.append(cfg) + + logger.info( + f"[DB] 已加载 {len(configs)} 个策略配置: {[c['name'] for c in configs]}" + ) + return configs + + except Exception as e: + logger.warning(f"[DB] load_strategy_configs_from_db 失败,将 fallback 到 JSON: {e}") + return [] diff --git a/backend/strategy_scoring.py b/backend/strategy_scoring.py new file mode 100644 index 0000000..f319f75 --- /dev/null +++ b/backend/strategy_scoring.py @@ -0,0 +1,554 @@ +""" +strategy_scoring.py — V5 策略工厂统一评分逻辑 + +从原来的 signal_engine.py 中拆分出的 V5 评分与 Gate 逻辑: + - evaluate_factory_strategy(state, now_ms, strategy_cfg, snapshot) + → 单条策略(含工厂产出的 custom_*)的核心评分逻辑 + - evaluate_signal(state, now_ms, strategy_cfg, snapshot) + → 对外统一入口(仅支持 v53*/custom_*) + +由 signal_engine / backtest 调用。V5.1/V5.2(v51_baseline / v52_8signals) +已在此模块中下线:若仍传入旧策略名,将返回空结果并打印 warning。 +""" + +import logging +from typing import Any, Optional + +from signal_state import SymbolState, to_float + +logger = logging.getLogger("strategy-scoring") + + +def evaluate_factory_strategy( + state: SymbolState, + now_ms: int, + strategy_cfg: dict, + snapshot: Optional[dict] = None, +) -> dict: + """ + V5 策略工厂统一评分(BTC/ETH/XRP/SOL + custom_*) + - 输入:动态 CVD 窗口 + 五门参数 + OI/拥挤/辅助指标 + - 输出:score / signal / tier + 详细 factors + - 支持:单币种策略(symbol)、方向限制(long_only/short_only/both)、自定义四层权重 + """ + strategy_name = strategy_cfg.get("name", "v53") + strategy_threshold = int(strategy_cfg.get("threshold", 75)) + flip_threshold = int(strategy_cfg.get("flip_threshold", 85)) + + # per-strategy 方向约束(long_only / short_only / both) + dir_cfg_raw = (strategy_cfg.get("direction") or "both").lower() + # 兼容策略工厂的 long_only / short_only 配置 + if dir_cfg_raw in ("long_only", "only_long"): + dir_cfg_raw = "long" + elif dir_cfg_raw in ("short_only", "only_short"): + dir_cfg_raw = "short" + if dir_cfg_raw not in ("long", "short", "both"): + dir_cfg_raw = "both" + + snap = snapshot or state.build_evaluation_snapshot(now_ms) + + # 按策略配置的 cvd_fast_window / cvd_slow_window 动态切片重算 CVD + cvd_fast_window = strategy_cfg.get("cvd_fast_window", "30m") + cvd_slow_window = strategy_cfg.get("cvd_slow_window", "4h") + + def _window_ms(code: str) -> int: + if not isinstance(code, str) or len(code) < 2: + return 30 * 60 * 1000 + unit = code[-1] + try: + val = int(code[:-1]) + except ValueError: + return 30 * 60 * 1000 + if unit == "m": + return val * 60 * 1000 + if unit == "h": + return val * 3600 * 1000 + return 30 * 60 * 1000 + + fast_ms = _window_ms(cvd_fast_window) + slow_ms = _window_ms(cvd_slow_window) + + if cvd_fast_window == "30m" and cvd_slow_window == "4h": + cvd_fast = snap["cvd_fast"] + cvd_mid = snap["cvd_mid"] + else: + cutoff_fast = now_ms - fast_ms + cutoff_slow = now_ms - slow_ms + buy_f = sell_f = buy_m = sell_m = 0.0 + src_fast = state.win_mid if fast_ms > state.win_fast.window_ms else state.win_fast + for t_ms, qty, _price, ibm in src_fast.trades: + if t_ms >= cutoff_fast: + if ibm == 0: + buy_f += qty + else: + sell_f += qty + for t_ms, qty, _price, ibm in state.win_mid.trades: + if t_ms >= cutoff_slow: + if ibm == 0: + buy_m += qty + else: + sell_m += qty + cvd_fast = buy_f - sell_f + cvd_mid = buy_m - sell_m + + price = snap["price"] + atr = snap["atr"] + atr_value = snap.get("atr_value", atr) + cvd_fast_accel = snap["cvd_fast_accel"] + environment_score_raw = snap["environment_score"] + + # 默认 result 零值(基础字段从 snapshot 填充,以兼容 save_indicator/save_feature_event) + # 注意:cvd_fast/cvd_mid 会在后面覆盖为「按策略窗口重算」后的值, + # 这里先用 snapshot 保证字段存在。 + result = { + "strategy": strategy_name, + "cvd_fast": snap["cvd_fast"], + "cvd_mid": snap["cvd_mid"], + "cvd_day": snap["cvd_day"], + "cvd_fast_slope": snap["cvd_fast_slope"], + "atr": atr, + "atr_value": atr_value, + "atr_pct": snap["atr_pct"], + "vwap": snap["vwap"], + "price": price, + "p95": snap["p95"], + "p99": snap["p99"], + "signal": None, + "direction": None, + "score": 0.0, + "tier": None, + "factors": {}, + } + + if state.warmup or price == 0 or atr == 0: + return result + + last_signal_ts = state.last_signal_ts.get(strategy_name, 0) + COOLDOWN_MS = 10 * 60 * 1000 + in_cooldown = now_ms - last_signal_ts < COOLDOWN_MS + + # ── 五门参数:优先读 DB config(V5.4),fallback 到 JSON symbol_gates ──── + db_gates = strategy_cfg.get("gates") or {} + symbol_gates = (strategy_cfg.get("symbol_gates") or {}).get(state.symbol, {}) + + gate_vol_enabled = db_gates.get("vol", {}).get("enabled", True) + min_vol = float( + db_gates.get("vol", {}).get("vol_atr_pct_min") + or symbol_gates.get("min_vol_threshold", 0.002) + ) + + gate_cvd_enabled = db_gates.get("cvd", {}).get("enabled", True) + + gate_whale_enabled = db_gates.get("whale", {}).get("enabled", True) + whale_usd = float( + db_gates.get("whale", {}).get("whale_usd_threshold") + or symbol_gates.get("whale_threshold_usd", 50000) + ) + whale_flow_pct = float( + db_gates.get("whale", {}).get("whale_flow_pct") + or symbol_gates.get("whale_flow_threshold_pct", 0.5) + ) + + gate_obi_enabled = db_gates.get("obi", {}).get("enabled", True) + obi_veto = float( + db_gates.get("obi", {}).get("threshold") + or symbol_gates.get("obi_veto_threshold", 0.35) + ) + + gate_spd_enabled = db_gates.get("spot_perp", {}).get("enabled", False) + spd_veto = float( + db_gates.get("spot_perp", {}).get("threshold") + or symbol_gates.get("spot_perp_divergence_veto", 0.005) + ) + + # 覆盖为按策略窗口重算后的 CVD(用于 signal_indicators 展示) + result["cvd_fast"] = cvd_fast + result["cvd_mid"] = cvd_mid + + gate_block = None + + # 门1:波动率下限(可关闭) + atr_pct_price = atr / price if price > 0 else 0 + if gate_vol_enabled and atr_pct_price < min_vol: + gate_block = f"low_vol({atr_pct_price:.4f}<{min_vol})" + + # 门2:CVD 共振(方向门,可关闭) + no_direction = False + cvd_resonance = 0 + if cvd_fast > 0 and cvd_mid > 0: + direction = "LONG" + cvd_resonance = 30 + no_direction = False + elif cvd_fast < 0 and cvd_mid < 0: + direction = "SHORT" + cvd_resonance = 30 + no_direction = False + else: + direction = "LONG" if cvd_fast > 0 else "SHORT" + cvd_resonance = 0 + if gate_cvd_enabled: + no_direction = True + if not gate_block: + gate_block = "no_direction_consensus" + else: + no_direction = False + + # per-strategy 方向限制:long/short 仅限制开仓方向,不影响评分与指标快照 + if dir_cfg_raw == "long" and direction == "SHORT": + strategy_direction_allowed = False + elif dir_cfg_raw == "short" and direction == "LONG": + strategy_direction_allowed = False + else: + strategy_direction_allowed = True + + # 门3:鲸鱼否决(BTC 用 whale_cvd_ratio,ALT 用大单对立,可关闭) + if gate_whale_enabled and not gate_block and not no_direction: + if state.symbol == "BTCUSDT": + whale_cvd = ( + state.whale_cvd_ratio + if state._whale_trades + else to_float(state.market_indicators.get("tiered_cvd_whale")) or 0.0 + ) + if (direction == "LONG" and whale_cvd < -whale_flow_pct) or ( + direction == "SHORT" and whale_cvd > whale_flow_pct + ): + gate_block = f"whale_cvd_veto({whale_cvd:.3f})" + else: + whale_adverse = any( + (direction == "LONG" and lt[2] == 1 and lt[1] * price >= whale_usd) + or (direction == "SHORT" and lt[2] == 0 and lt[1] * price >= whale_usd) + for lt in state.recent_large_trades + ) + whale_aligned = any( + (direction == "LONG" and lt[2] == 0 and lt[1] * price >= whale_usd) + or (direction == "SHORT" and lt[2] == 1 and lt[1] * price >= whale_usd) + for lt in state.recent_large_trades + ) + if whale_adverse and not whale_aligned: + gate_block = f"whale_adverse(>${whale_usd/1000:.0f}k)" + + # 门4:OBI 否决(实时 WS 优先,fallback DB,可关闭) + obi_raw = state.rt_obi if state.rt_obi != 0.0 else to_float( + state.market_indicators.get("obi_depth_10") + ) + if gate_obi_enabled and not gate_block and not no_direction and obi_raw is not None: + if direction == "LONG" and obi_raw < -obi_veto: + gate_block = f"obi_veto({obi_raw:.3f}<-{obi_veto})" + elif direction == "SHORT" and obi_raw > obi_veto: + gate_block = f"obi_veto({obi_raw:.3f}>{obi_veto})" + + # 门5:期现背离否决(实时 WS 优先,fallback DB,可关闭) + spot_perp_div = ( + state.rt_spot_perp_div if state.rt_spot_perp_div != 0.0 else to_float( + state.market_indicators.get("spot_perp_divergence") + ) + ) + if gate_spd_enabled and not gate_block and not no_direction and spot_perp_div is not None: + if (direction == "LONG" and spot_perp_div < -spd_veto) or ( + direction == "SHORT" and spot_perp_div > spd_veto + ): + gate_block = f"spd_veto({spot_perp_div:.4f})" + + gate_passed = gate_block is None + + # ── Direction Layer(55 分,原始尺度)─────────────────────── + has_adverse_p99 = any( + (direction == "LONG" and lt[2] == 1) or (direction == "SHORT" and lt[2] == 0) + for lt in state.recent_large_trades + ) + has_aligned_p99 = any( + (direction == "LONG" and lt[2] == 0) or (direction == "SHORT" and lt[2] == 1) + for lt in state.recent_large_trades + ) + p99_flow = 20 if has_aligned_p99 else (10 if not has_adverse_p99 else 0) + accel_bonus = 5 if ( + (direction == "LONG" and cvd_fast_accel > 0) or + (direction == "SHORT" and cvd_fast_accel < 0) + ) else 0 + + # v53_fast:accel 独立触发路径(不要求 cvd 双线同向) + is_fast = strategy_name.endswith("fast") + accel_independent_score = 0 + if is_fast and not no_direction: + accel_cfg = strategy_cfg.get("accel_independent", {}) + if accel_cfg.get("enabled", False): + accel_strong = ( + (direction == "LONG" and cvd_fast_accel > 0 and has_aligned_p99) + or (direction == "SHORT" and cvd_fast_accel < 0 and has_aligned_p99) + ) + if accel_strong: + accel_independent_score = int( + accel_cfg.get("min_direction_score", 35) + ) + + direction_score = max( + min(cvd_resonance + p99_flow + accel_bonus, 55), + accel_independent_score, + ) + + # ── Crowding Layer(25 分,原始尺度)─────────────────────── + long_short_ratio = to_float(state.market_indicators.get("long_short_ratio")) + if long_short_ratio is None: + ls_score = 7 + elif (direction == "SHORT" and long_short_ratio > 2.0) or ( + direction == "LONG" and long_short_ratio < 0.5 + ): + ls_score = 15 + elif (direction == "SHORT" and long_short_ratio > 1.5) or ( + direction == "LONG" and long_short_ratio < 0.7 + ): + ls_score = 10 + elif (direction == "SHORT" and long_short_ratio > 1.0) or ( + direction == "LONG" and long_short_ratio < 1.0 + ): + ls_score = 7 + else: + ls_score = 0 + + top_trader_position = to_float(state.market_indicators.get("top_trader_position")) + if top_trader_position is None: + top_trader_score = 5 + else: + if direction == "LONG": + top_trader_score = ( + 10 + if top_trader_position >= 0.55 + else (0 if top_trader_position <= 0.45 else 5) + ) + else: + top_trader_score = ( + 10 + if top_trader_position <= 0.45 + else (0 if top_trader_position >= 0.55 else 5) + ) + crowding_score = min(ls_score + top_trader_score, 25) + + # ── Environment Layer(15 分,原始尺度)──────────────────── + oi_base_score = round(environment_score_raw / 15 * 10) + + obi_raw = state.rt_obi if state.rt_obi != 0.0 else to_float( + state.market_indicators.get("obi_depth_10") + ) + obi_bonus = 0 + if is_fast and obi_raw is not None: + obi_cfg = strategy_cfg.get("obi_scoring", {}) + strong_thr = float(obi_cfg.get("strong_threshold", 0.30)) + weak_thr = float(obi_cfg.get("weak_threshold", 0.15)) + strong_sc = int(obi_cfg.get("strong_score", 5)) + weak_sc = int(obi_cfg.get("weak_score", 3)) + obi_aligned = (direction == "LONG" and obi_raw > 0) or ( + direction == "SHORT" and obi_raw < 0 + ) + obi_abs = abs(obi_raw) + if obi_aligned: + if obi_abs >= strong_thr: + obi_bonus = strong_sc + elif obi_abs >= weak_thr: + obi_bonus = weak_sc + + environment_score = ( + min(oi_base_score + obi_bonus, 15) + if is_fast + else round(environment_score_raw / 15 * 15) + ) + + # ── Auxiliary Layer(5 分,原始尺度)────────────────────── + coinbase_premium = to_float(state.market_indicators.get("coinbase_premium")) + if coinbase_premium is None: + aux_score = 2 + elif ( + (direction == "LONG" and coinbase_premium > 0.0005) + or (direction == "SHORT" and coinbase_premium < -0.0005) + ): + aux_score = 5 + elif abs(coinbase_premium) <= 0.0005: + aux_score = 2 + else: + aux_score = 0 + + # ── 根据策略权重缩放四层分数(direction/env/aux/momentum)──── + weights_cfg = strategy_cfg.get("weights") or {} + w_dir = float(weights_cfg.get("direction", 55)) + w_env = float(weights_cfg.get("env", 25)) + w_aux = float(weights_cfg.get("aux", 15)) + w_mom = float(weights_cfg.get("momentum", 5)) + + total_w = w_dir + w_env + w_aux + w_mom + if total_w <= 0: + # Fallback 到默认 55/25/15/5 + w_dir, w_env, w_aux, w_mom = 55.0, 25.0, 15.0, 5.0 + total_w = 100.0 + + # 归一化到 100 分制 + norm = 100.0 / total_w + w_dir_eff = (w_dir + w_mom) * norm # 动量权重并入方向层 + w_env_eff = w_env * norm + w_aux_eff = w_aux * norm + + # 原始最大值:direction 55 + crowding 25 = 80 + DIR_RAW_MAX = 55.0 + CROWD_RAW_MAX = 25.0 + ENV_RAW_MAX = 15.0 + AUX_RAW_MAX = 5.0 + DIR_PLUS_CROWD_RAW_MAX = DIR_RAW_MAX + CROWD_RAW_MAX + + # 把方向+拥挤总权重按 55:25 拆分 + dir_max_scaled = w_dir_eff * (DIR_RAW_MAX / DIR_PLUS_CROWD_RAW_MAX) + crowd_max_scaled = w_dir_eff * (CROWD_RAW_MAX / DIR_PLUS_CROWD_RAW_MAX) + env_max_scaled = w_env_eff + aux_max_scaled = w_aux_eff + + # 按原始分数比例缩放到新的权重上 + def _scale(raw_score: float, raw_max: float, scaled_max: float) -> float: + if raw_max <= 0 or scaled_max <= 0: + return 0.0 + return min(max(raw_score, 0) / raw_max * scaled_max, scaled_max) + + direction_score_scaled = _scale(direction_score, DIR_RAW_MAX, dir_max_scaled) + crowding_score_scaled = _scale(crowding_score, CROWD_RAW_MAX, crowd_max_scaled) + environment_score_scaled = _scale(environment_score, ENV_RAW_MAX, env_max_scaled) + aux_score_scaled = _scale(aux_score, AUX_RAW_MAX, aux_max_scaled) + + total_score = min( + direction_score_scaled + + crowding_score_scaled + + environment_score_scaled + + aux_score_scaled, + 100, + ) + total_score = max(0, round(total_score, 1)) + if not gate_passed: + total_score = 0 + + whale_cvd_display = ( + state.whale_cvd_ratio + if state._whale_trades + else to_float(state.market_indicators.get("tiered_cvd_whale")) + ) if state.symbol == "BTCUSDT" else None + + result.update( + { + "score": total_score, + "direction": direction if (not no_direction and gate_passed) else None, + "atr_value": atr_value, + "cvd_fast_5m": cvd_fast if is_fast else None, + "factors": { + "track": "BTC" if state.symbol == "BTCUSDT" else "ALT", + "gate_passed": gate_passed, + "gate_block": gate_block, + "atr_pct_price": round(atr_pct_price, 5), + "obi_raw": obi_raw, + "spot_perp_div": spot_perp_div, + "whale_cvd_ratio": whale_cvd_display, + "direction": { + "score": round(direction_score_scaled, 2), + "max": round(dir_max_scaled, 2), + "raw_score": direction_score, + "raw_max": DIR_RAW_MAX, + "cvd_resonance": cvd_resonance, + "p99_flow": p99_flow, + "accel_bonus": accel_bonus, + }, + "crowding": { + "score": round(crowding_score_scaled, 2), + "max": round(crowd_max_scaled, 2), + "raw_score": crowding_score, + "raw_max": CROWD_RAW_MAX, + }, + "environment": { + "score": round(environment_score_scaled, 2), + "max": round(env_max_scaled, 2), + "raw_score": environment_score, + "raw_max": ENV_RAW_MAX, + "oi_change": snap["oi_change"], + }, + "auxiliary": { + "score": round(aux_score_scaled, 2), + "max": round(aux_max_scaled, 2), + "raw_score": aux_score, + "raw_max": AUX_RAW_MAX, + "coinbase_premium": coinbase_premium, + }, + }, + } + ) + + # 赋值 tier/signal(和原逻辑一致) + if total_score >= strategy_threshold and gate_passed and strategy_direction_allowed: + result["signal"] = direction + # tier 简化:score >= flip_threshold → heavy;否则 standard + result["tier"] = "heavy" if total_score >= flip_threshold else "standard" + + state.last_signal_ts[strategy_name] = now_ms + state.last_signal_dir[strategy_name] = direction + + return result + + +def _empty_result(strategy_name: str, snap: dict) -> dict: + """返回空评分结果(symbol 不匹配 / 无信号时使用)""" + return { + "strategy": strategy_name, + "cvd_fast": snap["cvd_fast"], + "cvd_mid": snap["cvd_mid"], + "cvd_day": snap["cvd_day"], + "cvd_fast_slope": snap["cvd_fast_slope"], + "atr": snap["atr"], + "atr_value": snap.get("atr_value", snap["atr"]), + "atr_pct": snap["atr_pct"], + "vwap": snap["vwap"], + "price": snap["price"], + "p95": snap["p95"], + "p99": snap["p99"], + "signal": None, + "direction": None, + "score": 0, + "tier": None, + "factors": {}, + } + + +def evaluate_signal( + state: SymbolState, + now_ms: int, + strategy_cfg: Optional[dict] = None, + snapshot: Optional[dict] = None, +) -> dict: + """ + 统一评分入口: + - v53*/custom_* → evaluate_v53 (V5.3/V5.4 策略工厂) + - 其他策略(v51_baseline/v52_8signals 等)→ 视为已下线,返回空结果并记录 warning + """ + strategy_cfg = strategy_cfg or {} + strategy_name = strategy_cfg.get("name", "v53") + + # v53 / custom_* 策略:走统一 V5 工厂打分 + if strategy_name.startswith("v53") or strategy_name.startswith("custom_"): + snap = snapshot or state.build_evaluation_snapshot(now_ms) + # 单币种策略:如 cfg.symbol 存在,仅在该 symbol 上有效 + strategy_symbol = strategy_cfg.get("symbol") + if strategy_symbol and strategy_symbol != state.symbol: + return _empty_result(strategy_name, snap) + + allowed_symbols = strategy_cfg.get("symbols", []) + if allowed_symbols and state.symbol not in allowed_symbols: + return _empty_result(strategy_name, snap) + # 直接复用工厂评分核心逻辑,并确保基础字段完整 + result = evaluate_factory_strategy(state, now_ms, strategy_cfg, snap) + # 补充缺失的基础字段(以 snapshot 为准) + base = _empty_result(strategy_name, snap) + for k, v in base.items(): + result.setdefault(k, v) + return result + + # 非 v53/custom_ 策略:视为已下线,返回空结果并记录 warning + snap = snapshot or state.build_evaluation_snapshot(now_ms) + logger.warning( + "[strategy_scoring] strategy '%s' 已下线 (仅支持 v53*/custom_*), 返回空结果", + strategy_name, + ) + return _empty_result(strategy_name, snap) + + +# 向后兼容别名(文档/历史代码中的名字) +evaluate_v53 = evaluate_factory_strategy diff --git a/docs/AB_TEST_CHECKLIST.md b/docs/AB_TEST_CHECKLIST.md deleted file mode 100644 index ce1065b..0000000 --- a/docs/AB_TEST_CHECKLIST.md +++ /dev/null @@ -1,36 +0,0 @@ -# AB测试观测清单(2026-03-02 ~ 03-16) - -## 冻结期规则 -- 不改权重、不改阈值、不改评分逻辑 -- 如需改动必须打新版本号并分段统计 -- 单写入源(小周生产环境) - -## 两周后评审项目 - -### 1. 确认层重复计分审计 -- **问题**:方向层和确认层都用CVD_fast/CVD_mid,同源重复 -- **审计方法**:统计确认层=15 vs 确认层=0时的胜率差异 -- **如果差异不显著**:V5.3降权或重构为"CVD斜率加速+趋势强度" - -### 2. 拥挤层 vs FR相关性 -- **审计**:`corr(FR_score, crowd_score)` -- **如果>0.7**:说明重复表达,降一层权重 - -### 3. OI持续性审计 -- **字段**:`oi_persist_n`(连续同向窗口数)— 目前未记录,需V5.3加 -- **审计**:高分单里`oi_persist_n=1`的胜率是否显著差于`>=2` -- **如果差异明显**:升为正式门槛 - -### 4. 清算触发率审计(按币种) -- 各币种清算信号触发率 -- 触发后净R分布 -- 避免某币种几乎不触发/过度触发 - -### 5. config_hash落库(V5.3) -- 每笔强制落库:`strategy`, `strategy_version`, `config_hash`, `engine_instance` -- 报表按config_hash分组 - -## 数据目标 -- V5.1:500+笔(当前282) -- V5.2:200+笔(当前12) -- 每策略每币种50+笔 diff --git a/docs/AI_HANDBOOK.md b/docs/AI_HANDBOOK.md new file mode 100644 index 0000000..f3964ac --- /dev/null +++ b/docs/AI_HANDBOOK.md @@ -0,0 +1,203 @@ +# AI 使用手册(Arbitrage Engine) + +> 面向 Codex / OpenClaw / 其他 AI 助手。 +> 目标:让 AI 在本项目中 **安全、可预期、可回溯** 地修改代码,而不是“乱改一通”。 + +本项目的**唯一权威系统说明**是:`docs/arbitrage-engine-full-spec.md`。 +本手册描述:AI 进入项目时的阅读顺序、允许/禁止修改的范围,以及每次改动必须遵守的流程。 + +--- + +## 1. 进入项目前必须阅读的内容 + +在对本项目做任何非文档类修改之前,AI 必须先做下面几件事: + +1. 打开并理解 `docs/arbitrage-engine-full-spec.md` 中至少以下章节: + - 项目概述、技术栈 + - 数据库结构(尤其是 `strategies` / `signal_indicators` / `paper_trades`) + - 信号引擎(signal_engine)逻辑 + - paper trading 行为与 PnL 计算 +2. 浏览以下后端文件的结构(无需逐行记住): + - `backend/main.py`(FastAPI 路由与业务分层) + - `backend/signal_engine.py`(信号引擎与策略工厂) + - `backend/paper_monitor.py`(模拟盘平仓逻辑) +3. 浏览前端入口结构: + - `frontend/app/` 下的页面目录(尤其是 `/strategy-plaza` / `/signals` / `/paper`) + +如发现文档与代码实际行为有冲突,应**优先相信 full-spec**,并在必要时向人类维护者报告。 + +--- + +## 2. 修改范围:可以动什么 / 不可以动什么 + +为减少细节 bug 和隐性破坏,本项目对 AI 的修改范围划分为三类: + +- `允许自由修改`:只要本地检查通过即可。 +- `允许修改,但需要特别小心`:需要有清晰计划 + 局部验证。 +- `禁止修改,除非任务明确要求且更新了 full-spec`。 + +下面按类型具体说明。 + +### 2.1 允许自由修改的内容(默认安全区) + +这些改动一般不会破坏系统核心语义,可以在合理前提下自由进行: + +1. **文档与注释** + - 新增或修改 Markdown 文档(除非明确标为“权威规范”,如 full-spec)。 + - 在代码中添加/改进注释,不改变实际逻辑。 +2. **测试代码** + - 新增/改进单元测试、集成测试、前端测试。 + - 修复由于实现变化导致的测试用例不匹配(前提是确认测试的预期确实已变化)。 +3. **前端 UI 层展示** + - 不改变 API 调用与数据结构的前提下: + - 布局调整、视觉优化、文案修改。 + - 新增只读型组件(图表、表格、标签等)。 +4. **纯新增的本地脚本与工具** + - 放在 `scripts/` 或明确的工具目录里,用于开发、调试、分析(不接入生产 PM2 流程)。 +5. **无行为变更的小型重构** + - 拆分过长函数、提取私有辅助函数。 + - 增加类型标注、引入更严格的 linters(在配置合理的前提下)。 + +### 2.2 可以修改但需要特别小心的内容(受控区) + +这类改动一旦出错,会引入细节 bug 或统计偏差,但不至于破坏整个系统结构。修改前必须: + +1. 在回答中给出**明确的改动计划**(改哪些文件、改变哪些行为)。 +2. 改完后尽量做局部验证(至少跑相关命令 / 人工检查相关页面)。 + +受控区包括: + +1. **现有 API 的实现细节** + - 位于 `backend/main.py` 中的 `/api/*` 路由实现。 + - 允许: + - 优化 SQL 查询; + - 修复明显的边界条件 bug; + - 增加非破坏性字段(向响应 json 增加可选字段)。 + - 不允许: + - 静默改变已有字段的含义(例如把 `score` 从 0–100 改成 0–1)。 + - 在无任务说明的前提下移除现有字段。 +2. **前端与现有 API 的交互逻辑** + - 包括 `frontend/app/*` 中对 `/api/*` 的调用与数据展示逻辑。 + - 修改前需确认: + - 请求路径、查询参数、响应结构与 full-spec 和实际后端实现一致。 +3. **信号引擎的非核心逻辑** + - `backend/signal_engine.py` 中: + - 日志输出格式; + - 错误处理与异常保护; + - 非打分核心的辅助逻辑(例如冷启动保护、调试输出等)。 + - 修改“评分公式”“门控条件”的逻辑时,属于 **禁止区**(见 2.3)。 +4. **paper trading 的展示与统计层** + - 后端统计接口(如 `/api/paper/stats`、`/api/paper/stats-by-strategy`)的实现细节; + - 前端 `/paper` 以及策略广场中与统计展示相关的逻辑。 + - 改动时需确认: + - 不改变 `pnl_r` / `status` 的语义; + - 汇总方式与 full-spec 中定义的规则保持一致。 + +### 2.3 禁止修改的内容(红线区) + +以下内容 **默认禁止 AI 修改**。 +只有在任务说明/人类明确要求的情况下,才可以动,并且必须同步更新 `arbitrage-engine-full-spec.md` 中对应章节。 + +1. **数据库结构与关键字段语义** + - 表结构(尤其是): + - `strategies` + - `signal_indicators` + - `paper_trades` + - `agg_trades` + - `liquidations` + - `market_indicators` + - 禁止: + - 修改已有字段的含义; + - 删除字段; + - 改名字段; + - 在没有文档更新的前提下添加字段。 + - 如确需变更,必须: + - 在任务文档中有清晰需求; + - 编写迁移 SQL 并在 full-spec 中更新结构说明。 +2. **核心业务指标的定义与计算方式** + - 包括但不限于: + - `pnl_r` 的计算规则; + - `status` 枚举值含义(`tp` / `sl` / `sl_be` / `timeout` / `signal_flip` 等); + - 胜率、盈亏比、权益曲线的汇总算法; + - 信号评分各层(direction/environment/auxiliary/momentum)的含义与权重机制。 + - 如果任务要求调整这些规则,必须: + - 明确标注新旧行为差异; + - 说明对历史数据/统计的影响; + - 更新 full-spec 中对应章节。 +3. **信号引擎核心打分/开仓/平仓逻辑** + - `evaluate_factory_strategy`(内部别名 `evaluate_v53`,原 `_evaluate_v53`)/ V5.4 策略工厂中的核心决策逻辑,包括: + - 门控系统(5 个 Gate)的通过/否决条件; + - 各层得分的具体计算公式; + - `entry_score` / `flip_threshold` 等核心阈值的语义; + - 开仓价/SL/TP 的确定方式; + - `paper_open_trade` / `paper_monitor` 中判定触发 TP1/TP2/SL/timeout 的条件。 + - 除非任务明确指出“需要调整策略模型/打分方式”,否则 AI 不应主动更改这些部分。 +4. **策略 ID / 名称约定与历史数据映射** + - `strategies.strategy_id`(UUID)与 `paper_trades.strategy_id`、`signal_indicators.strategy_id` 的关联关系; + - `strategy` 字段中已有的命名约定(例如 `custom_` 前缀)。 + - 禁止: + - 修改已有策略的 `strategy_id`; + - 静默重命名已有策略的 `strategy` 文本值; + - 移除 `custom_` 策略路由或相关兼容逻辑。 +5. **认证与安全配置** + - `auth.py` 中的认证逻辑和权限控制; + - JWT Secret、数据库连接信息、PM2 配置等运维层配置。 + - AI 不应在代码中硬编码新的敏感信息,也不应修改现有的密钥配置。 + +--- + +## 3. 每次修改必须遵守的流程(AI 视角) + +无论修改的是允许区还是受控区,AI 在本项目中的工作流程应遵循: + +1. **确认任务范围** + - 从用户指令或任务文档中提取: + - 要实现/修复的目标; + - 涉及的页面/API/模块。 +2. **查阅相关文档** + - 总是先查 `docs/arbitrage-engine-full-spec.md`; + - 如涉及信号引擎/策略工厂/模拟盘,需额外查阅对应的 GUIDE 文档(如果存在)。 +3. **列出改动计划** + - 在回答中简要列出: + - 计划修改的文件; + - 每个文件预期的改动类型(bugfix/重构/新增功能)。 +4. **实施改动(保持最小必要范围)** + - 避免“一次改太多文件”; + - 避免引入与任务无关的重构。 +5. **本地验证** + - 尽量运行至少以下检查之一(视任务而定): + - 后端:相关单测 / 手工调用关键 API; + - 前端:`npm run build` 或启动本地开发服务器检查关键页面; + - 如无法真实运行,至少从代码结构和文档上做自洽性检查。 +6. **更新文档(如必要)** + - 若改动影响 full-spec 中描述的行为/结构,必须同步更新 full-spec; + - 若改动属于策略/信号模型的重大变更,应在决策记录中添加条目(例如 `DECISIONS.md`)。 + +--- + +## 4. 冲突处理原则 + +当出现以下冲突时,AI 应遵循: + +1. **代码 vs full-spec** + - 如果代码实现与 full-spec 的描述不一致: + - 默认以 full-spec 为“期望行为”; + - 优先考虑修代码使其符合 full-spec; + - 如判断 full-spec 已过时,应在回答中明确提出,并建议人类确认后更新文档。 +2. **用户即时指令 vs 文档约束** + - 如果用户指令要求做的事情与本手册“禁止修改”部分冲突: + - AI 必须在回答中提醒用户这是“红线操作”; + - 只有在用户明确确认且任务需求足够清晰时,才执行,并同步修改 full-spec。 + +--- + +## 5. 总结 + +本手册的核心目的只有一个: + +> 让本项目在 AI 长期参与维护的前提下,仍然保持: +> - 核心业务逻辑不被轻易破坏; +> - 历史数据和统计口径具有可比性; +> - 每一次变更都有据可查、可解释。 + +如果本手册与 `arbitrage-engine-full-spec.md` 或实际代码行为存在不一致,应优先保持三者的一致性,并在必要时征求人类维护者的确认。 diff --git a/docs/API_CONTRACTS.md b/docs/API_CONTRACTS.md new file mode 100644 index 0000000..0d7e265 --- /dev/null +++ b/docs/API_CONTRACTS.md @@ -0,0 +1,1364 @@ +# Arbitrage Engine API Contracts(前端使用接口契约) + +> 本文从“前端页面需要什么数据”的角度,描述各接口的请求参数和响应结构。 +> 后端实现细节和数据库字段说明仍以 `docs/arbitrage-engine-full-spec.md` 为准;如有冲突,以 full-spec 为权威。 + +约定: + +- 所有接口路径均以 `/api` 开头; +- 响应示例使用 TypeScript 风格的类型定义 + 简单字段说明; +- 未特别说明时,时间戳字段单位为毫秒(ms)。 + +--- + +## 1. 首页 `/` 仪表盘相关接口 + +### 1.1 GET /api/rates + +用途: +首页顶部两张资金费率卡片(BTC/ETH)的数据源,用于展示当前资金费率、标记价格、指数价格等。 + +请求: + +- Method: `GET` +- Query: 无 + +响应示例: + +```ts +type RatesResponse = { + BTC: { + symbol: "BTCUSDT"; + markPrice: number; // 标记价格 + indexPrice: number; // 指数价格 + lastFundingRate: number; // 最新资金费率(例:0.0001 = 0.01%) + nextFundingTime: number; // 下次资金费率结算时间(毫秒时间戳) + timestamp: number; // 当前数据时间(毫秒时间戳) + }; + ETH: { + symbol: "ETHUSDT"; + markPrice: number; + indexPrice: number; + lastFundingRate: number; + nextFundingTime: number; + timestamp: number; + }; +}; +``` + +被使用位置: + +- 首页 `/`:`RateCard` 组件。 + +### 1.2 GET /api/stats + +用途: +首页中部三张“套利统计”卡片(BTC 套利 / ETH 套利 / 50/50 组合)的数据源。 + +请求: + +- Method: `GET` +- Query: 无 + +响应示例: + +```ts +type StatsResponse = { + BTC: { + mean7d: number; // 过去7天平均日收益率(例如 0.0005 = 0.05%/d) + annualized: number; // 折算年化收益率(百分数,例如 12.34) + }; + ETH: { + mean7d: number; + annualized: number; + }; + combo: { + mean7d: number; + annualized: number; + }; +}; +``` + +被使用位置: + +- 首页 `/`:三张 `StatsCard`。 + +### 1.3 GET /api/history + +用途: +首页的“历史费率走势(过去7天)”折线图以及“历史费率明细(最近30条)”表格。 + +请求: + +- Method: `GET` +- Query: 可选(full-spec 中定义),前端通常使用默认 7 天窗口。 + +响应示例(简化版): + +```ts +type HistoryPoint = { + fundingTime: number; // 资金费率结算时间(秒或毫秒时间戳,详见实现) + fundingRate: number; // 当前时间点资金费率(小数,例:0.0001 = 0.01%) + timestamp: string; // ISO8601 时间字符串,例如 "2026-03-10T15:00:00Z" +}; + +type HistoryResponse = { + BTC: HistoryPoint[]; + ETH: HistoryPoint[]; +}; +``` + +被使用位置: + +- 首页 `/`: + - 将 BTC/ETH 的 `fundingRate` 转成百分比,绘制 7 天折线图; + - 截取最近 30 条记录,填充历史明细表(时间 + BTC% + ETH%)。 + +### 1.4 GET /api/signals/history + +用途: +首页右下角“信号历史”表格,用于展示最近触发的套利机会(满足一定年化阈值)。 + +请求: + +- Method: `GET` +- Query: 可选,例如 `limit`,具体见 full-spec。 + +响应示例: + +```ts +type SignalHistoryItem = { + id: number; + symbol: string; // 币种简称,如 "BTC" / "ETH" + rate: number; // 当时资金费率(小数) + annualized: number; // 触发时对应的年化收益率(百分数) + sent_at: string; // 发送时间(ISO8601 字符串) + message: string; // 文本说明,用于展示/推送 +}; + +type SignalsHistoryResponse = { + items: SignalHistoryItem[]; +}; +``` + +被使用位置: + +- 首页 `/`:信号历史表,显示时间 / 币种 / 年化。 + +### 1.5 GET /api/stats/ytd + +用途: +为 `RateCard` 等组件提供 YTD(年初至今)统计,用于资金费率卡片上的补充信息。 + +请求: + +- Method: `GET` +- Query: 无 + +响应示例(示意): + +```ts +type YtdStats = { + mean: number; // 年初至今平均日收益率 + annualized: number; // 折算年化 + days: number; // 统计天数 +}; + +type YtdStatsResponse = { + BTC: YtdStats; + ETH: YtdStats; +}; +``` + +被使用位置: + +- 首页 `/`:`RateCard` 内部展示 YTD 指标。 + +### 1.6 GET /api/kline + +用途: +首页 K 线卡片,用于绘制资金费率 K 线和标记价格 K 线。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + symbol: "BTC" | "ETH"; // 前端传 BTC/ETH,无 USDT + interval: string; // "1m" | "5m" | "30m" | "1h" | "4h" | "8h" | "1d" | "1w" | "1M" + limit?: number; // 可选,K 线数量 +} +``` + +响应示例: + +```ts +type KBar = { + time: number; // K 线起始时间(秒级时间戳或 ms,前端按 full-spec 与实现处理) + open: number; + high: number; + low: number; + close: number; + price_open: number; // 标记价格的 OHLC + price_high: number; + price_low: number; + price_close: number; +}; + +type KlineResponse = { + symbol: string; // "BTC" + interval: string; // 请求时的 interval + count: number; // 实际返回的 bar 数量 + data: KBar[]; +}; +``` + +被使用位置: + +- 首页 `/`:`MiniKChart` 组件两次使用: + - mode="rate":使用 `open/high/low/close` 作为资金费率蜡烛; + - mode="price":使用 `price_*` 作为价格蜡烛。 + +--- + +## 2. /trades 成交流页面接口 + +### 2.1 GET /api/trades/latest + +用途: +`/trades` 页面左侧“实时成交”表格的数据源,拉取最近若干条逐笔成交。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + symbol: "BTC" | "ETH" | "XRP" | "SOL"; // 币种简称 + limit?: number; // 返回条数,默认 20 +} +``` + +响应示例: + +```ts +type LatestTradesResponse = { + data: Array<{ + agg_id: number; // Binance aggTrade ID + price: number; // 成交价格 + qty: number; // 成交量(合约单位) + time_ms: number; // 成交时间(毫秒) + is_buyer_maker: 0 | 1; // 0=主动买(多方), 1=主动卖(空方) + }>; +}; +``` + +被使用位置: + +- `/trades`:`LiveTrades` 组件。 + +### 2.2 GET /api/trades/summary + +用途: +`/trades` 页面右侧“成交流分析”图表的数据源,按时间窗口聚合买/卖量、Delta、VWAP 等信息。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + symbol: "BTC" | "ETH" | "XRP" | "SOL"; + start_ms: number; // 统计窗口起始时间(毫秒) + end_ms: number; // 统计窗口结束时间(毫秒) + interval: "1m" | "5m" | "15m" | "1h"; // 聚合粒度 +} +``` + +响应示例: + +```ts +type TradeSummaryBar = { + time_ms: number; // 时间段起始时间(毫秒) + buy_vol: number; // 该段主动买入量 + sell_vol: number; // 该段主动卖出量 + delta: number; // buy_vol - sell_vol + total_vol: number; // 总成交量 + trade_count: number;// 成交笔数 + vwap: number; // 成交量加权平均价 + max_qty: number; // 单笔最大成交量 +}; + +type TradeSummaryResponse = { + data: TradeSummaryBar[]; +}; +``` + +被使用位置: + +- `/trades`:`FlowAnalysis` 组件,绘制 Delta + 价格双轴图和“主动买 vs 主动卖”面积图,并算总买卖量、买方占比等摘要。 + +--- + +## 3. /live 实盘交易页面接口 + +> 本节仅列出被 `/live` 页面使用的接口,详细业务规则参考 full-spec。 + +### 3.1 GET /api/live/risk-status + +用途: +L0 风险条和 L6 风控状态,用于展示日内 R 使用情况、熔断状态、规则是否触发等。 + +请求: + +- Method: `GET` +- Query: 无 + +响应字段示意: + +```ts +type LiveRiskStatus = { + status: "normal" | "warning" | "circuit_break" | string; // 当前风险状态 + today_realized_r: number; // 今日已实现收益R + today_unrealized_r: number; // 今日未实现收益R + today_total_r: number; // 今日总 R(含未实现) + consecutive_losses: number; // 连续亏损笔数 + block_new_entries: boolean; // 是否禁止新开仓 + reduce_only: boolean; // 是否只减仓 + circuit_break_reason?: string | null; // 熔断原因(如有) + auto_resume_time?: number | null; // 预计自动恢复时间(秒或毫秒时间戳,详见实现) +}; +``` + +被使用位置: + +- `/live`:`L0_RiskBar`、`L6_RiskStatus`。 + +### 3.2 GET /api/live/reconciliation + +用途: +对账与清算距离计算,确保本地持仓与交易所真实持仓一致。 + +请求: + +- Method: `GET` + +响应字段示意: + +```ts +type ReconPositionLocal = { + id: number; + symbol: string; // 如 "BTCUSDT" + direction: "LONG"|"SHORT"; + entry_price: number; +}; + +type ReconPositionExchange = { + symbol: string; + direction: "LONG"|"SHORT"; + amount: number; + mark_price: number; + liquidation_price: number; +}; + +type ReconDiff = { + symbol: string; + severity: "warn" | "critical" | string; + detail: string; +}; + +type ReconciliationStatus = { + status: "ok" | "mismatch" | string; + local_positions: ReconPositionLocal[]; + exchange_positions: ReconPositionExchange[]; + local_orders: number; + exchange_orders: number; + diffs: ReconDiff[]; +}; +``` + +被使用位置: + +- `/live`:`L0_RiskBar`(对账状态)、`L3_Positions`(清算距离)、`L5_Reconciliation`。 + +### 3.3 GET /api/live/account + +用途: +实盘账户整体情况,包括权益、保证金使用、今日 PnL 等。 + +请求: + +- Method: `GET` + +响应字段示意: + +```ts +type LiveAccount = { + equity: number; // 总账户权益 + available_margin: number; // 可用保证金 + used_margin: number; // 已用保证金 + effective_leverage: number; // 有效杠杆 + today_realized_r: number; + today_realized_usdt: number; + // 其它字段按实现扩展 +}; +``` + +被使用位置: + +- `/live`:`L0_RiskBar`、`L2_AccountOverview`。 + +### 3.4 GET /api/live/config + +用途: +读取实盘配置,用于 L1.5 实盘配置面板与 L3 用于计算 1R 对应 USD。 + +请求: + +- Method: `GET` + +响应示例: + +```ts +type LiveConfigField = { + label: string; // 展示名,如 "每笔风险(USDT)" + value: string; // 配置值,统一为字符串表示 +}; + +type LiveConfigResponse = { + risk_per_trade_usd: LiveConfigField; // 每笔风险金额 + initial_capital: LiveConfigField; + risk_pct: LiveConfigField; + max_positions: LiveConfigField; + leverage: LiveConfigField; + enabled_strategies: LiveConfigField; + trade_env: LiveConfigField; +}; +``` + +被使用位置: + +- `/live`:`L15_LiveConfig`、`L3_Positions`(读取 risk_per_trade_usd)。 + +### 3.5 PUT /api/live/config + +用途: +更新实盘配置,来自 L1.5 配置面板的保存操作。 + +请求: + +- Method: `PUT` +- Body(JSON): + +```ts +type LiveConfigUpdate = { + risk_per_trade_usd?: string; + initial_capital?: string; + risk_pct?: string; + max_positions?: string; + leverage?: string; + enabled_strategies?: string; + trade_env?: string; +}; +``` + +响应: + +- 通常返回更新后的配置或简单的 `{ ok: true }`,以实际实现为准。 + +被使用位置: + +- `/live`:`L15_LiveConfig`。 + +### 3.6 实盘控制操作接口 + +用途: +L1 一键止血面板使用的控制接口。 + +#### POST /api/live/emergency-close + +- 功能:立即平掉所有实盘持仓; +- 请求:无 body; +- 响应:包含执行结果信息,例如: + +```ts +{ ok: boolean; message?: string; error?: string } +``` + +#### POST /api/live/block-new + +- 功能:阻止新开仓(维持只减仓模式); +- 请求/响应结构同上。 + +#### POST /api/live/resume + +- 功能:恢复正常交易(允许新开仓); +- 请求/响应结构同上。 + +被使用位置: + +- `/live`:`L1_EmergencyPanel`。 + +### 3.7 其他 /live 页面接口 + +下面补充 `/live` 页面中其他接口的前端契约(只列出前端实际使用到的字段)。 + +#### 3.7.1 GET /api/live/summary?strategy= + +用途: +L2 账户概览中策略层面的汇总统计(总净 R、总净 USDT、费用、资金费率贡献、胜率等)。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + strategy: string; // 策略标识,例如 "v52_8signals" 或某个 strategy_id 映射 +} +``` + +响应示例(仅包含前端用到字段): + +```ts +type LiveSummary = { + total_pnl_r: number; // 总净 R(含历史所有平仓) + total_pnl_usdt: number; // 总净 USDT + total_fee_usdt: number; // 手续费总额(USDT) + total_funding_usdt: number; // 资金费率贡献(USDT,可为正/负) + win_rate: number; // 胜率(百分数) + profit_factor: number; // 盈亏比(Gross 赢 / Gross 亏) + // 可以包含更多分解字段(按 symbol、按方向等),前端当前未使用 +}; +``` + +被使用位置: + +- `/live`:`L2_AccountOverview`。 + +#### 3.7.2 GET /api/live/positions?strategy= + +用途: +L3 当前持仓列表,用于展示实盘持仓、TP/SL、水位、执行质量指标等。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + strategy: string; // 当前关注策略,如 "v52_8signals" +} +``` + +响应示例(前端用到字段): + +```ts +type LivePosition = { + id: number; + symbol: string; // 如 "BTCUSDT" + direction: "LONG"|"SHORT"; + tier: "light"|"standard"|"heavy"; + + entry_price: number; + fill_price?: number | null; + current_price?: number | null; + entry_ts: number; // 入场时间(毫秒) + hold_time_min?: number; // 持仓分钟数(可选,前端如不存在则按 now-entry_ts 计算) + + score: number; // 开仓评分 + risk_distance: number; // 1R 对应的价格距离 + + tp1_price: number; + tp2_price: number; + sl_price: number; + tp1_hit: boolean; + + // 执行质量指标(以 R 或 ms 为单位) + slippage_bps?: number; // 滑点(基点) + protection_gap_ms?: number; // 裸奔时间(毫秒) + signal_to_order_ms?: number; // 信号触发到下单的延迟 + order_to_fill_ms?: number; // 下单到成交的延迟 + binance_order_id?: string | null; +}; + +type LivePositionsResponse = { + data: LivePosition[]; +}; +``` + +被使用位置: + +- `/live`:`L3_Positions`。 + +#### 3.7.3 GET /api/live/execution-quality + +用途: +L4 执行质量面板,总体和按币种统计滑点、延迟、裸奔时间。 + +请求: + +- Method: `GET` +- Query: 无 + +响应示例(简化为前端用到字段): + +```ts +type MetricStats = { + avg: number; + p50: number; + p95: number; +}; + +type ExecutionQualityResponse = { + total_trades: number; + overall: { + slippage_bps: MetricStats; + signal_to_order_ms: MetricStats; + order_to_fill_ms: MetricStats; + protection_gap_ms: MetricStats; + }; + by_symbol?: Record; + error?: string; // 如存在错误则 overall/by_symbol 可能为空 +}; +``` + +被使用位置: + +- `/live`:`L4_ExecutionQuality`。 + +#### 3.7.4 GET /api/live/events?limit=&level= + +用途: +L7 实时事件流,用于查看交易、风险、系统、对账等事件日志。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + limit?: number; // 默认约 30 + level?: "all"|"critical"|"warn"|"info"; +} +``` + +响应示例: + +```ts +type LiveEvent = { + id: number; + ts: number | string; // 事件时间(秒/毫秒时间戳或 ISO 字符串) + level: "info"|"warn"|"error"|"critical"|string; + category?: "trade"|"risk"|"system"|"reconciliation"|string; + symbol?: string | null; // 如 "BTCUSDT" + message: string; +}; + +type LiveEventsResponse = { + data: LiveEvent[]; +}; +``` + +被使用位置: + +- `/live`:`L7_EventStream`。 + +#### 3.7.5 GET /api/live/paper-comparison?limit= + +用途: +L8 实盘 vs 模拟盘对比,比较同一信号在实盘与模拟盘的入场/收益差异。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + limit?: number; // 默认约 20 +} +``` + +响应示例: + +```ts +type PaperComparisonRow = { + symbol: string; // 如 "BTCUSDT" + direction: "LONG"|"SHORT"; + + live_entry?: number | null; // 实盘入场价 + paper_entry?: number | null; // 模拟盘入场价 + entry_diff_bps?: number | null; // 入场价差(基点) + + live_pnl?: number | null; // 实盘 R 或 USDT(前端仅格式化) + paper_pnl?: number | null; // 模拟盘 R 或 USDT + pnl_diff_r?: number | null; // 实盘 vs 模拟盘的 R 差 +}; + +type PaperComparisonResponse = { + avg_pnl_diff_r: number; // 平均 R 差 + data: PaperComparisonRow[]; +}; +``` + +被使用位置: + +- `/live`:`L8_PaperComparison`。 + +#### 3.7.6 GET /api/live/equity-curve?strategy= + +用途: +L9 权益曲线 + 回撤图,按时间展示累计 R 和回撤。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + strategy: string; // 如 "v52_8signals" 或相关策略 ID 映射 +} +``` + +响应示例: + +```ts +type EquityPoint = { + ts: number; // 时间戳(毫秒),前端按北京时间格式化展示 + pnl: number; // 截至当前的累计 R +}; + +type EquityCurveResponse = { + data: EquityPoint[]; +}; +``` + +前端会基于 `pnl` 计算回撤 `dd` 字段,并绘制两条 area 曲线(累计PnL 和 回撤),因此响应中无需直接提供回撤值。 + +被使用位置: + +- `/live`:`L9_EquityCurve`。 + +#### 3.7.7 GET /api/live/trades?symbol=&result=&strategy=&limit= + +用途: +L10 历史交易表,用于按币种和结果(盈/亏)筛选实盘历史交易。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + symbol?: "all"|"BTC"|"ETH"|"XRP"|"SOL"; + result?: "all"|"win"|"loss"; + strategy: string; // 当前策略标识,如 "v52_8signals" + limit?: number; // 默认约 50 +} +``` + +响应示例(前端用到字段): + +```ts +type LiveTrade = { + id: number; + symbol: string; // 如 "BTCUSDT" + direction: "LONG"|"SHORT"; + entry_price: number; + exit_price?: number | null; + entry_ts: number; // 入场时间(毫秒) + exit_ts?: number | null; // 出场时间(毫秒) + + gross_pnl_r?: number; // 毛收益 R + fee_r?: number; // 手续费 R + funding_r?: number; // 资金费率 R + slippage_r?: number; // 滑点 R + net_pnl_r?: number; // 净 R(优先使用) + pnl_r?: number; // 兼容字段,net_pnl_r 不存在时退回使用 + + status: string; // "tp" | "sl" | "sl_be" | 其他(前端映射为 止盈/止损/保本/原样展示) +}; + +type LiveTradesResponse = { + data: LiveTrade[]; +}; +``` + +被使用位置: + +- `/live`:`L10_TradeHistory`。 + +#### 3.7.8 GET /api/live/health + +用途: +L11 系统健康,用于展示运行进程状态和数据新鲜度(行情等)。 + +请求: + +- Method: `GET` + +响应示例(前端使用到字段): + +```ts +type LiveHealthResponse = { + processes?: Record; + data_freshness?: { + market_data?: { + age_sec: number; // 距离最新行情数据的秒数 + status: "green"|"yellow"|"red"|string; // 绿=正常, 黄/红=延迟或异常 + }; + // 其它数据源可在此扩展 + }; +}; +``` + +被使用位置: + +- `/live`:`L11_SystemHealth`。 + +--- + +## 4. /strategy-plaza 策略广场接口 + +### 4.1 GET /api/strategies + +用途: +`/strategy-plaza` 页面策略卡片列表的数据源;账号/其他页面也可使用该接口获取策略列表。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + include_deprecated?: "true" | "false"; // 为 "true" 时包含废弃策略 +} +``` + +响应示例(部分字段): + +```ts +type StrategyItem = { + strategy_id: string; // UUID + display_name: string; // 展示名 + symbol: string; // 交易对,如 "BTCUSDT" + status: "running" | "paused" | "deprecated" | "error"; + started_at: number; // 启动时间(毫秒) + + initial_balance: number; + current_balance: number; + + net_usdt: number; // 累计盈亏(USDT) + net_r: number; // 累计 R + trade_count: number; + win_rate: number; // 胜率(百分数) + avg_win_r: number; + avg_loss_r: number; + + open_positions: number; + pnl_usdt_24h: number; // 24 小时净收益(USDT) + pnl_r_24h: number; // 24 小时净 R + std_r: number; // 收益波动(标准差),如有 + last_trade_at: number | null; // 最近一笔交易时间(毫秒) +}; + +type StrategiesResponse = { + strategies: StrategyItem[]; +}; +``` + +被使用位置: + +- `/strategy-plaza`:展示 running/paused 策略卡片; +- `/strategy-plaza/deprecated`:配合过滤,仅展示 `status="deprecated"`。 + +### 4.2 POST /api/strategies/{sid}/add-balance + +用途: +为指定策略追加模拟资金,调整初始余额与当前余额。 + +请求: + +- Method: `POST` +- Path 参数: + +```ts +{ sid: string } // strategy_id +``` + +- Body(JSON): + +```ts +{ amount: number } // 追加金额(USDT) +``` + +响应: + +- 典型形式:`{ ok: true }` 或返回更新后的策略;具体以实现为准。 + +被使用位置: + +- `/strategy-plaza`:`AddBalanceModal`。 + +### 4.3 POST /api/strategies/{sid}/deprecate + +用途: +将策略状态标记为 `deprecated`,停止其运行但保留所有历史数据。 + +请求: + +- Method: `POST` +- Body(JSON): + +```ts +{ confirm: true } +``` + +响应: + +- 通常为 `{ ok: true }`;错误时返回错误说明。 + +被使用位置: + +- `/strategy-plaza`:卡片右侧垃圾桶按钮,带浏览器 confirm。 + +### 4.4 POST /api/strategies/{sid}/restore + +用途: +恢复废弃策略到运行状态(`status=running`),继续使用原有余额与历史记录。 + +请求: + +- Method: `POST` + +响应: + +- 类似 `{ ok: true }`。 + +被使用位置: + +- `/strategy-plaza/deprecated`:每张废弃策略卡片底部的“重新启用”按钮。 + +> 单策略详情、策略创建/编辑等接口(`/api/strategies/{sid}`、`/api/strategies` POST/PATCH 等)主要被 `/strategy-plaza/[id]` 和 `/strategy-plaza/create` 等页面使用,可在需要时进一步补充契约说明。 + +--- + +## 5. /server 服务器监控接口 + +### 5.1 GET /api/server/status + +用途: +为 `/server` 页面提供服务器与 PM2/数据库状态。 + +请求: + +- Method: `GET` + +响应示意: + +```ts +type ServerStatus = { + timestamp: number; // 采集时间(毫秒) + + cpu: { + percent: number; + cores: number; + }; + memory: { + total_gb: number; + used_gb: number; + percent: number; + swap_percent: number; + }; + disk: { + total_gb: number; + used_gb: number; + free_gb: number; + percent: number; + }; + load: { + load1: number; + load5: number; + load15: number; + }; + uptime_hours: number; + network: { + bytes_sent_gb: number; + bytes_recv_gb: number; + }; + + pm2: Array<{ + name: string; + status: string; // "online" 等 + cpu: number; // CPU 使用率 + memory_mb: number; // 内存 MB + restarts: number; + uptime_ms: number; + pid: number; + }>; + + postgres: { + db_size_mb: number; + agg_trades_count: number; + rate_snapshots_count: number; + symbols: Record; + }; + + backfill_running: boolean; +}; +``` + +被使用位置: + +- `/server`:系统概览卡片、PM2 进程表、PostgreSQL 概览和回补状态。 + +--- + +## 6. 认证与账号接口 + +### 6.1 POST /api/auth/login + +用途: +用户登录,获取 JWT 并在前端存储。 + +请求: + +- Method: `POST` +- Body: + +```ts +{ email: string; password: string } +``` + +响应: + +- 一般形式:`{ access_token: string, token_type: "bearer", user: {...} }`;具体字段以 full-spec 与 auth 模块实现为准。 + +被使用位置: + +- `/login`:通过 `useAuth().login()` 封装调用。 + +### 6.2 POST /api/auth/register + +用途: +邀请码注册新账号。 + +请求: + +- Method: `POST` +- Body: + +```ts +{ email: string; password: string; invite_code: string } +``` + +响应: + +- 一般为新用户信息 + token 或简单成功标记,具体以实现为准。 + +被使用位置: + +- `/register`:通过 `useAuth().register()` 封装调用。 + +### 6.3 GET /api/auth/me + +用途: +获取当前登录用户信息和订阅状态。 + +请求: + +- Method: `GET` +- 需带 Authorization: Bearer token + +响应示意: + +```ts +type AuthMeResponse = { + id: number; + email: string; + discord_id: string | null; + subscription?: { + tier: string; // "free" | "pro" | "premium" ... + expires_at: string | null; // ISO 时间字符串 + }; +}; +``` + +被使用位置: + +- `/dashboard`:"我的账户" 页面。 + +### 6.4 POST /api/user/bind-discord + +用途: +绑定或更新当前用户的 Discord 用户 ID。 + +请求: + +- Method: `POST` +- Body: + +```ts +{ discord_id: string } +``` + +响应: + +- 成功:`{ ok: true, ... }` 或包含简单 message; +- 失败:返回带有 `detail` 字段的错误响应,前端直接展示。 + +被使用位置: + +- `/dashboard`:Discord 信号推送卡片的“绑定”按钮。 + +--- + +## 7. /signals 信号接口 + +> 这些接口为 `/signals`、`/signals-v52`、`/signals-v53` 等信号页面,以及 `/paper` 中的“最新信号”区域提供数据。 + +### 7.1 GET /api/signals/latest?strategy= + +用途: +返回指定策略下各币种(BTC/ETH/XRP/SOL)的最新指标快照与评分结果,用于信号页顶部“实时指标卡片”。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + strategy: string; // 如 "v51_baseline" | "v52_8signals" | "v53" 等 +} +``` + +响应示例(基础字段 + 可选 factors): + +```ts +type SignalTier = "light" | "standard" | "heavy"; + +type LayerInfo = { + score?: number; + max?: number; + // 各版本策略可能包含额外字段,如 cvd_resonance/p99_flow 等 + [key: string]: unknown; +}; + +type LatestIndicatorFactors = { + // 通用层 + direction?: LayerInfo & { cvd_resonance?: number; p99_flow?: number; accel_bonus?: number }; + crowding?: LayerInfo & { lsr_contrarian?: number; top_trader_position?: number }; + environment?: LayerInfo; + confirmation?: LayerInfo; + auxiliary?: LayerInfo & { coinbase_premium?: number }; + + // V5.2 新增层 + funding_rate?: LayerInfo & { value?: number }; + liquidation?: LayerInfo & { long_usd?: number; short_usd?: number }; + + // Gate 控制相关(V5.3/BTC 轨) + gate_passed?: boolean; + block_reason?: string | null; // BTC 轨使用 + gate_block?: string | null; // ALT 轨使用 + + // 市场结构细节 + obi_raw?: number; // 订单簿不平衡 + spot_perp_div?: number; // 期现价差 + whale_cvd_ratio?: number; + atr_pct_price?: number; // ATR/price + alt_score_ref?: number; // 替代参考分 + track?: string; // 当前轨道标记(BTC/ALT) +}; + +type LatestIndicator = { + ts: number; // 时间戳(毫秒) + cvd_fast: number; // 快 CVD(例如 30m) + cvd_mid: number; // 中 CVD(例如 4h) + cvd_day: number; // 日 CVD + cvd_fast_slope: number; + atr_5m: number; + atr_percentile: number; + vwap_30m: number; + price: number; + p95_qty: number; + p99_qty: number; + + score: number; // 综合评分(0~100) + display_score?: number; // V5.3 BTC 轨参考分 + signal: "LONG" | "SHORT" | null; + tier?: SignalTier | null; // "light" | "standard" | "heavy" 或无 + gate_passed?: boolean; // V5.3 顶层 gate 结果 + + factors?: LatestIndicatorFactors | null; +}; + +type LatestSignalsResponse = { + BTC?: LatestIndicator | null; + ETH?: LatestIndicator | null; + XRP?: LatestIndicator | null; + SOL?: LatestIndicator | null; +}; +``` + +被使用位置: + +- `/signals`:V5.1 信号页顶部指标卡片(strategy=v51_baseline); +- `/signals-v52`:V5.2 信号页(strategy=v52_8signals); +- `/signals-v53`:V5.3 信号页(strategy=v53); +- 其它变体页(fast/middle)仅改变 strategy 参数。 + +### 7.2 GET /api/signals/indicators?symbol=&minutes=&strategy= + +用途: +返回某币种在指定时间窗口内的指标时间序列,用于绘制 “CVD 三轨 + 币价” 图表。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + symbol: "BTC" | "ETH" | "XRP" | "SOL"; // 部分页面传 "BTCUSDT" 形式,由后端兼容 + minutes: number; // 统计窗口长度(例如 60/240/720/1440) + strategy?: string; // V5.3 页面会传入 strategy=v53 +} +``` + +响应示例: + +```ts +type IndicatorRow = { + ts: number; // 时间戳(毫秒) + cvd_fast: number; + cvd_mid: number; + cvd_day: number; + atr_5m: number; + vwap_30m: number; + price: number; + score: number; + signal: string | null; // "LONG"|"SHORT"|null +}; + +type IndicatorsResponse = { + data: IndicatorRow[]; +}; +``` + +被使用位置: + +- `/signals`、`/signals-v52`、`/signals-v53`:CVD+价格图。 + +### 7.3 GET /api/signals/market-indicators + +用途: +为信号页中的“Market Indicators”卡片提供多空比、大户持仓、OI、Coinbase Premium 等市场指标。 + +请求: + +- Method: `GET` +- Query: 无 + +响应示例(按 symbol 分组): + +```ts +type MarketIndicatorValue = { + value: unknown | string; // 原始 JSON 对象或 JSON 字符串 + ts: number; // 指标数据时间戳(毫秒) +}; + +type MarketIndicatorSet = { + long_short_ratio?: MarketIndicatorValue; // 多空比 + top_trader_position?: MarketIndicatorValue; // 大户持仓 + open_interest_hist?: MarketIndicatorValue; // OI 历史 + coinbase_premium?: MarketIndicatorValue; // Coinbase Premium +}; + +type MarketIndicatorsResponse = { + BTC?: MarketIndicatorSet; + ETH?: MarketIndicatorSet; + XRP?: MarketIndicatorSet; + SOL?: MarketIndicatorSet; +}; +``` + +前端会对 `value` 做二次解析,将其视为对象并读取: + +- `long_short_ratio.value.longAccount/shortAccount`; +- `top_trader_position.value.longAccount/shortAccount`; +- `open_interest_hist.value.sumOpenInterestValue`; +- `coinbase_premium.value.premium_pct`。 + +被使用位置: + +- `/signals`、`/signals-v52`:`MarketIndicatorsCards` 组件。 + +### 7.4 GET /api/signals/signal-history?symbol=&limit=&strategy= + +用途: +为各版本信号页中的“最近信号”列表,以及 `/paper` 页的“最新信号”区提供数据。 + +请求: + +- Method: `GET` +- Query: + +```ts +{ + symbol: string; // 如 "BTC" | "ETH" 或 "BTCUSDT" + limit?: number; // 条数,默认约 20 + strategy: string; // 如 "v51_baseline" | "v52_8signals" | "v53" +} +``` + +响应示例(简化): + +```ts +type SignalHistoryRow = { + ts: number; // 时间戳(毫秒) + score: number; // 当时评分 + signal: "LONG"|"SHORT"|string; // 方向或其他标记 + factors?: LatestIndicatorFactors; // 可选:各层分数快照,用于链路说明 +}; + +type SignalHistoryResponse = { + data: SignalHistoryRow[]; +}; +``` + +被使用位置: + +- `/signals`:最近信号(strategy=v51_baseline); +- `/signals-v52`、`/signals-v53`:对应策略最近信号; +- `/paper`:`LatestSignals` 组件(从各 symbol 取最新一条,显示 score + 分层 factors 摘要)。 + +--- + +## 8. /paper 模拟盘接口 + +> 这些接口为 `/paper`、`/paper-v52`、`/paper-v53*` 等模拟盘页面,以及部分策略详情页提供数据。 diff --git a/docs/BACKEND_RUNTIME.md b/docs/BACKEND_RUNTIME.md new file mode 100644 index 0000000..5bfdb3d --- /dev/null +++ b/docs/BACKEND_RUNTIME.md @@ -0,0 +1,219 @@ +# Arbitrage Engine 后端运行说明(Backend Runtime Guide) + +> 本文从“后端工程师 / 运维”的角度,描述项目的后端模块、进程拓扑和数据流。 +> 详细字段与业务规则仍以 `docs/arbitrage-engine-full-spec.md` 为准。 + +--- + +## 1. 后端整体结构概览 + +后端主要由三类组件组成: + +1. **FastAPI HTTP API(arb-api)** + - 文件:`backend/main.py` + - 职责: + - 提供 `/api/...` 下的所有 HTTP 接口; + - 负责认证(集成 `auth.py` 的路由); + - 对数据库执行查询和聚合逻辑,为前端和脚本服务; + - 启动少量后台任务(如资金费率快照)。 + +2. **信号与交易引擎进程** + - `backend/signal_engine.py`:信号引擎(策略评估 + 模拟盘开仓); + - `backend/paper_monitor.py`:模拟盘平仓(TP/SL/timeout/signal_flip); + - `backend/live_executor.py`(未来/暂定):连接实盘执行; + - `backend/risk_guard.py`:风控守护进程; + - `backend/position_sync.py`:实盘仓位同步。 + +3. **数据采集与维护进程** + - `backend/agg_trades_collector.py`:从币安 WebSocket 收集逐笔成交写入 `agg_trades`; + - `backend/market_data_collector.py`:收集资金费率、OI、多空比等写入 `market_indicators`; + - `backend/liquidation_collector.py`:收集爆仓数据写入 `liquidations`; + - `backend/backfill_agg_trades.py`:历史数据回补; + - `backend/fix_historical_pnl.py` 等维护脚本:用于修复历史统计。 + +这些组件通过 PostgreSQL 共享状态,FastAPI 作为对外唯一 HTTP 入口,信号/模拟盘引擎作为“后端大脑”,采集进程负责填充数据湖。 + +--- + +## 2. FastAPI API(arb-api) + +入口文件:`backend/main.py` +运行方式:由 PM2 管理的 `arb-api` 进程(端口 4332)。 + +主要职责: + +- 提供 `/api/...` REST 接口: + - 资金费率/历史数据(`/api/rates`、`/api/history`、`/api/kline`、`/api/snapshots`); + - 信号相关接口(`/api/signals/*`); + - 模拟盘接口(`/api/paper/*`); + - 策略管理接口(`/api/strategies*`、`/api/strategy-plaza*`); + - 实盘状态与控制接口(`/api/live/*`); + - 服务器监控接口(`/api/server/status`)。 +- 初始化数据库连接: + - 在 `startup` 事件中调用 `init_schema()` 和 `ensure_auth_tables()`; + - 初始化 asyncpg 连接池(`get_async_pool()`)。 +- 启动后台任务: + - `background_snapshot_loop()`:每 2 秒从币安拉资金费率 + 标记价,写入 `rate_snapshots`。 + +开发/调试提示: + +- 本地如需仅调试 API 层,可以只启动 `main.py`(例如 `uvicorn main:app --reload`),连接已有 Cloud SQL; +- 复杂查询(如 stats / strategy-plaza)建议先查阅 `docs/API_CONTRACTS.md`,再看对应路由实现。 + +--- + +## 3. 信号与交易引擎进程 + +### 3.1 signal_engine.py — 信号引擎 + +文件:`backend/signal_engine.py` +PM2 名称:`signal-engine` + +职责(结合 full-spec): + +- 定时循环(约每 15 秒): + 1. 从 `agg_trades` 滑动窗口计算 CVD、ATR、VWAP 等指标; + 2. 从 `market_indicators` / `liquidations` 读取宏观指标和爆仓数据; + 3. 对每个策略(`strategies` 表中 `status=running` 的记录)调用评估函数(V5.3 之前 `_evaluate_v53`,V5.4 起为 `evaluate_factory_strategy`,内部别名 `evaluate_v53` 的策略工厂流程); + 4. 生成评分与信号(`score`、`signal`),写入 `signal_indicators`; + 5. 在满足开仓条件时写入 `paper_trades`,通过 `paper_open_trade()` 等逻辑开模拟仓。 + +- 与前端/上层接口的关系: + - 前端 `/signals*` 页面只读 `signal_indicators` 和相关汇总接口,不直接调用 signal_engine; + - `/paper`、`/strategy-plaza` 通过 `paper_trades` 和 `strategies`/`signal_indicators` 组合呈现结果。 + +注意: + +- `signal_engine.py` 是最复杂的单体文件之一,对其进行修改前建议: + - 阅读 `arbitrage-engine-full-spec.md` 中“信号引擎”章节; + - 阅读 `docs/AI_HANDBOOK.md` 中关于“禁止/谨慎修改”的约束; + - 在未来可以为其单独增加 `GUIDE_SIGNAL_ENGINE.md` 做更细粒度说明。 + +### 3.2 paper_monitor.py — 模拟盘平仓 + +文件:`backend/paper_monitor.py` +PM2 名称:`paper-monitor` + +职责: + +- 通过币安 WebSocket 获取 mark price; +- 监控所有 `paper_trades` 中 `status="active"` 或 `tp1_hit` 的持仓; +- 根据价格触发规则: + - 触及 TP1 → 将 status 改为 `tp1_hit`,移动 SL 到保本价; + - 触及 TP2 → status=`tp`,计算 `pnl_r`; + - 触及 SL → status=`sl`,`pnl_r=-1`; + - 超时(持仓超过 timeout_minutes)→ status=`timeout`,按价差换算 R; + - 被反向信号强平 → status=`signal_flip`。 + +信号引擎负责开仓逻辑,paper_monitor 负责平仓逻辑,两者通过 `paper_trades` 协作。 + +### 3.3 其他进程(概要) + +- `live_executor.py`: + - 负责将信号转换为实盘订单(当前可能处于试验/非默认启用状态); + - 与 `/api/live/*` 接口、`risk_guard.py` 共同控制实盘行为。 +- `risk_guard.py`: + - 独立风控守护进程,通过检查日内 R、连续亏损、API 状态等判断是否触发熔断; + - 与 `/api/live/risk-status` 和 `/api/live/emergency-close` 等控制接口关联。 +- `position_sync.py`: + - 定期从交易所拉取真实仓位,对齐本地 `live_positions` 记录; + - 为 `/api/live/reconciliation` 提供比对数据。 + +这些进程对模拟盘的影响有限,但对实盘 `/live` 页至关重要。 + +--- + +## 4. 数据采集与维护脚本 + +### 4.1 市场数据采集 + +- `agg_trades_collector.py` + - 订阅币安逐笔成交 WebSocket 流; + - 将成交写入 `agg_trades` 分区表; + - 为 CVD/ATR/VWAP 等指标计算提供原始成交数据。 + +- `market_data_collector.py` + - 定期从币安/其他来源拉取资金费率、未平仓合约(OI)、多空比、订单簿不平衡等; + - 写入 `market_indicators`; + - 供 signal_engine 的环境层/拥挤层/辅助层使用。 + +- `liquidation_collector.py` + - 拉取爆仓(强平)数据; + - 写入 `liquidations`; + - 供 V5.2/V5.3 的 liquidation 层打分使用。 + +### 4.2 历史数据与修复 + +- `backfill_agg_trades.py` + - 用于历史数据回补; + - 配合 `/api/server/status` 中 `backfill_running` 标记监控运行状态。 + +- `fix_historical_pnl.py` 等修复脚本: + - 针对旧数据的 PnL 计算错误或结构变更进行一次性修复; + - 修改前请查阅 full-spec 中对应章节,并记录在决策/变更文档中。 + +--- + +## 5. 后端数据流(服务视角) + +从“服务/进程”的角度看,主数据流可简化为: + +1. **行情写入** + - 币安 WebSocket → `agg_trades_collector.py` → `agg_trades`; + - 市场宏观指标 API → `market_data_collector.py` → `market_indicators`; + - 强平数据 → `liquidation_collector.py` → `liquidations`; + - 资金费率快照 → `main.py` 中后台任务 → `rate_snapshots`。 + +2. **信号生成** + - `signal_engine.py` 周期性读取: + - `agg_trades` 滑动窗口; + - `market_indicators`; + - `liquidations`; + - `strategies` 配置; + - 计算指标与评分 → 写入 `signal_indicators`; + - 满足条件时 → 写入 `paper_trades`(开仓记录)。 + +3. **模拟盘结算** + - `paper_monitor.py` 通过 WebSocket 获取 price; + - 根据 `paper_trades` 的 SL/TP/timeout 规则更新 `paper_trades.status` 和 `pnl_r`。 + +4. **API 展示与控制** + - `main.py` 通过 `/api/paper/*`、`/api/signals/*`、`/api/strategy-plaza*` 等接口对外提供: + - 信号历史和当前状态; + - 模拟盘持仓、交易历史、统计与权益曲线; + - 策略列表与管理操作。 + - `/api/live/*` 接口与 `live_executor.py` / `risk_guard.py` 等进程协作控制实盘。 + +--- + +## 6. 本地开发与调试建议 + +1. **仅调试前端 + API(不跑引擎)** + - 使用现有 Cloud SQL 作为只读数据源: + - 启动 `backend/main.py`(uvicorn 或 PM2); + - 启动前端 `frontend`(Next.js dev 模式); + - 适用于: + - 调整页面布局与数据展示; + - 优化 `/api/...` 查询逻辑; + - 编写/验证新接口契约。 + +2. **调试信号引擎/模拟盘** + - 启动 `signal_engine.py` 和 `paper_monitor.py` 所需的最小进程集合; + - 确保 `agg_trades` / `market_indicators` 至少有一部分历史数据(可以从线上复制或运行简化版 collector); + - 使用日志和 `signal_indicators` / `paper_trades` 查询验证新策略。 + +3. **调试实盘页面 `/live`** + - 在本地/测试环境建议仅连接测试网账户; + - 启动相关进程: + - `live_executor.py`、`risk_guard.py`、`position_sync.py`(视具体实现和需求而定); + - 通过 `/api/live/*` 接口与 `/live` 页面观察状态变化,避免直接在生产环境实验未验证的逻辑。 + +4. **变更规则** + - 修改后端关键逻辑(尤其是 signal_engine、risk_guard)时,建议遵守: + - 先更新/确认 `arbitrage-engine-full-spec.md` 中对应章节; + - 再确保 `API_CONTRACTS.md` 与前端用法一致; + - 最后通过少量回测/模拟盘运行做 smoke test。 + +--- + +本文件的目标是提供一个“后端模块地图”和“进程视角的数据流”,让未来的你(以及 AI 助手)在需要修改/排障时,不必反复从头翻 full-spec 和代码,就能快速知道该看哪几个模块、查哪几张表、重启哪些进程。 diff --git a/docs/DETAILED_CODE_REVIEW.md b/docs/DETAILED_CODE_REVIEW.md deleted file mode 100644 index 7a4413d..0000000 --- a/docs/DETAILED_CODE_REVIEW.md +++ /dev/null @@ -1,379 +0,0 @@ -# 全面代码审阅报告 - -> 生成时间:2026-03-03 -> 审阅范围:全部后端文件(15个)完整阅读 -> 基于 commit `a17c143` 的代码 - ---- - -## 摘要 - -本报告基于对所有后端文件的逐行阅读。发现 **4个致命级别问题**(直接导致实盘无法运行)、**5个高危问题**(全新部署直接报错)、**4个安全漏洞**、**6个架构设计缺陷**。其中若干问题在前一份报告(PROBLEM_REPORT.md)中已提及,但本报告基于完整代码阅读,提供了更精确的定位和更全面的覆盖。 - ---- - -## 🔴 致命问题(实盘链路完全断裂) - -### [F1] live_executor 永远读不到信号 - -**定位**:`live_executor.py:fetch_pending_signals()` + `signal_engine.py:save_indicator()` - -**证据链**: -1. `signal_engine.py:690-706`:`save_indicator()` 用 `get_sync_conn()`(即 db.py 的 `PG_HOST=127.0.0.1`)将信号写入**本地 PG 的** `signal_indicators` 表 -2. `live_executor.py:50-55`(已知):`DB_HOST` 默认 `10.106.0.3`(Cloud SQL) -3. `signal_engine.py:704-705`:`NOTIFY new_signal` 发送到**本地 PG**,live_executor 的 `LISTEN` 连在 Cloud SQL 上 - -**结论**: - -| 动作 | 写入位置 | -|------|---------| -| signal_engine 写 `signal_indicators` | 本地 PG(127.0.0.1)| -| live_executor 的 LISTEN 监听 | Cloud SQL(10.106.0.3)| -| live_executor 的轮询查 `signal_indicators` | Cloud SQL(10.106.0.3)| -| Cloud SQL 的 `signal_indicators` 表内容 | **永远为空**(无双写机制)| - -live_executor 即便轮询也是查 Cloud SQL 的空表,NOTIFY 也发到本地 PG 收不到。**只要实盘进程跑在不同数据库实例上,永远不会执行任何交易。** - ---- - -### [F2] risk_guard 的数据新鲜度检查永远触发熔断 - -**定位**:`risk_guard.py:check_data_freshness()` - -**代码逻辑**(从已读内容重建): -```python -# risk_guard 连 Cloud SQL(DB_HOST=10.106.0.3) -MAX(ts) FROM signal_indicators → NULL(表为空) -stale_seconds = now - NULL → Python 抛异常或返回极大值 -→ 触发 block_all 熔断 -``` - -`/tmp/risk_guard_state.json` 中 `block_all=true`,live_executor 执行前读此文件(Fail-Closed),**所有交易被直接拒绝**。 - -**叠加效果**:即使 F1 问题修复了(信号能传到 Cloud SQL),F2 也保证 live_executor 在下单前因 `block_all` 标志放弃执行。 - ---- - -### [F3] risk_guard 与 live_executor 必须同机运行,但无任何保障 - -**定位**:`risk_guard.py`(写 `/tmp/risk_guard_state.json`)、`live_executor.py`(读同一路径) - -**问题**:两个进程通过本地文件系统文件交换状态。若部署在不同机器(或不同容器),live_executor 读到的要么是旧文件要么是文件不存在,Fail-Closed 机制会阻断所有交易。目前无任何文档说明"两进程必须共机",无任何启动脚本检查,无任何报警。 - ---- - -### [F4] signal_pusher.py 仍使用 SQLite,与 V5 PG 系统完全脱节 - -**定位**:`signal_pusher.py:1-20` - -```python -import sqlite3 -DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "arb.db") -SYMBOLS = ["BTCUSDT", "ETHUSDT"] # ← XRP/SOL 不在监控范围 -``` - -**完整问题列表**: -1. 读 `arb.db`(SQLite),V5 信号全在 PG 的 `signal_indicators` 表,此脚本从不读取 -2. 只覆盖 BTC/ETH,XRP/SOL 的信号永远不会被推送 -3. **Discord Bot Token 硬编码**(详见 [S1]) -4. 是一个一次性运行脚本,不是守护进程,PM2 管理无意义 -5. 查询的 SQLite 表 `signal_logs` 在 V5 体系下已废弃 - -**结论**:signal_pusher.py 是遗留代码,从未迁移到 V5 PG 架构。如果 PM2 中运行的是此文件,通知系统完全失效。 - ---- - -## 🔴 高危问题(全新部署直接崩溃) - -### [H1] `signal_indicators` 表缺少 `strategy` 和 `factors` 列 - -**定位**:`db.py:205-224`(建表 SQL)vs `signal_engine.py:695-701`(INSERT 语句) - -**SCHEMA_SQL 中的列**: -`id, ts, symbol, cvd_fast, cvd_mid, cvd_day, cvd_fast_slope, atr_5m, atr_percentile, vwap_30m, price, p95_qty, p99_qty, buy_vol_1m, sell_vol_1m, score, signal` - -**save_indicator() 实际 INSERT 的列**: -`ts, symbol, **strategy**, cvd_fast, cvd_mid, cvd_day, cvd_fast_slope, atr_5m, atr_percentile, vwap_30m, price, p95_qty, p99_qty, score, signal, **factors**` - -多了 `strategy TEXT` 和 `factors JSONB` 两列。`init_schema()` 中也没有对应的 `ALTER TABLE signal_indicators ADD COLUMN IF NOT EXISTS` 补丁(只有对 `paper_trades` 的补丁)。 - -**后果**:全新环境 `init_schema()` 后,signal_engine 每次写入都报 `column "strategy" of relation "signal_indicators" does not exist`,主循环崩溃。 - -**补充**:`main.py:/api/signals/latest` 的查询也包含 `strategy` 和 `factors` 字段,全新部署 API 也会报错。 - ---- - -### [H2] `paper_trades` 表缺少 `risk_distance` 列 - -**定位**:`db.py:286-305`(建表 SQL)vs `signal_engine.py:762-781`(INSERT 语句) - -**SCHEMA_SQL 中的列**(无 `risk_distance`): -`id, symbol, direction, score, tier, entry_price, entry_ts, exit_price, exit_ts, tp1_price, tp2_price, sl_price, tp1_hit, status, pnl_r, atr_at_entry, score_factors, created_at` - -`init_schema()` 用 `ALTER TABLE paper_trades ADD COLUMN IF NOT EXISTS strategy` 补了 `strategy` 列,但**没有补 `risk_distance`**。 - -`paper_open_trade()` 的 INSERT 包含 `risk_distance`,`paper_monitor.py:59` 和 `signal_engine.py:800` 也从 DB 读取 `risk_distance`。 - -**后果**:全新部署后,第一次模拟开仓就报 `column "risk_distance" does not exist`。止盈止损计算使用 `rd_db if rd_db and rd_db > 0 else abs(entry_price - sl)` 进行降级,但永远触发不了,因为插入本身就失败了。 - ---- - -### [H3] `users` 表双定义,`banned` 和 `discord_id` 字段在新环境缺失 - -**定位**:`db.py:269-276` vs `auth.py:28-37` - -| 字段 | db.py SCHEMA_SQL | auth.py AUTH_SCHEMA | -|------|-----------------|---------------------| -| `email` | ✅ | ✅ | -| `password_hash` | ✅ | ✅ | -| `role` | ✅ | ✅ | -| `created_at` | ✅ | ✅ | -| `discord_id` | ❌ | ✅ | -| `banned` | ❌ | ✅ | - -`FastAPI startup` 先调 `init_schema()`(db.py 版建表),再调 `ensure_auth_tables()`(auth.py 版),`CREATE TABLE IF NOT EXISTS` 第二次静默跳过。实际建的是旧版本,缺少 `discord_id` 和 `banned`。 - -**后果**:封禁用户功能在新部署上完全失效(`banned` 字段不存在)。 - ---- - -### [H4] `/api/kline` 只支持 BTC/ETH,XRP/SOL 静默返回错误数据 - -**定位**:`main.py:151-152` - -```python -rate_col = "btc_rate" if symbol.upper() == "BTC" else "eth_rate" -price_col = "btc_price" if symbol.upper() == "BTC" else "eth_price" -``` - -XRP 和 SOL 请求均被路由到 ETH 的数据列。返回的是 ETH 的费率 K 线,但 symbol 标记为 XRP/SOL。前端图表展示完全错误。根本原因:`rate_snapshots` 表只有 `btc_rate` 和 `eth_rate` 两列,不支持 4 个币种的独立存储。 - ---- - -### [H5] `subscriptions.py` 是孤立 SQLite 路由,定义了重名的 `/api/signals/history` - -**定位**:`subscriptions.py:1-23` - -```python -import sqlite3 -DB_PATH = "arb.db" # SQLite - -@router.get("/api/signals/history") # ← 与 main.py 同名 -def signals_history(): ... -``` - -**三个问题**: -1. 路由路径与 `main.py:221` 的 `@app.get("/api/signals/history")` 完全相同 -2. 查询 SQLite `arb.db`,V5 体系已无此数据 -3. `main.py` **从未** `include_router(subscriptions.router)`,所以目前是死代码 - -若将来有人误把 `subscriptions.router` 加进来,会与现有 PG 版本的同名路由冲突,FastAPI 会静默使用先注册的那个,导致难以排查的 bug。 - ---- - -## 🟠 安全漏洞 - -### [S1] Discord Bot Token 硬编码在源代码(高危) - -**定位**:`signal_pusher.py:~25` - -```python -DISCORD_TOKEN = os.getenv("DISCORD_BOT_TOKEN", "MTQ3Mjk4NzY1NjczNTU1OTg0Mg.GgeYh5.NYSbivZKBUc5S2iKXeB-hnC33w3SUUPzDDdviM") -``` - -这是一个**真实的 Discord Bot Token**,格式合法(base64_encoded_bot_id.timestamp.signature)。任何有代码库读权限的人都可以用此 Token 以 bot 身份发消息、读频道历史、修改频道。 - -**立即行动**:在 Discord 开发者后台吊销此 Token 并重新生成,从代码中删除默认值。 - ---- - -### [S2] 数据库密码硬编码(三处) - -**定位**: -- `db.py:19`:`os.getenv("PG_PASS", "arb_engine_2026")` -- `live_executor.py:44`:`os.getenv("DB_PASSWORD", "arb_engine_2026")` -- `risk_guard.py:42`:`os.getenv("DB_PASSWORD", "arb_engine_2026")` - -三处使用同一个默认密码。代码一旦泄露,测试网数据库直接暴露。此外 `db.py:28` 还有 Cloud SQL 的默认密码:`os.getenv("CLOUD_PG_PASS", "arb_engine_2026")`。 - ---- - -### [S3] JWT Secret 有已知测试网默认值 - -**定位**:`auth.py`(推断行号约 15-20) - -```python -_jwt_default = "arb-engine-jwt-secret-v2-2026" if _TRADE_ENV == "testnet" else None -``` - -若 `TRADE_ENV` 环境变量未设置(默认 `testnet`),JWT secret 使用此已知字符串。所有 JWT token 均可被任何知道此 secret 的人伪造,绕过身份验证。 - ---- - -### [S4] CORS 配置暴露两个本地端口 - -**定位**:`main.py:16-20` - -```python -allow_origins=["https://arb.zhouyangclaw.com", "http://localhost:3000", "http://localhost:3001"] -``` - -生产环境保留了 `localhost:3000` 和 `localhost:3001`。攻击者如果能在本地运行浏览器页面(e.g. XSS 注入到其他本地网站),可以绕过 CORS 跨域限制向 API 发请求。生产环境应移除 localhost origins。 - ---- - -## 🟡 架构缺陷 - -### [A1] 策略 JSON 不支持热重载(与文档声称相反) - -**定位**:`signal_engine.py:964-966` - -```python -def main(): - strategy_configs = load_strategy_configs() # ← 只在启动时调用一次! - ... - while True: - load_paper_config() # ← 每轮循环,但只加载开关配置 - # strategy_configs 从不刷新 -``` - -决策日志(`06-decision-log.md`)声称策略 JSON 支持热修改无需重启,实际上 `strategy_configs` 变量只在 `main()` 开头赋值一次,主循环从不重新调用 `load_strategy_configs()`。 - -**修改 v51_baseline.json 或 v52_8signals.json 后必须重启 signal_engine。** - -注:每 60 轮循环确实会 `load_paper_config()` 热加载"哪些策略启用"的开关,但权重/阈值/TP/SL 倍数不会热更新。 - ---- - -### [A2] 三套数据库连接配置,极易迁移时漏改 - -| 进程 | 读取的环境变量 | 默认连接 | -|------|-------------|---------| -| `main.py`, `signal_engine.py`, `market_data_collector.py`, `agg_trades_collector.py`, `liquidation_collector.py`, `paper_monitor.py` | `PG_HOST`(db.py) | 127.0.0.1 | -| `live_executor.py`, `risk_guard.py`, `position_sync.py` | `DB_HOST` | 10.106.0.3 | -| `market_data_collector.py` 内部 | `PG_HOST` | 127.0.0.1 | - -六个进程用 `PG_HOST`,三个进程用 `DB_HOST`,变量名不同,默认值不同,修改时需要同时更新两套 `.env`。 - ---- - -### [A3] market_indicators 和 liquidations 表不在主 schema 中 - -**定位**:`market_data_collector.py:ensure_table()`、`liquidation_collector.py:ensure_table()` - -两张表由各自 collector 进程单独创建,不在 `db.py:SCHEMA_SQL` 里。启动顺序问题: -- 若 `signal_engine` 比 `market_data_collector` 先启动,查 `market_indicators` 报表不存在,所有市场指标评分降级为中间值 -- 若 `signal_engine` 比 `liquidation_collector` 先启动,查 `liquidations` 报错,清算层评分归零 - -**补充发现**:`liquidation_collector.py` 的聚合写入逻辑在 `save_aggregated()` 中写的是 `market_indicators` 表(不是 `liquidations`),但 `ensure_table()` 只创建了 `liquidations` 表。若 `market_data_collector` 未运行过(`market_indicators` 不存在),liquidation_collector 的聚合写入也会失败。 - ---- - -### [A4] paper_monitor 和 signal_engine 的止盈止损逻辑完全重复 - -**定位**:`signal_engine.py:788-878`(`paper_check_positions()`)、`paper_monitor.py:44-143`(`check_and_close()`) - -两个函数逻辑几乎一模一样(均检查 TP1/TP2/SL/超时)。当前 signal_engine 主循环中注释说"持仓检查由 paper_monitor.py 实时处理",所以 `paper_check_positions()` 是**死函数**(定义了但从不调用)。 - -**风险**:未来如果有人修改止盈止损逻辑,只改了 paper_monitor.py 或只改了 signal_engine.py,两份代码就会产生不一致。 - ---- - -### [A5] rate_snapshots 表只存 BTC/ETH,XRP/SOL 数据永久丢失 - -**定位**:`db.py:167-177`(建表)、`main.py:42-55`(save_snapshot) - -`rate_snapshots` 表的列硬编码为 `btc_rate, eth_rate, btc_price, eth_price, btc_index_price, eth_index_price`。XRP/SOL 的资金费率数据只从 Binance 实时拉取,不存储,无法做历史分析或 K 线展示。 - ---- - -### [A6] `/api/signals/history` 返回的是废弃表的数据 - -**定位**:`main.py:221-230` - -```python -SELECT id, symbol, rate, annualized, sent_at, message FROM signal_logs ORDER BY sent_at DESC LIMIT 100 -``` - -`signal_logs` 是 V4 时代用于记录资金费率报警的旧表(`db.py:259-267`),在 V5 体系下不再写入任何数据。这个端点对前端返回的是永久为空的结果,但没有任何错误信息,调用方无从判断是数据为空还是系统正常运行。 - ---- - -## 🟢 值得记录的正确设计 - -以下是审阅过程中发现的值得肯定的设计,供参考: - -1. **`position_sync.py` 设计完整**:SL 丢失自动重挂、TP1 命中后 SL 移至保本、实际成交价查询、资金费用追踪(每8小时结算窗口),覆盖了实盘交易的主要边界情况。 - -2. **risk_guard Fail-Closed 模式正确**:`/tmp/risk_guard_state.json` 不存在时,live_executor 默认拒绝交易,而不是放行,安全方向正确。 - -3. **paper_monitor.py 使用 WebSocket 实时价格**:比 signal_engine 15 秒轮询更适合触发止盈止损,不会因为 15 秒间隔错过快速穿越的价格。 - -4. **agg_trades_collector.py 的数据完整性保障**:每 60 秒做连续性检查,断点处触发 REST 补录,每小时做完整性报告,设计周全。 - -5. **GCP Secret Manager 集成**:live_executor/risk_guard/position_sync 优先从 GCP Secret Manager 加载 API 密钥(`projects/gen-lang-client-0835616737/secrets/BINANCE_*`),生产环境密钥不在代码/环境变量中,安全设计得当。 - ---- - -## 📋 修复优先级清单 - -### 立即(防止实盘上线后资金损失) - -| 编号 | 问题 | 修复方向 | -|------|------|---------| -| **S1** | Discord Bot Token 泄露 | 立即在 Discord 开发者后台吊销并重新生成,代码中删除默认值 | -| **F1** | signal_engine 写本地 PG,live_executor 读 Cloud SQL,信号永远不传递 | 统一所有进程连接同一 PG 实例,或为 `signal_indicators` 表添加双写逻辑 | -| **F2** | risk_guard 查 Cloud SQL 空表永远触发熔断 | 与 F1 一起解决(统一 DB 连接) | -| **F3** | risk_guard/live_executor 必须共机无文档说明 | 在 PM2 配置和部署文档中明确说明;或改为 DB-based 状态通信 | -| **F4** | signal_pusher 是废弃 SQLite 脚本 | 从 PM2 配置中移除;按需重写成 PG 版本 | - -### 本周(防止全新部署报错) - -| 编号 | 问题 | 修复方向 | -|------|------|---------| -| **H1** | `signal_indicators` 缺 `strategy`、`factors` 列 | 在 `SCHEMA_SQL` 中补列;在 `init_schema()` 中加 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` | -| **H2** | `paper_trades` 缺 `risk_distance` 列 | 同上,在 `init_schema()` 中补 ALTER | -| **H3** | `users` 表双定义,`banned`/`discord_id` 缺失 | 从 `SCHEMA_SQL` 删除 `users` 建表语句,统一由 `auth.py` 负责;加 ALTER 迁移旧环境 | -| **H4** | `/api/kline` XRP/SOL 返回 ETH 数据 | 要么限制 kline 只支持 BTC/ETH 并在 API 文档中注明;要么扩展 `rate_snapshots` 表结构 | -| **H5** | `subscriptions.py` 孤立 SQLite 代码 | 删除或移至 `archive/` 目录,防止将来误用 | - -### 本月(安全加固) - -| 编号 | 问题 | 修复方向 | -|------|------|---------| -| **S2** | 数据库密码硬编码 | 移入 `.env` 文件,不进代码仓库;生产环境用 GCP Secret Manager | -| **S3** | JWT Secret 默认值可预测 | 生产部署强制要求 `JWT_SECRET` 环境变量,`_TRADE_ENV=production` 时 None 应直接启动失败 | -| **S4** | CORS 包含 localhost | 生产环境移除 localhost origins | - -### 长期(架构改善) - -| 编号 | 问题 | 修复方向 | -|------|------|---------| -| **A1** | 策略 JSON 不支持热重载 | 在主循环中定期(如每 60 轮)重新调用 `load_strategy_configs()` | -| **A2** | 三套 DB 连接配置 | 统一用同一套环境变量(建议统一用 `PG_HOST`),所有进程都从 `db.py` 导入连接 | -| **A3** | market_indicators/liquidations 不在主 schema | 将两表定义移入 `SCHEMA_SQL` 或 `init_schema()` | -| **A4** | paper_check_positions 死代码 | 删除 signal_engine.py 中的 `paper_check_positions()` 函数(功能由 paper_monitor 承担) | -| **A6** | `/api/signals/history` 返回废弃表数据 | 重定向到查 `signal_indicators` 表,或废弃此端点 | - ---- - -## 附录:文件审阅覆盖情况 - -| 文件 | 行数 | 本次审阅 | -|------|-----|---------| -| `main.py` | ~500 | ✅ 全文 | -| `db.py` | ~415 | ✅ 全文 | -| `signal_engine.py` | ~1085 | ✅ 全文 | -| `live_executor.py` | ~708 | ✅ 全文 | -| `risk_guard.py` | ~644 | ✅ 全文 | -| `auth.py` | ~389 | ✅ 全文 | -| `position_sync.py` | ~687 | ✅ 全文 | -| `paper_monitor.py` | ~194 | ✅ 全文 | -| `agg_trades_collector.py` | ~400 | ✅ 全文 | -| `market_data_collector.py` | ~300 | ✅ 全文 | -| `liquidation_collector.py` | ~141 | ✅ 全文 | -| `signal_pusher.py` | ~100 | ✅ 全文 | -| `subscriptions.py` | ~24 | ✅ 全文 | -| `trade_config.py` | ~15 | ✅ 全文 | -| `backtest.py` | ~300 | 前100行 + 签名扫描 | -| `admin_cli.py` | ~100 | 签名扫描 | diff --git a/docs/FRONTEND_PAGES.md b/docs/FRONTEND_PAGES.md new file mode 100644 index 0000000..8dd130e --- /dev/null +++ b/docs/FRONTEND_PAGES.md @@ -0,0 +1,1249 @@ +# Arbitrage Engine 前端页面说明 + +> 本文从“用户看到的界面”出发,按页面/导航顺序描述整个前端。 +> 代码和数据层的详细结构,请参考 `docs/arbitrage-engine-full-spec.md`。 + +--- + +## 0. 全局框架与导航 + +### 0.1 布局 + +前端采用 Next.js App Router,根布局在 `frontend/app/layout.tsx` 中定义: + +- 左侧:`` + - 固定在左侧,白底,带边框,可折叠(桌面端)。 + - 包含项目 Logo 和主导航按钮。 +- 右侧:顶部为 ``,下面为当前页面内容: + - `AuthHeader` 只在桌面端显示,用于展示登录状态(邮箱/Admin 徽章/退出按钮)。 + - 实际页面内容通过 `children` 渲染。 + +移动端(`md` 以下)顶栏和抽屉菜单也由 `Sidebar` 组件负责: + +- 顶部条:左侧为 Logo,右侧为“登录/注册”或“退出”按钮 + 菜单汉堡按钮。 +- 点击菜单按钮后,从左侧滑出抽屉式侧边栏,内容与桌面端导航一致。 + +### 0.2 顶部登录区(AuthHeader) + +文件:`frontend/components/AuthHeader.tsx` +仅在桌面端显示(`hidden md:flex`)。 + +- 未登录: + - 右上角显示: + - `登录` 按钮:灰边,hover 变为蓝边。 + - `注册` 按钮:蓝底白字主按钮。 + - 点击分别跳转到 `/login` 与 `/register`。 + +- 已登录: + - 显示当前用户邮箱(`user.email`)。 + - 如果 `user.role === "admin"`,在邮箱后显示一个黄色的 `Admin` 徽章。 + - 右侧是 `退出` 按钮,点击调用 `logout()` 退出登录。 + +> 手机端的登录/注册/退出按钮由 `Sidebar` 内部负责,不走 `AuthHeader`。 + +### 0.3 侧边栏导航(Sidebar) + +文件:`frontend/components/Sidebar.tsx` + +- 顶部 Logo: + - 闪电图标(`Zap`)+ 文本 `Arbitrage Engine`(两行排版)。 +- 导航项(`navItems`)定义如下: + +```ts +const navItems = [ + { href: "/", label: "仪表盘", icon: LayoutDashboard }, + { href: "/trades", label: "成交流", icon: Activity }, + { href: "/live", label: "⚡ 实盘交易", icon: Bolt, section: "── 实盘 ──" }, + { href: "/strategy-plaza", label: "策略广场", icon: Zap, section: "── 策略 ──" }, + { href: "/strategy-plaza/deprecated", label: "废弃策略", icon: Archive }, + { href: "/server", label: "服务器", icon: Monitor }, + { href: "/about", label: "说明", icon: Info }, +]; +``` + +从用户视角,这些入口对应的主要功能是: + +- `/` → **仪表盘**:资金费率实时监控与套利统计的首页。 +- `/trades` → **成交流**:逐笔成交明细与交易流量分析。 +- `/live` → **⚡ 实盘交易**:实盘风控与一键止血面板(L0/L1/L1.5 等区域)。 +- `/strategy-plaza` → **策略广场**:V5.4 策略工厂的主入口,展示所有策略表现。 +- `/strategy-plaza/deprecated` → **废弃策略**:已停用策略的列表视图。 +- `/server` → **服务器**:服务器与 PM2 进程监控。 +- `/about` → **说明**:策略原理、历史年化与风险说明。 + +> 账号相关页面 `/login`、`/register`、`/dashboard` 不在侧边栏中出现,通过右上角或跳转访问。 + +--- + +## 1. 首页 `/`(仪表盘) + +文件:`frontend/app/page.tsx` +导出的组件名:`Dashboard` + +### 1.1 页面目标 + +从用户视角,首页是一个**资金费率套利的总览仪表盘**,具备两个层次: + +1. **未登录用户**:能看到 BTC/ETH 的实时资金费率、运行状态等,了解当前市场情况。 +2. **已登录用户**:解锁完整的统计信息: + - 套利策略的历史统计(7天均值、年化、50/50组合等); + - 资金费率与价格的 K 线图; + - 历史费率明细表格; + - 已触发的资金费率“信号历史”; + - 策略原理说明。 + +首页不直接操作策略或模拟盘,更像是一个“观测面板 + 教程”,帮助用户理解当前资金费率环境和策略收益。 + +### 1.2 未登录状态下的展示 + +未登录逻辑由组件 `AuthGate` 控制: + +- 上半部分(不受登录状态影响): + 1. **标题行** + - 左侧: + - 主标题:`资金费率套利监控` + - 副标题(小字):`实时监控 BTC / ETH 永续合约资金费率` + - 右侧: + - 圆点状态灯:`运行中`(绿色) / `连接失败`(红色) / `加载中...`(灰色),根据 `/api/rates` 请求结果变化。 + - “更新于 HH:MM:SS” 时间戳,表示最近一次成功更新资金费率的本地时间。 + + 2. **资金费率卡片(RateCard)** + - 左卡:BTC,右卡:ETH。 + - 每张卡展示: + - 当前资金费率; + - 标记价格、指数价格; + - 相关统计(具体字段由 `RateCard` 渲染)。 + +- 下半部分(被 `AuthGate` 包裹): + - 未登录时,`AuthGate` 渲染一个**模糊遮罩**效果: + - 内部真实内容会被 blur + 降低不透明度,作为背景。 + - 上方覆盖一层白色半透明遮罩。 + - 中间显示: + - 图标:`🔒` + - 文案:`登录后查看完整数据` + - 按钮: + - `登录`(蓝底按钮) → 跳转 `/login` + - `注册`(灰边按钮) → 跳转 `/register` + +**结论**:未登录用户只能看到**实时费率卡片**和运行状态,不能查看历史统计、K 线和详细信号数据。 + +### 1.3 登录后的完整内容 + +当用户已登录且 `AuthGate` 放行时,首页显示以下完整模块: + +1. **套利统计卡片(StatsCard,三张)** + - 在 `stats` 数据存在时渲染: + - `BTC 套利`:`mean7d` + `annualized` + - `ETH 套利` + - `50/50 组合`:BTC/ETH 各 50% 的组合年化表现 + - 这些数据由 `/api/stats` 提供,用于给出近 7 天和年化收益的粗略估计。 + +2. **K 线大卡片(资金费率 & 标记价格)** + - 卡片标题:`K 线图` + - 币种切换按钮:`BTC` / `ETH`(控制 `symbol` 状态)。 + - 内含两个子图: + 1. **资金费率 K 线** + - 标题:`{symbol} 资金费率(万分之)` + - 顶部右侧 interval 选择器(`INTERVALS`),可选: + - `1m`, `5m`, `30m`, `1h`, `4h`, `8h`, `日(1d)`, `周(1w)`, `月(1M)` + - 使用 `MiniKChart` + `mode="rate"`,展示资金费率蜡烛图。 + 2. **标记价格 K 线** + - 标题:`{symbol} 标记价格(USD)` + - 使用同样的 interval 选择器,但 `mode="price"`,展示价格蜡烛图。 + - 下层数据来源:`/api/kline?symbol=BTC&interval=...`(通过 `api.kline` 封装)。 + +3. **历史费率走势(7天双线图)** + - 标题:`历史费率走势(过去7天)` + - 使用 Recharts `LineChart` 绘制: + - X 轴:时间(按小时聚合,如 `MM-DD HH`) + - Y 轴:资金费率(百分比,格式化显示如 `0.1234%`) + - 两条线: + - `BTC`:蓝色; + - `ETH`:紫色; + - 中间有一条 `y=0` 的虚线参考线。 + - 数据来自 `api.history()` 返回的 `HistoryResponse` 中的 `BTC` / `ETH` 历史 fundingRate 序列。 + +4. **历史费率明细表(左侧表格)** + - 标题:`历史费率明细(最近30条)` + - 表头:时间 / BTC / ETH + - 每行: + - 时间:`yyyy-MM-dd HH:mm`(字符串处理自 `timestamp`) + - BTC/ETH 列: + - 正值:绿色; + - 负值:红色; + - 无数据:灰色、显示 `--`。 + - 数据来源同上(`HistoryResponse`),只是格式化和截取最近 30 条。 + +5. **信号历史表(右侧表格)** + - 标题:`信号历史` + - 表头:时间 / 币种 / 年化 + - 内容: + - 当 `signals.length === 0`:显示提示行 `暂无信号(年化>10%时自动触发)`。 + - 否则每行显示: + - 触发时间 `sent_at`(本地时间格式); + - 币种 `symbol`; + - 年化收益率 `annualized`(加 `%`)。 + - 数据来源:`api.signalsHistory()`(封装了 `/api/signals/signal-history` 或类似接口)。 + +6. **策略原理说明卡片(蓝色 Info 区域)** + - 蓝色边框+淡蓝底的 info 卡片,标题 `策略原理:` + - 内容简述: + - 使用“现货多头 + 永续空头”构建无方向风险的套利组合; + - 每 8 小时收取资金费率作为收益; + - 核心是赚“费率”,不是赌方向。 + +整体上,登录后的首页是一个聚合视图,把“当前状态 + 历史表现 + 策略简介”集中在一个页面上。 + +### 1.4 首页依赖的主要 API + +首页通过 `api` 封装(`frontend/lib/api`)访问后端,核心依赖至少包括: + +- `GET /api/rates` → `api.rates()` + - 读取最新资金费率快照(BTC/ETH 的 markPrice/indexPrice/lastFundingRate 等)。 +- `GET /api/stats` → `api.stats()` + - 返回 BTC/ETH 以及 50/50 组合的近 7 天均值与年化统计。 +- `GET /api/history` → `api.history()` + - 返回过去若干天(如 7 天)的按小时聚合资金费率序列。 +- `GET /api/signals/history` 或类似 → `api.signalsHistory()` + - 返回最近触发的资金费率套利信号(满足一定年化阈值)。 +- `GET /api/stats/ytd` → `api.statsYtd()` + - 年初至今(YTD)的收益统计,用于 `RateCard` 等组件。 +- `GET /api/kline?symbol=&interval=` → `api.kline(symbol, interval)` + - 提供资金费率和价格 K 线数据,用于首页的 K 线卡片。 + +这些接口的详细字段与 SQL 逻辑请以 `docs/arbitrage-engine-full-spec.md` 和 `backend/main.py` 中定义为准。 + +--- + +## 2. /trades(成交流) + +文件:`frontend/app/trades/page.tsx` +导出的组件名:`TradesPage` + +### 2.1 页面目标 + +`/trades` 页面从“逐笔成交”的角度,帮助用户观察: + +- 当前市场是多方主动吃单更多,还是空方主动砸盘更多; +- 在过去一段时间内,不同时间粒度下买卖量的累积情况; +- 成交换手是否集中(成交量、最大单笔成交量等)。 + +它是一个 **成交流量分析工具页**,为信号研究和盘前/盘后复盘服务,不直接开仓/平仓。 + +### 2.2 登录要求与未登录展示 + +页面开头使用 `useAuth()` 判断登录状态: + +- `loading === true`:显示“加载中...”占位。 +- `!isLoggedIn`:渲染一个居中的锁屏提示: + - 图标:`🔒` + - 文案:`请先登录查看成交流数据` + - 按钮: + - `登录`(蓝底) → `/login` + - `注册`(灰边) → `/register` + +只有登录用户可以看到实时成交流和图表。 + +### 2.3 页面主要区域布局 + +登录后,`TradesPage` 页面结构如下: + +1. **标题 + 币种切换** + - 标题:`成交流分析` + - 副标题:`实时成交记录 + 买卖 Delta 分析` + - 右侧有一个币种选择按钮组: + - 支持四种:`BTC` / `ETH` / `XRP` / `SOL` + - 通过内部状态 `symbol` 切换,影响下方所有数据请求。 + +2. **实时成交 + 分析图 并排** + - 布局:`lg` 以上为两列,左侧实时成交,右侧流量分析;小屏则上下排列。 + - 左侧组件:`` + - 右侧组件:`` + +3. **读图说明 Info 卡片** + - 底部一块蓝色边框的说明区域: + - “读图方法”:解释 Delta 为正/负的含义(多方/空方占优)。 + - “复盘思路”:建议用户结合 K 线大涨大跌时间点,回看前 15–30 分钟的 Delta 走势寻找前兆。 + +### 2.4 左侧:实时成交流列表(LiveTrades) + +组件:`LiveTrades` + +功能:展示选中币种的最新 20 条逐笔成交记录,实时滚动更新。 + +行为与布局: + +- 加载逻辑: + - 使用 `authFetch("/api/trades/latest?symbol=...&limit=20")` 每 2 秒轮询一次。 + - 首次加载时 `loading=true`,显示“加载中...”; + - 后续每次拉取: + - 根据返回的 `data.data` 更新本地 `trades` 列表; + - 新来的成交在顶部(最新在前),最多保留 20 条; + - 用 `agg_id` 去重,避免重复插入。 + +- 卡片顶部: + - 标题:`实时成交 · {symbol}/USDT` + - 右上角状态: + - 一个绿色闪烁的小圆点; + - 文案:`实时`。 + - 下方说明: + - `▲ 绿 = 主动买(多方发动)` + - `▼ 红 = 主动卖(空方发动)` + +- 表格内容: + - 表头:价格 / 数量 / 时间 + - 每行表示一笔 agg_trade: + - 价格列: + - 用 `fmtPrice` 按数量级格式化; + - 根据 `is_buyer_maker` 渲染颜色和三角符号: + - `0` → 主动买:绿色、带 `▲`; + - `1` → 主动卖:红色、带 `▼`。 + - 数量列: + - 大额数量(>= 1000)以 `K` 单位显示(如 `1.23K`); + - 不同币种有不同小数位(BTC 显示 4 位,小币种 3 位)。 + - 时间列: + - 使用 `timeStr` 将 `time_ms` 格式化为北京时间 `HH:MM:SS`。 + +- 滚动与响应式: + - 桌面端:内部区域固定高度,启用垂直滚动(`lg:h-[420px]`)。 + - 移动端:不限制高度,避免嵌套滚动影响体验,表头 sticky 仅在桌面端启用。 + +### 2.5 右侧:成交流量分析(FlowAnalysis) + +组件:`FlowAnalysis` + +功能:按时间聚合的买卖量与 Delta 分析图,帮助判断一段时间内多空力量对比。 + +行为与布局: + +- 时间粒度切换: + - 顶部右侧有一组 interval 按钮: + - `1m`, `5m`, `15m`, `1h` + - 使用本地状态 `interval` 控制聚合粒度。 + +- 数据获取: + - 使用 `authFetch("/api/trades/summary?...")` 周期性拉取数据: + - 路径:`/api/trades/summary` + - 查询参数: + - `symbol`:当前币种(BTC/ETH/XRP/SOL) + - `start_ms`:根据 interval 动态计算(例如 1m 粒度时取过去 1 小时) + - `end_ms`:当前时间 + - `interval`:`"1m" | "5m" | "15m" | "1h"` + - 接口返回 `data` 数组,每个元素为一个 `SummaryBar`: + - `buy_vol` / `sell_vol`:该时间段的主动买/卖量; + - `delta`:买-卖; + - `trade_count`:成交笔数; + - `vwap`:该段时间的成交量加权平均价; + - `time_ms`:该段开始时间。 + - 首次加载时显示 loading 提示; + - 之后每 30 秒刷新一次数据,但不再显示 loading 动画,以避免视觉抖动。 + +- 上方摘要卡片: + - “统计时间窗口”:根据 interval 用人话表达: + - 1m → 过去 1 小时 + - 5m → 过去 4 小时 + - 15m → 过去 12 小时 + - 1h → 过去 48 小时 + - 四个小卡片: + 1. 主动买量(绿色):总 buy_vol; + 2. 主动卖量(红色):总 sell_vol; + 3. Delta(买-卖):正值多头占优,负值空头占优; + 4. 买方占比: + - 计算公式:`buy_vol / (buy_vol + sell_vol)`; + - 文案提示: + - >= 55%:强势买盘; + - 45–55%:买卖均衡; + - < 45%:强势卖盘。 + +- 中间图一:Delta + 价格双轴图 + - 使用 `ComposedChart` 绘制: + - X 轴:时间(短格式,如 `HH:MM`); + - 左 Y 轴:Delta 数值; + - 右 Y 轴:价格(VWAP),带 ±0.3% 的缓冲区,自动压缩显示范围; + - 一条 `ReferenceLine` 表示 Delta = 0; + - `Area` 图展示 Delta 区域,颜色偏蓝; + - `Line` 图展示价格,右侧刻度以美元单位,并在大数值时转换成 `$xx.xk` 形式。 + - Tooltip 中同时显示 Delta 和价格。 + +- 下方图二:主动买量 vs 主动卖量 + - 再次使用 `ComposedChart`: + - X 轴:时间; + - Y 轴:成交量; + - 两个 `Area`: + - `buy`:绿色区域; + - `sell`:红色区域。 + - Tooltip 展示各时间段买/卖量。 + +- 底部说明: + - 一行小字:`数据跨度约 X 分钟 · N 根K · 每30秒刷新`: + - `X` 为从当前时间到最早一根 bar 的跨度; + - `N` 为当前聚合出的 K 数量。 + +如果接口当前没有返回任何数据(例如新启动时),则展示一个“暂无数据,正在积累中”的占位说明,而不是空白图表。 + +### 2.6 /trades 依赖的主要 API + +`/trades` 页面通过 `authFetch` 调用后端,需要登录状态。主要接口包括: + +- `GET /api/trades/latest?symbol=&limit=20` + - 用于 `LiveTrades`; + - 返回最近的逐笔成交(agg_trades 快照)。 +- `GET /api/trades/summary?symbol=&start_ms=&end_ms=&interval=` + - 用于 `FlowAnalysis`; + - 返回按时间粒度聚合的买卖量、Delta、VWAP 等。 + +接口的具体字段与实现细节以 `docs/arbitrage-engine-full-spec.md` 和 `backend/main.py` 中对应章节为准。 + +--- + +## 3. /live(⚡ 实盘交易) + +文件:`frontend/app/live/page.tsx` +导出的组件名:`LiveTradingPage` + +### 3.1 页面目标 + +`/live` 是实盘控制与风控中枢页面,主要功能: + +- 全局监控:实盘账户的当前风险状态、R 预算、对账情况、系统健康; +- 快速止血:一键全平、禁新仓、恢复交易; +- 配置管理:在线调整实盘参数(每笔风险、杠杆、最大仓位数、启用策略等); +- 执行与对比:查看当前持仓、执行质量、实盘 vs 模拟盘表现差异。 + +整体设计成多层级面板(L0–L11),从“风险总览 → 止血按钮 → 配置 → 持仓 → 质量 → 对账 → 事件流 → 对比与历史”逐层深入。 + +### 3.2 登录要求与未登录展示 + +页面使用 `useAuth()` 进行访问控制: + +- `loading === true`:显示简单的“加载中...”提示。 +- `!isLoggedIn`: + - 显示锁屏提示: + - 图标:`🔒` + - 文案:`请先登录查看实盘` + - 按钮:`登录`(蓝底),跳转 `/login` + - 不提供注册入口(实盘页面假定仅对已有账号开放)。 + +只有登录用户可以访问 L0–L11 各层面板。 + +### 3.3 总体布局与层级概览 + +`LiveTradingPage` 登录后渲染顺序如下: + +1. `L0_RiskBar`:顶部固定风险条(sticky) +2. 页面标题区:`⚡ 实盘交易` + 简短说明(V5.2 策略、币安 USDT 永续、测试网) +3. `L1_EmergencyPanel`:一键止血区(全平/禁新仓/恢复) +4. `L15_LiveConfig`:实盘配置(风险参数) +5. `L2_AccountOverview`:账户概览 8 卡片(权益、保证金、PnL、胜率等) +6. `L3_Positions`:当前持仓(实时价+风险指标) +7. `L4_ExecutionQuality`:执行质量统计 +8. `L5_Reconciliation`:本地 vs 交易所对账 +9. `L6_RiskStatus`:风控门槛状态 +10. `L7_EventStream`:实时事件流 +11. `L8_PaperComparison`:实盘 vs 模拟盘对照 +12. `L9_EquityCurve`:权益曲线 + 回撤 +13. `L10_TradeHistory`:历史交易表 +14. `L11_SystemHealth`:系统健康(进程与数据新鲜度) + +下面按层级简述关键行为和用途。 + +### 3.4 L0 风险条(L0_RiskBar) + +功能:作为页面顶部的 sticky 风险指示条,始终可见,用于快速扫一眼当日风险与对账情况。 + +- 数据来源: + - 周期性轮询: + - `/api/live/risk-status` + - `/api/live/reconciliation` + - `/api/live/account` + - 每 5 秒刷新一次。 + +- 显示内容: + 1. **交易状态** + - 彩色圆点 + 文案: + - `normal` → 绿色点 + `运行中` + - `circuit_break` → 红点 + `熔断` 提示(有表情符号) + - `warning` → 黄点 + `警告` + - 其它 → 灰色 + `未知` + - 标记: + - `禁新仓`:如果 `risk.block_new_entries` 为真; + - `只减仓`:如果 `risk.reduce_only` 为真。 + + 2. **R 预算** + - 已实现 R:`today_realized_r`(正绿负红); + - 未实现 R:`today_unrealized_r`; + - 日限对比:`totalR = realized + min(unrealized, 0)`,显示为 `totalR/-5R`: + - 根据占用比例着色:<80% 绿,80–100% 黄,>=100% 红。 + + 3. **对账与连亏** + - 对账状态:从 `/api/live/reconciliation` 的 `status` 推导: + - ok → 绿色小点 + `对账✓` + - 其它 → 红色小点 + `对账✗(差异条数)` + - 连续亏损次数:`risk.consecutive_losses`; + - 如果存在 `risk.circuit_break_reason`,在右侧以小字体显示熔断原因。 + +### 3.5 L1 一键止血区(L1_EmergencyPanel) + +功能:提供三种紧急操作: + +- `🔴 全平`:调用 `POST /api/live/emergency-close`; +- `🟡 禁新仓`:调用 `POST /api/live/block-new`; +- `✅ 恢复`:调用 `POST /api/live/resume`。 + +交互特点: + +- 对危险操作(全平/禁新仓)增加二次确认: + - 首次点击显示“确认?” + 确认/取消按钮; + - 确认后才真正发起请求。 +- 操作结果通过顶端一行小字提醒:成功/失败提示文本会显示数秒后自动消失。 + +使用场景:实盘出现异常时,快速全平或冻结新仓入口,降低风险暴露时间。 + +### 3.6 L1.5 实盘配置面板(L15_LiveConfig) + +功能:展示并编辑实盘关键配置,用于调整风险参数与交易环境。 + +- 数据来源: + - 初始化时调用 `GET /api/live/config` 获取配置对象; + - 保存时调用 `PUT /api/live/config`,之后再 `GET` 一次以刷新展示。 + +- 配置项顺序(configOrder): + - `risk_per_trade_usd`:每笔风险金额(USDT),决定 1R 对应的资金; + - `initial_capital`:初始资金; + - `risk_pct`:每笔风险占比(%); + - `max_positions`:最大持仓数; + - `leverage`:杠杆倍数; + - `enabled_strategies`:启用的策略列表; + - `trade_env`:交易环境(正式/测试网等)。 + +- 展示形式: + - 标题:`⚙️ 实盘配置`,旁边强调 `1R = $X.XX`(从 `risk_per_trade_usd` 推导)。 + - 每个配置项显示图标 + label + 当前值: + - windown 模式:显示只读值,如 `5%`、`10x`; + - 编辑模式:变成文本输入框。 + +- 编辑流程: + 1. 点击“编辑”按钮 → 进入编辑模式,当前配置复制到 `draft`; + 2. 修改各字段; + 3. 点击“保存”: + - 发 `PUT /api/live/config`,体是 `draft`; + - 成功后重新拉配置并退出编辑模式; + 4. 点击“取消” → 撤销编辑,恢复显示模式。 + +该面板是实盘风险参数的唯一修改入口,页面其他部分(仓位 R 计算等)会引用这里的 `risk_per_trade_usd`。 + +### 3.7 L2 账户概览(L2_AccountOverview) + +功能:用 8 个小卡片概览账户与策略表现: + +- 数据来源: + - `GET /api/live/account`:账户层面数据; + - `GET /api/live/summary?strategy=v52_8signals`:指定策略的汇总表现。 + +- 卡片内容示例: + 1. 账户权益:`equity`(USDT) + 2. 可用保证金:`available_margin` + 3. 已用保证金:`used_margin` + 4. 有效杠杆:`effective_leverage`(超过 10x 时高亮为红色) + 5. 今日净 PnL:`today_realized_r` + 对应 USDT 金额(正绿负红) + 6. 总净 PnL:`total_pnl_r` + `total_pnl_usdt` + 7. 成本占比:费用 + 资金费率成本(`total_fee_usdt + total_funding_usdt`) + 8. 胜率/PF:`win_rate` 和 `profit_factor` + +这些卡片给出“当前实盘表现是否健康”的快速量化视图。 + +### 3.8 L3 当前持仓(L3_Positions) + +功能:实时展示实盘持仓详情,包括价格、TP/SL、风险、执行质量等。 + +- 数据来源: + - 周期性轮询: + - `GET /api/live/positions?strategy=v52_8signals`:当前实盘持仓列表; + - `GET /api/live/reconciliation`:用于计算清算价与距离; + - `GET /api/live/config`:获取 `risk_per_trade_usd`。 + - WebSocket 实时价格: + - 连接 `wss://fstream.binance.com/stream?streams=btcusdt@aggTrade/...`; + - 实时更新 `wsPrices[symbol]` 作为当前价。 + +- 每个持仓块显示信息包括: + - 头行: + - 标题:`🟢/🔴 {symbol} {LONG/SHORT}`(方向 + 币种),颜色区分多空; + - 评分与仓位档位:`评分 score · 加仓/标准`; + - 清算距离 badge: + - 从对账数据里计算 mark price 到 liquidation price 的距离百分比; + - 不同区间用不同颜色强调(例如 <8% 深色、8–12% 红色、12–20% 橙色、>20% 绿色)。 + - 实时浮盈: + - 以 R 表示:`unrealR`(考虑 TP1 是否已触发,用全程 R 和 TP1 R 的组合); + - 以 USDT 表示:`unrealUsdt = unrealR * riskUsd`。 + - 持仓时长:`holdMin` 分钟,超过 45/60 分钟用不同颜色提示。 + + - 价格行: + - 入场价、实际成交价、当前价(websocket/备选 current_price)、TP1/TP2/SL; + - TP1 打到时在 TP1 旁边标记 `✅`。 + + - 执行指标行: + - 滑点(bps); + - “裸奔时间”(保护下单前裸暴露时长); + - 信号→下单延迟(S→O)、下单→成交延迟(O→F); + - 交易所订单 ID。 + - 根据阈值用红/黄/绿背景表达是否超出合理范围。 + +如果当前没有持仓,则显示一条“暂无活跃持仓”的提示卡片。 + +### 3.9 L4–L11 其他面板简述 + +1. **L4 执行质量(L4_ExecutionQuality)** + - 数据源:`GET /api/live/execution-quality`; + - 展示整体统计和按币种统计: + - 滑点、信号→下单延迟、下单→成交延迟、裸奔时间; + - 每项有 avg / P50 / P95 三个分位数; + - 根据 P95 与阈值比较决定颜色(绿/黄/红)。 + +2. **L5 对账面板(L5_Reconciliation)** + - 数据源:`GET /api/live/reconciliation`; + - 显示: + - 本地持仓列表 vs 交易所持仓列表; + - 挂单数量对比; + - 差异列表 `diffs`,按严重程度着色(critical 用红色,警告用橙色)。 + +3. **L6 风控状态(L6_RiskStatus)** + - 数据源:`GET /api/live/risk-status`; + - 将风控规则用几条检查项呈现: + - 单日亏损是否大于 -5R; + - 连续亏损是否小于 5 次; + - API 是否正常; + - 如存在 `circuit_break_reason` 和 `auto_resume_time`,以红色块形式显示熔断原因和预计恢复时间。 + +4. **L7 实时事件流(L7_EventStream)** + - 数据源:`GET /api/live/events?limit=30&level=...`(每 5 秒刷新); + - 支持按级别过滤:全部/严重/警告/信息; + - 每条事件包含: + - 时间(日期 + 时分秒); + - category(trade/risk/system/reconciliation 等); + - symbol(如 BTC/ETH); + - message 文本; + - 使用不同颜色和图标表示级别(info/warn/error/critical)。 + +5. **L8 实盘 vs 模拟盘(L8_PaperComparison)** + - 数据源:`GET /api/live/paper-comparison?limit=20`; + - 表格比较每笔实盘 vs 对应模拟盘交易: + - 入场价差(bps)、PnL 差异(R)等; + - 表头包括币种、方向、实盘/模拟入场、价差、PnL、R 差; + - 标题中显示平均 R 差。 + +6. **L9 权益曲线 + 回撤(L9_EquityCurve)** + - 数据源:`GET /api/live/equity-curve?strategy=v52_8signals`; + - 绘制以 R 为单位的累计 PnL 曲线,以及对应回撤曲线: + - 计算峰值与当前差值作为回撤; + - 以两条 Area 图显示(绿色累计 PnL、红色回撤)。 + +7. **L10 历史交易(L10_TradeHistory)** + - 数据源:`GET /api/live/trades?symbol=&result=&strategy=&limit=50`; + - 支持币种(全部/BTC/ETH/XRP/SOL)和结果(全部/盈/亏)筛选; + - 表格字段: + - 币种、方向、入场/出场价; + - Gross R、Fee R、FR R(资金费率)、Slip R; + - Net R(总盈亏); + - 状态(止盈/止损/保本/其他); + - 持仓时长(分钟)。 + +8. **L11 系统健康(L11_SystemHealth)** + - 数据源:`GET /api/live/health`; + - 展示: + - 各进程状态(online/offline、内存占用、重启次数等); + - 行情数据新鲜度(最近一次数据更新时间与状态)。 + +整体上,`/live` 将实盘运行过程中所有关键视角集中在一个页面里: +从风险和止血,到配置与持仓,再到执行质量、对账、事件和系统健康,方便在出现问题时快速定位和处理。 + +--- + +## 4. /strategy-plaza(策略广场) + +主页面文件:`frontend/app/strategy-plaza/page.tsx` +废弃策略页面:`frontend/app/strategy-plaza/deprecated/page.tsx` + +### 4.1 页面目标 + +`/strategy-plaza` 是 V5.4 策略工厂的“策略广场”,面向策略研究者,用来: + +- 一眼看到当前所有策略的运行状态与收益表现; +- 对比不同策略(币种、CVD 周期、TP/SL 方案)的表现好坏; +- 快速进入单策略详情页(查看信号引擎 + 模拟盘全貌); +- 对策略进行管理:调整参数、追加余额、废弃/恢复。 + +从用户视角,它更像是一个“策略排行榜 + 控制台”,而不是直接操作信号详情的页面。 + +### 4.2 登录与加载行为 + +`StrategyPlazaPage` 顶部调用 `useAuth()`,用于确保身份信息已经加载(具体权限控制由后端决定)。 + +- 加载数据: + - 使用 `authFetch("/api/strategies")` 获取所有策略; + - 响应中取 `data.strategies` 填充本地 `strategies` 列表; + - 首次加载时显示“加载中...”; + - 之后每 30 秒自动刷新一次数据(定时调用 `fetchData`)。 + +这里的 `/api/strategies` 会返回当前所有策略实例(包括运行中和暂停中的,废弃策略则在 deprecated 页通过 `include_deprecated=true` 过滤)。 + +### 4.3 主页面结构(/strategy-plaza) + +页面由三部分组成: + +1. 顶部 Header +2. 策略卡片网格 +3. 追加余额弹窗 + +#### 4.3.1 Header + +- 标题区: + - 标题:`策略广场` + - 副标题:`点击策略名查看信号引擎和模拟盘详情`(提示点击行为会进入 `/strategy-plaza/[id]` 类页面) + +- 右侧工具区: + - 最近更新时间: + - 一个绿色小圆点 + “HH:MM:SS” 本地时间,用 `lastUpdated` 来表示最近一次成功拉取 `/api/strategies` 的时间; + - “新增策略”按钮: + - 蓝色按钮,文案 `新增策略`; + - 点击跳转到 `/strategy-plaza/create`,进入新建策略表单。 + +#### 4.3.2 策略卡片网格 + +- 使用 `StrategyCardComponent` 渲染每个 `StrategyCard`(从 `/api/strategies` 得到的数据结构); +- 网格布局: + - 小屏:单列; + - 中屏:两列; + - 大屏:三列。 + +**每张卡片内容:** + +1. **Header 行** + - 左侧: + - 策略名:`display_name`,点击标题会跳转到 `/strategy-plaza/{strategy_id}`(单策略详情页,展示信号+paper 详情); + - 状态徽章 `StatusBadge`: + - `running` → 绿色 “运行中”; + - `paused` → 橙色 “已暂停”; + - 其它 → 红色 “异常”; + - 币种标签:例如 `BTC` / `ETH`,从 `symbol.replace("USDT","")` 抽取。 + - 右侧: + - 启动时长:`formatDuration(started_at)`,以“x天 x小时 / x小时 x分 / x分钟”形式显示策略持续运行时间。 + +2. **主要收益与余额** + - 左侧: + - 标题:`当前余额`; + - 数值:`current_balance`(USDT),大号字体。 + - 右侧: + - 标题:`累计盈亏`; + - 数值:`net_usdt`: + - 正数加 `+` 号,并用绿色; + - 负数用红色。 + +3. **余额进度条** + - 显示当前余额相对于初始资金的百分比: + - `balancePct = current_balance / initial_balance * 100%`; + - 文字显示 `balancePct%` 和 `initial_balance`; + - 下方进度条宽度按百分比缩放,颜色随盈亏变动: + - 盈利 → 绿色; + - 亏损 → 红色。 + +4. **关键统计行** + - 三个小卡片: + 1. 胜率:`win_rate%`,>50% 绿色,45–50% 橙色,<45% 红色; + 2. 净 R:`net_r`,正值绿色、负值红色; + 3. 交易数:`trade_count`。 + +5. **平均赢/亏** + - 左:绿色背景的“平均赢”,显示 `+avg_win_r R`; + - 右:红色背景的“平均亏”,显示 `avg_loss_r R`; + - 进一步帮助判断盈亏比是否合理。 + +6. **Footer 行:24 小时表现 + 持仓状态** + - 左侧: + - 24 小时 PnL: + - 图标 `TrendingUp` 或 `TrendingDown`; + - 文案:`24h +X U` 或 `24h -X U`,根据 `pnl_usdt_24h` 的正负着色(绿/红)。 + - 右侧: + - 如果 `open_positions > 0`: + - 显示 `{open_positions} 仓持仓中`,着橙色强调; + - 否则: + - 显示最近一次交易时间:`上次: formatTime(last_trade_at)`(格式为 `MM-DD HH:mm`)。 + +7. **操作按钮行** + - 三个按钮,从左到右: + 1. `调整参数`: + - 链接到 `/strategy-plaza/{strategy_id}/edit`; + - 进入策略编辑表单(修改权重、窗口、TP/SL 等配置)。 + 2. `追加余额`: + - 打开 `AddBalanceModal`; + - 输入追加金额(USDT),调用 `/api/strategies/{id}/add-balance`,更新初始资金与当前余额。 + 3. 红色垃圾桶按钮: + - 点击后调用 `onDeprecate`,触发废弃流程。 + +#### 4.3.3 底部空状态与追加余额弹窗 + +- 当 `strategies.length === 0`: + - 显示“暂无运行中的策略”提示; + - 提供 “创建第一个策略” 按钮,跳转 `/strategy-plaza/create`。 + +- `AddBalanceModal`: + - 显示当前选中策略名与输入框; + - 校验金额 > 0; + - 提交时调用 `POST /api/strategies/{sid}/add-balance`; + - 成功后关闭弹窗并重新拉取策略列表。 + +### 4.4 废弃策略页(/strategy-plaza/deprecated) + +文件:`frontend/app/strategy-plaza/deprecated/page.tsx` +导出的组件名:`DeprecatedStrategiesPage` + +功能:展示所有 `status="deprecated"` 的策略,并允许“重新启用”。 + +- 数据加载: + - 调用 `GET /api/strategies?include_deprecated=true`; + - 前端在得到的 `strategies` 中筛选 `status === "deprecated"`。 + +- 页面结构: + 1. Header: + - 标题:`废弃策略`; + - 副标题:`数据永久保留,可随时重新启用`; + - 右侧链接:`← 返回策略广场`,跳转 `/strategy-plaza`。 + 2. 如果没有废弃策略: + - 显示“暂无废弃策略”。 + 3. 否则: + - 使用网格展示每个废弃策略卡片(布局类似主广场,但视觉上更淡一些)。 + +- 废弃策略卡片要点: + - Header: + - 策略名; + - “已废弃”灰色徽章; + - 右侧显示币种简称。 + - PnL 与余额: + - 展示“废弃时余额”和累计盈亏; + - 余额进度条同样显示当前余额与初始资金的比例(但整体卡片透明度略低,以示已停用)。 + - 统计卡片: + - 胜率、净 R、交易数。 + - Footer: + - 左侧:最近废弃时间 `废弃于 {deprecated_at}`; + - 右侧:`重新启用` 按钮: + - 点击前二次确认:“确认重新启用策略,将继续使用原有余额和历史数据。”; + - 调用 `POST /api/strategies/{sid}/restore`; + - 成功后刷新列表。 + +### 4.5 主要依赖的 API + +`/strategy-plaza` 和 `/strategy-plaza/deprecated` 主要依赖以下后端接口: + +- `GET /api/strategies` + - 返回所有非废弃策略; + - 前端使用:策略广场卡片列表; + - 数据中包含策略 ID、展示名、币种、状态、余额、PnL、胜率、平均盈亏、近 24 小时表现等。 + +- `GET /api/strategies?include_deprecated=true` + - 返回包括废弃策略在内的完整列表; + - 前端从中筛出 `status="deprecated"` 用于“废弃策略”页面。 + +- `POST /api/strategies/{sid}/deprecate` + - 废弃策略:将 `status` 设为 `deprecated`,停止策略运行但保留数据; + - 前端在废弃前弹出确认对话框。 + +- `POST /api/strategies/{sid}/restore` + - 恢复废弃策略为运行状态; + - 保持原有余额和历史记录。 + +- `POST /api/strategies/{sid}/add-balance` + - 向指定策略追加模拟资金; + - 影响 `initial_balance` 和 `current_balance`,并在 card 上体现。 + +单个策略的详细行为(信号引擎配置、模拟盘表现)由 `/strategy-plaza/[id]` 下的页面承载,这里作为入口不展开细节;实现上这部分是 V5.4 Strategy Factory 的前端展示层。 + +--- + +## 5. /server(服务器监控) + +文件:`frontend/app/server/page.tsx` +导出的组件名:`ServerPage` + +### 5.1 页面目标 + +`/server` 页面用来监控部署 Arbitrage Engine 的服务器与关键进程状态,帮助你: + +- 了解 CPU / 内存 / 硬盘 / 网络使用情况; +- 看到 PM2 管理的各个进程是否正常(arb-web / arb-api / signal-engine / collectors 等); +- 观察 PostgreSQL 数据库的体量和各 symbol 数据覆盖范围; +- 了解回补任务是否在运行。 + +它的目标是提供运维层的**健康检查面板**,方便在出现延迟、缺数据或者服务挂掉时快速判断是资源问题还是进程问题。 + +### 5.2 登录要求与数据刷新 + +- 使用 `useAuth()` 判断登录状态: + - 未登录:显示提示“请先登录查看服务器状态”,并给出 `登录` / `注册` 按钮; + - 已登录:开始加载 `/api/server/status`。 + +- 刷新策略: + - 初次加载时调用 `fetchData()`; + - 之后每 10 秒自动轮询一次; + - `loading` 为真时显示“加载中...”;如果请求失败则显示“获取失败”。 + +接口返回的数据结构在前端定义为 `ServerStatus`,包含 CPU/内存/磁盘/负载/网络/PM2 列表/Postgres 信息/回补标志等。 + +### 5.3 页面布局概览 + +登录且成功获取数据后,布局为: + +1. 顶部标题行; +2. 系统概览四张卡片(CPU / 内存 / 硬盘 / 系统); +3. PM2 进程表格; +4. PostgreSQL 概览与数据覆盖范围; +5. 底部说明提示。 + +### 5.4 顶部标题行 + +- 标题:`服务器监控`; +- 副标题:`GCP asia-northeast1-b · 每10秒自动刷新`(标明部署区域和刷新频率); +- 右侧状态标记: + - 当 `data.backfill_running === true` 时,显示: + - 蓝色小圆点 + 文案 `回补运行中`; + - 提示当前 CPU/网络负载偏高可能是正常的回补行为。 + +### 5.5 系统概览四卡片 + +使用一个 2x2 或 4 列的网格展示四个方面: + +1. **CPU** + - 显示: + - 核数:`data.cpu.cores`; + - 当前使用率:`data.cpu.percent%`; + - 下方以 `ProgressBar` 表示 CPU 利用率,>70% 黄色,>90% 红色; + - 一行 load 信息:`load1 / load5 / load15`。 + +2. **内存** + - 显示: + - 占用:`used_gb / total_gb`; + - 当前使用率百分比; + - `ProgressBar` 显示利用率; + - 如果 `swap_percent > 0`,额外显示 Swap 使用率,以橙色提示。 + +3. **硬盘** + - 显示: + - 可用空间:`free_gb`; + - 使用率百分比 + `ProgressBar`; + - 已用/总容量:`used_gb / total_gb`。 + +4. **系统运行时间与网络** + - 显示: + - 运行时间:`uptime_hours`(小时),超过 24 小时时会用“X天”的形式; + - 网络: + - 上行总量:`bytes_sent_gb`; + - 下行总量:`bytes_recv_gb`。 + +### 5.6 PM2 进程表格 + +功能:查看每个 PM2 管理的进程状态,确认关键服务是否在线。 + +- 数据来源:`data.pm2` 数组,每个元素 `PM2Proc` 包含: + - `name`:进程名(例如 arb-web/arb-api/signal-engine 等); + - `status`:`online` 或其他; + - `cpu`:CPU 使用率; + - `memory_mb`:内存使用 MB; + - `restarts`:重启次数; + - `uptime_ms`:运行时长。 + +- 表格列: + - 名称(等宽字体显示); + - 状态: + - 使用圆点 + badge 样式: + - `online` → 绿色背景圆角标; + - 其他状态 → 红色背景圆角标; + - CPU 百分比; + - 内存 MB; + - 重启次数; + - 运行时间:通过 `uptimeStr` 格式化为 `Xd Yh` 或 `Xh Ym`。 + +这部分是判断“某个服务是不是挂了 / 频繁重启”的第一视角。 + +### 5.7 PostgreSQL 概览 + +功能:监控数据库大小、关键表数据量以及各交易对的历史数据覆盖范围。 + +数据来自 `data.postgres`: + +- 上半部分卡片: + 1. 数据库大小: + - `db_size_mb`,> 1024 MB 时以 GB 显示; + 2. `agg_trades` 行数: + - `agg_trades_count`,以千分位格式化(如 89,000,000); + 3. `rate_snapshots` 行数: + - `rate_snapshots_count`; + 4. 回补状态: + - 使用 `data.backfill_running` 显示 “运行中” 或 “已停止”,并用颜色区分。 + +- 下半部分数据覆盖范围: + - `data.postgres.symbols` 是一个记录,键为 symbol(如 BTCUSDT/ETHUSDT 等),值包含: + - `earliest_ms`:最早一条数据的时间戳; + - `latest_ms`:最新一条数据时间戳; + - `span_hours`:时间跨度(小时)。 + - 对每个 symbol,展示: + - 左侧:symbol 名,等宽字体; + - 右侧:时间范围: + - `earliest_ms` → `latest_ms`,使用 `bjtStr` 转换为北京时间(简短日期+时间); + - 下一行显示 `span_hours`,如果 >24 小时,则用“X.X 天”,否则“X.X 小时”。 + +这部分帮助确认“数据从什么时候开始积累、到什么时候为止、是否有断档”。 + +### 5.8 底部说明 + +最后是一个蓝色 Info 卡片,说明: + +- 数据每 10 秒自动刷新; +- PM2 进程状态实时反映服务运行情况; +- 回补运行中时 CPU 和网络负载偏高属于正常现象,不一定是异常。 + +### 5.9 依赖的主要 API + +`/server` 页面只依赖一个后端接口: + +- `GET /api/server/status` + - 返回 `ServerStatus` 结构; + - 包含: + - CPU/内存/磁盘/负载信息; + - uptime 和网络流量; + - PM2 进程列表; + - Postgres 概览(整体大小、表行数、各 symbol 的数据覆盖范围); + - 回补任务状态标记。 + +具体字段定义与计算方式以 `docs/arbitrage-engine-full-spec.md` 和 `backend/main.py` 中对应接口描述为准。 + +--- + +## 6. /about(策略说明与风险) + +文件:`frontend/app/about/page.tsx` +导出的组件名:`AboutPage` + +### 6.1 页面目标 + +`/about` 页面是一个纯信息页,用来向用户解释: + +- 资金费率套利策略的基本原理(现货多 + 永续空,对冲方向风险,收资金费率); +- 历史年化收益数据(基于长期统计的 BTC/ETH/组合毛年化和净年化); +- 策略的主要风险点及其等级。 + +这个页面不依赖登录状态、不调用任何 API,主要用于教育和风险披露。 + +### 6.2 页面结构 + +页面宽度限制在 `max-w-3xl`,内容自上而下分为三块: + +1. 标题区; +2. 策略原理说明; +3. 历史年化数据; +4. 风险说明。 + +#### 6.2.1 标题区 + +- 主标题:`策略说明` +- 副标题:`资金费率套利原理与历史数据` + +用于告诉用户:本页不是实时数据,而是解释策略设计与历史表现。 + +#### 6.2.2 策略原理卡片 + +第一块白底卡片标题:`策略原理`。 + +内容用通俗文字说明永续合约资金费率机制和套利思路: + +- 永续合约每 8 小时结算一次资金费率; +- 多头多时,资金费率为正,多头付钱给空头; +- 空头多时,资金费率为负,空头付钱给多头。 + +套利做法用一段高亮文字说明: + +- “套利做法:现货买入 + 永续做空,完全对冲币价风险,净收资金费率(USDT 结算)。” + +下面有一个小示例框(等宽字体): + +- 买入 1 BTC 现货(例如 96,000 美元); +- 做空 1 BTC 永续(同样 96,000 美元,1 倍杠杆); +- 分隔线; +- 结论: + - `BTC 涨跌:两边对冲,净盈亏 = 0`; + - `资金费率:每 8 小时直接收 USDT`。 + +这个例子帮助用户在不看数学公式的情况下理解“赚费率、不赌方向”的本质。 + +#### 6.2.3 历史年化数据卡片 + +第二块白底卡片标题: +`历史年化数据(2019-2026,露露×小周15轮验证)` + +内容是一个简单表格,列出: + +- 资产:BTC / ETH / 50/50 组合; +- 全周期毛年化; +- PM 净年化(考虑 Portfolio Margin 下的资金利用率和费用); +- 负费率占比(BTC/ETH)。 + +表格中的示例数值(固定写死在前端): + +- BTC: + - 全周期毛年化:12.33%; + - PM 净年化:11.67%; + - 负费率占比:13.07%。 +- ETH: + - 全周期毛年化:14.87%; + - PM 净年化:14.09%; + - 负费率占比:12.17%。 +- 50/50 组合: + - 全周期毛年化:13.81%; + - PM 净年化:13.08%; + - 负费率占比:`—`(组合没有单独统计)。 + +表格下面有一行小字说明: + +- `PM = Portfolio Margin 模式,资金利用率约 95%。数据来源:Binance fapi/v1/fundingRate 官方 API` + +这块内容用于给出“如果长期做这个套利,大致收益区间在哪”的历史参考,但没有和当前系统直接联动。 + +#### 6.2.4 风险说明卡片 + +第三块白底卡片标题:`风险说明`。 + +以列表形式列出若干风险点,每一行包括: + +- 风险等级:例如 `🟡 中` 或 `🟢 低`; +- 风险类型:如“市场周期”、“费率持续为负”、“交易所对手方”、“爆仓风险”、“基差波动”等; +- 简短说明文本。 + +示例风险点: + +- 市场周期(中):熊市年化可降至 0–4%,但本金不亏; +- 费率持续为负(低):历史负费率占比仅 12–13%,长期均值为正; +- 交易所对手方(中):有交易所倒闭风险(FTX 教训),建议资金分散; +- 爆仓风险(低):1 倍杠杆 + 对冲,理论需要 BTC 翻倍才触发爆仓; +- 基差波动(低):长期持有不影响盈亏,只影响平仓时机。 + +整块卡片通过背景色和图标表达“这是一份认真写过的风险披露”,帮助用户建立合理预期。 + +### 6.3 依赖的 API 与状态 + +`/about` 页面完全静态: + +- 不依赖任何远端 API; +- 不区分登录/未登录展示; +- 所有数据都写死在前端代码中(作为文案与示例数值)。 + +因此,它更像是项目内置的“策略白皮书摘要”,是用户理解系统的背景材料,也可以为将来的 AI 提供策略语义上的上下文。 + +--- + +## 7. 账号相关页面(/login /register /dashboard) + +相关文件: + +- 登录:`frontend/app/login/page.tsx`(`LoginPage`) +- 注册:`frontend/app/register/page.tsx`(`RegisterPage`) +- 我的账户:`frontend/app/dashboard/page.tsx`(`DashboardPage`) + +### 7.1 /login(登录页) + +目标:为已有账号提供登录入口,获取 JWT 后进入系统。 + +- 表单字段: + - 邮箱(必填,type=email); + - 密码(必填,type=password)。 +- 提交逻辑: + - 使用 `useAuth().login(email, password)` 调用后端登录接口; + - 成功后 `router.push("/")` 返回首页; + - 失败时在表单下方显示错误信息文本(`err.message` 或 “登录失败”)。 +- 布局/交互: + - 全屏居中白卡片,标题为 `⚡ Arbitrage Engine` + “登录您的账户”; + - 提交按钮文案: + - 未提交:`登录`; + - 提交中:`登录中...`,按钮禁用且透明度降低; + - 卡片底部有引导文案: + - `没有账户? 注册` → 点击跳转 `/register`。 + +### 7.2 /register(注册页) + +目标:在邀请码机制下创建新账户。 + +- 表单字段: + - 邀请码(必填,字符串,自动转换为大写); + - 邮箱(必填,type=email); + - 密码(必填,type=password,至少 6 位)。 + +- 提交逻辑: + - 提交前本地校验密码长度: + - 如果 `< 6`,直接显示 “密码至少6位”,不发送请求; + - 调用 `useAuth().register(email, password, inviteCode)`; + - 成功后重定向到首页 `/`; + - 失败时在表单下显示错误信息(`err.message` 或 “注册失败”)。 + +- 布局/交互: + - 布局与登录页相似,标题同样是 `⚡ Arbitrage Engine`; + - 副标题提示:`注册新账户(需要邀请码)`; + - 提交按钮文案: + - 未提交:`注册`; + - 提交中:`注册中...`; + - 卡片底部提供: + - `已有账户? 登录` 链接回 `/login`。 + +### 7.3 /dashboard(我的账户) + +目标:展示当前登录用户的账号信息、推送设置与订阅等级,并提供登出入口。 + +- 访问控制: + - 使用 `useAuth()`: + - 如果 `loading` 还未结束,暂时显示“加载中...”; + - 如果 `!isLoggedIn`,直接 `router.push("/login")`; + - 数据加载: + - 使用 `authFetch("/api/auth/me")` 获取当前用户信息; + - 如果请求失败,回退到 `/login`。 + +- 用户信息结构(前端视角): + - `id`:用户 ID; + - `email`:邮箱; + - `discord_id`:绑定的 Discord 用户 ID(可空); + - `subscription`: + - `tier`:订阅等级("free" / "pro" / "premium" 等); + - `expires_at`:到期时间(可空,空则视为“永久免费”)。 + +- 页面结构: + +1. 顶部标题与登出按钮 + - 标题:`我的账户`; + - 右侧:`退出` 按钮,调用 `logout()`。 + +2. 账户信息卡片 + - 展示: + - 邮箱; + - 订阅等级: + - 使用 `tierLabel` 将内部值映射为 “免费版” / “Pro” / “Premium”; + - 未识别的 tier 值直接原样显示; + - 到期时间: + - 有值时转为本地日期显示; + - 无值时显示“永久免费”。 + +3. Discord 信号推送卡片 + - 说明:绑定 Discord ID 后,当套利信号触发时会自动 @ 用户; + - 输入框: + - 绑定的 Discord 用户 ID(18 位数字),初始值为后端返回的 `discord_id`; + - “绑定”按钮: + - 点击调用 `POST /api/user/bind-discord`: + - body:`{ discord_id }`; + - 提交中按钮禁用,文本为 “保存中...”; + - 完成后: + - 成功:显示 “绑定成功”(绿字),并更新本地 user 对象; + - 失败:显示错误提示(红字); + - 下方有一条小字说明如何在 Discord 客户端中获取用户 ID(开启开发者模式后右键复制)。 + +4. 升级订阅卡片 + - 展示三个订阅档位的静态信息: + - Free:`¥0`,功能如“实时费率面板”; + - Pro:`¥99/月`,功能如“实时费率面板 + 信号 Discord 推送 + 历史数据”; + - Premium:`¥299/月`,功能如“Pro 全部功能 + 定制阈值 + 优先客服”。 + - 当前 tier 对应的卡片会用高亮边框显示; + - 对于非当前档位且不为 Free 的卡片,会展示一个 `升级(即将开放)` 的占位按钮(目前仅为 UI 提示,不实际发请求)。 + +整体上,`/dashboard` 是一个账号控制面板,主要用于查看订阅信息、管理 Discord 推送绑定、执行登出操作。 diff --git a/docs/PROBLEM_REPORT.md b/docs/PROBLEM_REPORT.md deleted file mode 100644 index 5c646c6..0000000 --- a/docs/PROBLEM_REPORT.md +++ /dev/null @@ -1,145 +0,0 @@ -# 项目问题报告 - -> 生成时间:2026-03-03 -> 基于 commit `0d9dffa` 的代码分析 - ---- - -## 🔴 高危问题(可能导致实盘出错) - -### P1:数据库从未真正迁移到云端 - -**现象**:你以为整个系统已经跑在 Cloud SQL 上,实际上只有 `agg_trades` 原始成交数据在双写。其他核心数据全在**本地 PG(127.0.0.1)**。 - -| 数据表 | 本地 PG | Cloud SQL | -|-------|---------|-----------| -| `agg_trades`(原始成交) | ✅ | ✅ 双写 | -| `signal_indicators`(信号输出) | ✅ | ❌ 没有 | -| `paper_trades`(模拟盘) | ✅ | ❌ 没有 | -| `rate_snapshots`(费率快照) | ✅ | ❌ 没有 | -| `market_indicators`(市场数据) | ✅ | ❌ 没有 | -| `live_trades`(实盘交易) | ❌ | ✅ 只在云端 | -| `live_config` / `live_events` | ❌ | ✅ 只在云端 | - -**最致命的问题**:`live_executor.py` 和 `risk_guard.py` 默认连 Cloud SQL(`DB_HOST=10.106.0.3`),但 `signal_engine.py` 只把信号写到本地 PG。这意味着: -- 实盘执行器读取的 `signal_indicators` 表在 Cloud SQL 里**可能是空的** -- 风控模块监控的 `live_trades` 和信号引擎写的数据完全在两个不同的数据库里 - -**影响**:实盘交易链路存在断裂风险,需立即核查服务器上各进程实际连接的数据库地址。 - -**修复方向**:统一所有进程连接到 Cloud SQL,或统一连接到本地 PG(通过 Cloud SQL Auth Proxy)。 - ---- - -### P2:`users` 表双定义字段不一致 - -`db.py` 和 `auth.py` 各自定义了一个 `users` 表,字段不同: - -| 字段 | db.py 版本 | auth.py 版本 | -|------|-----------|-------------| -| `email` | ✅ | ✅ | -| `password_hash` | ✅ | ✅ | -| `role` | ✅ | ✅ | -| `created_at` | ✅ | ✅ | -| `discord_id` | ❌ | ✅ | -| `banned` | ❌ | ✅ | - -FastAPI 启动时先跑 `init_schema()`(db.py 版),再跑 `ensure_auth_tables()`(auth.py 版),因为 `CREATE TABLE IF NOT EXISTS` 第一次成功后就不再执行,**实际创建的是缺少 `discord_id` 和 `banned` 字段的旧版本**。 - -**影响**:封禁用户功能(`banned` 字段)在新装环境下可能失效。 - ---- - -### P3:`signal_indicators` 表 INSERT 包含 `strategy` 字段但 schema 没有 - -`save_indicator()` 函数向 `signal_indicators` 插入数据时包含 `strategy` 字段(`signal_engine.py:697`),但 `SCHEMA_SQL` 里的建表语句没有这个字段(`db.py:205-224`)。 - -**影响**:在全新环境初始化后,信号引擎写入会报列不存在的错误。 - ---- - -## 🟡 中危问题(影响稳定性和维护) - -### P4:`requirements.txt` 严重不完整 - -文件只列了 5 个包,实际运行还需要: - -| 缺失依赖 | 用于 | -|---------|------| -| `asyncpg` | FastAPI 异步数据库 | -| `psycopg2-binary` | 同步数据库(signal_engine 等) | -| `aiohttp` | live_executor、risk_guard | -| `websockets` 或 `httpx` | agg_trades_collector WS 连接 | -| `psutil` | 已在文件里,但版本未锁定 | - -**影响**:新机器部署直接失败。 - ---- - -### P5:`market_indicators` 和 `liquidations` 表不在主 schema 中 - -这两张表由各自的 collector 进程单独创建,不在 `init_schema()` 里。如果 collector 没跑过,signal_engine 查这两张表时会报错(会降级为默认中间分,不会崩溃,但数据不准)。 - ---- - -### P6:没有 CI/CD,没有自动化测试 - -- 代码变更完全靠人工验证 -- 策略逻辑(`evaluate_signal`)没有任何单元测试,重构风险极高 -- 部署流程:手动 ssh + git pull + pm2 restart - ---- - -## 🟠 安全风险 - -### P7:测试网密码硬编码在源代码里 - -三个文件里都有: -```python -os.getenv("PG_PASS", "arb_engine_2026") # db.py:19 -os.getenv("DB_PASSWORD", "arb_engine_2026") # live_executor.py:44 -os.getenv("DB_PASSWORD", "arb_engine_2026") # risk_guard.py:42 -``` - -代码一旦泄露(GitHub public、截图等),测试网数据库直接裸奔。 - -### P8:JWT Secret 有测试网默认值 - -```python -_jwt_default = "arb-engine-jwt-secret-v2-2026" if _TRADE_ENV == "testnet" else None -``` - -如果生产环境 `TRADE_ENV` 没有正确设置,会静默使用这个已知 secret,所有 JWT 都可伪造。 - ---- - -## 🔵 架构债务(长期) - -### P9:三套数据库连接配置并存,极易混淆 - -| 配置方式 | 使用的进程 | 默认连哪 | -|---------|----------|---------| -| `db.py` 的 `PG_HOST` | main.py、signal_engine、collectors | `127.0.0.1`(本地) | -| 进程内 `DB_HOST` | live_executor、risk_guard、position_sync | `10.106.0.3`(Cloud SQL) | -| `market_data_collector.py` 内 `PG_HOST` | market_data_collector | `127.0.0.1`(本地) | - -没有统一的连接配置入口,每个进程各自读各自的环境变量,迁移时极容易漏改。 - -### P10:前端轮询压力 - -`/api/rates` 每 2 秒轮询一次,用户多了服务器压力线性增长。目前 3 秒缓存有一定缓冲,但没有限流保护。 - ---- - -## 📋 建议优先级 - -| 优先级 | 任务 | -|-------|------| -| 🔴 立即 | 登服务器确认各进程实际连的数据库地址,核查实盘链路是否完整 | -| 🔴 立即 | 补全 `signal_indicators` 表的 `strategy` 字段 | -| 🔴 本周 | 统一数据库连接配置,所有进程用同一套环境变量 | -| 🟡 本周 | 修复 `users` 表双定义问题,合并到 auth.py 版本 | -| 🟡 本周 | 补全 `requirements.txt` | -| 🟠 本月 | 把硬编码密码移到 `.env` 文件,不进代码仓库 | -| 🔵 长期 | 添加 signal_engine 核心逻辑的单元测试 | -| 🔵 长期 | 配置 GitHub Actions 做基础 lint 和安全扫描 | diff --git a/docs/PROJECT.md b/docs/PROJECT.md deleted file mode 100644 index 5c4ee84..0000000 --- a/docs/PROJECT.md +++ /dev/null @@ -1,425 +0,0 @@ -# Arbitrage Engine V5.1 — 项目文档 - -> 最后更新:2026-03-01 -> 版本:V5.1 -> 作者:琪智科技 - -## 一、项目概述 - -**Arbitrage Engine** 是一套加密货币合约交易信号系统,基于多因子评分模型,实时分析市场微观结构数据(Order Flow),生成做多/做空信号并执行模拟盘交易。 - -### 核心能力 -- **5层100分评分体系**:方向层(45) + 拥挤层(20) + 环境层(15) + 确认层(15) + 辅助层(5) -- **4个交易币种**:BTCUSDT、ETHUSDT、XRPUSDT、SOLUSDT -- **8个信号源**(6个已接入评分,2个在采集中) -- **模拟盘自动交易**:信号触发 → 开仓 → TP/SL → 平仓,全自动 -- **实时数据采集**:aggTrades、市场指标、清算数据 -- **Web仪表盘**:信号展示、模拟盘监控、收益统计 - -### 技术栈 -| 层级 | 技术 | -|------|------| -| 后端 | Python 3 + FastAPI + uvicorn | -| 前端 | Next.js 15 + React + TypeScript + Tailwind CSS | -| 数据库 | PostgreSQL 18(GCP Cloud SQL) | -| 进程管理 | PM2 | -| 数据源 | Binance Futures API + WebSocket | -| 反向代理 | Caddy | - -### 代码统计 -| 模块 | 文件数 | 代码行数 | -|------|--------|---------| -| Backend (Python) | 15个 | 4,598行 | -| Frontend (TSX/TS) | 21个 | 3,518行 | -| **总计** | **36个** | **8,116行** | - ---- - -## 二、项目结构 - -``` -arbitrage-engine/ -├── backend/ # Python后端 -│ ├── main.py # FastAPI主入口 + 全部API路由 -│ ├── db.py # 数据库连接层(PG同步+异步池+Cloud SQL双写) -│ ├── auth.py # 用户认证系统(JWT + 邀请码注册) -│ ├── signal_engine.py # 🔥 核心:信号评估引擎(5层评分) -│ ├── paper_monitor.py # 模拟盘监控(WebSocket实时TP/SL) -│ ├── agg_trades_collector.py # aggTrades实时采集(Binance WS) -│ ├── market_data_collector.py# 市场指标采集(多空比/OI/FR等) -│ ├── liquidation_collector.py# 清算数据采集(Binance forceOrder) -│ ├── backtest.py # 回测框架 -│ ├── backfill_agg_trades.py # aggTrades历史回补工具 -│ ├── admin_cli.py # 管理命令行工具 -│ ├── signal_pusher.py # [已废弃] 旧版信号推送 -│ ├── subscriptions.py # [预留] 订阅系统 -│ ├── migrate_sqlite_to_pg.py # [一次性] SQLite→PG数据迁移 -│ └── migrate_auth_sqlite_to_pg.py # [一次性] Auth SQLite→PG迁移 -├── frontend/ # Next.js前端 -│ ├── app/ -│ │ ├── page.tsx # 首页(仪表盘概览) -│ │ ├── layout.tsx # 全局布局 -│ │ ├── signals/page.tsx # 信号引擎页面(5层评分展示) -│ │ ├── paper/page.tsx # 模拟盘页面(持仓+历史+统计) -│ │ ├── trades/page.tsx # 历史交易页面 -│ │ ├── server/page.tsx # 服务器监控页面 -│ │ ├── kline/page.tsx # K线图页面 -│ │ ├── live/page.tsx # 实时数据页面 -│ │ ├── dashboard/page.tsx # 仪表盘页面 -│ │ ├── login/page.tsx # 登录页面 -│ │ ├── register/page.tsx # 注册页面(需邀请码) -│ │ ├── about/page.tsx # 关于页面 -│ │ └── history/page.tsx # [占位] 历史页面 -│ ├── components/ -│ │ ├── Sidebar.tsx # 侧边导航栏 -│ │ ├── Navbar.tsx # 顶部导航栏 -│ │ ├── AuthHeader.tsx # 认证头部 -│ │ ├── LiveTradesCard.tsx # 实时交易卡片 -│ │ ├── FundingChart.tsx # 资金费率图表 -│ │ ├── RateCard.tsx # 费率卡片 -│ │ └── StatsCard.tsx # 统计卡片 -│ ├── lib/ -│ │ ├── auth.tsx # 前端认证逻辑(JWT管理) -│ │ └── api.ts # API请求封装 -│ ├── next.config.ts # Next.js配置(API代理) -│ ├── package.json # 依赖配置 -│ └── tsconfig.json # TypeScript配置 -├── docs/ # 项目文档 -│ └── PROJECT.md # 本文件 -├── .gitignore -└── README.md -``` - ---- - -## 三、后端文件详解 - -### 3.1 `main.py`(949行)— API主入口 - -FastAPI应用,包含全部HTTP API路由: - -**API路由:** -| 路径 | 方法 | 功能 | -|------|------|------| -| `/api/health` | GET | 健康检查 | -| `/api/signals/latest` | GET | 最新信号数据 | -| `/api/signals/history` | GET | 历史信号 | -| `/api/paper/summary` | GET | 模拟盘概览 | -| `/api/paper/positions` | GET | 当前持仓 | -| `/api/paper/trades` | GET | 历史交易 | -| `/api/paper/equity-curve` | GET | 权益曲线 | -| `/api/paper/stats` | GET | 详细统计(支持按币种) | -| `/api/paper/config` | GET/POST | 模拟盘配置 | -| `/api/server/status` | GET | 服务器监控 | - -**依赖:** FastAPI, uvicorn, asyncpg, psutil - -**运行方式:** `uvicorn main:app --host 0.0.0.0 --port 4332` - ---- - -### 3.2 `signal_engine.py`(739行)— 🔥 核心信号引擎 - -**最重要的文件**。每15秒评估一次4个币种的交易信号。 - -**5层评分体系(100分满分):** - -| 层级 | 权重 | 信号源 | 逻辑 | -|------|------|--------|------| -| 方向层 | 45分 | CVD三轨(fast/mid) + P99大单 + CVD加速度 | 资金流向判断多空 | -| 拥挤层 | 20分 | 多空比 + 大户持仓比 | 市场拥挤度,反向指标 | -| 环境层 | 15分 | OI变化率 | 合约持仓量变化 | -| 确认层 | 15分 | CVD-Price背离 + 大单方向确认 | 信号交叉验证 | -| 辅助层 | 5分 | Coinbase Premium | 机构资金流向 | - -**核心类:** -- `TradeWindow`:滑动窗口,维护buy_vol/sell_vol/CVD,自动trim过期数据 -- `ATRCalculator`:ATR计算器,用于仓位管理和TP/SL计算 -- `SymbolState`:每个币种的完整状态(4个窗口 + ATR + 大单 + 市场指标) - -**数据流:** -``` -aggTrades(PG) → process_trade() → 4个TradeWindow更新 - ↓ - evaluate_signal() → 5层打分 - ↓ - score >= 60 → 开仓信号 - ↓ - paper_trading_open() → 写入paper_trades -``` - -**关键参数:** -- `WINDOW_FAST = 30min`:CVD快线窗口 -- `WINDOW_MID = 4h`:CVD慢线窗口 -- `WINDOW_DAY = 24h`:P99大单计算窗口 -- `EVAL_INTERVAL = 15s`:评估间隔 -- `SIGNAL_THRESHOLD = 60`:开仓阈值分数 -- `COOLDOWN = 300s`:同币种同方向冷却时间 - -**冷启动机制:** -1. 启动时`load_historical()`从PG读取最近4小时aggTrades灌入窗口 -2. 前3轮(45秒)不出信号(冷启动保护) - ---- - -### 3.3 `paper_monitor.py`(179行)— 模拟盘监控 - -独立PM2进程,通过WebSocket连接Binance实时监控持仓的TP/SL。 - -**核心逻辑:** -1. 启动时加载所有active/tp1_hit持仓 -2. 连接Binance aggTrade WS获取实时价格 -3. 每笔成交检查是否触发TP1/TP2/SL -4. 触发后更新paper_trades状态 - -**TP/SL规则:** -- TP1 = entry ± 1.5×ATR(平半仓) -- TP2 = entry ± 3.0×ATR(平剩余) -- SL = entry ∓ 1.0×ATR -- TP1触发后SL移动到成本价(保本) - -**为什么独立进程:** TP/SL必须毫秒级响应,不能和15秒评估循环共享进程。 - ---- - -### 3.4 `db.py`(357行)— 数据库连接层 - -统一的PostgreSQL连接管理,支持同步和异步两种模式。 - -**连接池:** -- `get_sync_pool()` / `get_sync_conn()`:psycopg2同步池(供collector/signal_engine用) -- `get_async_pool()` / `async_fetch()`:asyncpg异步池(供FastAPI用) -- `get_cloud_sync_pool()` / `get_cloud_sync_conn()`:Cloud SQL双写池 - -**配置(环境变量):** -- `PG_HOST`:数据库地址(默认127.0.0.1,现在应指向Cloud SQL) -- `CLOUD_PG_HOST`:Cloud SQL地址(10.106.0.3) -- `CLOUD_PG_ENABLED`:双写开关 - -**Schema管理:** `init_schema()` + `ensure_partitions()`(按月自动建分区表) - ---- - -### 3.5 `agg_trades_collector.py`(307行)— aggTrades采集器 - -实时采集Binance合约aggTrades数据,是数据的源头。 - -**架构:** -- **WebSocket主链路**:4个币种各一条WS连接,实时推送 -- **REST补洞**:断线重连后用REST API从last_agg_id追平 -- **连续性巡检**:每60秒检查agg_id连续性,发现断档自动补洞 -- **批量写入**:攒200条或1秒flush一次 - -**双写机制:** flush_buffer同时写本地PG和Cloud SQL,Cloud SQL写失败不影响主流程。 - -**数据量:** 目前约7000万条(BTC 23天 + ETH 3天 + XRP/SOL 3天) - ---- - -### 3.6 `market_data_collector.py`(192行)— 市场指标采集 - -每5分钟从Binance REST API采集市场指标,存入`market_indicators`表。 - -**采集指标(5种):** -| 指标 | API | 用途 | -|------|-----|------| -| long_short_ratio | /futures/data/globalLongShortAccountRatio | 多空比 | -| top_trader_position | /futures/data/topLongShortPositionRatio | 大户持仓比 | -| open_interest_hist | /futures/data/openInterestHist | 持仓量变化 | -| coinbase_premium | Coinbase vs Binance价差 | 机构资金流 | -| funding_rate | /fapi/v1/fundingRate | 资金费率 | - ---- - -### 3.7 `liquidation_collector.py`(140行)— 清算数据采集 - -连接Binance WebSocket `forceOrder`流,实时采集强制平仓事件。 - -**双层存储:** -1. `liquidations`表:每笔清算原始记录 -2. `market_indicators`表:每5分钟聚合(long_liq_usd/short_liq_usd/total/count) - -**Side映射:** BUY order = SHORT被清算,SELL order = LONG被清算 - ---- - -### 3.8 `auth.py`(384行)— 认证系统 - -基于JWT的用户认证,邀请码注册制。 - -**功能:** -- 注册(需邀请码)/ 登录 / Token刷新 -- Admin管理(邀请码生成/用户封禁) -- JWT签发(access_token 24h + refresh_token 7d) - -**安全:** scrypt密码哈希 + HMAC-SHA256 JWT签名 - ---- - -### 3.9 `backtest.py`(503行)— 回测框架 - -对历史aggTrades数据运行信号引擎,验证策略表现。 - -**输出指标:** 胜率、PF、夏普比率、最大回撤、按方向/币种统计 - ---- - -### 3.10 其他文件 - -| 文件 | 行数 | 说明 | -|------|------|------| -| `backfill_agg_trades.py` | 209 | aggTrades历史数据回补(REST API批量拉取) | -| `admin_cli.py` | 123 | 命令行管理工具(生成邀请码、管理用户) | -| `signal_pusher.py` | 108 | **[已废弃]** 旧版信号推送(仍用SQLite,可删除) | -| `subscriptions.py` | 23 | **[预留]** 订阅系统占位 | -| `migrate_sqlite_to_pg.py` | 215 | **[一次性]** SQLite→PG数据迁移脚本 | -| `migrate_auth_sqlite_to_pg.py` | 170 | **[一次性]** Auth SQLite→PG迁移脚本 | - ---- - -## 四、前端文件详解 - -### 4.1 页面 - -| 文件 | 行数 | 功能 | -|------|------|------| -| `app/page.tsx` | 320 | 首页仪表盘(资金费率卡片 + 概览) | -| `app/signals/page.tsx` | 523 | 信号引擎(4币种实时评分 + 5层分数展示) | -| `app/paper/page.tsx` | 459 | 模拟盘(当前持仓 + 历史交易 + 权益曲线 + 按币种统计) | -| `app/trades/page.tsx` | 384 | 历史交易(筛选、排序、详情) | -| `app/server/page.tsx` | 279 | 服务器监控(PM2进程 + CPU/内存 + PG状态) | -| `app/kline/page.tsx` | 170 | K线图 | -| `app/live/page.tsx` | 130 | 实时数据流 | -| `app/dashboard/page.tsx` | 112 | 仪表盘 | -| `app/login/page.tsx` | 75 | 登录 | -| `app/register/page.tsx` | 88 | 注册(邀请码) | -| `app/about/page.tsx` | 81 | 关于 | -| `app/layout.tsx` | 34 | 全局布局 | - -### 4.2 组件 - -| 文件 | 行数 | 功能 | -|------|------|------| -| `components/Sidebar.tsx` | 139 | 侧边导航栏 | -| `components/LiveTradesCard.tsx` | 124 | 实时交易卡片组件 | -| `components/FundingChart.tsx` | 95 | 资金费率图表(Recharts) | -| `components/RateCard.tsx` | 82 | 费率展示卡片 | -| `components/Navbar.tsx` | 71 | 顶部导航 | -| `components/StatsCard.tsx` | 57 | 统计卡片 | -| `components/AuthHeader.tsx` | 37 | 认证状态头部 | - -### 4.3 工具库 - -| 文件 | 行数 | 功能 | -|------|------|------| -| `lib/auth.tsx` | 137 | JWT管理(存储/刷新/authFetch封装) | -| `lib/api.ts` | 116 | API请求基础封装 | - ---- - -## 五、数据库Schema - -### PostgreSQL(Cloud SQL) - -**核心表:** - -| 表名 | 说明 | 数据量 | -|------|------|--------| -| `agg_trades` | aggTrades分区父表 | 7039万+ | -| `agg_trades_202602` | 2月分区 | 6854万 | -| `agg_trades_202603` | 3月分区 | 185万+ | -| `agg_trades_meta` | 每币种最新agg_id | 4条 | -| `signal_indicators` | 信号评分记录(每15秒×4币种) | 2.7万+ | -| `market_indicators` | 市场指标(每5分钟×4币种×5指标) | 5400+ | -| `paper_trades` | 模拟盘交易记录 | 163+ | -| `liquidations` | 清算事件原始记录 | 3600+ | -| `rate_snapshots` | 资金费率快照(旧,K线图用) | 12万+ | -| `users` | 用户表 | 1 | -| `invite_codes` | 邀请码 | 1 | -| `subscriptions` | 订阅信息 | 1 | -| `refresh_tokens` | JWT刷新令牌 | — | -| `signal_indicators_1m` | 1分钟粒度信号(备用) | — | -| `signal_trades` | 信号交易记录(旧) | — | -| `signal_logs` | 信号日志(旧) | — | - ---- - -## 六、PM2进程列表 - -| 进程名 | 文件 | 端口 | 说明 | -|--------|------|------|------| -| `arb-api` | backend/main.py | 4332 | FastAPI后端 | -| `arb-web` | frontend/ | 4333 | Next.js前端 | -| `signal-engine` | backend/signal_engine.py | — | 信号评估引擎 | -| `paper-monitor` | backend/paper_monitor.py | — | 模拟盘TP/SL监控 | -| `agg-collector` | backend/agg_trades_collector.py | — | aggTrades采集 | -| `market-collector` | backend/market_data_collector.py | — | 市场指标采集 | -| `liq-collector` | backend/liquidation_collector.py | — | 清算数据采集 | - ---- - -## 七、信号源清单 - -| # | 信号源 | 采集方式 | 存储 | 评分使用 | -|---|--------|---------|------|---------| -| 1 | CVD三轨(fast 30m / mid 4h) | aggTrades WS实时计算 | 内存 | ✅ 方向层 | -| 2 | P99大单流 | aggTrades实时统计 | 内存 | ✅ 方向层 | -| 3 | CVD加速度 | CVD差分计算 | 内存 | ✅ 方向层+5 bonus | -| 4 | 多空比 + 大户持仓比 | Binance REST 5min | market_indicators | ✅ 拥挤层 | -| 5 | OI变化率 | Binance REST 5min | market_indicators | ✅ 环境层 | -| 6 | Coinbase Premium | Coinbase+Binance价差 | market_indicators | ✅ 辅助层 | -| 7 | Funding Rate | Binance REST 5min | market_indicators | ⬜ 采集中 | -| 8 | 清算数据 | Binance WS forceOrder | liquidations + market_indicators | ⬜ 采集中 | - ---- - -## 八、模拟盘配置 - -| 参数 | 值 | -|------|-----| -| 初始资金 | $10,000 | -| 单笔风险 | 2%($200 = 1R) | -| 最大同时持仓 | 4个 | -| TP1 | 1.5× ATR(平半仓) | -| TP2 | 3.0× ATR(平剩余) | -| SL | 1.0× ATR | -| 手续费 | Taker 0.05% × 2(开仓+平仓) | -| 反向信号 | 先平仓再开新仓 | -| 冷却时间 | 同币种同方向300秒 | - ---- - -## 九、部署信息 - -| 项目 | 详情 | -|------|------| -| 运行服务器 | GCP asia-northeast1-b (n2-standard-2, 2核8G) | -| 数据库 | GCP Cloud SQL PG18 (8核64G 100G) | -| Cloud SQL 内网IP | 10.106.0.3 | -| Cloud SQL 公网IP | 34.85.117.248 | -| Web访问 | https://arb.zhouyangclaw.com | -| Git仓库 | https://git.darkerilclaw.com/lulu/arbitrage-engine.git | -| 反向代理 | Caddy(SSL自动) | - ---- - -## 十、版本路线图 - -| 版本 | 状态 | 内容 | -|------|------|------| -| V5.0 | ✅ 完成 | 基础信号系统 + PG迁移 | -| V5.1 | ✅ 当前 | 5层评分 + 模拟盘 + 6信号源评分 + 2信号源采集 | -| V5.2 | 📋 计划 | FR+清算加入评分 + 策略配置化 + AB测试 + 24h warmup | -| V5.3 | 📋 远期 | 推特新闻情绪信号(多模型投票制) | - ---- - -## 十一、已知问题与待改进 - -1. **signal_pusher.py 已废弃**:仍用SQLite,应删除或重写 -2. **subscriptions.py 空文件**:预留的订阅系统未实现 -3. **history/page.tsx 空页面**:只有5行占位代码 -4. **冷启动warmup只有4小时**:P99大单需要24小时数据(V5.2改进) -5. **开仓价用信号评估价**:实盘需改为真实成交价 -6. **双写机制**:切主库后agg_collector的本地双写可关闭 -7. **前端缺少错误边界**:API异常时无友好提示 diff --git a/docs/STRATEGY_FACTORY_VALIDATION.md b/docs/STRATEGY_FACTORY_VALIDATION.md new file mode 100644 index 0000000..dfd1e2b --- /dev/null +++ b/docs/STRATEGY_FACTORY_VALIDATION.md @@ -0,0 +1,497 @@ +# 策略工厂与信号引擎验证清单(Strategy Factory & Signal Engine Validation) + +> 目的:把需要“逐项验证”的点全部列清楚,变成可打勾的 checklist。 +> 范围:`backend/signal_engine.py`(single-engine 数据发射源) + V5.4 策略工厂(`strategies` 表 → 信号 / 模拟盘)。 +> 权威规格仍以 `docs/arbitrage-engine-full-spec.md` 为准,本文件只列“需要检验的点”。 + +--- + +## 1. 背景与角色划分(人话版) + +- single-engine(`backend/signal_engine.py`)在本系统里扮演的是: + - 从 `agg_trades` + `market_indicators` 等数据源,滚动算出各种指标; + - 每 15 秒左右,对每个运行中策略打分、判断是否开仓; + - 把结果写入: + - `signal_indicators`(信号与指标快照); + - `paper_trades`(模拟盘开仓记录); + - 并通过 NOTIFY 推送给其他进程。 + +- V5.4 策略工厂的职责是: + - 把 `strategies` 表中 `status='running'` 的每一行,当成一个“策略实例”; + - 对每个实例,组合: + - 多窗口 CVD(5m / 15m / 30m / 1h / 4h); + - ALT 四层评分(Direction / Crowding / Environment / Auxiliary); + - 5 个否决 Gate(vol / cvd / whale / obi / spot_perp)的阈值与开关; + - 在统一的信号模型上,跑出不同风格的策略。 + +当前假设: + +- 你只对 **aggTrades 数据流** 有信心,其他所有东西(指标/门控/评分/策略映射/落库)都需要重新审计; +- 我们需要一个尽可能完整的“验证清单”,后续可以按照这份清单逐条打勾。 + +下面把需要检查的点分成 3 层: + +- A. 数据与指标层:所有输入信号是不是对的(基础指标本身) +- B. 策略配置与工厂层:DB 字段是否正确映射成策略参数 +- C. 决策与落库层:评分 / 门控 / 开仓 / 写库 / 冷却 / 方向限制等是否符合规则 + +--- + +## 2. 总体结构视图(先有个地图) + +- **数据来源** + - `agg_trades`:逐笔成交 → 多窗口 CVD / ATR / VWAP / 大单 P95/P99 / 巨鲸成交; + - `market_indicators`:OI、多空比、Top Trader、资金费率、Coinbase Premium、OBI、期现背离等; + - 实时 WebSocket:OBI 实时值、spot-perp divergence 实时值(如果有)。 + +- **策略配置** + - `strategies` 表:策略实例的参数(symbol、方向、CVD 窗口、四层权重、五门阈值、TP/SL、timeout、flip_threshold 等); + - `load_strategy_configs_from_db()`:把这些行映射成内部配置 dict。 + +- **决策 / 落库** + - `evaluate_factory_strategy()`(别名 `evaluate_v53`,原 `_evaluate_v53`):统一的评分与 Gates 逻辑; + - `save_indicator()`:写 `signal_indicators`; + - `paper_open_trade()`:写 `paper_trades`; + - `NOTIFY new_signal`:推送给 live_executor 等其他进程。 + +这 3 层每一处的小错误,都可能直接导致“赚钱 / 亏钱”的行为偏差,所以需要逐项验证。 + +--- + +## 3. A 层:数据与指标验证(single-engine 基础指标) + +### A1. 时间线与窗口基础 + +- ✅ `agg_trades.time_ms` 的语义是否统一为 **毫秒时间戳**,无混用秒/毫秒的情况。 +- ✅ 冷启动加载历史时,时间范围是否覆盖至少 `24h+`,足够支持所有窗口(5m/15m/30m/1h/4h/24h)。 + - 当前实现:冷启动预载 4h(WINDOW_MID),之后由实时数据自然填充到 24h。此行为已知且暂视为接受的设计,不视为 bug。 +- ✅ 滚动窗口裁剪规则是否一致(例如统一使用 `t >= now_ms - window_ms`),避免 off-by-one。 +- ✅ 历史回补或乱序插入时,是否会导致窗口内时间回跳,进而影响 CVD/ATR 等指标。 + - 当前实现:agg_trades_collector 通过 REST 补洞 + 连续性巡检,配合按 agg_id/time_ms 有序读取,认为不会产生系统性时间回跳问题。 + +### A2. CVD 多窗口计算 + +目标:确认 5m / 15m / 30m / 1h / 4h 所有 CVD 计算都符合“主动买入量 - 主动卖出量”的定义。 + +- ✅ 对 is_buyer_maker 的解释是否正确: + - 实现:采集时 `is_buyer_maker = 1 if t["m"] else 0`,CVD 里 `is_buyer_maker == 0` 计为买量(taker 买),`==1` 计为卖量(taker 卖); + - 结论:与 Binance aggTrade 语义一致,CVD = taker 买入量 - taker 卖出量,符合预期。 +- ✅ 基础 30m / 4h 窗口(`win_fast` / `win_mid`)中,新增/裁剪 trade 时 buy/sell 累计是否严格同步更新。 + - 实现:所有 trade 通过 `TradeWindow.add()` 进入窗口,`trim()` 时同步回退 buy/sell 累计,CVD 始终等于当前窗口内的买减卖。 +- ✅ 其他窗口(5m/15m/1h)的 CVD 是否通过对已有窗口(win_fast/win_mid)的列表按时间切片重算,而不是重新查库。 + - 实现:通过 `_window_ms()` 把窗口字符串转毫秒,从 `win_fast`/`win_mid` 的 `trades` 列表按 `t_ms >= now_ms - window_ms` 切片重算,未重复查库。 +- ✅ 所有窗口的截止时间点是否都使用当前 `now_ms`,不会出现“5m 用旧 now_ms,30m 用新 now_ms”的错位。 + - 实现:`build_evaluation_snapshot(now_ms)` 与 `evaluate_factory_strategy(now_ms, ...)`(内部别名 `evaluate_v53`)使用同一个 `now_ms`,动态切片和快照在同一时间基准上计算。 +- ⚠️ 斜率与加速度: + - ✅ 定义:`cvd_fast_slope = cvd_fast(now) - prev_cvd_fast`,`cvd_fast_accel = cvd_fast_slope(now) - prev_cvd_fast_slope`,在持续运行时语义正确; + - ⚠️ 冷启动第一帧使用 `prev_* = 0` 计算,未做专门回退为 0 的处理,可能使第一帧 slope/accel 数值偏大;目前视为需要后续评估/是否调整的注意点。 + +### A3. ATR 与 ATR 百分位 + +- ✅ 5m K 线 bucket 是否按 `bar_ms = (time_ms // period_ms) * period_ms` 计算,保证时间对齐。 + - 实现:`ATRCalculator.update()` 中按 `time_ms // period_ms` 对齐 5m bar,符合预期。 +- ✅ Candle 的 open/high/low/close 更新逻辑是否正确,所有 trade 都被处理到。 + - 实现:新 bar 时写入旧 bar 到队列,当前 bar 逐笔更新 high/low/close。 +- ✅ TR(True Range)计算是否符合标准定义: + - 实现:`max(high - low, |high - prev_close|, |low - prev_close|)`,与标准 TR 定义一致。 +- ✅ ATR 是按何种算法计算(简单平均 / Wilder EMA),与 full-spec 中的期望是否一致。 + - 实现:先取第一根 TR 作为初始值,再用 `(atr_val*(length-1)+tr)/length` 逐步平滑,为 Wilder 风格 EMA,符合设计。 +- ✅ 当 candle 数量不足(小于 2 或小于 ATR length)时,ATR 返回 0 或合理默认值。 + - 实现:`len(candles) < 2` 时直接返回 0.0,避免未成熟 ATR 参与决策。 +- ✅ `atr_percentile`: + - ✅ 历史 ATR 样本长度、维护方式(队列长度)是否合理; + - ✅ 百分位算法(<= current / count)是否正确; + - ✅ 样本太少时是否返回 50.0,避免无意义的极端值。 + - 实现:`atr_history` 长度上限 288,少于 10 条或当前 ATR=0 时返回 50.0,其余按 rank 百分位计算。 + +### A4. P95 / P99 大单阈值 + +- ✅ 使用的样本是否来自 24h 窗口(`win_day`),并且只取 qty(不乘 price)。 + - 实现:`compute_p95_p99()` 直接遍历 `win_day.trades`,取 `t[1]`(qty)构造样本数组,窗口长度即 24h。 +- ✅ 样本数 < 100 时是否使用保底常数(BTC: p95>=5, p99>=10;ALT: p95>=50, p99>=100),是否与策略设计一致。 + - 实现:少于 100 条时直接返回 `(5.0, 10.0)`;样本足够时再按 symbol 区分 BTC/ALT 保底。 +- ✅ 排序和索引算法:`sorted(qtys)` 后,`int(n * 0.95)` / `int(n * 0.99)` 是否符合预期的分位点定义。 + - 实现:对 qtys 排序后按整数下标取 95% / 99% 位置,属于合理的分位点近似。 +- ✅ ALT 的分类逻辑是否只是“非 BTC 即 ALT”,以及这是否满足未来新增 symbol 的需求。 + - 实现:判断条件为 `"BTC" in self.symbol`,否则按 ALT 处理;目前支持的 symbol 集合有限(BTC/ETH/XRP/SOL),该简化规则可接受,如未来扩展再细化。 + +### A5. 巨鲸成交与 `whale_cvd_ratio` + +- ✅ 巨鲸交易过滤条件是否为 `price * qty >= 100_000`(100k USD),阈值可配置或硬编码? + - 实现:`usd_val = price * qty`,`usd_val >= 100_000` 视为巨鲸成交,阈值目前硬编码为 100k。 +- ✅ `_whale_trades` 窗口长度是否为 15 分钟,裁剪逻辑是否正确(按 time_ms)。 + - 实现:窗口长度 `WHALE_WINDOW_MS = 15min`,按 trade 的 `time_ms` 滑动裁剪。 +- ✅ `whale_cvd_ratio` 计算: + - ✅ buy_usd = 所有巨鲸买单金额之和; + - ✅ sell_usd = 所有巨鲸卖单金额之和; + - ✅ ratio = (buy_usd - sell_usd) / (buy_usd + sell_usd); + - ✅ 无数据时返回 0.0 是否符合预期。 +- ✅ BTC 与 ALT 对巨鲸数据的使用差异是否符合设计: + - 实现:BTC 用 `whale_cvd_ratio`(或 DB 中 `tiered_cvd_whale`)做 Gate;ALT 使用 `recent_large_trades` 中的对立/同向大单判断。 + - 备注:鲸鱼 Gate 的“占比阈值”在 C2 中有一个缩放 bug(`whale_flow_pct` 被多除以 100),已在 Gate 小节标记为问题点。 + +### A6. `market_indicators` 中的 JSON 指标 + +需要逐个核对字段名称与意义(对照实际 JSON): + +- ✅ `long_short_ratio`: + - 实现:从 `market_indicators` 读取 JSON `value`,使用 key `longShortRatio` 转为 float;默认 1.0。 +- ✅ `top_trader_position`: + - 实现:使用 key `longAccount`,代表 top trader 多头占比(0~1)。 +- ✅ `open_interest_hist`: + - 实现:使用 key `sumOpenInterestValue` 作为 OI 数值,后续只用来计算相对变化率。 +- ✅ `coinbase_premium`: + - 实现:使用 key `premium_pct`,并统一除以 100 转成小数,避免量纲混乱。 +- ✅ `funding_rate`: + - 实现:优先使用 `fundingRate`,否则 fallback `lastFundingRate`。 +- ✅ `obi_depth_10`: + - 实现:使用 key `obi`,作为 [-1,1] 区间内的订单簿不平衡(正=买压,负=卖压),与 Gate4 逻辑一致。 +- ✅ `spot_perp_divergence`: + - 实现:使用 key `divergence`,注释中定义为 `(spot - mark) / mark`,与 Gate5 使用方式一致。 + +### A7. OI 环境指标 + +- ✅ OI 读取:`open_interest_hist` 是否转换为 float; + - 实现:通过 `to_float(self.market_indicators.get("open_interest_hist"))` 转为 float。 +- ✅ OI 变化率定义:`oi_change = (oi_now - oi_prev) / oi_prev`,oi_prev>0 的保护是否到位。 + - 实现:仅在 `prev_oi_value > 0` 时计算变化率,否则 oi_change=0.0。 +- ✅ environment_score_raw 的区间划分(例如 >=3%、0~3%、<0)对应基础分是否与 full-spec 一致。 + - 实现:oi_change >=3% → 15 分;>0 → 10 分;<=0 → 5 分,符合设计。 +- ✅ `prev_oi_value` 的更新时机:只在 oi_value>0 时更新是否会导致长时间停留在 0。 + - 实现:oi_value>0 时才覆盖 prev,启动后首帧用默认 10 分,后续随 OI 变化更新,行为符合预期。 + +### A8. 其他环境与拥挤指标 + +- ✅ `long_short_ratio` 的逻辑:>1 多头拥挤,<1 空头拥挤,这个假设需要与真实数据验证。 + - 实现:LONG 方向时 lsr 较低(<1,<0.7,<0.5)给更高分,SHORT 方向时 lsr 较高(>1,>1.5,>2)给更高分,符合“多空占比”直觉。 +- ✅ crowding 层中对 LONG / SHORT 的条件: + - 实现与上述区间一致,并在 V5.1 路径中用 crowding_score=ls_score+top_trader_score;V5.3 路径中 crowding_score capped 为 25 分。 +- ✅ `top_trader_position` 映射: + - 实现:LONG:>=0.55 给 10 分、<=0.45 给 0 分、其他给 5 分;SHORT 反向,对应“top trader 站在我们这一边时加分”的逻辑。 +- ✅ `coinbase_premium` 阈值: + - 实现:>0.0005 / <-0.0005 视为强信号得 5 分;|premium|<=0.0005 得 2 分;其他情况 0 分,与 full-spec 描述一致。 + - 备注:精确阈值是否需要微调属于策略优化问题,暂不视为实现 bug。 + +### A9. 时间与时区统一 + +- ✅ `agg_trades.time_ms`、`market_indicators.timestamp_ms`、`signal_indicators.ts` 是否全部为 UTC 毫秒。 + - 实现:agg_trades 来自 Binance aggTrade `T`(毫秒),market_indicators 查询按 `timestamp_ms` 排序,signal_indicators.ts 由 `int(time.time()*1000)` 生成,语义统一为毫秒。 +- ✅ signal_engine 的 `now_ms` 是否与最新成交的时间差在可接受范围内(比如 <5 秒)。 + - 实现:每轮循环用当前 `time.time()` 作为 now_ms,立刻用 `fetch_new_trades` 拉取自上次 agg_id 之后的 tick 并入窗口,逻辑上不会积累大延迟。 +- ✅ 所有窗口、冷却时间、timeout 等是否都用毫秒表达,避免混用秒/分钟。 + - 实现:窗口常量、冷却时间、timeout 以及 liquidations 窗口等全部以毫秒表示。 + +--- + +## 4. B 层:策略配置与工厂验证(DB → 内存策略) + +### B1. 行筛选与顺序 + +- ✅ `load_strategy_configs_from_db()` 是否只选择 `status='running'` 的行。 + - 实现:SQL 显式 `WHERE status = 'running'`。 +- ⚠️ 是否有必要基于 `schema_version` 做过滤(未来版本升级时)。 + - 实现:当前未按 `schema_version` 过滤,所有 running 策略视为同一版本;如未来引入多版本策略,需要补充。 +- ✅ ORDER BY created_at 是否会在行为上产生影响(一般无关,但需要意识到)。 + - 实现:仅影响策略评估与写入的顺序,对行为影响有限。 + +### B2. 字段映射完整性 + +逐字段确认 DB → cfg 的映射: + +- ✅ `strategy_id`(uuid)是否被转为 string,并赋值到 `cfg["strategy_id"]`。 + - 实现:SQL 中 `strategy_id::text`,后续直接写入 cfg。 +- ✅ `display_name` 是否写入 `cfg["strategy_name_snapshot"]`。 +- ✅ `symbol` / `direction` 是否原样保留,大小写约定是否固定。 + - 实现:直接从 DB 取值赋入 cfg,不做大小写转换。 +- ✅ `cvd_fast_window` / `cvd_slow_window` 是否直接映射到 cfg。 +- ✅ `weight_direction` / `weight_env` / `weight_aux` / `weight_momentum` 是否正确组成 `cfg["weights"]`。 +- ✅ `entry_score` / `flip_threshold` 是否正确映射到 `cfg["threshold"]` / `cfg["flip_threshold"]`。 +- ✅ `gate_obi_enabled` / `gate_whale_enabled` / `gate_vol_enabled` / `gate_spot_perp_enabled` / `gate_cvd_enabled` 是否被正确翻译成 bool。 + - 实现:直接使用 DB 中的 boolean 字段。 +- ✅ 所有 Gate 相关 float 字段(`obi_threshold`、`whale_usd_threshold`、`whale_flow_pct`、`vol_atr_pct_min`、`spot_perp_threshold`) + 在 None 时有合理默认(与 full-spec 匹配)。 + - 实现:在转入 cfg 时对 None 做 `or 默认值` 处理,默认值来源于迁移脚本/旧 symbol_gates。 +- ✅ `sl_atr_multiplier` / `tp1_ratio` / `tp2_ratio` 是否正确映射到 `cfg["tp_sl"]`。 +- ✅ `timeout_minutes` 是否正确映射到 `cfg["timeout_minutes"]`。 + +### B3. legacy 策略兼容逻辑 + +- [ ] 固定 UUID: + - `...000053` → `"v53"`; + - `...000054` → `"v53_middle"`; + - `...000055` → `"v53_fast"`; + 是否只对 deprecated legacy 策略生效。 +- [ ] 其他策略是否统一命名为 `custom_`,与历史 `strategy` 字段约定一致。 +- [ ] 当 DB 无策略时,是否 fallback 到 JSON 文件配置;当 DB 有策略时是否会重复加载 JSON。 + +更新: + +- ✅ 固定 UUID 的映射逻辑正确,且只通过 `LEGACY_UUID_MAP` 在内存层生效,不会修改 DB 中的 strategy_id。 +- ✅ 其他策略统一命名为 `custom_`,与历史 `strategy` 字段兼容。 +- ✅ main() 中当 DB 读不到策略(异常)时才 fallback 到 JSON,正常情况下不会 DB+JSON 混用。 + +### B4. CVD 窗口参数 + +- ✅ `cvd_fast_window` / `cvd_slow_window` 的合法取值集合:是否只支持 {5m,15m,30m,1h,4h}。 + - 实现:`_window_ms()` 支持任意 m/h,策略层目前只用上述几种组合。 +- ✅ 非法值时行为:抛出异常还是回退到默认 30m/4h。 + - 实现:未匹配 "m"/"h" 后缀时回退到 30m,对异常配置有兜底。 +- ✅ `_window_ms()` 的实现是否覆盖所有这些枚举值。 +- ✅ `evaluate_factory_strategy()` 中选择 trade 源时,fast/slow 窗口与 win_fast/win_mid 的映射是否一致。 + - 实现:fast 窗口长度 <=30m 用 win_fast,否则用 win_mid;slow 总是用 win_mid,与 full-spec 描述一致。 + +### B5. 四层权重与 entry/flip + +- ✅ `weights` 是否已真实参与 scoring(direction/env/aux/momentum 四层权重)。 + - 现状:V5.4 `strategies` 中的权重字段被映射到 cfg["weights"](direction/env/aux/momentum),`evaluate_factory_strategy()` 会将四个权重归一化到 100 分,并把 direction+momentum 总权重按 55:25 的比例拆分给 direction/crowding,再按各层原始得分比例缩放,真实影响 total_score。 +- ✅ `entry_score` 是否只用于“达到该分数即可考虑开仓”,不会在别处被重载。 + - 实现:在 `evaluate_factory_strategy()` 中作为标准开仓阈值(total_score < entry_score 时不会产生 signal),signal_engine 通过是否有 signal 决定是否尝试开仓。 +- ✅ `flip_threshold` 是否用于“重仓 / 反向强平”逻辑,阈值关系是否是 `flip_threshold >= entry_score`。 + - 实现:在 `evaluate_factory_strategy()` 中,total_score >= flip_threshold 时记为重仓 tier="heavy";在 `signal_engine` 中,当反向信号的 `score >= flip_threshold` 时触发 signal_flip 平仓。代码允许设置任意数值,上层应保证配置关系。 +- ⚠️ 当 `flip_threshold < entry_score` 时,系统行为是否有定义或需要禁止。 + - 现状:代码未禁止这种配置,会导致实际行为变成“更容易触发 heavy 仓”;需要在策略配置侧约束,暂不视为实现错误。 + +### B6. Gate 参数与开关 + +逐 Gate 检查 DB → cfg 的映射: + +- [ ] Gate vol: + - `gate_vol_enabled` → `gates["vol"]["enabled"]`; + - `vol_atr_pct_min` → `gates["vol"]["vol_atr_pct_min"]`,默认值与 full-spec 一致。 +- [ ] Gate cvd: + - `gate_cvd_enabled` → `gates["cvd"]["enabled"]`; + - 默认是否为 True,并与 full-spec 一致。 +- ✅ Gate whale: + - `gate_whale_enabled` → `gates["whale"]["enabled"]`; + - `whale_usd_threshold` → `gates["whale"]["whale_usd_threshold"]`; + - ✅ `whale_flow_pct` → `gates["whale"]["whale_flow_pct"]`(注意是否有 /100 的处理)。 + - 更新:`evaluate_factory_strategy()` 中已去掉多余的 `/100` 缩放,现使用 0~1 的比例值与 DB/迁移脚本一致,鲸鱼 Gate 灵敏度恢复到设计水平。 +- [ ] Gate obi: + - `gate_obi_enabled` → `gates["obi"]["enabled"]`; + - `obi_threshold` → `gates["obi"]["threshold"]`。 +- [ ] Gate spot_perp: + - `gate_spot_perp_enabled` → `gates["spot_perp"]["enabled"]`; + - `spot_perp_threshold` → `gates["spot_perp"]["threshold"]`。 + +### B7. 策略方向限制 + +- ⚠️ DB 中的 `direction` 字段枚举是否固定为 `"long" | "short" | "both"`(大小写统一)。 + - 现状:代码层直接使用 DB 文本,不验证枚举/大小写;依赖上游迁移与 API 保证有效值。 +- ✅ `evaluate_factory_strategy()` 或上层逻辑中是否有对该字段的应用: + - 预期:direction=long → 禁止 SHORT 信号开仓;direction=short → 禁止 LONG 信号开仓;direction=both → 不限制。 + - 现状:已在 `evaluate_factory_strategy()` 内增加 per-strategy 方向约束: + - long 策略在方向判定为 SHORT 时不会产生 signal(但仍计算评分与指标快照); + - short 策略在方向判定为 LONG 时不会产生 signal; + - both 不做限制。 +- ⚠️ 若当前实现未对 strategy direction 做约束,需要在此记为一个重点检查项。 + - 更新:方向约束已在信号生成阶段生效,但反向 signal_flip 平仓仍按“环境方向”判断,是否也需要 direction 约束留待后续决策。 + +--- + +## 5. C 层:决策与落库验证(评分 + 门控 + 开仓 + 写库) + +### C1. 逐 symbol / 逐策略评估流程 + +- ✅ 评估循环是否为:先按 symbol 分组,再对每个 symbol 内的所有策略依次评估。 +- ⚠️ 对于每个策略,是否在评估前检查 `cfg["symbol"] == state.symbol`,确保不会在错误币种上开仓。 + - 现状:评估阶段对所有策略都计算分数,但在开/平仓阶段才按 `strategy_cfg["symbol"]` 过滤,因此不会在错误币种上实际开仓,只是多算了一些“无用分”。 +- ✅ 是否存在“一个策略在多个 symbol 上同时运行”的预期场景,如果有,需要明确规则。 + - 现状:当前配置一条策略只绑定一个 symbol,引擎逻辑允许同一策略在多个 symbol 开仓,但上层 API 不这么用。 + +### C2. 五个 Gate 的逻辑 + +对于每一门,需要验证条件与 full-spec 一致: + +- Gate1:波动率门(ATR/price 下限) + - ✅ `atr_pct_price = atr / price` 的计算是否正确; + - ✅ 阈值 `min_vol` 是否来自 DB 的 `vol_atr_pct_min` 或 JSON fallback; + - ✅ 条件:`atr_pct_price < min_vol` 时,gate_block = `"low_vol(...)"`,并且后续直接视为不通过。 + +- Gate2:CVD 共振门 + - ✅ 快慢 CVD 同向时方向判断是否为: + fast>0 且 mid>0 → LONG;fast<0 且 mid<0 → SHORT; + - ✅ 不同向时,若 gate_cvd_enabled=True,则设置 `no_direction=True` 且 `gate_block="no_direction_consensus"`; + - ✅ 当 Gate2 关闭(enabled=False)时,是否允许仅根据 fast CVD 决定方向。 + +- Gate3:鲸鱼否决门 + - BTC: + - ✅ 使用 `whale_cvd_ratio` 或 fallback 到 `market_indicators["tiered_cvd_whale"]`; + - ✅ 条件: + - LONG 且 whale_cvd_ratio < -阈值 → 否决; + - SHORT 且 whale_cvd_ratio > 阈值 → 否决。 + - ALT: + - ✅ `recent_large_trades` 中,对立大单与同向大单的判定逻辑是否正确; + - ✅ 只有“有对立大单且无同向大单”时才否决,且金额阈值是否为 whale_usd_threshold × price。 + +- Gate4:OBI 否决门 + - ✅ OBI 采样顺序是否为:先用实时 `rt_obi`,否则用 DB 中 `obi_depth_10`; + - ✅ 条件: + - LONG 且 obi_raw < -obi_veto → Gate 拒绝; + - SHORT 且 obi_raw > obi_veto → Gate 拒绝。 + +- Gate5:期现背离否决门 + - ✅ 采样顺序:先用实时 `rt_spot_perp_div`,否则用 DB 中 `spot_perp_divergence`; + - ✅ 条件: + - LONG 且 divergence < -spd_veto → Gate 拒绝; + - SHORT 且 divergence > spd_veto → Gate 拒绝。 + +### C3. gate_block / gate_passed 对评分和信号的影响 + +- ✅ 任意一门触发后,`gate_block` 是否被设置为对应字符串,`gate_passed=False`。 +- ✅ 评分计算完后,如果 `gate_passed=False`,是否将 `total_score` 强制置为 0。 +- ✅ `result["signal"]` 在 gate_passed=False 或 no_direction=True 时是否总是 None。 +- ✅ `factors` 中是否仍然保留原始四层分数和 Gate 细节,方便事后分析。 + +### C4. 四层评分计算细节 + +- Direction Layer: + - ✅ CVD 共振基础分数(例如 30 分)是否与 full-spec 一致; + - ✅ p99_flow:有同向大单 / 无大单 / 有对立大单 三种情况的得分是否符合预期; + - ✅ accel_bonus:cvd_fast_accel 与方向同向时报 5 分; + - ✅ v53_fast 的独立加速路径(不要求双线共振)是否正确实现。 + +- Crowding Layer: + - ✅ long_short_ratio 与 top_trader_position 的区间划分与得分是否符合 full-spec; + - ✅ crowding_score capped 到 25 分。 + +- Environment Layer: + - ✅ ALT(或默认)路径:只用 OI 变化率映射出 environment_score_raw; + - ✅ v53_fast 额外的 OBI bonus:强/弱阈值与得分 5/3 是否正确; + - ✅ 总分 capped 到 15。 + +- Auxiliary Layer: + - ✅ coinbase_premium 的区间与得分(5/2/0)是否正确实施。 + +- 汇总: + - ✅ total_score = 四层得分之和 capped 到 100,并四舍五入到 0.1; + - ✅ 如果 `gate_passed=False`,最终 total_score 是否强制为 0。 + +### C5. 权重的实际使用情况 + +- ✅ 当前实现已经使用 `weights` 来调整各层最大分/权重(direction/env/aux/momentum),需要确认缩放规则是否符合预期; +- ✅ future:如要进一步调整权重逻辑,需要设计迁移/验证方案,并评估对历史统计的影响。 + +### C6. signal 生成与冷却(entry / flip / cooldown) + +- ✅ 条件逻辑: + - total_score >= flip_threshold → `tier="heavy"`; + - entry_score <= total_score < flip_threshold → `tier="standard"`; + - total_score < entry_score → 不开仓。 +- ✅ 冷却: + - COOLDOWN_MS = 10 分钟; + - 以 `strategy_name` 为 key 的 last_signal_ts 是否正确记录; + - 冷却期内不再生成新 signal。 +- ✅ `direction` 限制: + - 策略 direction=long/short/both 是否在最终生成 signal 和 signal_flip 时被应用: + - 只多策略:不会生成空头 signal,也不会因为空头评估方向触发 signal_flip 平仓; + - 只空策略:反之同理; + - both:行为与原来一致。 + +### C7. `signal_indicators` 落库字段映射 + +- ✅ ts:是否使用当前 `now_ms`,单位为毫秒。 +- ✅ symbol:与当前 `SymbolState.symbol` 一致。 +- ✅ strategy:写入 `cfg["name"]`(legacy 名称)。 +- ✅ strategy_id:写入 `cfg["strategy_id"]`(uuid string)。 +- ✅ strategy_name_snapshot:写入 `cfg["strategy_name_snapshot"]`。 +- ✅ 所有指标字段: + - cvd_fast / cvd_mid / cvd_day / cvd_fast_slope; + - atr_5m / atr_percentile / atr_value; + - vwap_30m / price; + - p95_qty / p99_qty; + - cvd_fast_5m(仅 v53_fast 有值); + 是否与 `snapshot` / 重新计算的一致。 +- ✅ factors(JSON): + - 是否包含 gate_passed / gate_block / atr_pct_price / obi_raw / spot_perp_div / whale_cvd_ratio; + - direction / crowding / environment / auxiliary 四个子对象的 score/max 等字段是否齐全。 + +### C8. `signal_feature_events`(如仍在使用) + +- ✅ 只对 v53 系列策略写入(策略名以 "v53" 开头)。 +- ✅ 原始字段(cvd_fast_raw 等)是否与 `result` 中的一致。 +- ✅ score_direction / score_crowding / score_environment / score_aux 是否与 factors 中一致。 +- ✅ gate_passed / block_reason 是否正确记录,便于后期分析。 + +### C9. `paper_open_trade` 模拟盘开仓逻辑 + +- ✅ ATR <= 0 时是否直接拒绝开仓,避免无意义 SL/TP。 +- ✅ SL/TP 价格计算: + - LONG:`SL = price - risk_distance`,`TP1 = price + tp1_ratio × risk_distance`,`TP2 = price + tp2_ratio × risk_distance`; + - SHORT:`SL = price + risk_distance`,`TP1 = price - tp1_ratio × risk_distance`,`TP2 = price - tp2_ratio × risk_distance`; + - 旧版 JSON 策略仍按 `tp*_mult × ATR` 方式计算,属于兼容分支; + 是否与 full-spec 中“以 R 计目标”的定义一致。 +- ✅ risk_distance = sl_multiplier × ATR 是否正确计算并写入。 +- ✅ SL 合理性校验: + - 实际 SL 距离是否在 [0.8, 1.2] × risk_distance 之间; + - 不满足时是否拒绝开仓并打日志(避免奇怪配置)。 +- ✅ 模拟盘全局开关: + - `PAPER_TRADING_ENABLED` / `PAPER_ENABLED_STRATEGIES` / `PAPER_MAX_POSITIONS` 是否生效; + - 同一策略是否有 per-strategy 的最大持仓控制(如无,需要记录)。 +- ✅ 写库字段: + - symbol / direction / score / tier / entry_price / entry_ts; + - tp1_price / tp2_price / sl_price / atr_at_entry; + - score_factors = factors JSON; + - strategy = `cfg["name"]`; + - strategy_id / strategy_name_snapshot; + - risk_distance; + 是否全部正确写入,对应字段类型与含义与 full-spec 一致。 + +### C10. 重复开仓与持仓交互 + +- ✅ 当前设计是否允许“同一策略在同一 symbol 上同时有多笔持仓”(分批进场)。 +- ✅ 是否存在逻辑限制“同一策略同方向仅允许一笔活动持仓”,如果没有,需要明确这一点。 +- ✅ 与 `paper_monitor` 的交互: + - 反向 signal 是否会触发 signal_flip 平仓; + - 平仓后冷却和再开仓的行为是否符合预期。 + +### C11. 通知与其他 side-effect + +- ✅ 当 `result["signal"]` 非空时,是否总是触发 `NOTIFY new_signal`,payload 格式是否是: + `"symbol:strategy:signal:score"`。 +- ✅ 当 gate_block 不为空(gate_passed=False)或 no_direction=True 时,是否绝不会发 NOTIFY。 +- ✅ 如果 live_executor 等进程依赖该 payload,是否已经约定好格式且长期不变。 + +--- + +## 6. 验证优先级建议 + +后续可以按下面优先顺序逐项打勾: + +**P0(必须最先验证的)** + +- A2:CVD 多窗口计算(5m/15m/30m/1h/4h 全链路)。 +- A3+A9:ATR / ATR 百分位 / 波动率门。 +- A5+A6:巨鲸、OBI、期现背离的数据源与符号方向。 +- C2+C3+C4+C6:五个 Gate + 四层评分 + entry/flip + cooldown 的交互。 +- C7+C9:`signal_indicators` / `paper_trades` 落库字段,特别是 strategy_id / strategy_name_snapshot 与 SL/TP/risk_distance。 + +**P1(中优先级)** + +- B2~B7:`strategies` 表字段映射完整性、CVD 窗口、Gate 参数、方向限制。 +- C1:per-symbol 遍历逻辑与 symbol 过滤。 +- C8:`signal_feature_events` 的字段映射(如还在使用)。 + +**P2(相对低优先级)** + +- 对权重缩放逻辑的长期行为做验证(C5),确保与预期一致; +- 日志/监控是否足够支撑线上排查; +- 未来如需长期在线学习,可在此基础上加更多验证点。 + +--- + +## 7. 使用方式(给人类 / AI 的操作建议) + +- 把本文件当作 **审计 checklist**: + - 每次改动前,先在这里找相关条目,确认“期望行为”是什么; + - 审计现有代码时,对照这些条目逐项比对,实现与期望不一致的地方要记录为 bug。 +- 对于 P0 条目,建议配合: + - 真实历史数据回放(从数据库抽样一段时间); + - 针对单一策略 / 单一 symbol 的离线重算脚本(对比 signal_indicators / paper_trades)。 +- 改动任何 C 层(评分/开仓/落库)逻辑时,必须同时: + - 更新 `docs/arbitrage-engine-full-spec.md` 中对应章节; + - 在本验证清单中勾选/更新受影响的条目; + - 附上简短的“为什么这样改 + 对历史行为的影响”说明。 + +> 一句话: +> 以后任何人(包括 AI)想动 signal_engine / 策略工厂,都应该先看 full-spec,再看这份验证清单, +> 确保“为什么这么算”和“有没有算对”都在一个有记录的地方。 diff --git a/docs/V52-TODO.md b/docs/V52-TODO.md deleted file mode 100644 index 10a8cf1..0000000 --- a/docs/V52-TODO.md +++ /dev/null @@ -1,61 +0,0 @@ -# V5.2 待修复清单 - -> 来源:Claude Code审阅报告 + 露露复查 -> 创建:2026-03-01 - -## 已在V5.1-hotfix中修复(P0) - -| ID | 问题 | 修复 | -|----|------|------| -| P0-1 | 冷却期阻断反向信号平仓 | evaluate_signal始终输出direction,主循环基于direction+score>=60触发反向平仓 | -| P0-2 | pnl_r TP场景虚高2倍 | paper_monitor+signal_engine统一用(exit-entry)/risk_distance计算 | -| P1-1 | 分区月份Bug(timedelta 30天) | 改为正确的月份加法 + UTC时区 | -| P2-2 | 分区边界用本地时区 | 改为datetime.timezone.utc | - -## V5.2 必须修复 - -### 后端 - -| ID | 优先级 | 文件 | 问题 | 建议修复 | -|----|--------|------|------|---------| -| P0-3 | P1 | signal_engine.py | 开仓价用30分VWAP而非实时价 | 改用win_fast.trades[-1][2]最新成交价 | -| P0-4 | P2 | signal_engine+paper_monitor | 双进程并发写竞态 | SELECT FOR UPDATE SKIP LOCKED | -| P1-2 | P2 | signal_engine.py | 浮点精度漂移(buy_vol/sell_vol) | 每N次trim后从deque重算sums | -| P1-3 | P1 | market_data_collector.py | 单连接无重连 | 改用db.get_sync_conn()连接池 | -| P1-4 | P3 | db.py | 连接池初始化线程不安全 | 加threading.Lock双重检查 | -| P2-1 | P2 | market_data_collector.py | XRP/SOL coinbase_premium KeyError | 不在pair_map中的跳过 | -| P2-3 | P2 | agg_trades_collector.py | flush_buffer每秒调ensure_partitions | 移到定时任务(每小时) | -| P2-4 | P3 | liquidation_collector.py | elif条件冗余 | 改为else | -| P2-5 | P2 | signal_engine.py | atr_percentile @property有写副作用 | 移到显式update_atr_history() | -| P2-6 | P2 | main.py | 1R=$200硬编码 | 从paper_config.json读取 | -| P3-1 | P2 | auth.py | JWT密钥硬编码默认值 | 启动时强制校验环境变量 | -| P3-2 | P3 | main.py | CORS allow_origins=["*"] | 限制为前端域名 | -| P3-3 | P3 | auth.py | refresh token刷新非原子 | UPDATE...RETURNING原子操作 | -| P3-4 | P3 | auth.py | 登录无频率限制 | slowapi或Redis计数器 | -| NEW | P1 | signal_engine.py | 冷启动warmup只有4小时 | 分批加载24小时数据,加载完再出信号 | - -### 前端 - -| ID | 优先级 | 文件 | 问题 | 建议修复 | -|----|--------|------|------|---------| -| FE-P1-1 | P1 | lib/auth.tsx | 并发401多次refresh竞态 | 单例Promise防并发刷新 | -| FE-P1-2 | P1 | lib/auth.tsx | 刷新失败AuthContext未同步 | 事件总线通知强制logout | -| FE-P1-3 | P1 | 所有页面 | catch{}静默吞掉API错误 | 加error state+用户提示 | -| FE-P1-4 | P2 | paper/page.tsx | LatestSignals串行4请求 | Promise.allSettled并行 | -| FE-P2-1 | P3 | app/page.tsx | MiniKChart每30秒销毁重建 | 只更新数据不重建chart | -| FE-P2-3 | P2 | paper/page.tsx | ControlPanel非admin可见 | 校验isAdmin | -| FE-P2-4 | P1 | paper/page.tsx | WebSocket无断线重连 | 指数退避重连+断线提示 | -| FE-P2-5 | P2 | paper/page.tsx | 1R=$200前端硬编码 | 从API读取配置 | -| FE-P2-6 | P2 | signals/page.tsx | 5秒轮询5分钟数据 | 改为300秒间隔 | -| FE-P2-8 | P3 | paper/signals | 大量any类型 | 定义TypeScript interface | -| FE-P3-1 | P3 | lib/auth.tsx | Token存localStorage | 评估httpOnly cookie | -| FE-P3-3 | P3 | app/page.tsx | Promise.all任一失败全丢 | 改Promise.allSettled | - -## V5.2 新功能(同步开发) - -| 功能 | 说明 | -|------|------| -| FR+清算加入评分 | 8信号源完整接入 | -| 策略配置化框架 | 一套代码多份配置 | -| AB测试 | V5.1 vs V5.2两套权重对比 | -| 24h warmup | 启动时分批加载24小时数据 | diff --git a/docs/ai/00-system-overview.md b/docs/ai/00-system-overview.md deleted file mode 100644 index 15dcb9d..0000000 --- a/docs/ai/00-system-overview.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: standard ---- - -# 00 — System Overview - -## Purpose -High-level description of the arbitrage-engine project: what it does, its tech stack, repo layout, and entry points. - -## TL;DR -- **Domain**: Crypto perpetual futures funding-rate arbitrage monitoring and short-term trading signal engine. -- **Strategy**: Hold spot long + perpetual short to collect funding rates every 8 h; plus a CVD/ATR-based short-term directional signal engine (V5.x). -- **Backend**: Python / FastAPI + independent PM2 worker processes; PostgreSQL (local + Cloud SQL dual-write). -- **Frontend**: Next.js 16 / React 19 / TypeScript SPA, charting via `lightweight-charts` + Recharts. -- **Targets**: BTC, ETH, XRP, SOL perpetual contracts on Binance USDC-M futures. -- **Deployment**: PM2 process manager on a GCP VM; frontend served via Next.js; backend accessible at `https://arb.zhouyangclaw.com`. -- **Auth**: JWT (access 24 h + refresh 7 d) + invite-code registration gating. -- **Trading modes**: Paper (simulated), Live (Binance Futures testnet or production via `TRADE_ENV`). - -## Canonical Facts - -### Repo Layout -``` -arbitrage-engine/ -├── backend/ # Python FastAPI API + all worker processes -│ ├── main.py # FastAPI app entry point (uvicorn) -│ ├── signal_engine.py # V5 signal engine (PM2 worker, 15 s loop) -│ ├── live_executor.py # Live trade executor (PM2 worker) -│ ├── risk_guard.py # Risk circuit-breaker (PM2 worker) -│ ├── market_data_collector.py # Binance WS market data (PM2 worker) -│ ├── agg_trades_collector.py # Binance aggTrades WS collector (PM2 worker) -│ ├── liquidation_collector.py # Binance liquidation WS collector (PM2 worker) -│ ├── signal_pusher.py # Discord signal notifier (PM2 worker) -│ ├── db.py # Dual-pool PostgreSQL layer (psycopg2 sync + asyncpg async) -│ ├── auth.py # JWT auth + invite-code registration router -│ ├── trade_config.py # Symbol / qty precision constants -│ ├── backtest.py # Offline backtest engine -│ ├── paper_monitor.py # Paper trade monitoring helper -│ ├── admin_cli.py # CLI for invite / user management -│ ├── subscriptions.py # Signal subscription query helper -│ ├── paper_config.json # Paper trading runtime toggle -│ ├── strategies/ # JSON strategy configs (v51_baseline, v52_8signals) -│ ├── ecosystem.dev.config.js # PM2 process definitions -│ └── logs/ # Rotating log files -├── frontend/ # Next.js app -│ ├── app/ # App Router pages -│ ├── components/ # Reusable UI components -│ ├── lib/api.ts # Typed API client -│ └── lib/auth.tsx # Auth context + token refresh logic -├── docs/ # Documentation (including docs/ai/) -├── scripts/ # Utility scripts -└── signal-engine.log # Live log symlink / output file -``` - -### Primary Language & Frameworks -| Layer | Technology | -|-------|-----------| -| Backend API | Python 3.x, FastAPI, uvicorn | -| DB access | asyncpg (async), psycopg2 (sync) | -| Frontend | TypeScript, Next.js 16, React 19 | -| Styling | Tailwind CSS v4 | -| Charts | lightweight-charts 5.x, Recharts 3.x | -| Process manager | PM2 (via `ecosystem.dev.config.js`) | -| Database | PostgreSQL (local + Cloud SQL dual-write) | - -### Entry Points -| Process | File | Role | -|---------|------|------| -| HTTP API | `backend/main.py` | FastAPI on uvicorn | -| Signal engine | `backend/signal_engine.py` | 15 s indicator loop | -| Trade executor | `backend/live_executor.py` | PG NOTIFY listener → Binance API | -| Risk guard | `backend/risk_guard.py` | 5 s circuit-breaker loop | -| Market data | `backend/market_data_collector.py` | Binance WS → `market_indicators` table | -| aggTrades collector | `backend/agg_trades_collector.py` | Binance WS → `agg_trades` partitioned table | -| Liquidation collector | `backend/liquidation_collector.py` | Binance WS → liquidation tables | -| Signal pusher | `backend/signal_pusher.py` | DB → Discord push | -| Frontend | `frontend/` | Next.js dev/prod server | - -### Monitored Symbols -`BTCUSDT`, `ETHUSDT`, `XRPUSDT`, `SOLUSDT` (Binance USDC-M Futures) - -### Environment Variables (key ones) -| Variable | Default | Description | -|----------|---------|-------------| -| `PG_HOST` | `127.0.0.1` | Local PG host | -| `PG_DB` | `arb_engine` | Database name | -| `PG_USER` / `PG_PASS` | `arb` / `arb_engine_2026` | PG credentials | -| `CLOUD_PG_HOST` | `10.106.0.3` | Cloud SQL host | -| `CLOUD_PG_ENABLED` | `true` | Enable dual-write | -| `JWT_SECRET` | (testnet default set) | JWT signing key | -| `TRADE_ENV` | `testnet` | `testnet` or `production` | -| `LIVE_STRATEGIES` | `["v52_8signals"]` | Active live trading strategies | -| `RISK_PER_TRADE_USD` | `2` | USD risk per trade | - -## Interfaces / Dependencies -- **External API**: Binance USDC-M Futures REST (`https://fapi.binance.com/fapi/v1`) and WebSocket. -- **Discord**: Webhook for signal notifications (via `signal_pusher.py`). -- **CORS origins**: `https://arb.zhouyangclaw.com`, `http://localhost:3000`, `http://localhost:3001`. - -## Unknowns & Risks -- [inference] PM2 `ecosystem.dev.config.js` not read in this pass; exact process restart policies and env injection not confirmed. -- [inference] `.env` file usage confirmed via `python-dotenv` calls in live modules, but `.env.example` absent. -- [unknown] Deployment pipeline (CI/CD) not present in repo. - -## Source Refs -- `backend/main.py:1-27` — FastAPI app init, CORS, SYMBOLS -- `backend/signal_engine.py:1-16` — V5 architecture docstring -- `backend/live_executor.py:1-10` — live executor architecture comment -- `backend/risk_guard.py:1-12` — risk guard circuit-break rules -- `backend/db.py:14-30` — PG/Cloud SQL env config -- `frontend/package.json` — frontend dependencies -- `frontend/lib/api.ts:1-116` — typed API client diff --git a/docs/ai/01-architecture-map.md b/docs/ai/01-architecture-map.md deleted file mode 100644 index d0eba3f..0000000 --- a/docs/ai/01-architecture-map.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: standard ---- - -# 01 — Architecture Map - -## Purpose -Describes the architecture style, component relationships, data flow, and runtime execution topology of the arbitrage engine. - -## TL;DR -- **Multi-process architecture**: each concern is a separate PM2 process; they communicate exclusively through PostgreSQL (tables + NOTIFY/LISTEN). -- **No message broker**: PostgreSQL serves as both the data store and the inter-process message bus (`NOTIFY new_signal`). -- **Dual-database write**: every PG write in `signal_engine.py` and `agg_trades_collector.py` attempts a secondary write to Cloud SQL (GCP) for durability. -- **FastAPI is read-only at runtime**: it proxies Binance REST for rates/history and reads the PG tables written by workers; it does not control the signal engine. -- **Signal pipeline**: raw aggTrades → in-memory rolling windows (CVD/VWAP/ATR) → scored signal → PG write + `NOTIFY` → live_executor executes Binance order. -- **Frontend polling**: React SPA polls `/api/rates` every 2 s (public) and slow endpoints every 120 s (auth required). -- **Risk guard is a separate process**: polls every 5 s, can block new orders (circuit-break) by writing a flag to `live_config`; live_executor reads that flag before each trade. - -## Canonical Facts - -### Architecture Style -Shared-DB multi-process monolith. No microservices; no message broker. All processes run on a single GCP VM. - -### Component Diagram (text) -``` -Binance WS (aggTrades) - └─► agg_trades_collector.py ──────────────────► agg_trades (partitioned table) - │ -Binance WS (market data) ▼ - └─► market_data_collector.py ──────────────► market_indicators table - │ -Binance WS (liquidations) ▼ - └─► liquidation_collector.py ──────────────► liquidation tables - │ - signal_engine.py - (15 s loop, reads agg_trades + - market_indicators) - │ - ┌─────────┴──────────────┐ - │ │ - signal_indicators paper_trades - signal_indicators_1m (paper mode) - signal_trades - NOTIFY new_signal - │ - live_executor.py - (LISTEN new_signal → - Binance Futures API) - │ - live_trades table - │ - risk_guard.py (5 s) - monitors live_trades, - writes live_config flags - │ - signal_pusher.py - (reads signal_indicators → - Discord webhook) - │ - FastAPI main.py (read/proxy) - + rate_snapshots (2 s write) - │ - Next.js Frontend - (polling SPA) -``` - -### Data Flow — Signal Pipeline -1. `agg_trades_collector.py`: streams `aggTrade` WS events for all symbols, batch-inserts into `agg_trades` partitioned table (partitioned by month on `time_ms`). -2. `signal_engine.py` (15 s loop per symbol): - - Cold-start: reads last N rows from `agg_trades` to warm up `TradeWindow` (CVD, VWAP) and `ATRCalculator` deques. - - Fetches new trades since `last_agg_id`. - - Feeds trades into three `TradeWindow` instances (30 m, 4 h, 24 h) and one `ATRCalculator` (5 m candles, 14-period). - - Reads `market_indicators` for long-short ratio, OI, coinbase premium, funding rate, liquidations. - - Scores signal using JSON strategy config weights (score 0–100, threshold 75). - - Writes to `signal_indicators` (15 s cadence) and `signal_indicators_1m` (1 m cadence). - - If score ≥ threshold: opens paper trade (if enabled), emits `NOTIFY new_signal` (if live enabled). -3. `live_executor.py`: `LISTEN new_signal` on PG; deserializes payload; calls Binance Futures REST to place market order; writes to `live_trades`. -4. `risk_guard.py`: every 5 s checks daily loss, consecutive losses, unrealized PnL, balance, data freshness, hold timeout; sets `live_config.circuit_break` flag to block/resume new orders. - -### Strategy Scoring (V5.x) -Two JSON configs in `backend/strategies/`: - -| Config | Version | Threshold | Signals | -|--------|---------|-----------|---------| -| `v51_baseline.json` | 5.1 | 75 | cvd, p99, accel, ls_ratio, oi, coinbase_premium | -| `v52_8signals.json` | 5.2 | 75 | cvd, p99, accel, ls_ratio, oi, coinbase_premium, funding_rate, liquidation | - -Score categories: `direction` (CVD), `crowding` (P99 large trades), `environment` (ATR/VWAP), `confirmation` (LS ratio, OI), `auxiliary` (coinbase premium), `funding_rate`, `liquidation`. - -### TP/SL Configuration -- V5.1: SL=1.4×ATR, TP1=1.05×ATR, TP2=2.1×ATR -- V5.2: SL=2.1×ATR, TP1=1.4×ATR, TP2=3.15×ATR -- Signal cooldown: 10 minutes per symbol per direction. - -### Risk Guard Circuit-Break Rules -| Rule | Threshold | Action | -|------|-----------|--------| -| Daily loss | -5R | Full close + shutdown | -| Consecutive losses | 5 | Pause 60 min | -| API disconnect | >30 s | Pause new orders | -| Balance too low | < risk×2 | Reject new orders | -| Data stale | >30 s | Block new orders | -| Hold timeout yellow | 45 min | Alert | -| Hold timeout auto-close | 70 min | Force close | - -### Frontend Architecture -- **Next.js App Router** (`frontend/app/`): page-per-route, all pages are client components (`"use client"`). -- **Auth**: JWT stored in `localStorage`; `lib/auth.tsx` provides `useAuth()` hook + `authFetch()` helper with auto-refresh. -- **API client**: `lib/api.ts` — typed wrapper, distinguishes public (`/api/rates`, `/api/health`) from protected (all other) endpoints. -- **Polling strategy**: rates every 2 s, slow data (stats, history, signals) every 120 s; kline charts re-render every 30 s. - -## Interfaces / Dependencies -- PG NOTIFY channel name: `new_signal` -- `live_config` table keys: `risk_per_trade_usd`, `max_positions`, `circuit_break` (inferred) -- `market_indicators` populated by `market_data_collector.py` with types: `long_short_ratio`, `top_trader_position`, `open_interest_hist`, `coinbase_premium`, `funding_rate` - -## Unknowns & Risks -- [inference] PM2 config (`ecosystem.dev.config.js`) not read; exact restart/watch/env-file settings unknown. -- [inference] `signal_pusher.py` exact Discord webhook configuration (env var name, rate limit handling) not confirmed. -- [unknown] Cloud SQL write failure does not block signal_engine but may create data divergence between local PG and Cloud SQL. -- [risk] Hardcoded testnet credentials in source code (`arb_engine_2026`); production requires explicit env var override. - -## Source Refs -- `backend/signal_engine.py:1-16` — architecture docstring -- `backend/live_executor.py:1-10` — executor architecture comment -- `backend/risk_guard.py:1-12, 55-73` — risk rules and config -- `backend/signal_engine.py:170-245` — `TradeWindow`, `ATRCalculator` classes -- `backend/signal_engine.py:44-67` — strategy config loading -- `backend/strategies/v51_baseline.json`, `backend/strategies/v52_8signals.json` -- `backend/main.py:61-83` — background snapshot loop -- `frontend/lib/api.ts:103-116` — API client methods -- `frontend/app/page.tsx:149-154` — polling intervals diff --git a/docs/ai/02-module-cheatsheet.md b/docs/ai/02-module-cheatsheet.md deleted file mode 100644 index 1f42418..0000000 --- a/docs/ai/02-module-cheatsheet.md +++ /dev/null @@ -1,218 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: standard ---- - -# 02 — Module Cheatsheet - -## Purpose -Module-by-module index: file path, role, key public interfaces, and dependencies. - -## TL;DR -- Backend has 20 Python modules; signal_engine.py is the largest and most complex (~1000+ lines). -- Frontend has 2 TypeScript lib files + 9 pages + 6 components. -- `db.py` is the only shared infrastructure module; all other backend modules import from it. -- `signal_engine.py` is the core business logic module; `live_executor.py` and `risk_guard.py` are independent processes that only use `db.py` and direct PG connections. -- Strategy configs are external JSON; no code changes needed to tune weights/thresholds. - -## Canonical Facts - -### Backend Modules - -#### `backend/main.py` — FastAPI HTTP API -- **Role**: Primary HTTP API server; rate/snapshot/history proxy; aggTrade query endpoints; signal history. -- **Key interfaces**: - - `GET /api/health` — liveness check (public) - - `GET /api/rates` — live Binance premiumIndex for 4 symbols (public, 3 s cache) - - `GET /api/snapshots` — rate snapshot history from PG (auth required) - - `GET /api/kline` — candlestick bars aggregated from `rate_snapshots` (auth required) - - `GET /api/stats` — 7-day funding rate stats per symbol (auth required, 60 s cache) - - `GET /api/stats/ytd` — YTD annualized stats (auth required, 3600 s cache) - - `GET /api/history` — 7-day raw funding rate history (auth required, 60 s cache) - - `GET /api/signals/history` — `signal_logs` table (auth required) - - `GET /api/trades/meta` — `agg_trades_meta` (auth required) - - `GET /api/trades/summary` — aggregated OHLCV from `agg_trades` (auth required) - - Many more: paper trades, signals v52, live trades, live config, position sync, etc. (full list in saved tool output) -- **Deps**: `auth.py`, `db.py`, `httpx`, `asyncio` -- **Background task**: `background_snapshot_loop()` writes `rate_snapshots` every 2 s. - -#### `backend/signal_engine.py` — V5 Signal Engine (PM2 worker) -- **Role**: Core signal computation loop; 15 s interval; in-memory rolling-window indicators; scored signal output. -- **Key classes**: - - `TradeWindow(window_ms)` — rolling CVD/VWAP calculator using `deque`; props: `cvd`, `vwap` - - `ATRCalculator(period_ms, length)` — 5-min candle ATR; props: `atr`, `atr_percentile` - - `SymbolState` — per-symbol state container holding `TradeWindow` ×3, `ATRCalculator`, large-order percentile deques -- **Key functions**: - - `load_strategy_configs() -> list[dict]` — reads JSON files from `strategies/` - - `fetch_market_indicators(symbol) -> dict` — reads `market_indicators` table - - `fetch_new_trades(symbol, last_id) -> list` — reads new rows from `agg_trades` - - `save_indicator(ts, symbol, result, strategy)` — writes to `signal_indicators` - - `paper_open_trade(...)` — inserts `paper_trades` row - - `paper_check_positions(symbol, price, now_ms)` — checks TP/SL for paper positions - - `main()` — entry point; calls `load_historical()` then enters main loop -- **Deps**: `db.py`, `json`, `collections.deque` - -#### `backend/live_executor.py` — Live Trade Executor (PM2 worker) -- **Role**: Listens on PG `NOTIFY new_signal`; places Binance Futures market orders; writes `live_trades`. -- **Key functions**: - - `reload_live_config(conn)` — refreshes `RISK_PER_TRADE_USD`, `MAX_POSITIONS` from `live_config` every 60 s - - `binance_request(session, method, path, params)` — HMAC-signed Binance API call -- **Config**: `TRADE_ENV` (`testnet`/`production`), `LIVE_STRATEGIES`, `RISK_PER_TRADE_USD`, `MAX_POSITIONS` -- **Deps**: `psycopg2`, `aiohttp`, HMAC signing - -#### `backend/risk_guard.py` — Risk Circuit-Breaker (PM2 worker) -- **Role**: Every 5 s; monitors PnL, balance, data freshness, hold timeouts; writes circuit-break flags. -- **Key classes**: `RiskState` — holds `status` (`normal`/`warning`/`circuit_break`), loss counters -- **Key functions**: - - `check_daily_loss(conn)` — sums `pnl_r` from today's `live_trades` - - `check_unrealized_loss(session, risk_usd_dynamic)` — queries Binance positions API - - `check_balance(session)` — queries Binance account balance - - `check_data_freshness(conn)` — checks `market_indicators` recency - - `check_hold_timeout(session, conn)` — force-closes positions held >70 min - - `trigger_circuit_break(session, conn, reason, action)` — writes to `live_events`, may flat positions - - `check_auto_resume()` — re-enables trading after cooldown - - `check_emergency_commands(session, conn)` — watches for manual DB commands -- **Deps**: `trade_config.py`, `aiohttp`, `psycopg2` - -#### `backend/db.py` — Database Layer -- **Role**: All PG connectivity; schema creation; partition management. -- **Key interfaces**: - - Sync (psycopg2): `get_sync_conn()`, `sync_execute()`, `sync_executemany()` - - Async (asyncpg): `async_fetch()`, `async_fetchrow()`, `async_execute()` - - Cloud SQL sync pool: `get_cloud_sync_conn()` (non-fatal on failure) - - `init_schema()` — creates all tables from `SCHEMA_SQL` - - `ensure_partitions()` — creates `agg_trades_YYYYMM` partitions for current+next 2 months -- **Deps**: `asyncpg`, `psycopg2` - -#### `backend/auth.py` — JWT Auth + Registration -- **Role**: FastAPI router at `/api`; register/login/refresh/logout endpoints. -- **Key interfaces**: - - `POST /api/register` — invite-code gated registration - - `POST /api/login` — returns `access_token` + `refresh_token` - - `POST /api/refresh` — token refresh - - `POST /api/logout` — revokes refresh token - - `GET /api/me` — current user info - - `get_current_user` — FastAPI `Depends` injector; validates Bearer JWT -- **Token storage**: HMAC-SHA256 hand-rolled JWT (no PyJWT); refresh tokens stored in `refresh_tokens` table. -- **Deps**: `db.py`, `hashlib`, `hmac`, `secrets` - -#### `backend/agg_trades_collector.py` — AggTrades Collector (PM2 worker) -- **Role**: Streams Binance `aggTrade` WebSocket events; batch-inserts into `agg_trades` partitioned table; maintains `agg_trades_meta`. -- **Key functions**: `ws_collect(symbol)`, `rest_catchup(symbol, from_id)`, `continuity_check()`, `flush_buffer(symbol, trades)` -- **Deps**: `db.py`, `websockets`/`httpx` - -#### `backend/market_data_collector.py` — Market Data Collector (PM2 worker) -- **Role**: Collects Binance market indicators (LS ratio, OI, coinbase premium, funding rate) via REST polling; stores in `market_indicators` JSONB. -- **Key class**: `MarketDataCollector` -- **Deps**: `db.py`, `httpx` - -#### `backend/liquidation_collector.py` — Liquidation Collector (PM2 worker) -- **Role**: Streams Binance liquidation WS; aggregates into `liquidation_events` and `liquidation_agg` tables. -- **Key functions**: `ensure_table()`, `save_liquidation()`, `save_aggregated()`, `run()` -- **Deps**: `db.py`, `websockets` - -#### `backend/backtest.py` — Offline Backtester -- **Role**: Replays `agg_trades` from PG to simulate signal engine and measure strategy performance. -- **Key classes**: `Position`, `BacktestEngine` -- **Key functions**: `load_trades()`, `run_backtest()`, `main()` -- **Deps**: `db.py` - -#### `backend/trade_config.py` — Symbol / Qty Config -- **Role**: Constants for symbols and Binance qty precision. -- **Deps**: none - -#### `backend/admin_cli.py` — Admin CLI -- **Role**: CLI for invite-code and user management (gen_invite, list_invites, ban_user, set_admin). -- **Deps**: `db.py` - -#### `backend/subscriptions.py` — Subscription Query Helper -- **Role**: Helpers for querying signal history (used internally). -- **Deps**: `db.py` - -#### `backend/paper_monitor.py` — Paper Trade Monitor -- **Role**: Standalone script to print paper trade status. -- **Deps**: `db.py` - -#### `backend/signal_pusher.py` — Discord Notifier (PM2 worker) -- **Role**: Polls `signal_indicators` for high-score events; pushes Discord webhook notifications. -- **Deps**: `db.py`, `httpx` - -#### `backend/position_sync.py` — Position Sync -- **Role**: Syncs live positions between `live_trades` table and Binance account state. -- **Deps**: `db.py`, `aiohttp` - -#### `backend/fix_historical_pnl.py` — PnL Fix Script -- **Role**: One-time migration to recalculate historical PnL in `paper_trades`. -- **Deps**: `db.py` - -### Frontend Modules - -#### `frontend/lib/api.ts` — API Client -- **Role**: Typed `api` object with all backend endpoint wrappers; distinguishes public vs. protected fetches. -- **Interfaces exported**: `RateData`, `RatesResponse`, `HistoryPoint`, `HistoryResponse`, `StatsResponse`, `SignalHistoryItem`, `SnapshotItem`, `KBar`, `KlineResponse`, `YtdStatsResponse`, `api` object -- **Deps**: `lib/auth.tsx` (`authFetch`) - -#### `frontend/lib/auth.tsx` — Auth Context -- **Role**: React context for current user; `useAuth()` hook; `authFetch()` with access-token injection and auto-refresh. -- **Deps**: Next.js router, `localStorage` - -#### `frontend/app/` Pages -| Page | Route | Description | -|------|-------|-------------| -| `page.tsx` | `/` | Main dashboard: rates, kline, history, signal log | -| `dashboard/page.tsx` | `/dashboard` | (inferred) extended dashboard | -| `signals/page.tsx` | `/signals` | Signal history view (V5.1) | -| `signals-v52/page.tsx` | `/signals-v52` | Signal history view (V5.2) | -| `paper/page.tsx` | `/paper` | Paper trades view (V5.1) | -| `paper-v52/page.tsx` | `/paper-v52` | Paper trades view (V5.2) | -| `live/page.tsx` | `/live` | Live trades view | -| `history/page.tsx` | `/history` | Funding rate history | -| `kline/page.tsx` | `/kline` | Kline chart page | -| `trades/page.tsx` | `/trades` | aggTrades summary | -| `server/page.tsx` | `/server` | Server status / metrics | -| `about/page.tsx` | `/about` | About page | -| `login/page.tsx` | `/login` | Login form | -| `register/page.tsx` | `/register` | Registration form | - -#### `frontend/components/` -| Component | Role | -|-----------|------| -| `Navbar.tsx` | Top navigation bar | -| `Sidebar.tsx` | Sidebar navigation | -| `AuthHeader.tsx` | Auth-aware header with user info | -| `RateCard.tsx` | Displays current funding rate for one asset | -| `StatsCard.tsx` | Displays 7d mean and annualized stats | -| `FundingChart.tsx` | Funding rate chart component | -| `LiveTradesCard.tsx` | Live trades summary card | - -## Interfaces / Dependencies - -### Key import graph (backend) -``` -main.py → auth.py, db.py -signal_engine.py → db.py -live_executor.py → psycopg2 direct (no db.py module import) -risk_guard.py → trade_config.py, psycopg2 direct -backtest.py → db.py -agg_trades_collector.py → db.py -market_data_collector.py → db.py -liquidation_collector.py → db.py -admin_cli.py → db.py -``` - -## Unknowns & Risks -- [inference] Content of `frontend/app/dashboard/`, `signals/`, `paper/`, `live/` pages not read; role described from filename convention. -- [unknown] `signal_pusher.py` Discord webhook env var name not confirmed. -- [inference] `position_sync.py` exact interface not read; role inferred from name and listing. - -## Source Refs -- `backend/main.py` — all API route definitions -- `backend/signal_engine.py:170-285` — `TradeWindow`, `ATRCalculator`, `SymbolState` -- `backend/auth.py:23-23` — router prefix `/api` -- `backend/db.py:35-157` — all public DB functions -- `frontend/lib/api.ts:103-116` — `api` export object -- `frontend/lib/auth.tsx` — auth context (not fully read) diff --git a/docs/ai/03-api-contracts.md b/docs/ai/03-api-contracts.md deleted file mode 100644 index 7321a60..0000000 --- a/docs/ai/03-api-contracts.md +++ /dev/null @@ -1,242 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: standard ---- - -# 03 — API Contracts - -## Purpose -Documents all REST API endpoints, authentication requirements, request/response shapes, and error conventions. - -## TL;DR -- Base URL: `https://arb.zhouyangclaw.com` (prod) or `http://localhost:8000` (local). -- Auth: Bearer JWT in `Authorization` header. Two public endpoints (`/api/health`, `/api/rates`) need no token. -- Token lifecycle: access token 24 h, refresh token 7 d; use `POST /api/refresh` to renew. -- Registration is invite-code gated: must supply a valid `invite_code` in register body. -- All timestamps are Unix epoch (seconds or ms depending on field; see per-endpoint notes). -- Funding rates are stored as decimals (e.g. `0.0001` = 0.01%). Frontend multiplies by 10000 for "万分之" display. -- Error responses: standard FastAPI `{"detail": "..."}` with appropriate HTTP status codes. - -## Canonical Facts - -### Authentication - -#### `POST /api/register` -```json -// Request -{ - "email": "user@example.com", - "password": "...", - "invite_code": "XXXX" -} -// Response 200 -{ - "access_token": "", - "refresh_token": "", - "token_type": "bearer", - "user": { "id": 1, "email": "...", "role": "user" } -} -// Errors: 400 (invite invalid/expired), 409 (email taken) -``` - -#### `POST /api/login` -```json -// Request -{ - "email": "user@example.com", - "password": "..." -} -// Response 200 -{ - "access_token": "", - "refresh_token": "", - "token_type": "bearer", - "user": { "id": 1, "email": "...", "role": "user" } -} -// Errors: 401 (invalid credentials), 403 (banned) -``` - -#### `POST /api/refresh` -```json -// Request -{ "refresh_token": "" } -// Response 200 -{ "access_token": "", "token_type": "bearer" } -// Errors: 401 (expired/revoked) -``` - -#### `POST /api/logout` -```json -// Request header: Authorization: Bearer -// Request body: { "refresh_token": "" } -// Response 200: { "ok": true } -``` - -#### `GET /api/me` -```json -// Auth required -// Response 200 -{ "id": 1, "email": "...", "role": "user", "created_at": "..." } -``` - -### Public Endpoints (no auth) - -#### `GET /api/health` -```json -{ "status": "ok", "timestamp": "2026-03-03T12:00:00" } -``` - -#### `GET /api/rates` -Returns live Binance premiumIndex for BTCUSDT, ETHUSDT, XRPUSDT, SOLUSDT. Cached 3 s. -```json -{ - "BTC": { - "symbol": "BTCUSDT", - "markPrice": 65000.0, - "indexPrice": 64990.0, - "lastFundingRate": 0.0001, - "nextFundingTime": 1234567890000, - "timestamp": 1234567890000 - }, - "ETH": { ... }, - "XRP": { ... }, - "SOL": { ... } -} -``` - -### Protected Endpoints (Bearer JWT required) - -#### `GET /api/history` -7-day funding rate history from Binance. Cached 60 s. -```json -{ - "BTC": [ - { "fundingTime": 1234567890000, "fundingRate": 0.0001, "timestamp": "2026-03-01T08:00:00" } - ], - "ETH": [ ... ], "XRP": [ ... ], "SOL": [ ... ] -} -``` - -#### `GET /api/stats` -7-day funding rate statistics. Cached 60 s. -```json -{ - "BTC": { "mean7d": 0.01, "annualized": 10.95, "count": 21 }, - "ETH": { ... }, - "combo": { "mean7d": 0.009, "annualized": 9.85 } -} -// mean7d in %; annualized = mean * 3 * 365 * 100 -``` - -#### `GET /api/stats/ytd` -Year-to-date annualized stats. Cached 3600 s. -```json -{ - "BTC": { "annualized": 12.5, "count": 150 }, - "ETH": { ... } -} -``` - -#### `GET /api/snapshots?hours=24&limit=5000` -Rate snapshots from local PG. -```json -{ - "count": 43200, - "hours": 24, - "data": [ - { "ts": 1709000000, "btc_rate": 0.0001, "eth_rate": 0.00008, "btc_price": 65000, "eth_price": 3200 } - ] -} -``` - -#### `GET /api/kline?symbol=BTC&interval=1h&limit=500` -Candlestick bars derived from `rate_snapshots`. Rates scaled by ×10000. -- `interval`: `1m`, `5m`, `30m`, `1h`, `4h`, `8h`, `1d`, `1w`, `1M` -```json -{ - "symbol": "BTC", - "interval": "1h", - "count": 24, - "data": [ - { - "time": 1709000000, - "open": 1.0, "high": 1.2, "low": 0.8, "close": 1.1, - "price_open": 65000, "price_high": 65500, "price_low": 64800, "price_close": 65200 - } - ] -} -``` - -#### `GET /api/signals/history?limit=100` -Legacy signal log from `signal_logs` table. -```json -{ - "items": [ - { "id": 1, "symbol": "BTCUSDT", "rate": 0.0001, "annualized": 10.95, "sent_at": "2026-03-01T08:00:00", "message": "..." } - ] -} -``` - -#### `GET /api/trades/meta` -aggTrades collection status. -```json -{ - "BTC": { "last_agg_id": 123456789, "last_time_ms": 1709000000000, "updated_at": "2026-03-03 12:00:00" } -} -``` - -#### `GET /api/trades/summary?symbol=BTC&start_ms=0&end_ms=0&interval=1m` -Aggregated OHLCV from `agg_trades` via PG native aggregation. -```json -{ - "symbol": "BTC", - "interval": "1m", - "count": 60, - "data": [ - { "bar_ms": 1709000000000, "buy_vol": 10.5, "sell_vol": 9.3, "trade_count": 45, "vwap": 65000.0, "max_qty": 2.5 } - ] -} -``` - -#### Signal V52 Endpoints (inferred from frontend routes) -- `GET /api/signals/v52` — signals for v52_8signals strategy -- `GET /api/paper/trades` — paper trade history -- `GET /api/paper/trades/v52` — v52 paper trade history -- `GET /api/live/trades` — live trade history -- `GET /api/live/config` — current live config -- `GET /api/live/events` — live trading event log -- `GET /api/server/stats` — server process stats (psutil) - -### Auth Header Format -``` -Authorization: Bearer -``` -Frontend auto-injects via `authFetch()` in `lib/auth.tsx`. On 401, attempts token refresh before retry. - -### Error Shape -All errors follow FastAPI default: -```json -{ "detail": "Human-readable error message" } -``` -Common HTTP status codes: 400 (bad request), 401 (unauthorized), 403 (forbidden/banned), 404 (not found), 422 (validation error), 502 (Binance upstream error). - -## Interfaces / Dependencies -- Binance USDC-M Futures REST: `https://fapi.binance.com/fapi/v1/premiumIndex`, `/fundingRate` -- CORS allowed origins: `https://arb.zhouyangclaw.com`, `http://localhost:3000`, `http://localhost:3001` -- `NEXT_PUBLIC_API_URL` env var controls the frontend base URL (empty = same-origin) - -## Unknowns & Risks -- [inference] Full endpoint list for signals-v52, paper-v52, live, server pages not confirmed by reading main.py lines 300+. The full saved output contains more routes. -- [inference] `POST /api/register` exact field validation (password min length, etc.) not confirmed. -- [risk] No rate limiting visible on public endpoints; `/api/rates` with 3 s cache could be bypassed by direct calls. - -## Source Refs -- `backend/main.py:101-298` — all confirmed REST endpoints -- `backend/auth.py:23` — auth router prefix -- `backend/main.py:16-21` — CORS config -- `frontend/lib/api.ts:90-116` — client-side API wrappers -- `frontend/lib/auth.tsx` — `authFetch` with auto-refresh (not fully read) diff --git a/docs/ai/04-data-model.md b/docs/ai/04-data-model.md deleted file mode 100644 index a634a9d..0000000 --- a/docs/ai/04-data-model.md +++ /dev/null @@ -1,301 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: standard ---- - -# 04 — Data Model - -## Purpose -Documents all PostgreSQL tables, columns, relations, constraints, storage design, and partitioning strategy. - -## TL;DR -- Single PostgreSQL database `arb_engine`; 15+ tables defined in `db.py` `SCHEMA_SQL` + `auth.py` `AUTH_SCHEMA`. -- `agg_trades` is a range-partitioned table (by `time_ms` in milliseconds); monthly partitions auto-created by `ensure_partitions()`. -- Dual-write: local PG is primary; Cloud SQL at `10.106.0.3` receives same writes via a secondary psycopg2 pool (non-fatal if down). -- All timestamps: `ts` columns are Unix seconds (integer); `time_ms` columns are Unix milliseconds (bigint); `created_at` columns are PG `TIMESTAMP`. -- JSONB used for `score_factors` in `paper_trades`/`live_trades`, `detail` in `live_events`, `value` in `market_indicators`. -- Auth tokens stored in DB: refresh tokens in `refresh_tokens` table (revocable); no session table. - -## Canonical Facts - -### Tables - -#### `rate_snapshots` — Funding Rate Snapshots -Populated every 2 s by `background_snapshot_loop()` in `main.py`. -| Column | Type | Description | -|--------|------|-------------| -| `id` | BIGSERIAL PK | | -| `ts` | BIGINT NOT NULL | Unix seconds | -| `btc_rate` | DOUBLE PRECISION | BTC funding rate (decimal) | -| `eth_rate` | DOUBLE PRECISION | ETH funding rate | -| `btc_price` | DOUBLE PRECISION | BTC mark price USD | -| `eth_price` | DOUBLE PRECISION | ETH mark price USD | -| `btc_index_price` | DOUBLE PRECISION | BTC index price | -| `eth_index_price` | DOUBLE PRECISION | ETH index price | - -Index: `idx_rate_snapshots_ts` on `ts`. - ---- - -#### `agg_trades` — Aggregate Trades (Partitioned) -Partitioned by `RANGE(time_ms)`; monthly child tables named `agg_trades_YYYYMM`. -| Column | Type | Description | -|--------|------|-------------| -| `agg_id` | BIGINT NOT NULL | Binance aggTrade ID | -| `symbol` | TEXT NOT NULL | e.g. `BTCUSDT` | -| `price` | DOUBLE PRECISION | Trade price | -| `qty` | DOUBLE PRECISION | Trade quantity (BTC/ETH/etc.) | -| `time_ms` | BIGINT NOT NULL | Trade timestamp ms | -| `is_buyer_maker` | SMALLINT | 0=taker buy, 1=taker sell | - -PK: `(time_ms, symbol, agg_id)`. -Indexes: `idx_agg_trades_sym_time` on `(symbol, time_ms DESC)`, `idx_agg_trades_sym_agg` on `(symbol, agg_id)`. - -Partitions auto-created for current + next 2 months. Named `agg_trades_YYYYMM`. - ---- - -#### `agg_trades_meta` — Collection State -| Column | Type | Description | -|--------|------|-------------| -| `symbol` | TEXT PK | e.g. `BTCUSDT` | -| `last_agg_id` | BIGINT | Last processed aggTrade ID | -| `last_time_ms` | BIGINT | Timestamp of last trade | -| `earliest_agg_id` | BIGINT | Oldest buffered ID | -| `earliest_time_ms` | BIGINT | Oldest buffered timestamp | -| `updated_at` | TEXT | Human-readable update time | - ---- - -#### `signal_indicators` — Signal Engine Output (15 s cadence) -| Column | Type | Description | -|--------|------|-------------| -| `id` | BIGSERIAL PK | | -| `ts` | BIGINT | Unix seconds | -| `symbol` | TEXT | | -| `cvd_fast` | DOUBLE PRECISION | CVD 30 m window | -| `cvd_mid` | DOUBLE PRECISION | CVD 4 h window | -| `cvd_day` | DOUBLE PRECISION | CVD UTC day | -| `cvd_fast_slope` | DOUBLE PRECISION | CVD momentum | -| `atr_5m` | DOUBLE PRECISION | ATR (5 m candles, 14 periods) | -| `atr_percentile` | DOUBLE PRECISION | ATR rank in 24 h history | -| `vwap_30m` | DOUBLE PRECISION | VWAP 30 m | -| `price` | DOUBLE PRECISION | Current mark price | -| `p95_qty` | DOUBLE PRECISION | P95 large-order threshold | -| `p99_qty` | DOUBLE PRECISION | P99 large-order threshold | -| `buy_vol_1m` | DOUBLE PRECISION | 1 m buy volume | -| `sell_vol_1m` | DOUBLE PRECISION | 1 m sell volume | -| `score` | INTEGER | Signal score 0–100 | -| `signal` | TEXT | `LONG`, `SHORT`, or null | - -Indexes: `idx_si_ts`, `idx_si_sym_ts`. - ---- - -#### `signal_indicators_1m` — 1-Minute Signal Snapshot -Subset of `signal_indicators` columns; written at 1 m cadence for lightweight chart queries. - ---- - -#### `signal_trades` — Signal Engine Trade Tracking -| Column | Type | Description | -|--------|------|-------------| -| `id` | BIGSERIAL PK | | -| `ts_open` | BIGINT | Open timestamp (Unix s) | -| `ts_close` | BIGINT | Close timestamp | -| `symbol` | TEXT | | -| `direction` | TEXT | `LONG` / `SHORT` | -| `entry_price` | DOUBLE PRECISION | | -| `exit_price` | DOUBLE PRECISION | | -| `qty` | DOUBLE PRECISION | | -| `score` | INTEGER | Signal score at entry | -| `pnl` | DOUBLE PRECISION | Realized PnL | -| `sl_price` | DOUBLE PRECISION | Stop-loss level | -| `tp1_price` | DOUBLE PRECISION | Take-profit 1 level | -| `tp2_price` | DOUBLE PRECISION | Take-profit 2 level | -| `status` | TEXT DEFAULT `open` | `open`, `closed`, `stopped` | - ---- - -#### `paper_trades` — Paper Trading Records -| Column | Type | Description | -|--------|------|-------------| -| `id` | BIGSERIAL PK | | -| `symbol` | TEXT | | -| `direction` | TEXT | `LONG`/`SHORT` | -| `score` | INT | Signal score | -| `tier` | TEXT | `light`/`standard`/`heavy` | -| `entry_price` | DOUBLE PRECISION | | -| `entry_ts` | BIGINT | Unix ms | -| `exit_price` | DOUBLE PRECISION | | -| `exit_ts` | BIGINT | | -| `tp1_price` | DOUBLE PRECISION | | -| `tp2_price` | DOUBLE PRECISION | | -| `sl_price` | DOUBLE PRECISION | | -| `tp1_hit` | BOOLEAN DEFAULT FALSE | | -| `status` | TEXT DEFAULT `active` | `active`, `tp1`, `tp2`, `sl`, `timeout` | -| `pnl_r` | DOUBLE PRECISION | PnL in R units | -| `atr_at_entry` | DOUBLE PRECISION | ATR snapshot at entry | -| `score_factors` | JSONB | Breakdown of signal score components | -| `strategy` | VARCHAR(32) DEFAULT `v51_baseline` | Strategy name | -| `created_at` | TIMESTAMP | | - ---- - -#### `live_trades` — Live Trading Records -| Column | Type | Description | -|--------|------|-------------| -| `id` | BIGSERIAL PK | | -| `symbol` | TEXT | | -| `strategy` | TEXT | | -| `direction` | TEXT | `LONG`/`SHORT` | -| `status` | TEXT DEFAULT `active` | | -| `entry_price` / `exit_price` | DOUBLE PRECISION | | -| `entry_ts` / `exit_ts` | BIGINT | Unix ms | -| `sl_price`, `tp1_price`, `tp2_price` | DOUBLE PRECISION | | -| `tp1_hit` | BOOLEAN | | -| `score` | DOUBLE PRECISION | | -| `tier` | TEXT | | -| `pnl_r` | DOUBLE PRECISION | | -| `fee_usdt` | DOUBLE PRECISION | Exchange fees | -| `funding_fee_usdt` | DOUBLE PRECISION | Funding fees paid while holding | -| `risk_distance` | DOUBLE PRECISION | Entry to SL distance | -| `atr_at_entry` | DOUBLE PRECISION | | -| `score_factors` | JSONB | | -| `signal_id` | BIGINT | FK → signal_indicators.id | -| `binance_order_id` | TEXT | Binance order ID | -| `fill_price` | DOUBLE PRECISION | Actual fill price | -| `slippage_bps` | DOUBLE PRECISION | Slippage in basis points | -| `protection_gap_ms` | BIGINT | Time between SL order and fill | -| `signal_to_order_ms` | BIGINT | Latency: signal → order placed | -| `order_to_fill_ms` | BIGINT | Latency: order → fill | -| `qty` | DOUBLE PRECISION | | -| `created_at` | TIMESTAMP | | - ---- - -#### `live_config` — Runtime Configuration KV Store -| Column | Type | Description | -|--------|------|-------------| -| `key` | TEXT PK | Config key | -| `value` | TEXT | Config value (string) | -| `label` | TEXT | Human label | -| `updated_at` | TIMESTAMP | | - -Known keys: `risk_per_trade_usd`, `max_positions`, `circuit_break` (inferred). - ---- - -#### `live_events` — Trade Event Log -| Column | Type | Description | -|--------|------|-------------| -| `id` | BIGSERIAL PK | | -| `ts` | BIGINT | Unix ms (default: NOW()) | -| `level` | TEXT | `info`/`warning`/`error` | -| `category` | TEXT | Event category | -| `symbol` | TEXT | | -| `message` | TEXT | | -| `detail` | JSONB | Structured event data | - ---- - -#### `signal_logs` — Legacy Signal Log -Kept for backwards compatibility with the original funding-rate signal system. -| Column | Type | -|--------|------| -| `id` | BIGSERIAL PK | -| `symbol` | TEXT | -| `rate` | DOUBLE PRECISION | -| `annualized` | DOUBLE PRECISION | -| `sent_at` | TEXT | -| `message` | TEXT | - ---- - -#### Auth Tables (defined in `auth.py` AUTH_SCHEMA) - -**`users`** -| Column | Type | -|--------|------| -| `id` | BIGSERIAL PK | -| `email` | TEXT UNIQUE NOT NULL | -| `password_hash` | TEXT NOT NULL | -| `discord_id` | TEXT | -| `role` | TEXT DEFAULT `user` | -| `banned` | INTEGER DEFAULT 0 | -| `created_at` | TEXT | - -**`subscriptions`** -| Column | Type | -|--------|------| -| `user_id` | BIGINT PK → users | -| `tier` | TEXT DEFAULT `free` | -| `expires_at` | TEXT | - -**`invite_codes`** -| Column | Type | -|--------|------| -| `id` | BIGSERIAL PK | -| `code` | TEXT UNIQUE | -| `created_by` | INTEGER | -| `max_uses` | INTEGER DEFAULT 1 | -| `used_count` | INTEGER DEFAULT 0 | -| `status` | TEXT DEFAULT `active` | -| `expires_at` | TEXT | - -**`invite_usage`** -| Column | Type | -|--------|------| -| `id` | BIGSERIAL PK | -| `code_id` | BIGINT → invite_codes | -| `user_id` | BIGINT → users | -| `used_at` | TEXT | - -**`refresh_tokens`** -| Column | Type | -|--------|------| -| `id` | BIGSERIAL PK | -| `user_id` | BIGINT → users | -| `token` | TEXT UNIQUE | -| `expires_at` | TEXT | -| `revoked` | INTEGER DEFAULT 0 | - ---- - -#### `market_indicators` — Market Indicator JSONB Store -Populated by `market_data_collector.py`. -| Column | Type | Description | -|--------|------|-------------| -| `symbol` | TEXT | | -| `indicator_type` | TEXT | `long_short_ratio`, `top_trader_position`, `open_interest_hist`, `coinbase_premium`, `funding_rate` | -| `timestamp_ms` | BIGINT | | -| `value` | JSONB | Raw indicator payload | - -Query pattern: `WHERE symbol=? AND indicator_type=? ORDER BY timestamp_ms DESC LIMIT 1`. - -### Storage Design Decisions -- **Partitioning**: `agg_trades` partitioned by month to avoid table bloat; partition maintenance is automated. -- **Dual-write**: Cloud SQL secondary is best-effort (errors logged, never fatal). -- **JSONB `score_factors`**: allows schema-free storage of per-strategy signal breakdowns without migrations. -- **Timestamps**: mix of Unix seconds (`ts`), Unix ms (`time_ms`, `timestamp_ms`, `entry_ts`), ISO strings (`created_at` TEXT in auth tables), and PG `TIMESTAMP`; be careful when querying across tables. - -## Interfaces / Dependencies -- `db.py:init_schema()` — creates all tables in `SCHEMA_SQL` -- `auth.py:ensure_tables()` — creates auth tables from `AUTH_SCHEMA` -- `db.py:ensure_partitions()` — auto-creates monthly `agg_trades_YYYYMM` partitions - -## Unknowns & Risks -- [unknown] `market_indicators` table schema not in `SCHEMA_SQL`; likely created by `market_data_collector.py` separately — verify before querying. -- [risk] Timestamp inconsistency: some tables use TEXT for timestamps (auth tables), others use BIGINT, others use PG TIMESTAMP — cross-table JOINs on time fields require explicit casting. -- [inference] `live_config` circuit-break key name not confirmed from source; inferred from `risk_guard.py` behavior. -- [risk] `users` table defined in both `SCHEMA_SQL` (db.py) and `AUTH_SCHEMA` (auth.py); duplicate CREATE TABLE IF NOT EXISTS; actual schema diverges between the two definitions (db.py version lacks `discord_id`, `banned`). - -## Source Refs -- `backend/db.py:166-356` — `SCHEMA_SQL` with all table definitions -- `backend/auth.py:28-71` — `AUTH_SCHEMA` auth tables -- `backend/db.py:360-414` — `ensure_partitions()`, `init_schema()` -- `backend/signal_engine.py:123-158` — `market_indicators` query pattern diff --git a/docs/ai/05-build-run-test.md b/docs/ai/05-build-run-test.md deleted file mode 100644 index 4fc7169..0000000 --- a/docs/ai/05-build-run-test.md +++ /dev/null @@ -1,251 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: deep ---- - -# 05 — Build, Run & Test - -## Purpose -所有构建、运行、测试、部署相关命令及环境变量配置说明。 - -## TL;DR -- 无 CI/CD 流水线;手动部署到 GCP VM,PM2 管理进程。 -- 后端无构建步骤,直接 `python3 main.py` 或 PM2 启动。 -- 前端标准 Next.js:`npm run dev` / `npm run build` / `npm start`。 -- 测试文件未发现;验证通过 backtest.py 回测和 paper trading 模拟盘进行。 -- 本地开发:前端 `/api/*` 通过 Next.js rewrite 代理到 `http://127.0.0.1:4332`(即 uvicorn 端口)。 -- 数据库 schema 自动在启动时初始化(`init_schema()`),无独立 migration 工具。 - -## Canonical Facts - -### 环境要求 -| 组件 | 要求 | -|------|------| -| Python | 3.10+(使用 `list[dict]` 等 3.10 语法) | -| Node.js | 推荐 20.x(package.json `@types/node: ^20`) | -| PostgreSQL | 本地实例 + Cloud SQL(`10.106.0.3`) | -| PM2 | 用于进程管理(需全局安装) | - -### 后端依赖安装 -```bash -cd backend -pip install -r requirements.txt -# requirements.txt 内容:fastapi, uvicorn, httpx, python-dotenv, psutil -# 实际还需要(从源码 import 推断): -# asyncpg, psycopg2-binary, aiohttp, websockets -``` - -> [inference] `requirements.txt` 内容不完整,仅列出 5 个包,但源码 import 了 `asyncpg`、`psycopg2`、`aiohttp` 等。运行前需确认完整依赖已安装。 - -### 后端启动 - -#### 单进程开发模式 -```bash -cd backend - -# FastAPI HTTP API(默认端口 4332,从 next.config.ts 推断) -uvicorn main:app --host 0.0.0.0 --port 4332 --reload - -# 信号引擎(独立进程) -python3 signal_engine.py - -# aggTrades 收集器 -python3 agg_trades_collector.py - -# 市场数据收集器 -python3 market_data_collector.py - -# 清算数据收集器 -python3 liquidation_collector.py - -# 实盘执行器 -TRADE_ENV=testnet python3 live_executor.py - -# 风控模块 -TRADE_ENV=testnet python3 risk_guard.py - -# 信号推送(Discord) -python3 signal_pusher.py -``` - -#### PM2 生产模式 -```bash -cd backend - -# 使用 ecosystem 配置(目前只定义了 arb-dev-signal) -pm2 start ecosystem.dev.config.js - -# 查看进程状态 -pm2 status - -# 查看日志 -pm2 logs arb-dev-signal - -# 停止所有 -pm2 stop all - -# 重启 -pm2 restart all -``` - -> [inference] `ecosystem.dev.config.js` 当前只配置了 `signal_engine.py`,其他进程需手动启动或添加到 PM2 配置。 - -### 环境变量配置 - -#### 数据库(所有后端进程共用) -```bash -export PG_HOST=127.0.0.1 # 本地 PG -export PG_PORT=5432 -export PG_DB=arb_engine -export PG_USER=arb -export PG_PASS=arb_engine_2026 # 测试网默认,生产需覆盖 - -export CLOUD_PG_HOST=10.106.0.3 # Cloud SQL -export CLOUD_PG_ENABLED=true -``` - -#### 认证 -```bash -export JWT_SECRET=<> # 生产环境必填,长度 ≥32 -# 测试网有默认值 "arb-engine-jwt-secret-v2-2026",生产环境 TRADE_ENV != testnet 时必须设置 -``` - -#### 交易环境 -```bash -export TRADE_ENV=testnet # 或 production -export LIVE_STRATEGIES='["v52_8signals"]' -export RISK_PER_TRADE_USD=2 # 每笔风险 USD -export MAX_POSITIONS=4 # 最大同时持仓数 -``` - -#### 实盘专用(live_executor + risk_guard) -```bash -export DB_HOST=10.106.0.3 -export DB_PASSWORD=<生产密码> -export DB_NAME=arb_engine -export DB_USER=arb - -# 币安 API Key(需在 Binance 配置) -export BINANCE_API_KEY= -export BINANCE_API_SECRET= -``` - -#### 前端 -```bash -# .env.local 或部署环境 -NEXT_PUBLIC_API_URL= # 留空=同源,生产时设为 https://arb.zhouyangclaw.com -``` - -### 数据库初始化 -```bash -# schema 在 FastAPI 启动时自动创建(init_schema + ensure_auth_tables) -# 手动初始化: -cd backend -python3 -c "from db import init_schema; init_schema()" - -# 分区维护(自动在 init_schema 内调用): -python3 -c "from db import ensure_partitions; ensure_partitions()" -``` - -### 前端构建与启动 -```bash -cd frontend -npm install - -# 开发模式(热重载,端口 3000) -npm run dev - -# 生产构建 -npm run build -npm start - -# Lint -npm run lint -``` - -### 前端 API 代理配置 -`frontend/next.config.ts` 将 `/api/*` 代理到 `http://127.0.0.1:4332`。 -- 本地开发时 uvicorn 需监听 **4332 端口**。 -- 生产部署时通过 `NEXT_PUBLIC_API_URL` 或 nginx 反向代理处理跨域。 - -### 回测(离线验证) -```bash -cd backend - -# 指定天数回测 -python3 backtest.py --symbol BTCUSDT --days 20 - -# 指定日期范围回测 -python3 backtest.py --symbol BTCUSDT --start 2026-02-08 --end 2026-02-28 - -# 输出:胜率、盈亏比、夏普比率、最大回撤等统计 -``` - -### 模拟盘(Paper Trading) -通过 `paper_config.json` 控制: -```json -{ - "enabled": true, - "enabled_strategies": ["v52_8signals"], - "initial_balance": 10000, - "risk_per_trade": 0.02, - "max_positions": 4 -} -``` -修改后 signal_engine 下次循环自动读取(无需重启)。 - -监控模拟盘: -```bash -cd backend -python3 paper_monitor.py -``` - -### 管理员 CLI -```bash -cd backend -python3 admin_cli.py gen_invite [count] [max_uses] -python3 admin_cli.py list_invites -python3 admin_cli.py disable_invite -python3 admin_cli.py list_users -python3 admin_cli.py ban_user -python3 admin_cli.py unban_user -python3 admin_cli.py set_admin -python3 admin_cli.py usage -``` - -### 日志位置 -| 进程 | 日志文件 | -|------|---------| -| signal_engine | `signal-engine.log`(项目根目录) | -| risk_guard | `backend/logs/risk_guard.log`(RotatingFileHandler,10MB×5) | -| 其他进程 | stdout / PM2 logs | - -### 无测试框架 -项目中未发现 `pytest`、`unittest` 或任何测试文件。验证策略依赖: -1. **回测**:`backtest.py` 逐 tick 回放历史数据 -2. **模拟盘**:paper trading 实时验证信号质量 -3. **手动测试**:前端页面人工验证 - -## Interfaces / Dependencies -- uvicorn 端口:**4332**(从 `next.config.ts` 推断) -- 前端开发端口:**3000**(Next.js 默认) -- CORS 允许 `localhost:3000` 和 `localhost:3001` - -## Unknowns & Risks -- [inference] uvicorn 端口 4332 从 `next.config.ts` 推断,未在 `main.py` 或启动脚本中显式确认。 -- [inference] `requirements.txt` 不完整,实际依赖需从源码 import 语句归纳。 -- [unknown] 生产部署的 nginx 配置未在仓库中。 -- [risk] 无自动化测试,代码变更风险完全依赖人工回测和 paper trading 验证。 - -## Source Refs -- `frontend/next.config.ts` — API rewrite 代理到 `127.0.0.1:4332` -- `backend/ecosystem.dev.config.js` — PM2 配置(仅 signal_engine) -- `backend/requirements.txt` — 后端依赖(不完整) -- `backend/backtest.py:1-13` — 回测用法说明 -- `backend/paper_config.json` — 模拟盘配置 -- `backend/admin_cli.py:88` — CLI usage 函数 -- `backend/risk_guard.py:81-82` — 日志 RotatingFileHandler 配置 diff --git a/docs/ai/06-decision-log.md b/docs/ai/06-decision-log.md deleted file mode 100644 index 84ff487..0000000 --- a/docs/ai/06-decision-log.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: deep ---- - -# 06 — Decision Log - -## Purpose -记录项目中关键的技术决策、选型原因及权衡取舍(从代码注释和架构特征推断)。 - -## TL;DR -- 选择 PostgreSQL 作为唯一消息总线(NOTIFY/LISTEN),避免引入 Kafka/Redis 等额外组件。 -- signal_engine 改为 15 秒循环(原 5 秒),CPU 降 60%,信号质量无影响。 -- 双写 Cloud SQL 作为灾备,失败不阻断主流程。 -- `agg_trades` 按月分区,避免单表过大影响查询性能。 -- 认证采用自研 HMAC-SHA256 JWT,不依赖第三方库。 -- 前端使用 Next.js App Router + 纯客户端轮询,不使用 WebSocket 推送。 -- 策略参数外置为 JSON 文件,支持热修改无需重启进程。 -- 信号评分采用多层加权体系(5层),每层独立可调,支持多策略并行。 - -## Canonical Facts - -### 决策 1:PostgreSQL 作为进程间消息总线 -**决策**:使用 PostgreSQL `NOTIFY/LISTEN` 在 signal_engine 和 live_executor 之间传递信号,而非 Redis pub/sub 或消息队列。 - -**原因**(从代码推断): -- 系统已强依赖 PG;避免引入新的基础设施依赖。 -- 信号触发频率低(每 15 秒最多一次),PG NOTIFY 完全满足延迟要求。 -- 信号 payload 直接写入 `signal_indicators` 表,NOTIFY 仅做触发通知,消费者可直接查表。 - -**取舍**:单点依赖 PG;PG 宕机时信号传递和持久化同时失败(可接受,因为两者本就强耦合)。 - -**来源**:`live_executor.py:1-10` 架构注释,`signal_engine.py:save_indicator` 函数。 - ---- - -### 决策 2:信号引擎循环间隔从 5 秒改为 15 秒 -**决策**:`LOOP_INTERVAL = 15`(原注释说明原值为 5)。 - -**原因**:代码注释明确写道 "CPU降60%,信号质量无影响"。 - -**取舍**:信号触发延迟最坏增加 10 秒;对于短线但非高频的策略(TP/SL 以 ATR 倍数计,通常 >1% 波动),10 秒的额外延迟影响可忽略不计。 - -**来源**:`signal_engine.py:39` `LOOP_INTERVAL = 15 # 秒(从5改15,CPU降60%,信号质量无影响)` - ---- - -### 决策 3:agg_trades 表按月范围分区 -**决策**:`agg_trades` 使用 `PARTITION BY RANGE(time_ms)`,按月创建子表(如 `agg_trades_202603`)。 - -**原因**: -- aggTrades 是最大的写入表(每秒数百条),无分区会导致单表膨胀。 -- 按月分区支持高效的时间范围查询(PG 分区裁剪)。 -- 旧分区可独立归档或删除,不影响主表。 - -**取舍**:分区管理需要维护(`ensure_partitions()` 自动创建当月+未来2个月分区,需定期执行);跨分区查询性能取决于分区裁剪是否生效(`time_ms` 条件必须是常量)。 - -**来源**:`db.py:191-201, 360-393` - ---- - -### 决策 4:Cloud SQL 双写(非阻塞) -**决策**:所有写入操作在本地 PG 成功后,尝试相同写入到 Cloud SQL(`10.106.0.3`),Cloud SQL 失败不影响主流程。 - -**原因**:提供数据异地备份;Cloud SQL 作为只读副本或灾备使用。 - -**取舍**: -- 本地 PG 和 Cloud SQL 可能出现数据不一致(local 成功 + cloud 失败)。 -- 双写增加每次写操作的延迟(两个网络 RTT),但因为是 best-effort 且使用独立连接池,实际阻塞极少。 -- live_executor 直接连 Cloud SQL(`DB_HOST=10.106.0.3`),绕过本地 PG。 - -**来源**:`db.py:23-29, 80-118`,`live_executor.py:50-55` - ---- - -### 决策 5:自研 JWT(不用 PyJWT 等第三方库) -**决策**:使用 Python 标准库 `hmac`、`hashlib`、`base64` 手动实现 JWT 签发和验证。 - -**原因**(推断):减少依赖;JWT 结构相对简单,HMAC-SHA256 签名几十行代码即可实现。 - -**取舍**: -- 需要自行处理过期、revoke、refresh token 等逻辑(代码中已有 `refresh_tokens` 表)。 -- 非标准实现可能在边界情况(时钟偏差、特殊字符等)上与标准库行为不同。 -- 无 JWT 生态工具支持(调试工具、密钥轮转库等)。 - -**来源**:`auth.py:1-6`(import hashlib, secrets, hmac, base64, json),`auth.py:16-19` - ---- - -### 决策 6:策略配置外置为 JSON 文件 -**决策**:V5.x 策略的权重、阈值、TP/SL 倍数等参数存放在 `backend/strategies/*.json`,signal_engine 每次 `load_strategy_configs()` 读取。 - -**原因**: -- 策略调优频繁(v51→v52 权重变化显著),外置避免每次改参数都要修改代码。 -- 多策略并行:signal_engine 同时运行 v51_baseline 和 v52_8signals,对每个 symbol 分别评分。 -- [inference] 支持未来通过前端或 API 修改策略参数而不重启进程(目前 signal_engine 每次循环重读文件 —— 需确认)。 - -**取舍**:JSON 文件无类型检查,配置错误在运行时才发现;缺少配置 schema 校验。 - -**来源**:`signal_engine.py:41-67`,`backend/strategies/v51_baseline.json`,`backend/strategies/v52_8signals.json` - ---- - -### 决策 7:信号评分采用五层加权体系 -**决策**:信号评分分为 5 个独立层次(方向层、拥挤层、资金费率层、环境层、确认层、清算层、辅助层),每层有独立权重,总分 0~100,阈值 75 触发信号。 - -**设计特点**: -- 方向层(CVD)权重最高(V5.1: 45分,V5.2: 40分),是核心指标。 -- "standard" 档位:score ≥ threshold(75);"heavy" 档位:score ≥ max(threshold+10, 85)。 -- 信号冷却:同一 symbol 同一策略触发后 10 分钟内不再触发。 -- CVD 快慢线需同向才产生完整方向信号;否则标记 `no_direction=True` 不触发。 - -**取舍**:权重缩放逻辑较复杂(各层原始满分不统一,需先归一化再乘权重);`market_indicators` 缺失时给默认中间分,保证系统在数据不完整时仍能运行。 - -**来源**:`signal_engine.py:410-651` - ---- - -### 决策 8:前端使用轮询而非 WebSocket -**决策**:React 前端对 `/api/rates` 每 2 秒轮询,慢速数据(stats/history/signals)每 120 秒轮询,K 线图每 30 秒刷新。 - -**原因**(推断): -- 实现简单,无需维护 WebSocket 连接状态和断线重连逻辑。 -- 数据更新频率(2 秒/30 秒)对轮询友好;WebSocket 的优势在于毫秒级推送。 -- FastAPI 已支持 WebSocket,但实现 SSE/WS 推送需要额外的后端状态管理。 - -**取舍**:每 2 秒轮询 `/api/rates` 会产生持续的服务器负载;当用户量增加时需要加缓存或换 WebSocket。 - -**来源**:`frontend/app/page.tsx:149-154` - ---- - -### 决策 9:live_executor 和 risk_guard 直连 Cloud SQL -**决策**:`live_executor.py` 和 `risk_guard.py` 默认 `DB_HOST=10.106.0.3`(Cloud SQL),而不是本地 PG。 - -**原因**(推断):这两个进程运行在与 signal_engine 不同的环境(可能是另一台 GCP VM 或容器),直连 Cloud SQL 避免通过本地 PG 中转。 - -**取舍**:live_executor 和 signal_engine 使用不同的 PG 实例,理论上存在数据读取延迟(双写同步延迟)。 - -**来源**:`live_executor.py:50-55`,`risk_guard.py:47-53` - -## Interfaces / Dependencies -无额外接口依赖,均为内部架构决策。 - -## Unknowns & Risks -- [inference] 所有决策均从代码推断,无明确的 ADR(Architecture Decision Record)文档。 -- [unknown] 策略配置是否支持热重载(signal_engine 是否每次循环都重读 JSON)未确认。 -- [risk] 决策 4(双写)+ 决策 9(live executor 直连 Cloud SQL)组合下,若本地 PG 和 Cloud SQL 数据不一致,live_executor 可能读到滞后的信号或重复执行。 - -## Source Refs -- `backend/signal_engine.py:39` — LOOP_INTERVAL 注释 -- `backend/signal_engine.py:44-67` — load_strategy_configs -- `backend/signal_engine.py:410-651` — evaluate_signal 完整评分逻辑 -- `backend/db.py:23-29, 80-118` — Cloud SQL 双写连接池 -- `backend/live_executor.py:50-55` — DB_HOST 配置 -- `backend/auth.py:1-6` — 自研 JWT import -- `frontend/app/page.tsx:149-154` — 轮询间隔 -- `backend/strategies/v51_baseline.json`, `v52_8signals.json` diff --git a/docs/ai/07-glossary.md b/docs/ai/07-glossary.md deleted file mode 100644 index f65d9f8..0000000 --- a/docs/ai/07-glossary.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: deep ---- - -# 07 — Glossary - -## Purpose -项目中使用的专业术语、领域术语和项目自定义术语的定义。 - -## TL;DR -- 项目混合使用量化交易、加密货币和工程术语,中英文混用。 -- "R" 是风险单位,1R = 单笔风险金额(PAPER_RISK_PER_TRADE × 余额)。 -- CVD 是核心指标:累计 delta = 主动买量 - 主动卖量,衡量买卖压力。 -- ATR 用于动态计算止盈止损距离(TP/SL 均以 ATR 倍数表示)。 -- "tier" 指仓位档位:light/standard/heavy,对应不同仓位大小倍数。 - -## Canonical Facts - -### 交易与量化术语 - -| 术语 | 定义 | -|------|------| -| **资金费率(Funding Rate)** | 永续合约中多空双方每8小时相互支付的费率。正费率:多头付给空头;负费率:空头付给多头。以小数表示(如 `0.0001` = 0.01%)。 | -| **永续合约(Perpetual / Perp)** | 无到期日的期货合约,通过资金费率机制锚定现货价格。本项目操作 Binance USDC-M 永续合约。 | -| **套利(Arbitrage)** | 持有现货多头 + 永续空头,资金费率为正时空头每8小时收取费率收益,实现无方向性风险的稳定收益。 | -| **年化(Annualized)** | `平均费率 × 3次/天 × 365天 × 100%`,将单次资金费率换算为年化百分比。 | -| **CVD(Cumulative Volume Delta)** | 累计成交量差值 = 主动买量 - 主动卖量。正值表示买方主导,负值表示卖方主导。本项目计算三个窗口:CVD_fast(30分钟)、CVD_mid(4小时)、CVD_day(UTC日内)。 | -| **aggTrade** | Binance 聚合成交数据:同一方向、同一价格、同一时刻的多笔成交合并为一条记录,包含 `is_buyer_maker` 字段(0=主动买,1=主动卖)。 | -| **is_buyer_maker** | `0`:买方是 taker(主动买入),`1`:买方是 maker(被动成交,即主动卖)。CVD 计算:0→买量,1→卖量。 | -| **VWAP(Volume Weighted Average Price)** | 成交量加权平均价格。用于判断当前价格相对于短期平均成本的位置。 | -| **ATR(Average True Range)** | 平均真实波动幅度,衡量市场波动性。本项目使用 5 分钟 K 线、14 周期 EMA 计算。 | -| **ATR Percentile** | 当前 ATR 在过去 24 小时内的历史分位数(0~100),衡量当前波动性是高还是低。 | -| **P95 / P99** | 过去 24 小时内成交量的第 95/99 百分位数,作为"大单阈值"。超过 P99 的成交视为大单,对信号评分有影响。 | -| **Long/Short Ratio(多空比)** | 全市场多头账户数 / 空头账户数。反映市场情绪拥挤程度。 | -| **Top Trader Position(顶级交易者持仓比)** | 大户多头持仓占比,范围 0~1。高于 0.55 视为多头拥挤,低于 0.45 视为空头拥挤。 | -| **Open Interest(OI,持仓量)** | 市场上所有未平仓合约的总名义价值(USD)。OI 增加 ≥3% 视为环境强势信号。 | -| **Coinbase Premium** | Coinbase Pro BTC/USD 现货价格相对 Binance BTC/USDT 的溢价比例。正溢价(>0.05%)被视为看涨信号(美国机构买入)。以比例存储(如 `0.0005` = 0.05%)。 | -| **清算(Liquidation)** | 爆仓事件。空头清算多于多头清算(短时间内)视为看涨信号(逼空)。本项目使用 5 分钟窗口内多空清算 USD 之比进行评分。 | -| **R(风险单位)** | 单笔风险金额。1R = `初始余额 × 风险比例`(默认 2%,即 200U)。盈亏以 R 倍数表示:1R=保本,2R=盈利1倍风险,-1R=全亏。 | -| **PnL_R** | 以 R 为单位的盈亏:`pnl_r = (exit_price - entry_price) / risk_distance × direction_sign`。 | -| **TP1 / TP2(Take Profit)** | 止盈目标价。TP1 为第一目标(触发后平一半仓位),TP2 为第二目标(平剩余)。 | -| **SL(Stop Loss)** | 止损价。SL 触发后视 TP1 是否已命中:未命中→亏损 1R;已命中→保本(sl_be 状态)。 | -| **Tier(档位)** | 仓位大小分级。`light`=0.5×R,`standard`=1.0×R,`heavy`=1.5×R。信号分数越高触发越重的档位:score ≥ max(threshold+10, 85) → heavy;score ≥ threshold → standard。 | -| **Warmup(冷启动)** | signal_engine 启动时读取历史 `agg_trades` 填充滚动窗口的过程,完成前不产生信号(`state.warmup=True`)。 | -| **Signal Cooldown(信号冷却)** | 同一 symbol 同一策略触发信号后,10 分钟内不再触发新信号,防止过度交易。 | - -### 策略术语 - -| 术语 | 定义 | -|------|------| -| **v51_baseline** | V5.1 基准策略。6 个信号:cvd, p99, accel, ls_ratio, oi, coinbase_premium。SL=1.4×ATR,TP1=1.05×ATR,TP2=2.1×ATR。 | -| **v52_8signals** | V5.2 扩展策略。8 个信号(v51 + funding_rate + liquidation)。SL=2.1×ATR,TP1=1.4×ATR,TP2=3.15×ATR(更宽止损,更高盈亏比目标)。 | -| **Score / 信号分数** | 0~100 的综合评分,由多层加权指标累加得出,阈值 75 触发信号。 | -| **Direction Layer(方向层)** | 评分第一层,最高 45 分(v51)或 40 分(v52)。基于 CVD_fast、CVD_mid 同向性和 P99 大单方向。 | -| **Crowding Layer(拥挤层)** | 基于多空比和顶级交易者持仓的市场拥挤度评分。 | -| **Environment Layer(环境层)** | 基于持仓量变化(OI change)的市场环境评分。 | -| **Confirmation Layer(确认层)** | CVD 快慢线同向确认,15 分(满足)或 0 分。 | -| **Auxiliary Layer(辅助层)** | Coinbase Premium 辅助确认,0~5 分。 | -| **Accel Bonus(加速奖励)** | CVD 快线斜率正在加速时额外加分(v51: +5分,v52: +0分)。 | -| **Score Factors** | 各层得分详情,以 JSONB 格式存储在 `paper_trades.score_factors` 和 `live_trades.score_factors`。 | - -### 工程术语 - -| 术语 | 定义 | -|------|------| -| **Paper Trading / 模拟盘** | 不真实下单、仅模拟记录的交易,用于验证策略。数据存储在 `paper_trades` 表。 | -| **Live Trading / 实盘** | 通过 Binance API 真实下单执行的交易。数据存储在 `live_trades` 表。 | -| **Testnet** | Binance 测试网(`https://testnet.binancefuture.com`),使用虚拟资金。`TRADE_ENV=testnet`。 | -| **Production** | Binance 生产环境(`https://fapi.binance.com`),使用真实资金。`TRADE_ENV=production`。 | -| **Circuit Break(熔断)** | risk_guard 触发的保护机制,阻止新开仓甚至强制平仓。通过 `live_config` 表的 flag 通知 live_executor。 | -| **Dual Write(双写)** | 同一数据同时写入本地 PG 和 Cloud SQL,Cloud SQL 写失败不阻断主流程。 | -| **Partition / 分区** | `agg_trades` 表的月度子表(如 `agg_trades_202603`),用于管理大表性能。 | -| **NOTIFY/LISTEN** | PostgreSQL 原生异步通知机制。signal_engine 用 `NOTIFY new_signal` 触发,live_executor 用 `LISTEN new_signal` 接收。 | -| **TradeWindow** | signal_engine 中的滚动时间窗口类,维护 CVD 和 VWAP 的实时滚动计算。 | -| **SymbolState** | 每个交易对的完整状态容器,包含三个 TradeWindow、ATRCalculator、market_indicators 缓存和信号冷却记录。 | -| **Invite Code(邀请码)** | 注册时必须提供的一次性(或限次)代码,由管理员通过 `admin_cli.py` 生成。 | -| **Subscription Tier** | 用户订阅等级(`free` 等),存储在 `subscriptions` 表,当前代码中使用有限。 | -| **万分之** | 前端显示资金费率时的单位表述,实际值 × 10000 展示。例如 `0.0001` 显示为 `1.0000 万分之`。 | - -## Interfaces / Dependencies -无。 - -## Unknowns & Risks -- [inference] `Subscription Tier` 功能在 schema 中有定义但实际业务逻辑中使用程度不确定(可能是预留字段)。 -- [inference] "no_direction" 状态(CVD_fast 和 CVD_mid 不一致时)的处理逻辑:方向取 CVD_fast,但标记为不触发信号,可用于反向平仓判断。 - -## Source Refs -- `backend/signal_engine.py:1-16` — CVD/ATR/VWAP/P95/P99 架构注释 -- `backend/signal_engine.py:69-81` — Paper trading 参数定义(R、tier 倍数) -- `backend/signal_engine.py:170-207` — TradeWindow 类(CVD/VWAP 定义) -- `backend/signal_engine.py:209-257` — ATRCalculator 类 -- `backend/signal_engine.py:410-651` — evaluate_signal(各层评分逻辑) -- `backend/strategies/v51_baseline.json`, `v52_8signals.json` — 策略参数 -- `backend/trade_config.py` — 交易对精度配置 -- `frontend/app/page.tsx:186` — "万分之" 显示注释 diff --git a/docs/ai/99-open-questions.md b/docs/ai/99-open-questions.md deleted file mode 100644 index 8154133..0000000 --- a/docs/ai/99-open-questions.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: deep ---- - -# 99 — Open Questions - -## Purpose -记录文档生成过程中发现的未解决问题、不确定点和潜在风险。 - -## TL;DR -- `requirements.txt` 不完整,实际依赖需手动补齐。 -- `users` 表在两个地方定义且 schema 不一致(db.py vs auth.py)。 -- live_executor / risk_guard 直连 Cloud SQL 但 signal_engine 写本地 PG,存在数据同步延迟风险。 -- 策略是否支持热重载(每次循环重读 JSON)未确认。 -- uvicorn 监听端口 4332 未在启动脚本中显式确认。 -- 无 CI/CD,无自动化测试。 - -## Open Questions - -### 高优先级(影响正确性) - -#### Q1:users 表 schema 双定义不一致 -**问题**:`db.py` 的 `SCHEMA_SQL` 和 `auth.py` 的 `AUTH_SCHEMA` 均定义了 `users` 表,但字段不同: -- `db.py` 版本:`id, email, password_hash, role, created_at`(无 `discord_id`、无 `banned`) -- `auth.py` 版本:`id, email, password_hash, discord_id, role, banned, created_at` - -`init_schema()` 和 `ensure_auth_tables()` 都在 FastAPI startup 中调用,两次 `CREATE TABLE IF NOT EXISTS` 第一次成功后第二次静默跳过。**实际创建的是哪个版本?** 取决于调用顺序(先 `init_schema` 后 `ensure_auth_tables`),如果本地 PG 已有旧版表则字段可能缺失。 - -**影响**:auth 相关功能(discord_id 关联、banned 状态检查)可能在 schema 未更新的环境下失效。 - -**建议行动**:统一到 auth.py 版本,或添加 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` 迁移。 - -**来源**:`db.py:269-276`,`auth.py:28-37` - ---- - -#### Q2:live_executor 读 Cloud SQL,signal_engine 写本地 PG,双写延迟是否可接受 -**问题**:signal_engine 写入本地 PG(`signal_indicators`),同时双写 Cloud SQL;live_executor 直连 Cloud SQL 读取信号。若某次双写失败或延迟,live_executor 可能错过信号或读到不一致数据。 - -**影响**:实盘信号丢失或执行延迟。 - -**建议行动**:确认 NOTIFY 是否也发送到 Cloud SQL(即 live_executor 通过 LISTEN 接收信号,不依赖轮询读表);或将 live_executor 改为连接本地 PG。 - -**来源**:`live_executor.py:50-55`,`db.py:76-95` - ---- - -#### Q3:requirements.txt 不完整 -**问题**:`requirements.txt` 只列出 `fastapi, uvicorn, httpx, python-dotenv, psutil`,但源码还 import 了 `asyncpg`、`psycopg2`(用于 psycopg2-binary)、`aiohttp`、`websockets`(推断)等。 - -**影响**:新环境安装后进程无法启动。 - -**建议行动**:执行 `pip freeze > requirements.txt` 或手动补全所有依赖。 - -**来源**:`backend/requirements.txt:1-5`,`backend/db.py:9-11`,`backend/live_executor.py:28-29` - ---- - -### 中优先级(影响维护性) - -#### Q4:策略 JSON 是否支持热重载 -**问题**:`load_strategy_configs()` 在 `main()` 函数开头调用一次。不清楚 signal_engine 的主循环是否每次迭代都重新调用此函数。 - -**影响**:如果不支持热重载,修改策略 JSON 后需要重启 signal_engine 进程。 - -**来源**:`signal_engine.py:44-67, 964`(需查看 main 函数结构) - ---- - -#### Q5:uvicorn 端口确认 -**问题**:从 `frontend/next.config.ts` 推断 uvicorn 运行在 `127.0.0.1:4332`,但没有找到后端启动脚本明确指定此端口。 - -**建议行动**:在 `ecosystem.dev.config.js` 或启动脚本中显式记录端口。 - -**来源**:`frontend/next.config.ts:8` - ---- - -#### Q6:market_indicators 表 schema 未在 SCHEMA_SQL 中定义 -**问题**:signal_engine 从 `market_indicators` 表读取数据(指标类型:`long_short_ratio`, `top_trader_position`, `open_interest_hist`, `coinbase_premium`, `funding_rate`),但该表的 CREATE TABLE 语句不在 `db.py` 的 `SCHEMA_SQL` 中。 - -**影响**:表由 `market_data_collector.py` 单独创建;如果该进程未运行过,表不存在,signal_engine 会报错或返回空数据。 - -**建议行动**:将 `market_indicators` 表定义加入 `SCHEMA_SQL`,确保 `init_schema()` 能覆盖全量 schema。 - -**来源**:`signal_engine.py:123-158`,`db.py:166-357`(未见 market_indicators 定义) - ---- - -#### Q7:liquidations 表 schema 未确认 -**问题**:signal_engine 查询 `liquidations` 表(`SELECT FROM liquidations WHERE symbol=%s AND trade_time >= %s`),但该表定义在 `SCHEMA_SQL` 中同样未找到。可能由 `liquidation_collector.py` 自行创建。 - -**来源**:`signal_engine.py:395-407`,`liquidation_collector.py:28`(`ensure_table()` 函数) - ---- - -### 低优先级(长期健康度) - -#### Q8:无 CI/CD 流水线 -**问题**:仓库中没有 `.github/workflows/`、Dockerfile、docker-compose.yml 等部署自动化文件。所有部署为手动操作(ssh + git pull + pm2 restart)。 - -**建议行动**:添加 GitHub Actions 用于基本 lint 检查和依赖安全扫描。 - ---- - -#### Q9:无自动化测试 -**问题**:未发现任何测试文件(`test_*.py`、`*.test.ts` 等)。策略验证完全依赖人工回测和模拟盘。 - -**建议行动**:至少为 `evaluate_signal()`、`TradeWindow`、`ATRCalculator` 添加单元测试,防止重构回归。 - ---- - -#### Q10:生产环境硬编码密码风险 -**问题**:`db.py`、`live_executor.py`、`risk_guard.py` 中均有 testnet 默认密码 `arb_engine_2026` 硬编码在源代码里(通过 `os.getenv(..., "arb_engine_2026")` 方式)。 - -**影响**:代码一旦泄露,testnet 数据库可被访问;生产环境如果环境变量设置失败,会静默使用错误密码(失败时的错误信息较明确,但仍有风险)。 - -**建议行动**:testnet 默认密码移除或通过单独的 `.env.testnet` 文件管理,不内嵌到源代码。 - -**来源**:`db.py:19`,`live_executor.py:44`,`risk_guard.py:42` - ---- - -#### Q11:`signal_indicators` 表含 `strategy` 字段但 schema 未声明 -**问题**:`save_indicator()` 函数的 INSERT 语句包含 `strategy` 字段,但 `SCHEMA_SQL` 中的 `signal_indicators` 表定义不包含该字段。可能通过 `ALTER TABLE ADD COLUMN IF NOT EXISTS` 在运行时补充,或是后续版本添加但忘记更新 schema。 - -**来源**:`signal_engine.py:690-699`,`db.py:205-224`(signal_indicators 定义) - -## Source Refs -- `backend/db.py:269-276` — db.py 版 users 表 -- `backend/auth.py:28-37` — auth.py 版 users 表(含 discord_id, banned) -- `backend/requirements.txt` — 不完整的依赖列表 -- `backend/live_executor.py:44, 50-55` — DB_HOST 和默认密码 -- `backend/risk_guard.py:42, 47-53` — DB_HOST 和默认密码 -- `backend/signal_engine.py:395-407` — liquidations 表查询 -- `backend/signal_engine.py:690-699` — strategy 字段 INSERT diff --git a/docs/ai/INDEX.md b/docs/ai/INDEX.md deleted file mode 100644 index 678da19..0000000 --- a/docs/ai/INDEX.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -generated_by: repo-insight -version: 1 -created: 2026-03-03 -last_updated: 2026-03-03 -source_commit: 0d9dffa -coverage: deep ---- - -# Arbitrage Engine — AI Documentation Index - -**Project**: `arbitrage-engine` -**Summary**: Full-stack crypto perpetual futures funding-rate arbitrage monitoring and V5.x CVD/ATR-based short-term trading signal engine. Python/FastAPI backend + Next.js frontend + PostgreSQL + Binance USDC-M Futures. - -## Generated Documents - -| File | Description | -|------|-------------| -| [00-system-overview.md](./00-system-overview.md) | Project purpose, tech stack, repo layout, entry points, environment variables | -| [01-architecture-map.md](./01-architecture-map.md) | Multi-process architecture, component diagram, signal pipeline data flow, risk guard rules, frontend polling | -| [02-module-cheatsheet.md](./02-module-cheatsheet.md) | Module-by-module index: role, public interfaces, dependencies for all 20 backend + 15 frontend files | -| [03-api-contracts.md](./03-api-contracts.md) | All REST endpoints, auth flows, request/response shapes, error conventions | -| [04-data-model.md](./04-data-model.md) | All PostgreSQL tables, columns, partitioning strategy, storage design decisions | -| [05-build-run-test.md](./05-build-run-test.md) | 构建/运行/部署命令,环境变量,PM2 配置,回测和模拟盘操作 | -| [06-decision-log.md](./06-decision-log.md) | 9 项关键技术决策:PG 消息总线、循环间隔、双写、分区、自研 JWT 等 | -| [07-glossary.md](./07-glossary.md) | 交易术语(CVD/ATR/R/tier)+ 工程术语(paper trading/warmup/circuit break) | -| [99-open-questions.md](./99-open-questions.md) | 11 个未解决问题:users 表双定义冲突、依赖不完整、硬编码密码、无测试等 | - -## Recommended Reading Order - -1. **Start here**: `00-system-overview.md` — 了解项目定位和结构。 -2. **Architecture**: `01-architecture-map.md` — 理解 7+ 进程的交互方式。 -3. **Data**: `04-data-model.md` — 任何 DB 相关工作的必读;注意时间戳格式不统一问题。 -4. **API**: `03-api-contracts.md` — 前端开发或 API 对接时参考。 -5. **Module detail**: `02-module-cheatsheet.md` — 修改特定文件前的参考。 -6. **Ops**: `05-build-run-test.md` — 部署和运维操作。 -7. **Concepts**: `07-glossary.md` — 不熟悉量化术语时查阅。 -8. **Risks**: `99-open-questions.md` — 开始开发前必读,了解已知风险点。 - -## Coverage Tier -**Deep** — 包含完整的模块签名读取、核心业务模块深度阅读(signal_engine 全文、evaluate_signal 评分逻辑、backtest.py)、构建运行指南、决策日志、术语表和开放问题。 - -## Key Facts for AI Agents -- **Signal engine is the core**: `backend/signal_engine.py` — change with care; affects all trading modes. -- **Strategy tuning via JSON**: modify `backend/strategies/v51_baseline.json` or `v52_8signals.json` to change signal weights/thresholds without code changes. -- **No ORM**: raw SQL via `asyncpg`/`psycopg2`; schema in `db.py:SCHEMA_SQL`. -- **Auth is custom JWT**: no third-party auth library; hand-rolled HMAC-SHA256 in `auth.py`. -- **`TRADE_ENV=testnet` default**: production use requires explicit env override + strong JWT_SECRET. -- **Dual timestamp formats**: `ts` = Unix seconds, `time_ms`/`entry_ts`/`timestamp_ms` = Unix milliseconds — do not confuse. - -## Generation Timestamp -2026-03-03T00:00:00 (UTC) diff --git a/docs/arbitrage-engine-full-spec.md b/docs/arbitrage-engine-full-spec.md index 9b1fa0f..8d2c28a 100644 --- a/docs/arbitrage-engine-full-spec.md +++ b/docs/arbitrage-engine-full-spec.md @@ -94,9 +94,9 @@ title: "Arbitrage Engine 完整项目规格文档" | gate_spot_perp_enabled | bool | 是否启用期现背离门 | | spot_perp_threshold | float | 期现背离阈值(默认 0.002) | | gate_cvd_enabled | bool | 是否启用 CVD 共振门 | -| sl_atr_multiplier | float | SL = sl_atr_multiplier × ATR(默认 1.5) | -| tp1_ratio | float | TP1 = tp1_ratio × ATR(默认 0.75) | -| tp2_ratio | float | TP2 = tp2_ratio × ATR(默认 1.5) | +| sl_atr_multiplier | float | SL 宽度倍数,`risk_distance = sl_atr_multiplier × ATR`,定义 1R 的价格距离(默认 1.5) | +| tp1_ratio | float | TP1 目标,以 R 计:`TP1 距离 = tp1_ratio × risk_distance`(默认 0.75) | +| tp2_ratio | float | TP2 目标,以 R 计:`TP2 距离 = tp2_ratio × risk_distance`(默认 1.5) | | timeout_minutes | int | 超时平仓分钟数(默认 240) | | flip_threshold | int | 反向信号强制平仓的最低分(默认 80) | | initial_balance | float | 初始模拟资金(默认 $10,000) | @@ -158,11 +158,12 @@ title: "Arbitrage Engine 完整项目规格文档" - `timeout`:持仓超时平仓(240分钟) - `signal_flip`:反向信号强制平仓 -**pnl_r 计算规则**: +**pnl_r 计算规则**(以价格距离除以 1R = risk_distance 为基准): - SL 触发:`pnl_r = -1.0`(恒定) - TP1 触发后 SL_BE:`pnl_r = 0.5 × tp1_ratio` - 全仓 TP2:`pnl_r = 0.5 × tp1_ratio + 0.5 × tp2_ratio` - timeout/flip:`pnl_r = (exit_price - entry_price) / risk_distance`(LONG 方向,SHORT 取反) +- 实际实现中,以上结果再统一减去手续费折算:`fee_r = (2 × fee_rate × entry_price) / risk_distance`,当前 `fee_rate = 0.0005`(0.05% taker),即最终写入的 `pnl_r = 上述值 - fee_r`。 **胜率定义**(重要):`pnl_r > 0` 的笔数 / 总闭仓笔数。不用 status 字段判断。 @@ -327,7 +328,7 @@ Admin 账号:`fzq1228@gmail.com`,role=admin。 - fast 周期 > 30m → 从 `win_mid.trades` 切片 - slow 周期 → 始终从 `win_mid.trades` 切片 -### 5.3 评分模型(`_evaluate_v53`) +### 5.3 评分模型(`evaluate_factory_strategy`,别名 `evaluate_v53`,原 `_evaluate_v53`) **四层线性评分**,总分 = 各层得分之和,满分 = 各层权重之和。 @@ -383,8 +384,8 @@ for each symbol: **SL/TP 计算**: - `risk_distance = sl_atr_multiplier × ATR`(1R) -- LONG:`SL = price - risk_distance`,`TP1 = price + tp1_ratio × ATR`,`TP2 = price + tp2_ratio × ATR` -- SHORT:`SL = price + risk_distance`,`TP1 = price - tp1_ratio × ATR`,`TP2 = price - tp2_ratio × ATR` +- LONG:`SL = price - risk_distance`,`TP1 = price + tp1_ratio × risk_distance`,`TP2 = price + tp2_ratio × risk_distance` +- SHORT:`SL = price + risk_distance`,`TP1 = price - tp1_ratio × risk_distance`,`TP2 = price - tp2_ratio × risk_distance` **当前标准TP/SL配置(BTC 18组对照)**: - 保守:`sl=2.0×ATR, tp1=0.75×ATR, tp2=1.5×ATR`(TP全到=+0.5625R) @@ -554,7 +555,7 @@ agg_trades 表(PostgreSQL 分区表) ↓ signal_engine.py(每15秒读取) 滑动窗口(win_fast 30m / win_mid 4h / win_day 24h) ↓ 计算CVD/ATR/VWAP -_evaluate_v53() +evaluate_factory_strategy() (内部别名 evaluate_v53,原 _evaluate_v53) ↓ 5门检查 → 四层评分 signal_indicators 写入 ↓ signal IS NOT NULL 且 score >= entry_score diff --git a/docs/arbitrage-engine/execution-state-machine.md b/docs/arbitrage-engine/execution-state-machine.md deleted file mode 100644 index 233f79b..0000000 --- a/docs/arbitrage-engine/execution-state-machine.md +++ /dev/null @@ -1,251 +0,0 @@ -# V5.4 Execution State Machine - -本文档描述 V5.4 策略的执行层状态机设计,重点是 maker/taker 组合策略,确保在不牺牲入场/出场时效性的前提下,最大程度降低手续费与滑点摩擦。 - -设计目标: -- 将"信号质量"和"执行质量"解耦,执行层只负责:更便宜、更稳定地实现既定 TP/SL/flip/timeout 规则。 -- 入场阶段在不丢失大行情的前提下尽量使用 maker; -- 出场阶段 TP 强制 maker 主路径,同时用 taker 做安全兜底; -- SL 始终使用 taker,优先保证风控。 - ---- - -## 1. 入场状态机(Entry State Machine) - -### 1.1 状态定义 - -- `IDLE`:无持仓、无挂单,等待信号。 -- `ENTRY_PENDING_MAKER`:已下入场限价挂单(post-only),等待成交或超时。 -- `ENTRY_FILL`:入场成交完成(全仓或部分)。 -- `ENTRY_FALLBACK_TAKER`:超时后使用 taker 市价单补齐未成交部分。 - -### 1.2 关键参数 - -- `entry_price_signal`:信号引擎给出的入场参考价(通常为最新价或中间价 mid)。 -- `tick_size`:交易所最小价格步长。 -- `entry_offset_ticks`:maker 入场挂单相对盘口的偏移(通常为 1–2 个 tick)。 -- `entry_timeout_ms`:入场 maker 挂单最大等待时间(如 3000–5000ms)。 -- `entry_fallback_slippage_bps`:fallback taker 允许的最大滑点(基础保护,超出则放弃补仓或缩小仓位)。 - -### 1.3 状态机伪代码 - -```text -state = IDLE - -on_signal_open(signal): - if state != IDLE: - return // 避免重复入场 - - // 计算 maker 挂单价格 - side = signal.side // LONG or SHORT - ref_price = best_bid_ask_mid() - - if side == LONG: - entry_price_maker = min(ref_price, best_bid() + entry_offset_ticks * tick_size) - else: // SHORT - entry_price_maker = max(ref_price, best_ask() - entry_offset_ticks * tick_size) - - // 下 post-only 入场挂单 - order_id = place_limit_post_only(side, entry_price_maker, target_size) - entry_start_ts = now() - state = ENTRY_PENDING_MAKER - - -on_timer(): - if state == ENTRY_PENDING_MAKER: - if order_filled(order_id): - filled_size = get_filled_size(order_id) - if filled_size >= min_fill_ratio * target_size: - state = ENTRY_FILL - return - - if now() - entry_start_ts >= entry_timeout_ms: - // 超时,取消剩余挂单 - cancel_order(order_id) - remaining_size = target_size - get_filled_size(order_id) - - if remaining_size <= 0: - state = ENTRY_FILL - return - - // 兜底:按容忍滑点发市价单 - mkt_price = best_bid_ask_mid() - theoretical_price = ref_price_at_signal - slippage_bps = abs(mkt_price - theoretical_price) / theoretical_price * 10000 - - if slippage_bps <= entry_fallback_slippage_bps: - place_market_order(side, remaining_size) - state = ENTRY_FILL - else: - // 滑点过大,放弃补仓或缩减仓位 - state = ENTRY_FILL // 仅保留已成交部分 -``` - ---- - -## 2. 出场状态机(TP/SL/Flip/Timeout) - -出场分为四类:TP(止盈)、SL(止损)、flip(信号翻转)、timeout(超时退出)。 - -### 2.1 通用状态 - -- `POSITION_OPEN`:持仓打开,已根据策略下好 TP/SL 限价单。 -- `TP_PENDING_MAKER`:TP 限价挂单等待成交。 -- `TP_FALLBACK_TAKER`:TP 越价未成交时,撤单+市价平仓兜底。 -- `SL_PENDING`:止损触发,直接发送 taker 单。 -- `FLIP_PENDING`:翻转触发,先平仓再反向开仓(可复用入场状态机)。 -- `TIMEOUT_PENDING`:超时触发,按策略规则离场(可偏 maker)。 - -### 2.2 关键参数 - -- `tp1_r`, `tp2_r`:TP1/TP2 的目标 R 距离(如 1.0R / 2.0R)。 -- `sl_r`:止损距离(如 -1.0R)。 -- `tp_timeout_ms`:价格越过 TP 水平后,TP 限价未成交的允许时间窗口。 -- `flip_threshold`:翻转触发条件(score + OBI + VWAP 等综合判断)。 -- `timeout_seconds`:最大持仓时间,用于 timeout 出场。 - -### 2.3 TP 状态机(maker 主路径 + taker 兜底) - -```text -on_position_open(pos): - // 开仓后立即挂 TP1 限价单(maker) - tp1_price = pos.entry_price + pos.side * tp1_r * pos.risk_distance - tp2_price = pos.entry_price + pos.side * tp2_r * pos.risk_distance - - // 半仓挂 TP1,半仓挂 TP2 - tp1_id = place_limit_post_only(exit_side(pos.side), tp1_price, pos.size * 0.5) - tp2_id = place_limit_post_only(exit_side(pos.side), tp2_price, pos.size * 0.5) - pos.state = POSITION_OPEN - - -on_timer(): - if pos.state == POSITION_OPEN: - current_price = best_bid_ask_mid() - - // 检查 TP1 越价兜底 - tp1_crossed = (pos.side == LONG and current_price >= tp1_price) or - (pos.side == SHORT and current_price <= tp1_price) - if tp1_crossed and not pos.tp1_cross_ts: - pos.tp1_cross_ts = now() - - if pos.tp1_cross_ts: - if order_filled(tp1_id): - pos.tp1_cross_ts = None // 成交,清除计时 - elif now() - pos.tp1_cross_ts >= tp_timeout_ms: - cancel_order(tp1_id) - remaining = size_tp1 - get_filled_size(tp1_id) - if remaining > 0: - place_market_order(exit_side(pos.side), remaining) - - // 检查 TP2 越价兜底 - tp2_crossed = (pos.side == LONG and current_price >= tp2_price) or - (pos.side == SHORT and current_price <= tp2_price) - if tp2_crossed and not pos.tp2_cross_ts: - pos.tp2_cross_ts = now() - - if pos.tp2_cross_ts: - if order_filled(tp2_id): - pos.tp2_cross_ts = None - elif now() - pos.tp2_cross_ts >= tp_timeout_ms: - cancel_order(tp2_id) - remaining = size_tp2 - get_filled_size(tp2_id) - if remaining > 0: - place_market_order(exit_side(pos.side), remaining) - - // 检查是否已全部平仓 - if pos_size(pos) <= 0: - pos.state = CLOSED -``` - -### 2.4 SL 状态机(纯 taker) - -```text -on_sl_trigger(pos, sl_price): - // 触发条件可以来自价格监控或止损订单触发 - // 这里策略层只关心:一旦触发,立即使用 taker - close_size = pos_size(pos) - if close_size > 0: - place_market_order(exit_side(pos.side), close_size) - pos.state = CLOSED -``` - -SL 不做 maker 逻辑,避免在极端行情下挂单无法成交。 - -### 2.5 Flip 状态机(平旧仓 + 新开仓) - -```text -on_flip_signal(pos, new_side, flip_context): - if not flip_condition_met(flip_context): - return - - // flip 条件:score < 85 AND OBI 翻转 AND 价格跌破 VWAP(三条件同时满足) - // flip_condition_met 由信号引擎判断 - - // 1) 先平旧仓(按 SL 逻辑,优先 taker) - close_size = pos_size(pos) - if close_size > 0: - place_market_order(exit_side(pos.side), close_size) - - pos.state = CLOSED - - // 2) 再按入场状态机开新仓 - new_signal = build_signal_from_flip(new_side, flip_context) - entry_state_machine.on_signal_open(new_signal) -``` - -flip 的关键是:**门槛更高**(如 score < 85 且 OBI 翻转且价格跌破 VWAP),尽量减少在震荡行情中来回打脸。 - -### 2.6 Timeout 状态机(超时出场) - -```text -on_timer(): - if pos.state == POSITION_OPEN and now() - pos.open_ts >= timeout_seconds: - // 可以偏 maker:先挂限价平仓,超时再 taker - timeout_price = best_bid_ask_mid() - size = pos_size(pos) - - oid = place_limit_post_only(exit_side(pos.side), timeout_price, size) - pos.timeout_order_id = oid - pos.timeout_start_ts = now() - pos.state = TIMEOUT_PENDING - - if pos.state == TIMEOUT_PENDING: - if order_filled(pos.timeout_order_id): - pos.state = CLOSED - elif now() - pos.timeout_start_ts >= timeout_grace_ms: - cancel_order(pos.timeout_order_id) - remaining = pos_size(pos) - if remaining > 0: - place_market_order(exit_side(pos.side), remaining) - pos.state = CLOSED -``` - ---- - -## 3. 监控指标(执行层 KPI) - -| 指标 | 说明 | 目标 | -|------|------|------| -| `maker_ratio_entry` | 入场成交中 maker 比例 | ≥ 50% | -| `maker_ratio_tp` | TP 成交中 maker 比例 | ≥ 80% | -| `avg_friction_cost_r` | 每笔平均摩擦成本(手续费+滑点,以 R 计) | ≤ 0.15R | -| `entry_timeout_rate` | 入场超时触发 taker 兜底比例 | ≤ 30% | -| `tp_overshoot_rate` | TP 越价后兜底比例 | ≤ 20% | -| `flip_frequency` | 每笔持仓中 flip 次数 | ≤ 1次/持仓 | - ---- - -## 4. 设计原则汇总 - -| 场景 | 主路径 | 兜底 | -|------|--------|------| -| 入场 | limit post-only(盘口内侧 1-2 tick) | 超时 → taker(滑点容忍内) | -| TP1 / TP2 | limit post-only(预挂) | 越价 X ms 未成交 → 撤单 + taker | -| SL | — | 纯 taker,立即执行 | -| Flip | 平仓用 taker,新开仓复用入场逻辑 | — | -| Timeout | limit post-only | grace period 后 → taker | - -> **标签**:`#EXECUTION-MAKER-TAKER` -> **状态**:V5.4 设计文档,待 3/11 A/B test 结束后进入实现阶段 -> **作者**:小范(xiaofan)+ 露露(lulu) -> **日期**:2026-03-06 diff --git a/docs/arbitrage-engine/funding-rate-arbitrage-plan.md b/docs/arbitrage-engine/funding-rate-arbitrage-plan.md deleted file mode 100644 index c5db978..0000000 --- a/docs/arbitrage-engine/funding-rate-arbitrage-plan.md +++ /dev/null @@ -1,205 +0,0 @@ ---- -title: Funding Rate Arbitrage Plan v2 ---- - -> 初版日期:2026年2月 -> v2更新:2026年2月24日(露露×小周15轮联合数据验证) -> 数据来源:Binance官方API实算,覆盖2019-2026全周期 - ---- - -## 核心定位 - -**自用低风险稳定收益策略**,不依赖行情判断,赚市场机制的钱。 - -- 目标年化:全周期均值 **11-14%**(PM模式净值) -- 风险等级:低(完全对冲,不暴露方向风险) -- 对标:大幅优于债基(年化2%)、银行理财(3-4%) -- 执行方式:**选好点位买入后长期持有不动** - ---- - -## 原理 - -永续合约每8小时结算一次资金费率: -- 多头多 → 多头付钱给空头(费率为正) -- 空头多 → 空头付钱给多头(费率为负) - -**套利做法**:现货买入 + 永续做空,完全对冲币价风险,净收资金费率(USDT结算)。 - -``` -买入1 BTC现货($96,000) -做空1 BTC永续($96,000,1倍杠杆) - -BTC涨跌:两边对冲,净盈亏=0 -资金费率:每8小时直接收USDT -``` - ---- - -## 全周期真实数据(2019-2026,Binance实算) - -### BTC(BTCUSDT永续,2019.9至今) - -| 年份 | 年化毛收益 | 市场特征 | -|------|-----------|---------| -| 2019 | 7.48% | 起步期 | -| 2020 | 17.19% | 牛市前奏 | -| **2021** | **30.61%** | **大牛市** | -| 2022 | 4.16% | 熊市 | -| 2023 | 7.87% | 复苏 | -| 2024 | 11.92% | 牛市 | -| 2025 | 5.13% | 震荡 | -| 2026 YTD | 2.71% | 震荡 | -| **全周期均值** | **12.33%** | - | -| **PM净年化** | **11.67%** | 扣0.06%手续费 | - -- 每8小时费率均值:0.011257% -- 负费率占比:13.07% - -### ETH(ETHUSDT永续,2019.11至今) - -| 年份 | 年化毛收益 | 市场特征 | -|------|-----------|---------| -| 2019 | 8.91% | 起步期 | -| 2020 | 27.41% | 牛市前奏 | -| **2021** | **37.54%** | **大牛市** | -| 2022 | 0.79% | 熊市 | -| 2023 | 8.26% | 复苏 | -| 2024 | 12.96% | 牛市 | -| 2025 | 4.93% | 震荡 | -| 2026 YTD | 0.83% | 震荡 | -| **全周期均值** | **14.87%** | - | -| **PM净年化** | **14.09%** | 扣0.06%手续费 | - -- 每8小时费率均值:0.013584% -- 负费率占比:12.17% - -### BTC+ETH 50/50组合 - -- **全周期组合年化毛收益:13.81%** -- **PM净年化:13.08%** - ---- - -## 关键结论(数据验证后确认) - -1. **全周期PM净年化11-14%**,远超债基和银行理财 -2. **收益是USDT**,不承受币价波动风险 -3. **费率为负时不动(A方案)**:负费率仅占12-13%,长期均值为正 -4. **只做BTC和ETH**:流动性最好、费率最稳定,不做山寨币 -5. **年际波动大**:牛市30%+,熊市0-4%,需要耐心 -6. **50万美金在BTC/ETH市场无滑点问题** -7. **策略已被机构验证**:Ethena(TVL数十亿)、Binance Smart Arbitrage、Presto Research回测 - ---- - -## Portfolio Margin(组合保证金)· 必须开通 - -**为什么必须用PM模式**: - -| 维度 | 传统模式 | Portfolio Margin | -|------|---------|-----------------| -| $50万可做规模 | ~$25万(一半做保证金) | ~$47.5万(95%利用率) | -| 额外USDT需求 | 需$25万USDT | 不需要 | -| 年化收益 | ~6%(资金效率低) | ~12%(资金效率高) | -| 风险识别 | 不识别对冲 | 识别对冲,保证金需求低 | - -**PM关键信息**: -- Binance 2024年10月已取消最低余额要求,所有用户可开 -- BTC/ETH抵押率:0.95(5%折扣) -- 支持360+种加密货币作为抵押品 - -**PM风控线**: -- uniMMR < 1.35 → 内部预警 -- uniMMR < 1.25 → 评估减仓 -- uniMMR < 1.20 → Binance限制开新仓 -- uniMMR < 1.05 → 触发强平 - ---- - -## 执行流程 - -### 开仓 -- 确认市场趋势(不急,等好点位) -- 开通Portfolio Margin -- 同时执行:现货买入BTC/ETH + 永续做空等值(1倍杠杆) - -### 持仓 -- **完全不动**,每8小时自动收取资金费率 -- 费率为负不平仓,长期均值为正 -- 只监控uniMMR安全垫 - -### 平仓条件(极端情况) -- 正常情况:**不平仓,长期持有** -- 极端情况:uniMMR接近1.25时评估是否减仓 - ---- - -## 手续费说明(PM模式下影响极小) - -| 操作 | 0.06%费率档 | $50万单次 | -|------|-----------|----------| -| 现货买入 | 0.06% | $285 | -| 永续开空 | 0.02% | $95 | -| **开仓合计** | | **$380** | - -因为"买入后不动",手续费只在开仓时发生一次。 -$50万本金年收益约$6万(12%),$380手续费可忽略。 - ---- - -## 风险清单 - -| 风险 | 严重程度 | 说明 | -|------|---------|------| -| 市场周期 | ⚠️ 中 | 熊市年化可降至0-4%,但不亏本金 | -| 费率持续为负 | 🟢 低 | 历史负费率占比仅12-13%,长期均值为正 | -| 交易所对手方 | ⚠️ 中 | FTX教训,建议未来考虑分散 | -| 爆仓(PM模式) | 🟢 低 | 1倍杠杆+对冲,理论需BTC翻倍才触发 | -| 基差波动 | 🟢 低 | 长期持有不影响,只影响平仓时机 | - ---- - -## 与替代方案对比 - -| 方案 | 年化 | 风险 | 操作难度 | 备注 | -|------|------|------|---------|------| -| **本方案(自建)** | 11-14% | 低 | 中 | 完全自主可控 | -| Ethena sUSDe | 4-15% | 中(合约风险) | 低 | DeFi,依赖协议安全 | -| Binance Smart Arbitrage | 未知 | 低 | 低 | 官方产品,黑盒 | -| 银行理财 | 3-4% | 极低 | 无 | 对标基准 | -| 债基 | 2% | 低 | 无 | 对标基准 | - ---- - -## 执行计划 - -### 第一阶段:准备(现在) -- [ ] 开通Binance Portfolio Margin -- [ ] 确认VIP等级和手续费档位 -- [ ] 准备资金(USDT入金) - -### 第二阶段:模拟验证(可选,1-2个月) -- [ ] 搭建模拟系统,实时跑数据验证 -- [ ] 对比模拟结果与历史回测 - -### 第三阶段:入场 -- [ ] 等待合适入场时机(趋势确认) -- [ ] 同时开BTC+ETH对冲仓位 -- [ ] 开始收费率 - -### 第四阶段:长期持有 -- [ ] 每日检查uniMMR安全垫 -- [ ] 每周记录累计收益 -- [ ] 不主动平仓,长期持有 - ---- - -## 数据验证过程 - -本文档数据经过露露(Claude Opus 4.6)和小周(GPT-5.3-Codex)15轮交叉验证: -- 数据来源:Binance fapi/v1/fundingRate 官方API -- 覆盖周期:2019年9月至2026年2月(约6.5年) -- 验证内容:费率均值、年化收益、负费率占比、手续费敏感性、PM模式资金效率 -- 外部参考:Presto Research、Ethena、CoinCryptoRank、FMZ量化 diff --git a/docs/arbitrage-engine/index.mdx b/docs/arbitrage-engine/index.mdx deleted file mode 100644 index 3f05adc..0000000 --- a/docs/arbitrage-engine/index.mdx +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Project ArbitrageEngine ---- - -套利引擎(ArbitrageEngine,简称 AE)— 资金费率自动化套利系统 + 短线交易信号系统。 - -## 当前状态 - -🟢 **V2-V4 已上线** — 权限管控 + aggTrades采集 + 成交流分析面板 -🟡 **V5 方案定稿** — 短线交易信号系统,待开发 -🔗 **面板地址**:https://arb.zhouyangclaw.com -⏳ **实盘阻塞**:等范总提供Binance API Key + PM + 资金 - -## 文档 - -- [V5 短线交易信号系统方案](/docs/project-arbitrage-engine/v5-signal-system) — 信号体系、风控、回测方案、开发路线图 🆕 -- [V2-V4 产品技术文档](/docs/project-arbitrage-engine/v2-v4-plan) — 权限管控 / aggTrades / 成交流面板 -- [Phase 0 开发进度](/docs/project-arbitrage-engine/phase0-progress) — 已完成功能、Git结构 -- [Funding Rate Arbitrage Plan v2](/docs/project-arbitrage-engine/funding-rate-arbitrage-plan) — 策略原理、数据验证、执行方案 -- [Requirements v1.3.1](/docs/project-arbitrage-engine/requirements-v1.3) — 完整PRD(产品+技术+商业) diff --git a/docs/arbitrage-engine/phase0-progress.md b/docs/arbitrage-engine/phase0-progress.md deleted file mode 100644 index 5723931..0000000 --- a/docs/arbitrage-engine/phase0-progress.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: Phase 0 开发进度 ---- - -> 更新日期:2026年2月27日 -> 状态:🟡 Phase 0 进行中(监控面板已上线,SaaS MVP已上线) - ---- - -## Phase 0 — 已完成 - -### 监控面板(arb.zhouyangclaw.com)✅ - -**上线时间**:2026-02-26 - -**技术栈**: -- 后端:FastAPI(Python,端口4332) -- 前端:Next.js 16 + shadcn/ui + Tailwind + Recharts(端口4333) -- 部署:小周服务器 34.84.9.167,Caddy反代,HTTPS - -**已实现功能**: - -| 页面 | 功能 | 状态 | -|------|------|------| -| 仪表盘(/) | BTC/ETH实时费率、标记价格、下次结算时间 | ✅ 2秒刷新 | -| 历史(/history) | 过去7天费率走势图(Recharts折线图)+ 历史记录表格 | ✅ | -| 信号(/signals) | 套利信号历史记录(触发时间/币种/年化) | ✅ | -| 说明(/about) | 策略原理、历史年化数据表、风险说明 | ✅ | - -**API端点**: -``` -GET /api/health — 健康检查 -GET /api/rates — 实时资金费率(BTC/ETH) -GET /api/history — 7天历史费率数据 -GET /api/stats — 7天统计(均值/年化/50-50组合) -``` - -**性能优化**: -- rates:3秒缓存(2秒前端刷新,不会触发限速) -- history/stats:60秒缓存(避免Binance API限速) -- User-Agent已设置(防403) - ---- - -### 信号推送系统 ✅ - -**逻辑**:BTC或ETH 7日年化 > 10% 时自动触发 - -**推送渠道**:Discord Bot API(#agent-team频道) - -**信号格式**: -``` -📊 套利信号 -BTC 7日年化: 12.33% -ETH 7日年化: 8.17% -建议:BTC 现货+永续对冲可开仓 -时间: 2026-02-26 14:00 UTC -``` - -**定时任务**:每小时检查一次(crontab,小周服务器) - -**数据库**:SQLite(arb.db),signal_logs表记录推送历史 - ---- - -### SaaS MVP 用户系统 ✅ - -**上线时间**:2026-02-26 - -**新增页面**: - -| 页面 | 功能 | -|------|------| -| /register | 邮箱+密码注册,支持绑定Discord ID | -| /login | JWT登录 | -| /dashboard | 用户面板:订阅等级、Discord绑定、升级入口 | - -**API端点**: -``` -POST /api/auth/register — 注册 -POST /api/auth/login — 登录(返回JWT) -POST /api/user/bind-discord — 绑定Discord ID -GET /api/user/me — 获取用户信息 -GET /api/signals/history — 信号历史 -``` - -**订阅等级预设**(支付接入前为占位): -| 等级 | 价格 | 功能 | -|------|------|------| -| 免费 | ¥0 | 实时费率面板 | -| Pro | ¥99/月 | 信号推送+历史数据 | -| Premium | ¥299/月 | Pro全部+定制阈值+优先客服 | - ---- - -## Git仓库 - -- **地址**:https://git.darkerilclaw.com/lulu/arbitrage-engine -- **主要目录结构**: - ``` - backend/ - main.py — FastAPI主文件(rates/history/stats + 缓存) - auth.py — 用户注册/登录/JWT - subscriptions.py — 订阅管理 - signal_pusher.py — 信号检测+Discord推送 - frontend/ - app/ - page.tsx — 仪表盘 - history/ — 历史页 - signals/ — 信号历史页 - about/ — 策略说明页 - register/ — 注册页 - login/ — 登录页 - dashboard/ — 用户面板 - components/ - Navbar.tsx — 响应式导航(手机端汉堡菜单) - RateCard.tsx — 费率卡片 - StatsCard.tsx — 统计卡片 - FundingChart.tsx — 费率走势图 - ``` - ---- - -## Phase 1 — 待开发 - -> 需范总提供Binance API Key后开始 - -| 功能 | 依赖 | 预估工时 | -|------|------|---------| -| 接入真实Binance账户余额/持仓 | Binance API Key(只读权限) | 1天 | -| 手动开仓/平仓界面 | 范总确认Portfolio Margin已开通 | 2天 | -| 自动再平衡(持仓期间) | Phase 1基础完成后 | 2天 | -| 风控熔断+自动告警 | — | 1天 | - -**Phase 1开启条件(范总需提供)**: -1. Binance API Key(Read + Trade,禁止Withdraw) -2. 确认Portfolio Margin账户已开通 -3. 初始资金就位(建议$500 = BTC$250 + ETH$250) diff --git a/docs/arbitrage-engine/requirements-v1.3.md b/docs/arbitrage-engine/requirements-v1.3.md deleted file mode 100644 index dd7c362..0000000 --- a/docs/arbitrage-engine/requirements-v1.3.md +++ /dev/null @@ -1,540 +0,0 @@ ---- -title: Requirements v1.3.1 ---- - -> 定稿日期:2026年2月24日 -> 参与:露露(Claude Opus 4.6)、小周(GPT-5.3-Codex)、范总确认 -> 状态:✅ 需求锁定,待开发 -> 版本:v1.3.1(部署方案确认) - ---- - -## 0. 文档定位 - -- **文档类型**:项目基石级 PRD + 技术需求定稿(用于研发、验收、后续商业化) -- **优先级**:安全性 > 可控性 > 稳定性 > 收益表现 > 开发速度 -- **约束前提**:先需求锁定,再开发;先 API 全测通,再实盘;先 Dry Run 1 周,再 Real - ---- - -## 1. 项目定义与商业化定位 - -- **产品名**:套利引擎(ArbitrageEngine,简称 AE) -- **定位升级**:从 v1.2 的"范总单用户工具"升级为"可商业化产品雏形" - - Phase 1:服务范总实盘验证(单用户形态) - - 但架构上 Day1 预埋多租户和计费能力,避免二次重构 -- **核心价值主张**: - - 对用户:低门槛执行资金费率套利(PM + 风控 + 可视化) - - 对团队:沉淀可复制交易基础设施,具备对外 SaaS 化能力 -- **竞争差异化**: - - 完整前端(非CLI黑框) - - 安全第一(TOTP、审计、熔断) - - Dry Run验证(降低用户心理门槛) - - PM优化(资金效率提升) - - 自定义风控(非黑盒) - ---- - -## 2. 目标与边界 - -**目标(Phase 1)**: -- Binance 上完成 BTC/ETH 对冲套利闭环 -- 支持 PM 模式,执行"开/平人工确认,中间自动运行" -- 全链路审计、风控、告警、报表可用 - -**非目标(Phase 1 不做)**: -- 不做多交易所聚合 -- 不做自动择时开平仓 -- 不做资金托管与代客资管 -- 不做公开注册与支付计费(仅预埋) - ---- - -## 3. 已确认业务决策(锁定) - -| 项目 | 确认结果 | -|------|---------| -| 执行模式 | C — 开仓/平仓需人工确认,持仓期间自动化 | -| 初始实盘资金 | $500(BTC $250 + ETH $250) | -| 收益处理 | 留账户复利,不自动提取 | -| 部署服务器 | 小周服务器 34.84.9.167(GCP东京,Binance延迟11ms) | -| 上线节奏 | Phase 0 API测通 → Dry Run 1周 → Real 2个月 | -| 费率为负 | 完全不动(A方案) | - ---- - -## 4. 系统状态机 - -### 模式状态 -``` -DRY_RUN ──(Phase 0 checklist 100%通过 + 范总确认)──→ REAL -REAL ──(手动降级)──→ DRY_RUN -``` - -### 交易生命周期 -``` -IDLE ──(用户点"开始执行"+二次确认)──→ OPENING -OPENING ──(双腿成交+偏差≤1%)──→ HOLDING -OPENING ──(单边失败+补偿失败/超时/API连续失败)──→ HALTED -HOLDING ──(用户点"平仓"+确认)──→ CLOSING -HOLDING ──(uniMMR<1.25)──→ HALTED(不自动平仓,禁止新操作,立即告警) -CLOSING ──(双腿平完+仓位归零)──→ IDLE -任意状态 ──(硬风控触发/关键系统异常)──→ HALTED -HALTED ──(范总手动恢复)──→ IDLE -``` - ---- - -## 5. 风控参数(硬编码,不可前端修改) - -| 参数 | 值 | 说明 | -|------|-----|------| -| 最大单次名义 | $600 | $500本金+20%缓冲 | -| 最大滑点 | 0.10% | 超过取消下单 | -| 最大对冲偏差 | 1.00% | \|spot-perp\|/target | -| 最大杠杆 | 1x | 不可修改 | -| uniMMR预警线 | 1.35 | 告警 | -| uniMMR危险线 | 1.25 | 自动HALTED | -| API失败熔断 | 连续5次 | 进入HALTED | -| 单腿超时(补单) | 8s | 触发补单逻辑 | -| 单腿超时(熔断) | 30s | 触发HALTED | -| 时钟漂移阈值 | >1000ms | 禁止交易请求 | - ---- - -## 6. Dry Run 对照机制 - -Dry Run 期间必须记录"拟交易账本": -- 拟下单价格、拟成交数量、拟手续费、拟持仓 -- 拟资金费率收入(按实际市场费率与拟仓位估算) - -**产出对照报告**: -- 如果 Real 执行,理论会得到的收益区间 -- 引擎计算准确性与一致性验证 - -**通过标准**:Dry Run 1周内无关键错配、无状态机死锁、无风险漏报 - ---- - -## 7. 功能模块清单(Phase 1) - -### 认证与安全 -- 账号密码 + TOTP(Google Authenticator) -- 会话超时(30分钟) -- 连续登录失败锁定(5次失败后锁定15分钟) -- 审计日志全记录(append-only) - -### Binance 接入 -- API权限自检(Read/Trade only,禁Withdraw) -- 行情、费率、账户、持仓、下单、回查 -- 连接状态实时监控(心跳检测) -- API调用频率控制(避免触发限流) - -### 执行引擎 -- REST并发下单(现货买入 + 永续做空同时发出) -- WebSocket订阅成交回报、仓位变化 -- 单边失败补偿、对冲偏差修复、熔断 -- 下单前检查:余额、价格、滑点预估 - -### 监控告警 -- uniMMR、对冲偏差、当前/预测费率、累计收益 -- Discord实时告警 + 每日自动汇报 -- 双通道告警预留(Discord + 邮件/短信) - -### 报表 -- 8小时/日/周/月收益统计 -- 手续费统计、资金费率贡献 -- 净值曲线图表 -- Dry Run对照报告 - -### 运维 -- PM2健康检查、自动拉起 -- 重启后自动对账恢复 -- 分api/worker进程 - ---- - -## 8. 多租户架构预埋(v1.3核心新增) - -### 数据模型要求 -- 所有核心业务表强制包含 `user_id`(或 `tenant_id`) - -### 执行隔离要求 -- Worker任务必须携带 `user_id` 上下文 -- 严禁跨用户读取订单、仓位、密钥、日志 - -### 密钥管理 -- 按用户分区加密存储(每用户独立密钥上下文) -- 主密钥与业务库分离,密钥不落盘 - -### 权限模型预留 -- 用户表保留 `role` 字段(RBAC扩展位) -- 前端不写死"唯一管理员" - -### 审计不可篡改 -- 审计日志 append-only,禁止更新/删除业务接口 -- 仅允许归档,不允许逻辑改写 -- 预留哈希链字段(后续可选) - ---- - -## 9. Binance API 接口清单 - -### 认证与账户(Phase 0 必测) -| 接口 | 用途 | -|------|------| -| `GET /api/v3/account` | Spot账户资产、权限 | -| `GET /fapi/v2/account` | Futures账户信息 | -| `GET /fapi/v2/balance` | Futures余额 | -| `GET /fapi/v2/positionRisk` | 永续持仓风险 | -| `GET /fapi/v1/leverageBracket` | 杠杆/名义分档 | -| `GET /sapi/v1/portfolio/account` | PM账户信息 | - -### 行情与费率 -| 接口 | 用途 | -|------|------| -| `GET /api/v3/ticker/bookTicker` | Spot最优买卖价 | -| `GET /fapi/v1/ticker/bookTicker` | Perp最优买卖价 | -| `GET /fapi/v1/premiumIndex` | 标记价格+当前/预测费率 | -| `GET /fapi/v1/fundingRate` | 历史费率 | -| `GET /api/v3/depth` | Spot深度(滑点估算) | -| `GET /fapi/v1/depth` | Perp深度(滑点估算) | - -### 交易执行 -| 接口 | 用途 | -|------|------| -| `POST /api/v3/order` | Spot下单(市价/限价) | -| `GET /api/v3/order` | Spot订单回查 | -| `DELETE /api/v3/order` | Spot撤单 | -| `POST /fapi/v1/order` | Futures下单(开空/平空) | -| `GET /fapi/v1/order` | Futures订单回查 | -| `DELETE /fapi/v1/order` | Futures撤单 | -| `GET /fapi/v1/openOrders` | 未完成单查询 | -| WebSocket listenKey | 成交回报+仓位变化 | - -### 资金与收益 -| 接口 | 用途 | -|------|------| -| `GET /fapi/v1/income` | 资金费率收入、手续费、盈亏 | -| `GET /fapi/v2/positionRisk` | 未实现PnL、保证金风险 | - ---- - -## 10. 前端页面清单 - -### `/login` -- 用户名/密码 + TOTP输入 -- 登录审计记录 - -### `/dashboard` -- 当前模式(Dry Run / Real)+ 状态机状态 -- BTC/ETH仓位卡片:持仓量、对冲偏差、当前/预测费率 -- uniMMR + 风险灯(🟢 >1.35 / 🟡 1.25-1.35 / 🔴 <1.25) -- 当日收益、累计收益、手续费 -- 净值收益曲线图 - -### `/execute` -- 参数展示(只读):BTC $250 / ETH $250 -- 「开始执行(开仓)」按钮(二次确认弹窗) -- 「请求平仓」按钮(二次确认弹窗) -- 执行过程实时日志流(SSE) - -### `/risk` -- 风控阈值参数表(只读展示) -- 告警历史列表 -- 熔断记录 + 恢复操作按钮 - -### `/records` -- 订单记录(Spot/Futures,可筛选) -- 费率收入记录(8h维度,可按日/周/月汇总) -- 审计日志(全操作记录) - -### `/settings` -- API Key配置(加密存储,显示为***) -- Discord Webhook配置 -- 模式切换(Dry Run ↔ Real,需二次确认) - ---- - -## 11. 数据库结构(PostgreSQL,v1.3) - -```sql --- 用户(多租户预埋) -CREATE TABLE users ( - id SERIAL PRIMARY KEY, - username VARCHAR(64) UNIQUE NOT NULL, - password_hash VARCHAR(256) NOT NULL, - totp_secret_enc VARCHAR(256) NOT NULL, - role VARCHAR(16) DEFAULT 'admin', -- RBAC预留 - status VARCHAR(16) DEFAULT 'active', - created_at TIMESTAMPTZ DEFAULT NOW(), - last_login_at TIMESTAMPTZ -); - --- API凭据(按用户分区) -CREATE TABLE api_credentials ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id) NOT NULL, - account_name VARCHAR(64), - api_key_enc TEXT NOT NULL, - api_secret_enc TEXT NOT NULL, - perm_read BOOLEAN DEFAULT true, - perm_trade BOOLEAN DEFAULT true, - perm_withdraw BOOLEAN DEFAULT false, - ip_whitelist_ok BOOLEAN DEFAULT false, - pm_enabled BOOLEAN DEFAULT false, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- 策略实例 -CREATE TABLE strategy_instances ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id) NOT NULL, - mode VARCHAR(16) NOT NULL, -- DRY_RUN / REAL - state VARCHAR(16) NOT NULL, -- IDLE / OPENING / HOLDING / CLOSING / HALTED - base_capital DECIMAL(18,2), - btc_alloc DECIMAL(18,2), - eth_alloc DECIMAL(18,2), - started_at TIMESTAMPTZ, - closed_at TIMESTAMPTZ -); - --- 订单 -CREATE TABLE orders ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id) NOT NULL, - strategy_id INT REFERENCES strategy_instances(id), - venue VARCHAR(8) NOT NULL, -- SPOT / FUTURES - symbol VARCHAR(16) NOT NULL, - side VARCHAR(8) NOT NULL, - type VARCHAR(16) NOT NULL, - client_order_id VARCHAR(64), - exchange_order_id VARCHAR(64), - price DECIMAL(18,8), - orig_qty DECIMAL(18,8), - executed_qty DECIMAL(18,8), - status VARCHAR(16), - is_reduce_only BOOLEAN DEFAULT false, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- 仓位快照 -CREATE TABLE positions_snapshot ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id) NOT NULL, - strategy_id INT REFERENCES strategy_instances(id), - symbol VARCHAR(16) NOT NULL, - spot_qty DECIMAL(18,8), - futures_qty DECIMAL(18,8), - spot_notional DECIMAL(18,2), - futures_notional DECIMAL(18,2), - hedge_deviation DECIMAL(8,4), - mark_price DECIMAL(18,2), - captured_at TIMESTAMPTZ DEFAULT NOW() -); - --- 资金费率收入 -CREATE TABLE funding_income ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id) NOT NULL, - symbol VARCHAR(16) NOT NULL, - funding_time TIMESTAMPTZ NOT NULL, - income_asset VARCHAR(8), - income_amount DECIMAL(18,8), - rate DECIMAL(18,8), - position_notional DECIMAL(18,2), - source_tx_id VARCHAR(64) -); - --- 风控事件 -CREATE TABLE risk_events ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id) NOT NULL, - strategy_id INT REFERENCES strategy_instances(id), - event_type VARCHAR(32) NOT NULL, - severity VARCHAR(16) NOT NULL, - metric_value DECIMAL(18,8), - threshold_value DECIMAL(18,8), - action_taken VARCHAR(64), - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- 审计日志(append-only,不可删改) -CREATE TABLE audit_logs ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id), - actor VARCHAR(64) NOT NULL, - action VARCHAR(64) NOT NULL, - target VARCHAR(128), - payload_json JSONB, - ip VARCHAR(64), - hash_chain VARCHAR(128), -- 预留哈希链字段 - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- 系统运行记录 -CREATE TABLE system_runs ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id) NOT NULL, - mode VARCHAR(16) NOT NULL, - start_at TIMESTAMPTZ NOT NULL, - end_at TIMESTAMPTZ, - result VARCHAR(16), - notes TEXT -); -``` - ---- - -## 12. 三阶段产品路线图 - -| 阶段 | 目标 | 交付 | 预计周期 | -|------|------|------|---------| -| **Phase 1** | 范总$500跑通2个月 | 单用户验证版(含风控+审计+告警+报表) | 开发2-3周 + 实盘2月 | -| **Phase 2** | 开放小规模内测 | 注册体系+RBAC+订阅计费+运维扩容 | 2-4周 | -| **Phase 3** | 公开获客规模化 | 营销站点+渠道投放+客户成功+合规增强 | 持续 | - ---- - -## 13. Phase 2 新增模块预览 - -### 账户体系 -- 注册、邮箱验证、找回密码、设备管理、2FA强制策略 - -### RBAC权限 -- Owner / Admin / Operator / Viewer 角色与资源权限矩阵 - -### 订阅计费 -- 套餐分层、试用期、续费、到期降级、账单与发票记录 - -### 多租户运维 -- 队列隔离、限流配额、租户级熔断、租户级监控面板 - -### 客服与支持 -- 工单、系统公告、状态页 - ---- - -## 14. 商业模式设计 - -### 方案A:SaaS订阅制(推荐先做) -- 参考定价:$29 / $59 / $99 月费分层(按功能与账户规模) -- 优势:合规压力相对低、收入稳定、扩展快 -- 用户资金在自己Binance账户,平台不碰钱 - -### 方案B:收益分成制(后评估) -- 管理费(1-2%/年)+ 超额收益分成(20%) -- 风险:合规、牌照、托管责任显著上升 - -### 建议路径 -- 先SaaS,再评估收益分成 -- 不在Phase 1/2落地资管模式 - ---- - -## 15. 技术选型(锁定) - -| 层 | 技术 | 说明 | -|-----|------|------| -| 执行层 | 自建引擎 | 不fork Hummingbot | -| API层 | Binance官方SDK | `@binance/connector` | -| 后端 | Node.js + Express | 和灵镜统一 | -| 前端 | Next.js | 深色UI | -| 数据库 | PostgreSQL | 多租户友好 | -| 进程管理 | PM2 | 分api/worker | -| 部署 | 34.84.9.167 (GCP东京) | Binance延迟11ms | - ---- - -## 15.1 部署与网络安全方案(v1.3.1 新增) - -### 部署服务器 -- **机器**:34.84.9.167(GCP asia-northeast1-b) -- **选择理由**: - - Binance API TCP延迟 11ms(露露服务器22ms的一半) - - 内存可用 6.4GB、磁盘可用 187GB - - 仅1个PM2服务运行,负载极低 - - GCP基础设施安全性高于普通VPS - - 非root用户运行(fzq1228) - -### 网络访问方案(域名 + HTTPS + 强认证) -- **访问方式**:子域名 + Caddy自动HTTPS -- **不使用IP白名单**(范总IP不固定) -- **安全靠认证层保障**: - -| 安全层 | 措施 | -|--------|------| -| 传输层 | HTTPS(Caddy自动TLS证书) | -| 认证层 | 账号密码 + TOTP双因素 | -| 会话层 | 30分钟超时自动登出 | -| 防暴破 | 连续5次登录失败锁定15分钟 | -| 审计层 | 所有登录/操作记录,append-only | -| 数据库 | PostgreSQL仅监听localhost | -| 进程隔离 | 单独系统用户运行套利引擎 | - -### GCP防火墙规则(需配置) -- 开放端口:仅HTTPS(443) -- 其他端口:全部关闭 -- SSH:仅密钥登录 - -### Phase 2 安全升级路径 -- 可选:Cloudflare Zero Trust(Tunnel + Access认证) -- 可选:WAF规则(防SQL注入/XSS) -- 可选:Cloudflare Access策略(邮箱OTP二次验证) - ---- - -## 16. Phase 0 验收Checklist - -### 认证与权限 -- [ ] API签名请求成功 -- [ ] 读取余额成功 -- [ ] 读取持仓成功 -- [ ] 验证无提现权限 -- [ ] PM状态可读 - -### 行情与费率 -- [ ] Spot/Perp bookTicker可读 -- [ ] premiumIndex可读 -- [ ] fundingRate历史可拉取(BTC/ETH) -- [ ] 深度数据可拉取 - -### 交易闭环 -- [ ] Spot下单/回查/撤单成功 -- [ ] Futures下单/回查/撤单成功 -- [ ] WebSocket成交推送正常 -- [ ] 单腿失败补偿流程演练通过 -- [ ] 熔断触发与恢复流程通过 - -### 风控机制 -- [ ] 滑点超阈拦截通过 -- [ ] 对冲偏差超阈拦截通过 -- [ ] API连续失败熔断通过 -- [ ] 重启后自动对账通过 -- [ ] uniMMR预警/危险触发通过 - -### 报表与告警 -- [ ] 8小时收益计算正确 -- [ ] 每日汇报自动推送成功 -- [ ] 异常告警实时送达成功 - -**验收标准**:Checklist 100%通过才允许进入 1周 Dry Run。 - ---- - -## 17. 风险与原则 - -- **原则**:真金白银系统,宁慢勿错 -- **风险优先级**:执行错误 > 权限泄露 > 可用性 > 收益偏差 -- **处置原则**:触发风险先停机后恢复,先对账后继续 - ---- - -## 18. 下一步(文档后动作) - -1. 独立服务器准备完成 -2. API Key权限配置完成(Read+Trade,禁Withdraw,白名单) -3. Phase 0执行计划与验收人确认 -4. 开始开发 diff --git a/docs/arbitrage-engine/strategy-plaza-data-contract.md b/docs/arbitrage-engine/strategy-plaza-data-contract.md deleted file mode 100644 index f77f5f9..0000000 --- a/docs/arbitrage-engine/strategy-plaza-data-contract.md +++ /dev/null @@ -1,203 +0,0 @@ -# 策略广场 数据合约文档 - -> 版本:v1.0 -> 日期:2026-03-07 -> 状态:待范总确认 ✅ -> 作者:露露 + 小范 -> 分支:`feature/strategy-plaza` - ---- - -## 1. 功能概述 - -**策略广场**(Strategy Plaza)将现有的 signals-v53 / paper-v53 / signals-v53fast 等分散页面整合为统一入口: - -- 总览页:策略卡片列表,展示每个策略的核心指标,30 秒自动刷新 -- 详情页:点击卡片进入,顶部 Tab 切换「信号引擎」和「模拟盘」视图 - ---- - -## 2. 前端路由 - -| 路由 | 说明 | -|------|------| -| `/strategy-plaza` | 策略广场总览(卡片列表) | -| `/strategy-plaza/[id]` | 策略详情页,默认「信号引擎」tab | -| `/strategy-plaza/[id]?tab=paper` | 策略详情页「模拟盘」tab | - -**侧边栏变更:** -- 新增「策略广场」单一入口 -- 原 `signals-v53` / `paper-v53` / `signals-v53fast` / `paper-v53fast` / `signals-v53middle` / `paper-v53middle` 页面:**保留但从侧边栏隐藏**(路由仍可访问) - ---- - -## 3. API 设计 - -### 3.1 `GET /api/strategy-plaza` - -返回所有策略的卡片摘要数据。 - -**Response:** -```json -{ - "strategies": [ - { - "id": "v53", - "display_name": "V5.3 标准版", - "status": "running", - "started_at": 1741234567000, - "initial_balance": 10000, - "current_balance": 8693, - "net_usdt": -1307, - "net_r": -6.535, - "trade_count": 63, - "win_rate": 49.2, - "avg_win_r": 0.533, - "avg_loss_r": -0.721, - "open_positions": 0, - "pnl_usdt_24h": -320, - "pnl_r_24h": -1.6, - "std_r": 0.9, - "last_trade_at": 1741367890000 - } - ] -} -``` - -**字段说明:** - -| 字段 | 类型 | 说明 | -|------|------|------| -| `id` | string | 策略唯一标识,与 DB strategy 字段一致 | -| `display_name` | string | 展示名称 | -| `status` | string | `running` / `paused` / `error` | -| `started_at` | number (ms) | 策略启动时间(暂用 paper_trades 第一条 entry_ts,后续补 strategy_meta 表) | -| `initial_balance` | number | 初始余额 USDT,固定 10000 | -| `current_balance` | number | 当前余额 = initial_balance + net_usdt | -| `net_usdt` | number | 累计盈亏 USDT = SUM(pnl_r) × 200 | -| `net_r` | number | 累计净 R | -| `trade_count` | number | 已出场交易数 | -| `win_rate` | number | 胜率 % | -| `avg_win_r` | number | 平均赢单 R | -| `avg_loss_r` | number | 平均亏单 R(负数) | -| `open_positions` | number | 当前活跃持仓数(exit_ts IS NULL) | -| `pnl_usdt_24h` | number | 最近 24h 盈亏 USDT | -| `pnl_r_24h` | number | 最近 24h 净 R | -| `std_r` | number | 所有已出场交易的 pnl_r 标准差(风险感知) | -| `last_trade_at` | number (ms) | 最近一笔成交的 exit_ts | - -**status 判断逻辑:** -- `running`:paper_config 中 enabled=true 且最近 signal_indicators 记录 < 5 分钟 -- `paused`:paper_config 中 enabled=false -- `error`:paper_config 中 enabled=true 但 signal_indicators 最新记录 > 5 分钟 - ---- - -### 3.2 `GET /api/strategy-plaza/[id]/summary` - -返回单个策略的完整摘要,包含卡片字段 + 详情字段。 - -**Response(在 3.1 基础上增加):** -```json -{ - "id": "v53", - "display_name": "V5.3 标准版", - "cvd_windows": "30m / 4h", - "description": "标准版:30分钟+4小时CVD双轨,适配1小时信号周期", - "symbols": ["BTCUSDT", "ETHUSDT", "SOLUSDT", "XRPUSDT"], - "weights": { - "direction": 55, - "crowding": 25, - "environment": 15, - "auxiliary": 5 - }, - "thresholds": { - "signal_threshold": 75, - "flip_threshold": 85 - }, - "...所有 3.1 字段..." -} -``` - ---- - -### 3.3 `GET /api/strategy-plaza/[id]/signals` - -复用现有 `/api/signals` 逻辑,增加 `strategy` 过滤。接口参数和返回格式与现有保持一致。 - ---- - -### 3.4 `GET /api/strategy-plaza/[id]/trades` - -复用现有 `/api/paper-trades` 逻辑,增加 `strategy` 过滤。接口参数和返回格式与现有保持一致。 - ---- - -## 4. 数据来源映射 - -| 字段 | 数据来源 | -|------|---------| -| `net_usdt`, `net_r`, `trade_count`, `win_rate`, `avg_win_r`, `avg_loss_r` | `paper_trades` WHERE strategy=id AND exit_ts IS NOT NULL | -| `open_positions` | `paper_trades` WHERE strategy=id AND exit_ts IS NULL | -| `pnl_usdt_24h`, `pnl_r_24h` | `paper_trades` WHERE strategy=id AND exit_ts > NOW()-24h | -| `std_r` | STDDEV(pnl_r) FROM paper_trades WHERE strategy=id AND exit_ts IS NOT NULL | -| `started_at` | MIN(entry_ts) FROM paper_trades WHERE strategy=id | -| `last_trade_at` | MAX(exit_ts) FROM paper_trades WHERE strategy=id AND exit_ts IS NOT NULL | -| `status` | paper_config.json + signal_indicators 最新记录时间 | -| `cvd_windows`, `weights`, `thresholds` | backend/strategies/[id].json | - ---- - -## 5. 前端组件规划 - -### 5.1 总览页组件 - -``` -StrategyPlaza -└── StrategyCardGrid - └── StrategyCard (×N) - ├── 策略名 + status badge (running/paused/error) - ├── 运行时长 (now - started_at) - ├── 当前余额 / 初始余额 - ├── 净盈亏 USDT + 净R(带颜色) - ├── 胜率 - ├── 最近24h盈亏(小字) - └── 点击 → /strategy-plaza/[id] -``` - -### 5.2 详情页组件 - -``` -StrategyDetail -├── 顶部:策略名 + status + 运行时长 -├── Tab 切换:[信号引擎] [模拟盘] -│ -├── Tab: 信号引擎 -│ └── 复用 SignalsV53Page 内容 -│ -└── Tab: 模拟盘 - └── 复用 PaperV53Page 内容 -``` - ---- - -## 6. 实现计划 - -| 阶段 | 内容 | 负责 | -|------|------|------| -| P0 | 后端 API `/api/strategy-plaza` | 露露 | -| P1 | 后端 API `/api/strategy-plaza/[id]/summary` | 露露 | -| P2 | 前端总览页(StrategyCard × 3) | 露露 | -| P3 | 前端详情页(Tab + 复用现有组件) | 露露 | -| P4 | 侧边栏整合(新增入口,隐藏旧页面) | 露露 | -| Review | 代码审阅 + 逻辑验证 | 小范 | - -> 开发前等范总确认数据结构,不提前动代码。 - ---- - -## 7. 变更记录 - -| 版本 | 日期 | 内容 | -|------|------|------| -| v1.0 | 2026-03-07 | 初版,露露起草 + 小范审阅 | diff --git a/docs/arbitrage-engine/v2-v4-plan.mdx b/docs/arbitrage-engine/v2-v4-plan.mdx deleted file mode 100644 index 9b9639b..0000000 --- a/docs/arbitrage-engine/v2-v4-plan.mdx +++ /dev/null @@ -1,417 +0,0 @@ ---- -title: Arbitrage Engine V2-V4 产品+技术文档 -description: 权限管控、aggTrades全量采集、成交流分析面板的完整设计 ---- - -# Arbitrage Engine V2-V4 产品+技术文档 - -## 1. 项目概述 - -### 1.1 当前状态(V1.0 ✅) -- 实时BTC/ETH资金费率监控(2秒刷新) -- K线图(本地2秒粒度聚合,9个周期) -- 历史费率走势 + 明细表 -- YTD年化统计 -- 信号推送(Discord) -- 用户注册/登录框架(无鉴权保护) -- URL: https://arb.zhouyangclaw.com - -### 1.2 战略升级方向 -从「公开费率监控面板」升级为「私有交易数据研究平台」。 - -核心目标: -1. 收集全量逐笔成交数据,建立独家数据资产 -2. 结合K线与成交流,研究价格变动的微观成因 -3. 通过复盘标注→模式识别→模拟盘验证,逐步量化短线策略 -4. 数据不公开,邀请制访问 - -### 1.3 核心定位 -> 炒币是情绪盘,每一段价格背后都代表着集体情绪走向。K线是情绪的结果,成交流是情绪的过程。我们要看到过程。 - ---- - -## 2. V2.0 — 权限管控 + 邀请制 - -### 2.1 JWT鉴权设计 - -**Token体系:** -| Token | 有效期 | 用途 | -|-------|--------|------| -| Access Token | 24小时 | API请求鉴权(放header) | -| Refresh Token | 7天 | 刷新access token | - -**实现策略:** 在现有`auth.py`基础上增量改造,不重写。 - -**新增接口:** -- `POST /api/auth/login` → 返回 `{access_token, refresh_token, expires_in}` -- `POST /api/auth/refresh` → 用refresh token换新access token -- `POST /api/auth/register` → 注册(必须提供有效邀请码) -- `GET /api/auth/me` → 当前用户信息 - -**依赖库:** `python-jose[cryptography]` 或 `PyJWT` - -### 2.2 邀请码机制 - -**表结构:** -```sql -CREATE TABLE invite_codes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - code TEXT UNIQUE NOT NULL, -- 8位随机码 - created_by INTEGER, -- admin user_id - max_uses INTEGER DEFAULT 1, -- 默认一码一用 - used_count INTEGER DEFAULT 0, - status TEXT DEFAULT 'active', -- active/disabled/exhausted - expires_at TEXT, -- 可选过期时间 - created_at TEXT DEFAULT (datetime('now')) -); - -CREATE TABLE invite_usage ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - code_id INTEGER NOT NULL, - user_id INTEGER NOT NULL, - used_at TEXT DEFAULT (datetime('now')), - FOREIGN KEY (code_id) REFERENCES invite_codes(id), - FOREIGN KEY (user_id) REFERENCES users(id) -); -``` - -**注册流程:** -1. 用户填写邀请码 + 用户名 + 密码 -2. 后端验证:邀请码存在 + status=active + used_count < max_uses + 未过期 -3. 创建用户 → 记录使用 → used_count+1 -4. 返回JWT token对 - -### 2.3 路由保护 - -**公开路由(无需登录):** -- `GET /api/health` -- `POST /api/auth/login` -- `POST /api/auth/register` -- `POST /api/auth/refresh` -- `GET /api/rates`(基础费率,作为引流) - -**受保护路由(需登录):** -- `GET /api/kline` -- `GET /api/snapshots` -- `GET /api/history` -- `GET /api/stats` -- `GET /api/stats/ytd` -- `GET /api/signals/history` -- `GET /api/trades/*`(V3新增) -- `GET /api/analysis/*`(V4新增) - -**Admin路由(需admin角色):** -- `POST /api/admin/invite-codes` — 生成邀请码 -- `GET /api/admin/invite-codes` — 查看所有邀请码 -- `DELETE /api/admin/invite-codes/:id` — 禁用邀请码 -- `GET /api/admin/users` — 查看所有用户 -- `PUT /api/admin/users/:id/ban` — 封禁用户 - -### 2.4 权限模型 - -| 资源 | Guest | User | Admin | -|------|-------|------|-------| -| 基础费率 | ✅ | ✅ | ✅ | -| K线/历史/统计 | ❌ | ✅ | ✅ | -| 成交流数据 | ❌ | ✅ | ✅ | -| 分析面板 | ❌ | ✅ | ✅ | -| 标注 | ❌ | ✅(自己) | ✅(全部) | -| 邀请码管理 | ❌ | ❌ | ✅ | -| 用户管理 | ❌ | ❌ | ✅ | - -### 2.5 Admin CLI工具 - -```bash -# 生成邀请码 -python3 admin_cli.py gen-invite --count 5 - -# 查看邀请码状态 -python3 admin_cli.py list-invites - -# 禁用邀请码 -python3 admin_cli.py disable-invite CODE123 - -# 查看用户 -python3 admin_cli.py list-users - -# 封禁用户 -python3 admin_cli.py ban-user 42 -``` - -### 2.6 前端改造 - -- 未登录用户:仪表盘只显示实时费率卡片(引流),其他区域显示blur遮挡 + 「登录后查看」 -- 登录页:用户名 + 密码 + 邀请码(注册时) -- Token存储:`localStorage`(access_token + refresh_token) -- 请求拦截器:自动带token、过期自动刷新 -- 401处理:跳转登录页 - -### 2.7 验收标准 -- [ ] 无邀请码无法注册 -- [ ] 无token访问受保护API返回401 -- [ ] Token过期后refresh成功 -- [ ] Admin API非admin用户返回403 -- [ ] 前端未登录正确遮挡数据区域 - ---- - -## 3. V3.0 — aggTrades全量采集 - -### 3.1 数据模型 - -**按月分表,单库(arb.db):** -```sql --- 自动建表(每月1张) -CREATE TABLE IF NOT EXISTS agg_trades_202602 ( - agg_id INTEGER PRIMARY KEY, -- Binance aggTradeId(天然唯一) - symbol TEXT NOT NULL, -- BTCUSDT / ETHUSDT - price REAL NOT NULL, - qty REAL NOT NULL, - first_trade_id INTEGER, - last_trade_id INTEGER, - time_ms INTEGER NOT NULL, -- 成交时间(毫秒) - is_buyer_maker INTEGER NOT NULL -- 0=主动买, 1=主动卖 -); - -CREATE INDEX IF NOT EXISTS idx_agg_trades_202602_time - ON agg_trades_202602(symbol, time_ms); -``` - -**辅助表:** -```sql -CREATE TABLE agg_trades_meta ( - symbol TEXT PRIMARY KEY, - last_agg_id INTEGER NOT NULL, -- 最后写入的agg_id - last_time_ms INTEGER NOT NULL, -- 最后写入的时间 - updated_at TEXT DEFAULT (datetime('now')) -); -``` - -### 3.2 采集架构 - -``` -┌─────────────────────┐ -│ WebSocket主链路 │ ← wss://fstream.binance.com/ws/btcusdt@aggTrade -│ (实时推送) │ ← wss://fstream.binance.com/ws/ethusdt@aggTrade -└──────┬──────────────┘ - │ 攒200条 or 1秒 - ▼ -┌─────────────────────┐ -│ 批量写入器 │ → INSERT OR IGNORE INTO agg_trades_YYYYMM -│ (去重+分表路由) │ → UPDATE agg_trades_meta -└──────┬──────────────┘ - │ -┌──────┴──────────────┐ -│ 补洞巡检(每分钟) │ → 检查agg_id连续性 -│ │ → REST /fapi/v1/aggTrades?fromId=X 补缺 -└─────────────────────┘ -``` - -**断线重连流程:** -1. WS断线 → 立即重连 -2. 读取`last_agg_id` → REST `fromId=last_agg_id+1` 批量拉取(每次1000条) -3. 追平后切回WS流 -4. 全程记日志 - -### 3.3 写入优化 - -- 批量大小:200条 or 1秒(取先到者) -- SQLite WAL模式 + `PRAGMA synchronous=NORMAL` -- 单线程写入,避免锁竞争 -- 月初自动建新表 - -### 3.4 去重策略 - -- `agg_id`作为PRIMARY KEY,`INSERT OR IGNORE`天然去重 -- WS和REST可能有重叠,完全靠PK去重,零额外成本 - -### 3.5 监控与告警 - -| 指标 | 阈值 | 动作 | -|------|------|------| -| 采集中断 | >30秒无新数据 | Discord告警 | -| agg_id断档 | 缺口>10 | 自动REST补洞 | -| 写入延迟P95 | >500ms | 日志警告 | -| 磁盘占用 | >80% | Discord告警 | -| 日数据完整性 | 缺口率>0.1% | 日报标红 | - -### 3.6 存储容量规划 - -| 时间跨度 | BTC | ETH | 合计 | -|---------|-----|-----|------| -| 1天 | ~200MB | ~150MB | ~350MB | -| 1个月 | ~6GB | ~4.5GB | ~10.5GB | -| 6个月 | ~36GB | ~27GB | ~63GB | -| 1年 | ~73GB | ~55GB | ~128GB | - -200GB磁盘可存1年+。超出时按月归档到外部存储。 - -### 3.7 数据查询接口(需登录) - -- `GET /api/trades/raw?symbol=BTC&start_ms=X&end_ms=Y&limit=10000` — 原始成交 -- `GET /api/trades/summary?symbol=BTC&start_ms=X&end_ms=Y&interval=1m` — 分钟级聚合 - - 返回:`{time, buy_vol, sell_vol, delta, trade_count, vwap, max_single_qty}` - -### 3.8 验收标准 -- [ ] BTC/ETH双流并行采集,PM2常驻 -- [ ] agg_id连续性>99.9% -- [ ] 断线重连+补洞在60秒内完成 -- [ ] 每日完整性报告自动生成 -- [ ] 查询API响应时间<2秒(10万条范围内) - ---- - -## 4. V4.0 — 成交流分析面板 - -### 4.1 页面布局 - -``` -┌────────────────────────────────────────────┐ -│ [BTC▼] [时间范围选择] [1m 5m 15m] │ ← 全局控制栏 -├────────────────────────────────────────────┤ -│ │ -│ K线图 (lightweight-charts) │ ← 可框选时间段 -│ │ -├────────────────────────────────────────────┤ -│ │ -│ 成交流热图 (ECharts heatmap) │ ← 时间×价格×密度 -│ 绿=主动买 红=主动卖 │ -│ │ -├──────────────────────┬─────────────────────┤ -│ 时段摘要 │ 标注面板 │ -│ 总成交量: 1,234 BTC │ [上涨前兆] [下跌前兆] │ -│ 买/卖比: 62%/38% │ [震荡] [放量突破] │ -│ Delta: +427 BTC │ │ -│ 最大单笔: 12.5 BTC │ [保存标注] │ -│ 成交速率: 89笔/秒 │ [AI分析] [导出] │ -└──────────────────────┴─────────────────────┘ -``` - -### 4.2 共享时间轴 - -使用`zustand`或React Context管理全局时间范围: - -```typescript -interface TimeRange { - startMs: number; - endMs: number; - source: 'kline' | 'heatmap' | 'control' | 'manual'; -} -``` - -K线框选、热图缩放、控制栏切换都更新同一个state,所有组件响应式联动。 - -### 4.3 热图实现 - -- 库:ECharts heatmap(首版) -- 数据格式:`[timeMs, priceLevel, volume]` -- 价格分档:按0.1%粒度(BTC约$67 = 1档) -- 颜色映射:买量→绿色深度,卖量→红色深度 -- 数据量控制:15分钟窗口 ≈ 几千个格子,ECharts轻松渲染 - -### 4.4 复盘工具交互 - -**核心流程:** -1. 选择日期和币种 -2. 浏览K线,找到明显波动区间 -3. 框选 → 下方热图+摘要自动聚焦 -4. 观察波动前5-15分钟的成交流特征 -5. 发现有意义的模式 → 标注保存 -6. 可选:点「AI分析」让AI解读该时段特征 - -### 4.5 标注系统 - -```sql -CREATE TABLE annotations ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - symbol TEXT NOT NULL, - start_ms INTEGER NOT NULL, - end_ms INTEGER NOT NULL, - label TEXT NOT NULL, -- 上涨前兆/下跌前兆/震荡/突破/假突破/洗盘... - note TEXT, -- 用户备注 - confidence INTEGER DEFAULT 3, -- 1-5 置信度 - version INTEGER DEFAULT 1, -- 版本号(修改时递增) - features_json TEXT, -- 当时的特征快照(delta/速率/大单等) - created_at TEXT DEFAULT (datetime('now')), - updated_at TEXT, - FOREIGN KEY (user_id) REFERENCES users(id) -); -``` - -### 4.6 AI辅助分析 - -**流程:** -``` -原始成交数据(该时段) - ↓ Python预处理(后端) -结构化摘要(~500字): - - 成交速率变化曲线(文字描述) - - Delta累计走势 - - 大单列表(>平均5倍) - - 价格关键点(高低点+突破位) - ↓ AI分析 -结论+建议(~200字) -``` - -**Token消耗:** ~3000 token/次分析,成本忽略不计。 - -### 4.7 验收标准 -- [ ] K线与热图时间同步,无偏移 -- [ ] 热图渲染15分钟窗口<1秒 -- [ ] 标注CRUD正常,支持版本回溯 -- [ ] AI分析响应<10秒 -- [ ] 手机端可用(响应式布局) - ---- - -## 5. 开发排期 - -| 版本 | 内容 | 预估工期 | 依赖 | -|------|------|---------|------| -| V2.0 | 权限管控+邀请制 | 2天 | 无 | -| V3.0 | aggTrades全量采集 | 2天 | V2.0(受保护路由) | -| V4.0 | 成交流分析面板 | 3-5天 | V3.0(数据源) | - -**里程碑:** -- V2.0完成:所有数据需登录访问,邀请码可用 -- V3.0完成:数据开始积累,每日完整性报告 -- V3.0+2周:积累足够数据,可以开始第一次复盘分析 -- V4.0完成:完整的复盘研究工具 - ---- - -## 6. 安全与隐私 - -1. **成交流数据不公开** — 所有`/api/trades/*`和`/api/analysis/*`必须登录 -2. **邀请码范总一人控制** — Admin角色仅范总 -3. **JWT密钥** — 32字节随机,存环境变量 -4. **SQLite安全** — WAL模式,每日备份 -5. **数据永久保留** — 不删不改,原始完整性最重要 - ---- - -## 7. 回滚与故障预案 - -| 故障 | 预案 | -|------|------| -| V2上线后鉴权阻断 | git回退到V1 commit + PM2 restart | -| V3采集进程崩溃 | PM2自动重启 + REST补洞 | -| 磁盘满 | 按月归档旧数据到外部存储 | -| SQLite损坏 | 每日备份恢复 | -| 前端build失败 | 保持上一版本运行,不restart | - ---- - -## 8. 数据库迁移规范 - -- 每次DDL变更写migration脚本,带版本号 -- 命名:`migration_001_add_invite_codes.sql` -- 所有migration必须幂等(`IF NOT EXISTS`) -- 发布顺序:先跑migration → 再更新代码 → 再restart服务 - ---- - -*文档版本: v1.0* -*最后更新: 2026-02-27* -*作者: 露露(Opus 4.6) × 小周(GPT-5.3-Codex)* diff --git a/docs/arbitrage-engine/v5-signal-system.mdx b/docs/arbitrage-engine/v5-signal-system.mdx deleted file mode 100644 index e5e5a9d..0000000 --- a/docs/arbitrage-engine/v5-signal-system.mdx +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: V5 短线交易信号系统方案 ---- - -# V5 短线交易信号系统方案 - -> 版本:v5.0 | 日期:2026-02-27 | 状态:方案定稿,待开发 -> -> 来源:露露(Opus 4.6)× 小周(GPT-5.3-Codex)10轮讨论 - ---- - -## 1. 目标 - -将 aggTrades 成交流数据转化为**可执行的短线做多/做空交易信号**,实现从"监控工具"到"交易工具"的升级。 - -## 2. 信号体系 - -### 2.1 核心门槛(3/3 必须全部满足) - -| # | 条件 | 做多 | 做空 | -|---|------|------|------| -| 1 | CVD_fast (30m 滚动) | > 0 且斜率正 | < 0 且斜率负 | -| 2 | CVD_mid (4h 滚动) | > 0 | < 0 | -| 3 | VWAP 位置 | price > VWAP_30m | price < VWAP_30m | - -**说明**:CVD = Cumulative Volume Delta(累计买卖差额),是成交流分析最核心的指标。 - -- **CVD_fast**:30分钟滚动窗口,捕捉短线动量 -- **CVD_mid**:4小时滚动窗口,确认大方向 -- **CVD_day**:UTC日内重置,作为盘中强弱基线参考(不作为入场条件) - -> ⚠️ CVD_fast 在剧烈波动时自适应:当1m成交量超过均值3倍时,窗口自动拉长到60m,防噪音误判。 - -### 2.2 加分条件(决定仓位大小,满分60分) - -| 条件 | 分值 | 说明 | -|------|------|------| -| ATR 压缩→扩张 | +25 | 5m ATR分位从 <40% 突破 >60%,波动开始放大 | -| 无反向 P99 超大单 | +20 | 最近15分钟内无极端反向成交 | -| 资金费率配合 | +15 | 做多时费率<0.01%,做空时费率>0.01% | - -### 2.3 仓位映射 - -| 加分总分 | 仓位等级 | 占总资金 | -|----------|----------|----------| -| 0-15 | 最小仓 | 2% | -| 20-40 | 中仓 | 5% | -| 45-60 | 满仓 | 8% | - -### 2.4 预估信号频率 - -核心3条件同时满足概率约 20-30%,每5分钟检查一次。去掉冷却期重复后,预计**日均 5-15 个有效信号**。 - -## 3. 指标计算 - -### 3.1 CVD(Cumulative Volume Delta) - -``` -CVD = Σ(主动买量) - Σ(主动卖量) - -三轨并行: -- CVD_fast:滚动30m窗口(入场信号) -- CVD_mid:滚动4h窗口(方向过滤) -- CVD_day:UTC日内重置(盘中基线) -``` - -入场优先看 fast,方向必须与 mid 同向。 - -### 3.2 大单阈值(动态分位数) - -``` -基于最近24h成交量分布: -- P99:超大单阈值 -- P95:大单阈值 -- 兜底下限:max(P95, 5 BTC) - -区分"大单买"和"大单卖"的不对称性: -- 上涨趋势中出现P99大卖单,意义远大于P99大买单 -``` - -### 3.3 ATR(Average True Range) - -``` -周期:5分钟K线,14根 -用于: -1. 波动压缩→扩张判断(分位数) -2. 止损距离计算 -``` - -### 3.4 VWAP(Volume Weighted Average Price) - -``` -滚动30分钟:VWAP_30m = Σ(price × qty) / Σ(qty) -用于:价格位置过滤(做多 price > VWAP,做空 price < VWAP) -``` - -## 4. 风控体系 - -### 4.1 止盈止损 - -| 参数 | 值 | 说明 | -|------|-----|------| -| 止损 (SL) | 1.2 × ATR(5m, 14) | 动态止损,适应波动 | -| 止盈1 (TP1) | 1.0R | 减仓50% | -| 止盈2 (TP2) | 2.0R | 剩余仓位移动止损 | -| 时间止损 | 30分钟 | 无延续即平仓,防磨损 | - -> R = 1倍止损距离 - -### 4.2 冲突与冷却 - -- **信号冲突**:持仓中出现反向信号 → 先平仓 + 10分钟冷却再接受新信号 -- **同方向冷却**:10分钟内不重复入场 -- **单币限频**:每小时最多2次入场 - -### 4.3 多品种风控 - -- BTC / ETH 独立出信号 -- 两个同时同向时,总仓位上限 10% -- 需要相关性过滤(ETH Delta 经常跟 BTC 走) - -## 5. 回测达标线 - -| 指标 | 达标线 | -|------|--------| -| 胜率 | ≥ 45% | -| 盈亏比 (Avg Win / Avg Loss) | ≥ 1.5 | -| 最大回撤 (MDD) | ≤ 5% | -| 日均信号数 | 2-8 个 | -| 扣手续费后 | 正收益 | - -**手续费模型**: -- Maker: 0.02%, Taker: 0.04%(Portfolio Margin 档位) -- 按 Taker 0.04% 双向估算(保守) -- 回测报告必须包含净收益/毛收益对比 - -**额外统计**: -- 持仓时长分布(验证30min时间止损合理性) -- Rate-limit 重试统计(回补脚本用) - -## 6. 技术架构 - -### 6.1 进程拓扑 - -``` - ┌─────────────────┐ -Binance WS ──────→ │ agg-collector │ ──→ agg_trades_YYYYMM - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ signal-engine │ ──→ signal_indicators (5s) - │ │ ──→ signal_indicators_1m (聚合) - │ │ ──→ signal_trades - │ │ ──→ Discord推送 - └─────────────────┘ - │ - ┌─────────────────┐ - │ backtest.py │ ──→ 回测报告 - └─────────────────┘ -``` - -- **agg-collector**:只做采集+落库,不动(已有) -- **signal-engine**:新建独立进程,指标计算 + 信号生成 -- **backtest.py**:离线回测脚本 - -### 6.2 数据库新增表 - -| 表名 | 用途 | 保留策略 | -|------|------|----------| -| signal_indicators | 每5秒指标快照(CVD/ATR/VWAP/P95等) | 30天 | -| signal_indicators_1m | 1分钟聚合(前端默认读此表) | 长期 | -| signal_trades | 信号触发的开仓/平仓记录 | 长期 | - -### 6.3 指标计算策略 - -- **内存滚动 + 增量更新**(不是每次SQL全量聚合) -- 启动时回灌历史窗口(30m/4h/24h)到内存 -- 之后只处理新增 agg_id 增量 -- 每5秒把快照落库(幂等) - -### 6.4 冷启动处理 - -signal-engine 重启后: -1. 从DB回读最近4h的aggTrades重算所有指标 -2. 前N根标记为 `warmup`,不出信号 -3. warmup 完成后开始正常信号生成 - -## 7. 历史数据回补 - -### 7.1 回补脚本(backfill_agg_trades.py) - -``` -参数:--symbol BTCUSDT --days 7 --batch-size 1000 -流程: -1. 查DB中最早的agg_id -2. 从最早agg_id向前REST分页补拉 -3. 每次1000条,sleep 200ms防限流 -4. INSERT OR IGNORE 写入agg_trades_YYYYMM -5. 断点续传:记录进度到meta表 -6. 完成后输出统计+连续性检查 -``` - -### 7.2 速率控制 - -- Binance aggTrades REST 限流:weight 20/min -- 每请求 sleep 200ms,实际约 3-5 req/s -- 带指数退避重试,429后等60s -- 记录 rate-limit 统计(429次数、退避次数) - -## 8. 开发时间线 - -| Day | 任务 | 交付物 | 负责 | -|-----|------|--------|------| -| 1 | 回补脚本 + 1天小样本 | backfill跑通,BTC/ETH各1天入库 | 露露开发,小周部署 | -| 2 | 全量7天回补 + 连续性验证 | 完整7天aggTrades,缺口=0 | 小周跑+验收 | -| 3-4 | signal-engine + 前端指标展示 | CVD三轨/ATR/VWAP/大单标记实时可视化 | 露露开发,小周部署 | -| 5 | 回测框架 + 首版回测报告 | 胜率/盈亏比/MDD/持仓分布/净收益 | 露露开发 | -| 6+ | 调参优化 → 达标后模拟盘 | 模拟交易记录 | 协同 | - -## 9. 前置依赖 - -| 依赖 | 状态 | 影响范围 | -|------|------|----------| -| aggTrades 实时采集 | ✅ 已运行 | Phase 1-3 已满足 | -| 历史数据回补 | ⏳ Day 1-2 | 回测需要 | -| Binance API Key | ⏳ 等范总 | 仅Phase 4实盘 | -| Portfolio Margin | ⏳ 等范总 | 仅Phase 4实盘 | -| 资金准备 | ⏳ 等范总 | 仅Phase 4实盘 | - -> Phase 1-3 不依赖范总,可立即开工。 - -## 10. 版本历史 - -| 版本 | 日期 | 内容 | -|------|------|------| -| v2-v4 | 2026-02-27 | 权限管控+aggTrades采集+成交流面板 | -| **v5.0** | **2026-02-27** | **短线交易信号系统方案定稿** | diff --git a/docs/arbitrage-engine/v51-optimization-plan.md b/docs/arbitrage-engine/v51-optimization-plan.md deleted file mode 100644 index 4833e41..0000000 --- a/docs/arbitrage-engine/v51-optimization-plan.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -title: V5.1 优化方案 -date: 2026-03-03 ---- - -# V5.1 优化方案 - -> 基于:V5.1模拟盘执行分析报告(2026-03-03) -> 核心目标:在不重建信号系统的前提下,将净R从-96.98R拉回正值 -> 策略:**降低手续费暴露 + 提升单笔期望值** - ---- - -## 优化原则 - -信号层毛R为+11.98R(微弱正收益),说明信号有效性存在但边际极薄。 -改造优先级:**先降频降费 → 再提盈亏比 → 最后优化信号质量**。 - ---- - -## 方向一:提高入场门槛(降频) - -### 当前问题 -- 75分以上即可入场,触发频率过高(500笔/历史周期) -- 各分数段胜率差异不大(85+仅比75-79高1.7%),说明75-84大量交易性价比差 - -### 建议改动 -| 参数 | 当前值 | 建议值 | -|------|--------|--------| -| 入场阈值 | 75 | **82** | -| 预期效果 | 500笔 | 约~200笔(减少约60%交易频次) | -| 手续费节省 | - | ~65R(108×60%) | - -> 根据数据,82分以上样本约170笔,需重新统计。需要验证胜率是否提升。 - ---- - -## 方向二:时段过滤(砍亏损时段) - -### 当前问题 -以下时段(北京时间)胜率<40%,是系统性亏损区: - -| 时段 | 胜率 | 合计R | -|------|------|-------| -| 01:00 | 31.8% | -15.69R | -| 06:00 | 33.3% | -13.73R | -| 07:00 | 36.4% | -7.40R | -| 09:00 | 38.7% | -16.71R | -| 11:00 | 28.6% | -5.51R | -| 13:00 | 31.8% | -13.62R | -| 18:00 | 30.0% | -6.47R | - -合计:约7个亏损时段,贡献约-79R亏损。 - -### 建议改动 -禁止在以下北京时间开仓:**01:00, 06:00, 07:00, 09:00, 11:00, 13:00, 18:00** -→ 预计减少交易约~100笔,直接节省约79R亏损 - ---- - -## 方向三:暂停BTC交易 - -### 当前问题 -| 币种 | 胜率 | 合计R | -|------|------|-------| -| BTCUSDT | 49.3% | -45.61R | - -BTC胜率低于随机水平(49.3%<50%),是最大单一亏损来源,贡献总亏损47%。 - -### 建议改动 -**暂停BTC交易**,等积累足够新数据(calc_version=2)后再评估是否恢复。 -→ 直接避免-45.61R(历史口径),减少约27%交易频次。 - ---- - -## 方向四:拉大TP/SL比(提盈亏比) - -### 当前问题 -- sl_multiplier=1.4, tp1=1.05, tp2=2.1 -- tp1_r=0.75, tp2_r=1.5 -- 平均TP净收益=0.90R,平均SL净亏损=-1.23R -- 盈亏比=0.73,手续费后需要胜率>58%才能打平 - -### 建议改动 -| 参数 | 当前值 | 建议值 | -|------|--------|--------| -| sl_multiplier | 1.4 | 2.0(扩大止损空间,减少噪声止损) | -| tp1_multiplier | 1.05 | 1.5 | -| tp2_multiplier | 2.1 | 3.0 | - -> 注意:扩大止损会增大单笔手续费(fee_r=2×0.0005×entry/rd,rd变大则fee_r变小) -> 同时能减少被噪声打止损的次数(SL平均仅18分钟持仓) - ---- - -## 组合改动预期效果(粗估) - -| 改动 | 预期节省R | -|------|----------| -| 提高入场门槛至82 | ~65R | -| 过滤7个亏损时段 | ~79R | -| 暂停BTC | ~46R | -| **合计** | **~190R** | - -> 当前净亏损-96.98R,三项改动合计节省190R,理论上净R可到+93R(乐观估计,存在重叠) -> 实际效果需要在模拟盘上验证后才能确认 - ---- - -## 实施计划 - -### Phase 1:参数调整(立即可做,不改代码) -1. 修改 `backend/strategies/v51_baseline.json`: - - threshold: 75 → 82 - - 添加 `forbidden_hours_bj: [1, 6, 7, 9, 11, 13, 18]` - - 添加 `disabled_symbols: ["BTCUSDT"]` -2. 修改 `backend/paper_config.json` 对应字段(如果有覆盖) -3. 重启 signal-engine - -### Phase 2:TP/SL调整(需验证历史数据影响) -1. 模拟不同sl_multiplier在历史数据上的表现 -2. 确认新参数下预期胜率和盈亏比 -3. 更新 `v51_baseline.json` - -### Phase 3:数据验证 -1. 积累150-200笔新口径数据(calc_version=2) -2. 对比优化前后各项指标 -3. 根据实际结果再次迭代 - ---- - -## 注意事项 - -1. **不要同时改太多参数**:每次只改1-2个变量,方便归因 -2. **记录每次改动时间**:便于后续对比数据 -3. **备份当前配置**:`v51_baseline.json` 改前先备份 -4. **V5.2同步评估**:V5.2目前-15.94R,比V5.1好但仍亏损,后续需同步分析 - ---- - -## 待讨论问题 - -- [ ] 入场门槛从75提到82合适吗?是否要先看82-84分的历史胜率数据? -- [ ] 时段过滤是全部禁止还是只禁BTC? -- [ ] TP/SL比调整是否应该先做回测再上模拟盘? -- [ ] 暂停BTC是否需要范总确认? diff --git a/docs/arbitrage-engine/v51-performance-analysis.md b/docs/arbitrage-engine/v51-performance-analysis.md deleted file mode 100644 index 8d1d474..0000000 --- a/docs/arbitrage-engine/v51-performance-analysis.md +++ /dev/null @@ -1,163 +0,0 @@ ---- -title: V5.1 模拟盘执行分析报告 -date: 2026-03-03 ---- - -# V5.1 模拟盘执行分析报告 - -> 数据口径:真实成交价(agg_trades)+ 手续费扣除,calc_version=2 -> 分析日期:2026-03-03 -> 参与分析:露露(Sonnet 4.6)、小范(GPT-5.3-Codex) - ---- - -## 一、总体概况 - -| 指标 | 数值 | -|------|------| -| 总交易笔数 | 503笔(含3笔活跃) | -| 已闭合笔数 | 500笔 | -| 有效样本(score≥75)| 496笔 | -| 净R(含手续费)| **-96.98R** | -| 毛R(不含手续费)| **+11.98R** | -| 总手续费 | **108.97R** | -| 平均单笔手续费 | 0.218R | -| 胜率 | 55.4% | -| 平均每笔净R | -0.193R | - -> 本金10,000 USD,1R=200 USD → 净亏损约19,396 USD - ---- - -## 二、出场类型分布 - -| 状态 | 笔数 | 平均R(净) | 合计R | -|------|------|-----------|-------| -| sl(止损)| 189 | -1.232R | **-232.85R** | -| tp(止盈)| 132 | +0.904R | +119.36R | -| sl_be(保本止损)| 118 | +0.161R | +19.04R | -| timeout(超时)| 41 | +0.073R | +2.99R | -| signal_flip(翻转)| 20 | -0.276R | -5.51R | - -**关键发现**:SL次数(189)远超TP(132),SL吃掉232.85R,TP只回收119.36R,实际盈亏比=0.77:1。 - -### SL均值拆解 - -| 组成 | 数值 | -|------|------| -| SL基础R | -1.000R(止损公式正确) | -| 手续费 | -0.232R | -| 净SL | **-1.232R** | - ---- - -## 三、方向分析 - -| 方向 | 笔数 | 胜率 | 合计R | -|------|------|------|-------| -| LONG | 281 | 54.7% | -46.32R | -| SHORT | 222 | 56.3% | -50.67R | - -**结论**:多空双向均亏,非方向性问题。 - ---- - -## 四、币种分析 - -| 币种 | 笔数 | 胜率 | 合计R | -|------|------|------|-------| -| BTCUSDT | 137 | **49.3%** | **-45.61R** | -| ETHUSDT | 119 | 54.2% | -19.37R | -| XRPUSDT | 129 | 62.0% | -16.05R | -| SOLUSDT | 118 | 56.4% | -15.95R | - -**关键发现**:BTC胜率仅49.3%(低于随机),是最大亏损来源,亏损占总量47%。 - ---- - -## 五、信号分数段分析 - -| 分数段 | 笔数 | 胜率 | 合计R | -|--------|------|------|-------| -| 75-79 | 179 | 57.3% | -30.54R | -| 80-84 | 214 | 54.7% | -45.21R | -| 85+ | 103 | 55.3% | -15.91R | - -**关键发现**:高分(85+)胜率与低分段基本持平,评分体系对预测质量的区分度不足。 - -> 另有6笔score=70-72(早期历史数据,入场门槛未设75时),不计入有效样本。 - ---- - -## 六、时段分析(北京时间) - -### 盈利时段(合计R>0) -| 时段 | R | 胜率 | -|------|---|------| -| 03:00 | +2.24R | 69.2% | -| 05:00 | +6.18R | 78.6% | -| 08:00 | +5.22R | 82.6% | -| 17:00 | +2.40R | 85.7% | -| 19:00 | +2.27R | 83.3% | -| 23:00 | +7.97R | 71.4% | - -### 重度亏损时段(胜率<40%) -| 时段 | R | 胜率 | -|------|---|------| -| 01:00 | -15.69R | 31.8% | -| 06:00 | -13.73R | 33.3% | -| 07:00 | -7.40R | 36.4% | -| 09:00 | -16.71R | 38.7% | -| 11:00 | -5.51R | 28.6% | -| 13:00 | -13.62R | 31.8% | -| 18:00 | -6.47R | 30.0% | - ---- - -## 七、持仓时间分析 - -| 出场类型 | 平均持仓 | -|----------|---------| -| timeout | 60.0分钟 | -| sl_be | 23.8分钟 | -| tp | 20.5分钟 | -| sl | **18.1分钟** | -| flip | 20.2分钟 | - -**发现**:SL平均仅持仓18分钟即被打出,说明入场时机存在问题(短时噪声触发入场)。 - ---- - -## 八、风险统计 - -| 指标 | 数值 | -|------|------| -| 单笔最大亏损 | -1.47R | -| 单笔最大盈利 | +1.04R | -| 标准差 | 0.89R | -| 中位数 | +0.12R | -| P25 | -1.19R | -| P75 | +0.78R | - -> 中位数为正(+0.12R)但均值为负(-0.19R),说明少数大亏拖累整体,分布右偏。 - ---- - -## 九、核心结论 - -### 最关键发现:毛R为正,费用致亏 -- **毛R(不含手续费):+11.98R** → 信号层有微弱预测优势,未完全失效 -- **总手续费:108.97R** → 手续费将毛R从+12压到-97 -- **结论:V5.1不是"不会预测",而是"预测优势太薄,被执行成本碾碎"** - -### 四大结构性问题 -1. **盈亏比天然劣势**:SL:TP=189:132(每次输更多,赢的次数更少) -2. **BTC信号质量差**:胜率49.3%,低于随机,应考虑暂停或单独优化BTC -3. **评分体系区分度不足**:85+高分与75-79低分胜率差不多,评分无效 -4. **时段敏感**:约6-7个时段胜率<40%,是系统性亏损区间 - ---- - -## 十、优化方向(待讨论) - -详见:[V5.1优化方案](./v51-optimization-plan.md) diff --git a/docs/arbitrage-engine/v51-signal-enhancement.md b/docs/arbitrage-engine/v51-signal-enhancement.md deleted file mode 100644 index 82319a6..0000000 --- a/docs/arbitrage-engine/v51-signal-enhancement.md +++ /dev/null @@ -1,250 +0,0 @@ ---- -title: V5.1 信号增强方案 -description: V5.1 信号评分体系 + 仓位管理 + TP/SL + 风控 + 回测框架 ---- - -# V5.1 信号增强方案 - -> 讨论参与:露露(Opus 4.6) + 小周(GPT-5.3-Codex) + 范总审核 -> 定稿时间:2026-02-28 - -## 1. 概述 - -V5.0 以 aggTrades 原始成交流为核心(CVD三轨 + ATR + VWAP + P95/P99大单),V5.1 在此基础上增加 4 个数据维度 + 完善交易管理系统。 - -**核心理念**:aggTrades 是我们的独特优势(别人没有原始成交流),新增数据源作为方向确认和风控补充,不替代 aggTrades 的核心地位。 - -## 2. 信号评分体系(100分制) - -### 2.1 权重分配 - -| 层级 | 数据源 | 权重 | 角色 | -|------|--------|------|------| -| **方向层** | aggTrades(CVD三轨 + P95/P99大单) | **45%** | 核心方向判断 | -| **拥挤层** | L/S Ratio + Top Trader Position | **20%** | 市场拥挤度 | -| **环境层** | Open Interest 变化 | **15%** | 资金活跃度/可交易性门槛 | -| **确认层** | 多时间框架一致性 | **15%** | 方向确认 | -| **辅助层** | Coinbase Premium | **5%** | 机构资金流向 | - -### 2.2 各层级详细计算 - -#### 方向层(45分) - -- **CVD_fast(30m滚动)方向**:与信号方向一致 +15 -- **CVD_mid(4h滚动)方向**:与信号方向一致 +15 -- **P95/P99 大单**:无反向 P99 大单 +10,有同向 P99 大单 +15 -- **CVD_fast 斜率加速**:斜率 > 阈值 +5(额外加分) - -#### 拥挤层(20分) - -- **L/S Ratio**: - - L/S > 2.0(做空信号)或 L/S < 0.5(做多信号):+10 - - L/S 1.5-2.0 / 0.5-0.67:+5 - - 中性区间:0 -- **Top Trader Position Ratio**: - - 大户方向与信号一致:+10 - - 大户方向中性:+5 - - 大户方向反向:0 - -#### 环境层(15分) - -- **OI 变化**(不判断方向,判断活跃度): - - OI 15分钟变化率 > 阈值(活跃):+15 - - OI 变化温和:+10 - - OI 萎缩(市场冷清):+5 - -#### 确认层(15分) - -- **多时间框架确认规则**: - - `1m` = 触发层(入场点) - - `5m/15m` = 方向确认层 - - `1h` = 风险闸门 -- **评分**: - - 5m AND 15m 同向:+15 - - 5m OR 15m 同向:+10 - - 无同向确认:+0 -- **1h 反向处理**:不在评分里扣分,而是在仓位管理里降仓(见 3.2) - -#### 辅助层(5分) - -- **Coinbase Premium**: - - Premium 方向与信号一致且 > 阈值:+5 - - 中性:+2 - - 反向:0 - -### 2.3 开仓门槛 - -| 总分 | 操作 | -|------|------| -| < 60 | **不开仓** | -| 60 - 74 | 轻仓(基础仓位 × 0.5) | -| 75 - 84 | 标准仓位 | -| ≥ 85 | 允许加仓(基础仓位 × 1.3) | - -## 3. 仓位管理 - -### 3.1 基础仓位 - -- **默认**:总资金的 10% -- **杠杆**:3X(可调) -- **单笔最大风险**:总资金的 2% - -### 3.2 1h 时间框架降仓规则 - -| 1h 状态 | 仓位调整 | -|---------|---------| -| 1h 同向 | 正常仓位 | -| 1h 弱反向 | 仓位 × 0.7 | -| 1h 强反向(CVD + 趋势都反) | 仓位 × 0.5,且仅允许 ≥85 分信号 | - -### 3.3 Funding Rate 偏置 - -- FR 不做触发因子,做"慢变量偏置" -- 计算 `FR z-score(7d)` + `FR 斜率(近3个结算点)` -- 映射为 `bias`(-1 ~ +1)叠加到总分 -- FR 极端且与信号方向冲突时:仅降仓,不反向开仓 - -## 4. TP/SL 管理(双ATR融合) - -### 4.1 ATR 计算 - -``` -risk_atr = 0.7 × ATR_5m + 0.3 × ATR_1h -``` - -- ATR_5m:5分钟K线,14周期 → 管入场灵敏度 -- ATR_1h:1小时K线,14周期 → 管极端波动保护 -- 好处:分钟级不钝化(靠5m),又不被短时噪音洗掉(靠1h兜底) - -### 4.2 止盈止损参数 - -| 参数 | 计算 | 说明 | -|------|------|------| -| **SL** | Entry ± 2.0 × risk_atr | 初始止损 | -| **TP1** | Entry ∓ 1.5 × risk_atr | 第一目标 | -| **TP2** | Entry ∓ 3.0 × risk_atr | 第二目标 | - -### 4.3 分批平仓逻辑 - -1. **TP1 触发**:平 50% 仓位,SL 移至成本价 + 手续费(Breakeven) -2. **TP2 触发**:平剩余 50%,信号标记 "tp" -3. **SL 触发(TP1 已达)**:标记 "sl_be"(保本止损,实际盈亏 ≈ +0.5R) -4. **SL 触发(TP1 未达)**:标记 "sl"(完整止损,亏损 -1.0R) - -### 4.4 期望值计算 - -假设 60% 胜率(TP1 命中率): -- 60% × 2.0R = +1.2R -- 40% × -1.0R = -0.4R -- **期望值 = +0.8R/笔** - -## 5. 风控系统 - -### 5.1 自适应冷却期 - -| 条件 | 冷却时间 | -|------|---------| -| 基础(同向信号开仓后) | 10 分钟 | -| 近 30min 同向连续 2 笔止损 | 升到 20 分钟 | -| 上一笔同向达到 TP1 | 缩短到 5 分钟 | -| 第 4 个同向信号 | 默认不开,除非上一笔已TP1 + 当前≥85分 + 1h不强反向 | - -- **反向信号**不受同向冷却限制,但需过最小反转阈值(防止来回翻单) - -### 5.2 清算瀑布检测 - -- **主通道(实时)**:aggTrades 异常成交密度 + 价格加速度 + 点差扩张 → 推断清算瀑布 -- **辅通道(校验)**:Binance `forceOrders` API → 事后校验和阈值再训练 -- 交易决策吃主通道,模型校准吃辅通道 - -### 5.3 盘口轻量监控(资源受限版) - -- 仅采集 Top-of-Book + 前5档聚合(每 100-250ms 采样) -- 保留 3 个指标:`microprice`、`imbalance`、`spread` -- 只存特征,不存全量快照 -- 后续评估是否升到10档 - -## 6. 数据源汇总 - -### 6.1 Binance 免费 API(V5.1 新增) - -| 数据 | 接口 | 更新频率 | -|------|------|---------| -| 多空比 | `GET /futures/data/globalLongShortAccountRatio` | 5min | -| 大户持仓比 | `GET /futures/data/topLongShortPositionRatio` | 5min | -| OI 历史 | `GET /futures/data/openInterestHist` | 5min | -| Funding Rate | `GET /fapi/v1/fundingRate` | 8h结算 | - -### 6.2 Coinbase Premium - -- 对比 Coinbase BTC/USD 与 Binance BTC/USDT 实时价差 -- 正 Premium = 机构买入(看多信号) -- 负 Premium = 机构卖出(看空信号) - -### 6.3 已有数据源(V5.0) - -| 数据 | 来源 | 存储 | -|------|------|------| -| aggTrades | Binance WebSocket 实时 + REST 回补 | PostgreSQL agg_trades 表 | -| CVD三轨 | signal_engine 内存计算 | signal_indicators 表 | -| ATR/VWAP | signal_engine 内存计算 | signal_indicators 表 | -| P95/P99大单 | signal_engine 24h滚动统计 | signal_indicators 表 | -| Funding Rate | agg_trades_collector 定时采集 | rate_snapshots 表 | - -## 7. 回测框架 - -### 7.1 架构:逐tick事件回放 - -**不用逐分钟K线回测**(会系统性高估策略),用 aggTrades 逐tick回放。 - -### 7.2 三层数据结构 - -``` -FeatureStore -├── 按时间索引缓存 1m/5m/15m/1h 特征 -├── CVD, L/S, OI, FR bias, 盘口因子 -└── 滚动窗口自动过期 - -SignalEvent -├── ts, symbol, side, score, regime -├── factors (各层评分明细) -└── entry_rule_id - -PositionState -├── entry_ts, entry_px, size -├── sl_px, tp1_px, tp2_px -├── status (active/tp1_hit/tp/sl/sl_be) -└── cooldown_until -``` - -### 7.3 撮合逻辑 - -1. 每个 tick 到来 → 先更新未平仓位(检查 TP/SL/时间止损) -2. 再评估新信号(检查冷却期、评分、仓位规则) -3. 输出交易记录 - -### 7.4 统计输出 - -- 胜率 (Win Rate) -- 总盈亏 (Total PnL in R) -- 盈亏比 (Profit Factor) -- 夏普比率 (Sharpe Ratio) -- 最大回撤 (MDD) -- 平均持仓时间 (Avg Hold) -- 滑点影响评估 (Slippage Impact) - -## 8. 远期规划 - -### V5.2(远期备选) - -- **Twitter 新闻情绪面**:监控关键账号,AI分析利好/利空 -- **范总判断**:新闻最终反映在 aggTrades 里,信号跑通后不急 - -### V5.3(数据充足后) - -- ML模型替换规则引擎(XGBoost/LightGBM集成) -- 需要足够回测数据训练 - ---- - -*文档版本:V5.1-draft | 待范总最终确认* diff --git a/docs/arbitrage-engine/v51-signal-system.md b/docs/arbitrage-engine/v51-signal-system.md deleted file mode 100644 index 64a729d..0000000 --- a/docs/arbitrage-engine/v51-signal-system.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: V5.1 信号系统文档 ---- - -# V5.1 基线信号系统(v51_baseline) - -## 概述 - -V5.1 是基于市场微观结构的短线交易信号系统,使用 5 层 100 分评分体系,通过 CVD(累积成交量差)、大单流、持仓结构等 6 个信号源综合判断交易方向和强度。 - -## 评分体系(5层100分) - -| 层级 | 权重 | 信号源 | 逻辑 | -|------|------|--------|------| -| 方向层 | 45分 | CVD_fast(30m) + CVD_mid(4h) + P99大单 + 加速度 | 三票共振定方向 | -| 拥挤层 | 20分 | 多空比 + 大户持仓 | 反向拥挤 = 机会 | -| 环境层 | 15分 | OI变化率 | 资金流入确认趋势 | -| 确认层 | 15分 | CVD_fast + CVD_mid 同向 | 双周期共振确认 | -| 辅助层 | 5分 | Coinbase Premium | 美国机构动向 | - -### 方向层详解(45分 + 5分加速奖金) - -信号方向由 CVD_fast 和 CVD_mid 综合判断: - -``` -CVD_fast > 0 且 CVD_mid > 0 → LONG -CVD_fast < 0 且 CVD_mid < 0 → SHORT -不一致 → 以 CVD_fast 方向为准,但标记 no_direction -``` - -评分项: -- CVD_fast 方向一致:+15 -- CVD_mid 方向一致:+15 -- P99 大单顺向流入:+15(无反向大单时+10) -- 加速度奖金:CVD_fast 加速度方向一致 → +5(可超过45) - -### 拥挤层详解(20分) - -| 子项 | 满分 | 逻辑 | -|------|------|------| -| 多空比(LSR) | 10分 | 做空+LSR>2.0=满分,做多+LSR<0.5=满分 | -| 大户持仓比 | 10分 | 做多+多头占比≥55%=满分 | - -数据缺失时给中间分(5分),避免因采集失败误杀信号。 - -### 环境层详解(15分) - -基于 OI(持仓量)变化率评分: -- OI 显著增长 → 趋势确认 → 满分 -- OI 变化不大 → 中等分 -- OI 下降 → 低分 - -### 确认层详解(15分) - -CVD_fast 和 CVD_mid 同时为正(LONG)或同时为负(SHORT)→ 15分,否则 0分。 - -> **已知问题**:与方向层存在同源重复(两者都用 CVD_fast/CVD_mid)。两周后根据数据评估是否重构。 - -### 辅助层详解(5分) - -Coinbase Premium = Coinbase 价格 vs Binance 价格差。 -- 正溢价 + 做多 → 5分(美国机构买入) -- 负溢价 + 做空 → 5分 -- 溢价绝对值 ≤ 0.05% → 2分(中性) -- 反向溢价 → 0分 - -## 开仓规则 - -| 档位 | 分数 | 行为 | -|------|------|------| -| 不开仓 | < 75 | 不触发 | -| 标准 | 75-84 | 正常开仓 | -| 加仓 | ≥ 85 | 加重仓位 | - -- **冷却期**:同币种同策略 10 分钟 -- **最大持仓**:4 笔/策略 -- **反向翻转**:收到反向 ≥75 分信号 → 平旧仓 + 开新仓 - -## TP/SL 设置 - -| 参数 | 值 | 说明 | -|------|------|------| -| SL | 1.4 × ATR | 止损 | -| TP1 | 1.05 × ATR | 第一止盈(平50%仓位) | -| TP2 | 2.1 × ATR | 第二止盈(平剩余仓位) | - -TP1命中后 SL 移到保本价(Break Even)。 - -> 对应R倍数:tp1_r=0.75, tp2_r=1.5(以SL为1R) - -## 信号源 - -| 信号 | 数据源 | 更新频率 | -|------|--------|----------| -| CVD_fast | aggTrades 30分钟窗口 | 15秒 | -| CVD_mid | aggTrades 4小时窗口 | 15秒 | -| P99 大单 | aggTrades P99分位数 | 实时 | -| 加速度 | CVD_fast 二阶导数 | 15秒 | -| 多空比/大户持仓 | Binance API | 5分钟 | -| OI | Binance API | 5分钟 | -| Coinbase Premium | 计算值 | 5分钟 | - -## 策略配置 - -```json -{ - "name": "v51_baseline", - "version": "5.1", - "threshold": 75, - "weights": { - "direction": 45, - "crowding": 20, - "environment": 15, - "confirmation": 15, - "auxiliary": 5 - }, - "accel_bonus": 5, - "tp_sl": { - "sl_multiplier": 1.4, - "tp1_multiplier": 1.05, - "tp2_multiplier": 2.1 - }, - "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium"] -} -``` - -## 历史表现(修正后数据,截至 2026-03-02) - -- 总交易:282+ 笔 -- 胜率:~65% -- 盈亏比:~0.72 -- 按档位:85+ 胜率73.6%,75-79 胜率78%(最佳档位) diff --git a/docs/arbitrage-engine/v51_gemini_analysis.md b/docs/arbitrage-engine/v51_gemini_analysis.md deleted file mode 100644 index c63a81f..0000000 --- a/docs/arbitrage-engine/v51_gemini_analysis.md +++ /dev/null @@ -1,175 +0,0 @@ -明白!既然马上要进入开发阶段,我们就不能只停留在“概念”上,必须精确到**参数配比、计算公式和逻辑边界**。 - -作为你的量化分析师,我将《V5.3 双轨信号系统设计案》中的高层设计,直接翻译成研发可以直接参考的**“伪代码级”拆解**。这份拆解去除了所有冗余,直指核心代码逻辑。 - ---- - -### 一、 轨道 A:山寨币模型 (ALT Track - 适用于 ETH, XRP, SOL) - -ALT 轨的核心是**线性评分加法模型**。满分 100 分。 - -#### 1. 方向层 (Direction) —— 总权重:55 分 - -**设计说明:** V5.1 中方向层(45分)和确认层(15分)存在多重共线性,V5.3 将其合并为 55 分。 - -* **子项 A:快慢期 CVD 共振 (30 分)** -* **逻辑:** `CVD_fast (30m)` 和 `CVD_mid (4h)` 必须同向。 -* **打分:** -* `CVD_fast` > 0 且 `CVD_mid` > 0(做多共振) $\rightarrow$ 给 30 分。 -* `CVD_fast` < 0 且 `CVD_mid` < 0(做空共振) $\rightarrow$ 给 30 分。 -* 方向不一致 $\rightarrow$ 0 分(并且整个信号应标记为 `no_direction` 终止计算)。 - - - - -* **子项 B:P99 大单流入 (20 分)** -* **逻辑:** 捕捉极值大单资金方向。 -* **打分:** -* P99 大单净流入方向与 CVD 共振方向一致 $\rightarrow$ 给 20 分。 -* 无明显反向大单阻击 $\rightarrow$ 给 10 分。 -* 反向大单压制 $\rightarrow$ 0 分。 - - - - -* **子项 C:加速度奖金 (Accel Bonus) (5 分)** -* **逻辑:** `CVD_fast` 的二阶导数(动能正在增强)。 -* **打分:** 加速度方向与共振方向一致 $\rightarrow$ 给 5 分。 - - - -#### 2. 拥挤层 (Crowding) —— 总权重:25 分 - -**设计说明:** 寻找散户的反向流动性(拔高了 V5.1 中该层的权重)。 - -* **子项 A:多空比 LSR (15 分)** -* **做多场景:** 如果 LSR < 0.5(散户极度看空) $\rightarrow$ 15 分满分。 -* **做空场景:** 如果 LSR > 2.0(散户极度看多) $\rightarrow$ 15 分满分。 -* *缺省/常态:* 数据缺失或在 0.8 - 1.2 之间 $\rightarrow$ 给 7.5 分中间分。 - - -* **子项 B:大户持仓比例 (10 分)** -* **逻辑:** 跟着大户吃散户。 -* **打分:** 做多且大户多头占比 $\ge$ 55% $\rightarrow$ 10 分满分(做空同理)。缺省给 5 分。 - - - -#### 3. 环境层 (Environment) —— 总权重:15 分 - -* **信号源:** OI (Open Interest) 5 分钟/30 分钟变化率。 -* **打分:** -* OI 显著正增长(有新资金入场,趋势真实) $\rightarrow$ 15 分。 -* OI 变化平缓 $\rightarrow$ 7.5 分。 -* OI 显著下降(说明只是存量平仓导致的假突破) $\rightarrow$ 0 分。 - - - -#### 4. 辅助层 (Auxiliary) —— 总权重:5 分 - -* **信号源:** Coinbase Premium (Coinbase 现货价格 - Binance 现货价格)。 -* **打分:** -* 做多且正溢价(美国资金在买) $\rightarrow$ 5 分。 -* 做空且负溢价(美国资金在砸) $\rightarrow$ 5 分。 -* 溢价绝对值 $\le$ 0.05% (中性) $\rightarrow$ 2 分。 -* 反向溢价 $\rightarrow$ 0 分。 - - - -> ⚙️ **ALT 轨开发执行参数:** -> * `open_threshold`: **75** (总分 $\ge$ 75 触发开仓) -> * `flip_threshold`: **85** (反向信号总分 $\ge$ 85 才允许平旧开新) -> -> - ---- - -### 二、 轨道 B:大饼专属模型 (BTC Track) - -BTC 轨**抛弃了线性加分**,采用**“布尔逻辑门控 (Boolean Logic Gates)”**。必须同时满足所有通过条件,且不触发任何否决条件,才允许开仓。 - -#### 1. 门控特征一:波动率状态过滤 (atr_percent_1h) —— 决定“能不能做” - -* **公式:** $Volatility = \frac{ATR(1h)}{Close Price}$ -* **开发逻辑 (Veto 否决条件):** -设定一个最小波动率阈值(如 `min_vol_threshold = 0.002`,即 0.2%)。 -`IF atr_percent_1h < min_vol_threshold THEN BLOCK_SIGNAL ("Garbage Time")` -*理由:BTC 在极低波动时,微观特征全是做市商噪音。* - -#### 2. 门控特征二:巨鲸 CVD (tiered_cvd_whale) —— 决定“真实方向” - -* **计算方式:** 在 aggTrades 聚合时,过滤掉单笔价值 $< \$100k$ 的成交。只对 $> \$100k$ 的大单计算 Net Flow。 -* **开发逻辑:** -`IF tiered_cvd_whale > strong_positive_threshold THEN Direction = LONG` - -#### 3. 门控特征三:前 10 档订单薄失衡 (obi_depth_10) —— 决定“有没有阻力” - -* **公式:** $OBI = \frac{Bid Volume - Ask Volume}{Bid Volume + Ask Volume}$ (取盘口前 10 档挂单量) -* **开发逻辑 (Veto 否决条件):** -*做多场景:* 如果 `Direction = LONG`,但 $OBI < -0.3$(上方有极重的卖盘墙压制)。 -`THEN BLOCK_SIGNAL ("Sell Wall Imbalance")` - -#### 4. 门控特征四:期现背离 (spot_perp_divergence) —— 决定“是不是陷阱” - -* **计算方式:** Binance BTCUSDT 现货 CVD 减去 BTCUSDT 永续合约 CVD。 -* **开发逻辑 (Veto 否决条件):** -*做多场景:* 合约 CVD 在疯狂飙升(散户开多),但现货 CVD 为负(机构在现货抛售)。 -`IF perp_cvd > 0 AND spot_cvd < 0 THEN BLOCK_SIGNAL ("Spot Selling Divergence")` - -> ⚙️ **BTC 轨开发执行伪代码:** -> `IF (Passed Volatility Gate) AND (Whale CVD confirms Direction) AND NOT (Blocked by OBI) AND NOT (Blocked by Divergence) THEN Execute Trade` - ---- - -### 三、 执行引擎边界条件(影响所有轨) - -这些是直接写死在 `signal_engine.py` 或订单执行模块里的硬性规则,用来保卫净收益。 - -1. **止损/止盈 (SL/TP) 基准:** -* `SL_multiplier`: **2.0** (止损设为入场价 $\pm 2.0 \times ATR$) -* `TP1_multiplier`: **1.5** -* `TP2_multiplier`: **3.0** - - -2. **Break-Even (BE) 滑点补偿:** -* 打到 TP1 后,SL 移动的位置不是 `Entry Price`。 -* `New_SL = Entry_Price + (Direction * 0.2 * ATR)` (这里的 0.2 ATR 是预留给手续费和滑点的缓冲值)。 - - -3. **TP 兜底状态机 (Fallback Logic):** -* *Step 1:* 信号触发,下市价单(Taker)开仓。 -* *Step 2:* 立即挂出 TP1/TP2 的**限价单 (Maker)**。 -* *Step 3 (Monitor):* 如果最新标记价格穿过了 TP 触发价,但限价单未成交,启动 `timeout` 计时器(例如 2 秒)。 -* *Step 4 (Fallback):* 超时未成交 $\rightarrow$ 发送 `Cancel Order` $\rightarrow$ 发送 `Market Order (Taker)` 强平。 - - - ---- - -## 四、开发注意事项(补充) - -1. **缺失数据默认策略(必须写死)** - - BTC 四个门控特征(`atr_percent_1h`, `tiered_cvd_whale`, `obi_depth_10`, `spot_perp_divergence`)任一缺失时,默认 `BLOCK_SIGNAL`。 - - 必须记录 `block_reason=missing_feature:`,避免静默放行。 - -2. **阈值治理(避免拍脑袋改参)** - - `min_vol_threshold`、`obi_veto_threshold`、`whale_flow_threshold` 必须配置化(不可硬编码散落在代码中)。 - - 文档标注为“初始值”,并明确回测校准窗口与更新频率。 - -3. **标签口径统一(防止回填偏差)** - - `Y_binary_60m` 使用 `Mark Price` 判定触发顺序。 - - ATR 必须使用信号触发时快照 `atr_value`,禁止回填时二次重算 ATR。 - -4. **TP 兜底状态机补全部分成交分支** - - 触发兜底前先查询订单成交量。 - - 若部分成交,只对剩余仓位执行 `Cancel -> Taker Market Close`,避免超平或漏平。 - -5. **并发与幂等保护** - - `Cancel -> Market` 流程增加订单状态锁(或行级锁)和幂等键。 - - 防止重复撤单、重复平仓、双写成交记录。 - -6. **发布闸门指标字段统一** - - 统一报表输出字段:`maker_ratio`, `avg_friction_cost_r`, `flip_loss_r`。 - - 发布闸门自动判断基于同一口径,避免人工解释偏差。 - -**给开发者的最终建议:** -你现在可以拿着这份拆解,直接去写 `v53_alt_config.json` 和 BTC 轨的条件判断代码了。建议你先从 **ALT 轨的 `v53_alt_config.json` 重写**开始,因为这个改动最小,见效最快。是否需要我帮你直接生成这个 JSON 文件的模板? \ No newline at end of file diff --git a/docs/arbitrage-engine/v52-development.md b/docs/arbitrage-engine/v52-development.md deleted file mode 100644 index 96ce5b0..0000000 --- a/docs/arbitrage-engine/v52-development.md +++ /dev/null @@ -1,383 +0,0 @@ ---- -title: V5.2 开发文档(完整版) -description: 套利引擎V5.2 — Bug修复 + 策略优化 + 8信号源 + 策略配置化 + AB测试 ---- - -# V5.2 开发文档(完整版) - -> **版本**:V5.2 | **状态**:待开发 | **负责人**:露露 -> **创建**:2026-03-01 | **前置**:V5.1 tag `v5.1` commit `d8ad879` -> **V5.1-hotfix**:commits `45bad25` → `4b841bc`(P0修复已上线) - ---- - -## 一、V5.1 现状总结 - -### 模拟盘数据(截至2026-03-01) - -| 指标 | 数值 | -|------|------| -| 总交易 | 181笔 | -| 总盈亏 | +37.07R | -| 每笔平均 | +0.20R | -| 初始资金 | $10,000 | -| 当前余额 | ~$17,414 | - -### 按档位统计(关键数据) - -| 档位 | 笔数 | 胜率 | 总PnL | 每笔平均 | 结论 | -|------|------|------|-------|---------|------| -| **85+ (heavy)** | 53 | 73.6% | +15.00R | +0.28R | ✅ 优质 | -| **80-84** | 81 | 65.4% | +4.11R | +0.05R | ⚠️ 平庸 | -| **75-79** | 41 | 78.0% | +17.05R | +0.42R | ✅ 最佳 | -| **70-74** | 6 | 50.0% | -2.65R | -0.44R | ❌ 亏钱 | - -### 手续费分析(核心发现) - -| 币种 | SL距离% | 仓位价值 | 隐含杠杆 | 手续费/R | -|------|---------|---------|---------|---------| -| BTC | 0.43% | $46,000 | 4.6x | 0.23R | -| ETH | 0.56% | $36,000 | 3.6x | 0.18R | -| XRP | 0.44% | $45,000 | 4.5x | 0.05R | -| SOL | 0.58% | $34,000 | 3.4x | 0.04R | - -**手续费公式**:`fee_R = 2 × 0.05% × position_value / risk_usd` - -**BTC盈亏比问题**:TP2净利+0.89R vs SL净亏-1.23R,盈亏比仅0.72,需55%以上胜率才保本。 - -### V5.1已修复的P0 Bug(hotfix已上线) - -| Bug | 影响 | 修复Commit | -|-----|------|-----------| -| pnl_r虚高2倍 | 统计数据全部失真 | `45bad25` | -| 冷却期阻断反向平仓 | 反向信号无法关仓 | `45bad25` | -| 分区月份Bug | 月底数据写入失败 | `45bad25` | -| SL/TP用市价不是限价 | SL超过1R | `2f9dce4` | -| 浮盈没算半仓 | 持仓盈亏虚高 | `4b841bc` | - ---- - -## 二、V5.2 目标 - -### 核心目标 -1. **修复所有已知Bug**(Claude Code审阅 + 实际使用发现的) -2. **FR+清算加入评分**(8信号源完整版) -3. **开仓阈值提到75分**(砍掉70-74垃圾信号) -4. **策略配置化框架**(一套代码多份配置) -5. **AB测试**(V5.1 vs V5.2并行对比) -6. **24小时warmup**(消除冷启动) - -### 设计原则 -- **55%胜率必须盈利**:盈亏比至少0.82:1 -- **无限趋近实盘**:模拟盘和实盘逻辑完全一致 -- **数据驱动**:所有决策基于数据,不拍脑袋 - ---- - -## 三、Bug修复清单 - -### 后端(15项) - -| ID | 优先级 | 文件 | 问题 | 修复方案 | -|----|--------|------|------|---------| -| P0-3 | **P1** | signal_engine.py:285 | 开仓价用30分VWAP而非实时价 | `price = win_fast.trades[-1][2]` | -| P0-4 | P2 | signal_engine+paper_monitor | 双进程并发写paper_trades | `SELECT FOR UPDATE SKIP LOCKED` | -| P1-2 | P2 | signal_engine.py:143-162 | 浮点精度漂移(buy_vol/sell_vol) | 每10000次trim从deque重算sums | -| P1-3 | **P1** | market_data_collector.py:51 | 单连接无重连 | 改用`db.get_sync_conn()`连接池 | -| P1-4 | P3 | db.py:36-43 | 连接池初始化线程不安全 | `threading.Lock`双重检查 | -| P2-1 | P2 | market_data_collector.py:112 | XRP/SOL coinbase_premium KeyError | `if symbol not in pair_map: return` | -| P2-3 | P2 | agg_trades_collector.py:77 | flush_buffer每秒调ensure_partitions | 移到定时任务(每小时一次) | -| P2-4 | P3 | liquidation_collector.py:127 | elif条件冗余 | 改为`else` | -| P2-5 | P2 | signal_engine.py:209 | atr_percentile @property有写副作用 | 拆成`update_atr_history()`方法 | -| P2-6 | P2 | main.py:554 | 1R=$200硬编码 | 从paper_config.json动态读取 | -| P3-1 | P2 | auth.py:15 | JWT密钥硬编码默认值 | 启动时强制校验`JWT_SECRET`环境变量 | -| P3-2 | P3 | main.py:17 | CORS allow_origins=["*"] | 限制为`https://arb.zhouyangclaw.com` | -| P3-3 | P3 | auth.py:316 | refresh token刷新非原子 | `UPDATE...WHERE revoked=0 RETURNING` | -| P3-4 | P3 | auth.py:292 | 登录无频率限制 | slowapi或Redis计数器 | -| NEW-1 | **P1** | signal_engine.py:664 | 冷启动warmup只有4小时 | 分批加载24小时,加载完再出信号 | - -### 前端(12项) - -| ID | 优先级 | 文件 | 问题 | 修复方案 | -|----|--------|------|------|---------| -| FE-P1-1 | **P1** | lib/auth.tsx:113 | 并发401多次refresh竞态 | 单例Promise + `_refreshPromise` | -| FE-P1-2 | **P1** | lib/auth.tsx:127 | 刷新失败AuthContext未同步 | `window.dispatchEvent("auth:session-expired")` | -| FE-P1-3 | **P1** | 所有页面 | catch{}静默吞掉API错误 | 每个组件加`error` state + 红色提示 | -| FE-P1-4 | P2 | paper/page.tsx:119 | LatestSignals串行4请求 | `Promise.allSettled`并行 | -| FE-P2-1 | P3 | app/page.tsx:52 | MiniKChart每30秒销毁重建 | 只更新data不重建chart | -| FE-P2-3 | P2 | paper/page.tsx:20 | ControlPanel非admin可见 | 校验`isAdmin`,非admin隐藏 | -| FE-P2-4 | **P1** | paper/page.tsx:181 | WebSocket无断线重连 | 指数退避重连 + 断线提示 | -| FE-P2-5 | P2 | paper/page.tsx:217 | 1R=$200前端硬编码 | 从`/api/paper/config`读取 | -| FE-P2-6 | P2 | signals/page.tsx:101 | 5秒轮询5分钟数据 | 改为300秒间隔 | -| FE-P2-8 | P3 | paper/signals | 大量`any`类型 | 定义TypeScript interface | -| FE-P3-1 | P3 | lib/auth.tsx:33 | Token存localStorage | 评估httpOnly cookie方案 | -| FE-P3-3 | P3 | app/page.tsx:144 | Promise.all任一失败全丢 | 改`Promise.allSettled` | - ---- - -## 四、新功能:8信号源评分 - -### 当前6信号源 → V5.2增加2个 - -| # | 信号源 | 层级 | 当前 | V5.2 | -|---|--------|------|------|------| -| 1 | CVD三轨(fast/mid) | 方向层 | ✅ 评分中 | 保持 | -| 2 | P99大单流 | 方向层 | ✅ 评分中 | 保持 | -| 3 | CVD加速度 | 方向层 | ✅ 评分中 | 保持 | -| 4 | 多空比+大户持仓比 | 拥挤层 | ✅ 评分中 | 保持 | -| 5 | OI变化率 | 环境层 | ✅ 评分中 | 保持 | -| 6 | Coinbase Premium | 辅助层 | ✅ 评分中 | 保持 | -| **7** | **资金费率(FR)** | **拥挤层** | ⬜ 仅采集 | **✅ 加入评分** | -| **8** | **清算数据** | **确认层** | ⬜ 仅采集 | **✅ 加入评分** | - -### FR评分逻辑(草案) - -```python -# 资金费率 → 拥挤层加分 -# FR > 0.03% = 多头过度拥挤 → SHORT加分 -# FR < -0.03% = 空头过度拥挤 → LONG加分 -# |FR| > 0.1% = 极端值 → 反向强信号 - -funding_rate = self.market_indicators.get("funding_rate") -if funding_rate is not None: - if direction == "LONG" and funding_rate < -0.0003: - fr_score = 5 # 空头拥挤,做多有利 - elif direction == "SHORT" and funding_rate > 0.0003: - fr_score = 5 # 多头拥挤,做空有利 - elif direction == "LONG" and funding_rate > 0.001: - fr_score = -5 # 多头极度拥挤,做多危险(减分) - elif direction == "SHORT" and funding_rate < -0.001: - fr_score = -5 # 空头极度拥挤,做空危险 - else: - fr_score = 0 -``` - -### 清算数据评分逻辑(草案) - -```python -# 大额清算 → 确认层加分 -# 大额空单清算 = 空头被清洗 → SHORT可能结束,LONG加分 -# 大额多单清算 = 多头被清洗 → LONG可能结束,SHORT加分 -# 5分钟内清算量 > 阈值 = 趋势加速信号 - -liq_long = self.market_indicators.get("long_liq_usd", 0) -liq_short = self.market_indicators.get("short_liq_usd", 0) -if direction == "LONG" and liq_short > 500000: - liq_score = 5 # 大量空单被清算,趋势确认 -elif direction == "SHORT" and liq_long > 500000: - liq_score = 5 -else: - liq_score = 0 -``` - -### V5.2权重分配(草案) - -| 层级 | V5.1权重 | V5.2权重 | 变化 | -|------|---------|---------|------| -| 方向层 | 45+5 | 40+5 | -5 | -| 拥挤层 | 20 | 25(+FR 5分) | +5 | -| 环境层 | 15 | 15 | 不变 | -| 确认层 | 15 | 20(+清算 5分) | +5 | -| 辅助层 | 5 | 5 | 不变 | -| **总计** | **100+5** | **105+5** | +5 | - -> 注:总分超过100不影响,阈值按绝对分数判断(75/80/85) - ---- - -## 五、策略调整 - -### 开仓阈值调整 - -| | V5.1 | V5.2 | 原因 | -|---|------|------|------| -| 最低开仓分 | 60分 | **75分** | 70-74档位50%胜率,扣手续费亏钱 | -| light档 | 60-74 | **取消** | 数据证明低分信号无价值 | -| standard档 | 75-84 | 75-84 | 保持 | -| heavy档 | 85+ | 85+ | 保持 | - -### TP/SL倍数(待AB测试确认) - -| 参数 | V5.1(方案A) | V5.2候选(方案B) | -|------|-------------|-----------------| -| SL | 2.0 × risk_atr | 3.0 × risk_atr | -| TP1 | 1.5 × risk_atr | 2.0 × risk_atr | -| TP2 | 3.0 × risk_atr | 4.0 × risk_atr | -| BTC手续费占比 | 0.23R | 0.15R | -| TP2净利 | +0.89R | +0.97R | -| SL净亏 | -1.23R | -1.15R | -| 盈亏比 | 0.72 | 0.84 | -| 保本胜率 | 58% | 54% | - ---- - -## 六、策略配置化框架 - -### 设计目标 -一个signal-engine进程,支持多套策略配置并行。 - -### 配置文件结构 -```json -// strategies/v51.json -{ - "name": "v51_baseline", - "threshold": 75, - "weights": { - "direction": 45, - "crowding": 20, - "environment": 15, - "confirmation": 15, - "auxiliary": 5 - }, - "tp_sl": { - "sl_multiplier": 2.0, - "tp1_multiplier": 1.5, - "tp2_multiplier": 3.0 - }, - "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium"] -} - -// strategies/v52.json -{ - "name": "v52_8signals", - "threshold": 75, - "weights": { - "direction": 40, - "crowding": 25, - "environment": 15, - "confirmation": 20, - "auxiliary": 5 - }, - "tp_sl": { - "sl_multiplier": 3.0, - "tp1_multiplier": 2.0, - "tp2_multiplier": 4.0 - }, - "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium", "funding_rate", "liquidation"] -} -``` - -### paper_trades表新增字段 -```sql -ALTER TABLE paper_trades ADD COLUMN strategy VARCHAR(32) DEFAULT 'v51_baseline'; -``` - ---- - -## 七、AB测试方案 - -### 架构 -``` -signal_engine.py - ├── evaluate_signal(state, strategy="v51") → score_A, signal_A - ├── evaluate_signal(state, strategy="v52") → score_B, signal_B - │ - ├── paper_open_trade(strategy="v51", ...) → paper_trades.strategy='v51' - └── paper_open_trade(strategy="v52", ...) → paper_trades.strategy='v52' -``` - -### 独立资金池 -- V5.1:$10,000虚拟资金,独立统计 -- V5.2:$10,000虚拟资金,独立统计 - -### 对比指标 -| 指标 | V5.1 | V5.2 | -|------|------|------| -| 总笔数 | ? | ? | -| 胜率 | ? | ? | -| 每笔平均R | ? | ? | -| 最大回撤 | ? | ? | -| 盈亏比 | ? | ? | -| PF | ? | ? | - -### 测试周期 -- **两周**(目标每策略100+笔) -- 结束后选表现更好的上实盘 - ---- - -## 八、24小时Warmup - -### 当前问题 -signal-engine启动时只加载4小时数据,P99大单需要24小时窗口。 - -### 方案(范总确认) -```python -def main(): - states = {sym: SymbolState(sym) for sym in SYMBOLS} - - # 分批加载24小时(每批100万条,避免OOM) - for sym, state in states.items(): - load_historical_chunked(state, 24 * 3600 * 1000) - logger.info(f"[{sym}] warmup complete") - - logger.info("=== 全部币种warmup完成,开始出信号 ===") - # 这之后才开始评估循环 -``` - -### 预估 -- 4币种24小时约2000-3000万条 -- 加载时间1-3分钟 -- 启动后信号质量从第一笔就是100% - ---- - -## 九、V5.1-hotfix已修复清单(参考) - -| Commit | 内容 | -|--------|------| -| `45bad25` | P0-1: 反向信号绕过冷却 | -| `45bad25` | P0-2: pnl_r统一(exit-entry)/risk_distance | -| `45bad25` | P1-1: 分区月份+UTC修复 | -| `2f9dce4` | TP/SL改限价单模式(趋近实盘) | -| `4b841bc` | 前端浮盈半仓计算 | -| `d351949` | 历史pnl_r修正脚本 | -| `8b73500` | Auth从SQLite迁移PG | -| `9528d69` | recent_large_trades去重 | - ---- - -## 十、开发排期(草案) - -| 阶段 | 时间 | 内容 | 产出 | -|------|------|------|------| -| Phase 1 | Day 1-2 | Bug修复(P1全部 + P2重要的) | 代码+测试 | -| Phase 2 | Day 2-3 | FR+清算评分逻辑 | signal_engine.py | -| Phase 3 | Day 3-4 | 策略配置化框架 | strategies/*.json + 代码 | -| Phase 4 | Day 4-5 | AB测试机制 | paper_trades.strategy字段 | -| Phase 5 | Day 5 | 24h warmup | signal_engine.py | -| Phase 6 | Day 6-7 | 前端Bug修复 + 策略对比页面 | 前端代码 | -| **部署** | Day 7 | 全部写好测好,一次性部署 | 减少重启 | - ---- - -## 十一、数据需求 - -### 当前数据量 -- aggTrades:7039万条(BTC 23天 + ETH 3天 + XRP/SOL 3天) -- signal_indicators:2.7万+条 -- paper_trades:181笔 -- market_indicators:5400+条 -- liquidations:3600+条 - -### V5.2需要的数据量 -- **AB测试**:每策略至少100笔 → 两周 -- **权重优化(Phase 1统计分析)**:300+笔 -- **回归分析**:500+笔 -- **ML优化**:1000+笔 - ---- - -## 十二、风险与注意事项 - -1. **手续费是盈亏关键** — BTC手续费0.23R/笔,必须控制交易频率 -2. **样本量不足** — 当前181笔,按分数段分析可能不稳定 -3. **过拟合风险** — 两周一次微调,每次±10-20%,不做大改 -4. **AB测试期间** — 两套策略共享最大持仓4个,需要分配 -5. **24h warmup** — 启动时间变长,需要告知运维(小周) -6. **策略配置化** — 改动signal_engine核心代码,必须充分测试 - ---- - -*本文档为V5.2开发的完整参考,开发过程中持续更新。* -*商业机密:策略细节不对外暴露。* diff --git a/docs/arbitrage-engine/v52-evolution-roadmap.md b/docs/arbitrage-engine/v52-evolution-roadmap.md deleted file mode 100644 index 5c43977..0000000 --- a/docs/arbitrage-engine/v52-evolution-roadmap.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -title: V5.2 策略进化路线图 -description: 信号引擎V5.2 — 数据驱动的策略迭代框架,将信号源视为特征、权重视为模型参数,通过模拟盘持续优化 ---- - -# V5.2 策略进化路线图 - -> **优先级:P0(最高)** | 负责人:露露 | 创建:2026-02-28 - ---- - -## 一、核心思路 ⭐⭐⭐ - -### 信号源 = 特征(Feature) - -每一个数据源都是模型的一个**输入特征**。特征越多、越有效,模型的预测能力越强。 - -当前V5.1的5层评分体系,本质上就是一个**手动设计的线性模型**: - -``` -总分 = W1×方向层 + W2×拥挤层 + W3×环境层 + W4×确认层 + W5×辅助层 -``` - -### 权重 = 模型参数(Parameter) - -当前权重是人工拍的(45/20/15/15/5),不一定是最优的。 - -**目标**:用真实交易数据(模拟盘+实盘),自动学习每个特征的最优权重。 - -### 迭代循环(正向飞轮) - -``` -┌─────────────────────────────────────────────┐ -│ │ -│ ① 加入新信号源(特征) │ -│ ↓ │ -│ ② 模拟盘跑数据(每笔记录5层分数+盈亏) │ -│ ↓ │ -│ ③ 分析数据(哪些特征有效、最优权重) │ -│ ↓ │ -│ ④ 调整权重 / 加减特征 │ -│ ↓ │ -│ ⑤ 实盘验证 │ -│ ↓ │ -│ ⑥ 数据反馈 → 回到① │ -│ │ -│ 数据越多 → 模型越准 → 赚越多 → 数据越多 │ -│ │ -└─────────────────────────────────────────────┘ -``` - -这就是量化交易的**核心方法论**,和大模型训练思路一样: -- 大模型:数据 → 特征提取 → 权重训练 → 验证 → 迭代 -- 信号引擎:行情数据 → 信号源/指标 → 评分权重 → 模拟盘验证 → 调参迭代 - -**护城河**:积累的数据和调优后的参数,是别人无法复制的。 - ---- - -## 二、当前架构(V5.1) - -### 已有信号源(特征) - -| 层 | 权重 | 信号源 | 数据来源 | 状态 | -|----|------|--------|---------|------| -| 方向层 | 45分 | CVD_fast(30m) | agg_trades计算 | ✅ 真实数据 | -| 方向层 | - | CVD_mid(4h) | agg_trades计算 | ✅ 真实数据 | -| 方向层 | - | P99大单流 | agg_trades计算 | ✅ 真实数据 | -| 方向层 | +5 | CVD加速度 | agg_trades计算 | ✅ 真实数据 | -| 拥挤层 | 20分 | 多空比 | 币安API globalLongShortAccountRatio | ✅ 真实数据 | -| 拥挤层 | - | 大户持仓比 | 币安API topLongShortAccountRatio | ✅ 真实数据 | -| 环境层 | 15分 | OI变化率 | 币安API openInterestHist | ✅ 真实数据 | -| 确认层 | 15分 | 多时间框架CVD一致性 | agg_trades计算 | ✅ 真实数据 | -| 辅助层 | 5分 | Coinbase Premium | 币安+CB价差 | ✅ 真实数据 | - -### 每笔交易记录的数据 - -```json -{ - "score": 85, - "score_factors": { - "direction": {"score": 45, "cvd_fast": 15, "cvd_mid": 15, "p99_flow": 15, "accel_bonus": 5}, - "crowding": {"score": 15, "long_short_ratio": 10, "top_trader_position": 5}, - "environment": {"score": 10, "open_interest_hist": 0.02}, - "confirmation": {"score": 15}, - "auxiliary": {"score": 5, "coinbase_premium": 0.0012} - }, - "pnl_r": 2.25, - "status": "tp" -} -``` - ---- - -## 三、V5.2 待加入信号源(按优先级) - -### 第一批(数据容易获取) - -| 信号源 | 类型 | 获取方式 | 预期价值 | -|--------|------|---------|---------| -| 资金费率(Funding Rate) | 拥挤指标 | 币安API /fapi/v1/fundingRate | 高 — 极端FR是反转信号 | -| 清算数据(Liquidation) | 情绪指标 | 币安WS forceOrder | 高 — 大额清算=趋势加速 | -| 期权PCR(Put/Call Ratio) | 情绪指标 | Deribit API | 中 — 机构对冲意愿 | -| 波动率指数(DVOL) | 环境指标 | Deribit API | 中 — 波动率扩张/收缩 | - -### 第二批(需要爬取/计算) - -| 信号源 | 类型 | 获取方式 | 预期价值 | -|--------|------|---------|---------| -| Twitter/X情绪 | 情绪指标 | Agent-Reach xsearch | 中 — 散户情绪反指标 | -| 恐贪指数 | 情绪指标 | alternative.me API | 低 — 日级更新太慢 | -| 链上大额转账 | 鲸鱼行为 | Etherscan/Blockchain API | 中 — 鲸鱼动向 | -| 交易所净流入 | 资金流 | CryptoQuant/Glassnode | 高 — 抛压预警 | - -### 第三批(高级) - -| 信号源 | 类型 | 获取方式 | 预期价值 | -|--------|------|---------|---------| -| 订单簿深度不对称 | 微观结构 | 币安WS depth | 中 — 支撑/阻力判断 | -| 跨交易所价差 | 套利信号 | 多交易所API | 低 — 实现复杂 | -| 新闻事件检测 | 事件驱动 | LLM分析 | 中 — 黑天鹅预警 | - ---- - -## 四、权重优化方法(数据足够后实施) - -### Phase 1:统计分析(200+笔交易后) - -```python -# 按各层分数分桶,看胜率 -# 例如:方向层>=40分时胜率65%,<30分时胜率45% -# → 说明方向层有效,保持高权重 - -# 按拥挤层分桶 -# 例如:拥挤层>=15分时胜率60%,<10分时胜率58% -# → 说明拥挤层区分度低,可以降权重 -``` - -### Phase 2:回归分析(500+笔交易后) - -```python -# 逻辑回归:各层分数 → 是否盈利 -# 输出:每个特征的系数 = 最优权重 -from sklearn.linear_model import LogisticRegression -model = LogisticRegression() -model.fit(X_factors, y_win) # X=5层分数, y=盈利/亏损 -optimal_weights = model.coef_ -``` - -### Phase 3:机器学习(1000+笔交易后) - -```python -# XGBoost/随机森林:自动发现非线性关系 -# 例如:方向层高+拥挤层低 = 最佳组合(简单线性模型发现不了) -from xgboost import XGBClassifier -model = XGBClassifier() -model.fit(X_factors, y_win) -# 特征重要性排序 → 指导信号源增减 -``` - ---- - -## 五、2026-02-28 开发记录 - -### V5.1完善 -- **5层评分真实数据修复**:signal_engine之前没正确解析market_indicators的JSONB,拥挤/环境/辅助层全是默认中间分。修了fetch_market_indicators正确解析JSONB → commit `317031a` -- **前端UI全面压缩**:标题改"⚡ 信号引擎 V5.1",所有面板字体/间距/padding缩小 → commits `9382d35`, `271658c` - -### 模拟盘上线(Paper Trading) -- **paper_trades表** + signal_engine集成 + 5个API → commit `e054db1` -- **开关机制**:默认关闭,前端按钮控制,API热更新 → commit `282aed1` -- **手续费**:Taker 0.05%×2=0.1%来回 → commit `47004ec` -- **反向信号翻转**:持多仓来空信号→先平后开 → commit `6681070` -- **WebSocket实时TP/SL**:独立paper_monitor.py进程,毫秒级平仓 → commit `7b901a2` -- **前端aggTrade实时价格**:逐笔成交推送 → commit `1d23042` -- **当前资金显示** → commit `d0e626a` -- **冷启动保护**:重启后跳过前3轮防重复开仓 → commit `95b45d0` -- **5层评分明细记录**:score_factors JSONB字段 → commit `022ead6` - -### Bug修复 -- arb-api缩进错误 → commit `cd17c76` -- Request未导入 → commit `b232270` -- useAuth登录检测 → commit `59910fe` -- 现价不准改币安API → commit `d177d28` -- 最新信号symbol参数修复 → commit `404cc68` -- 资金字体超框 → commit `95fec35` -- gitignore __pycache__ → commit `961cbc6` - ---- - -## 六、下一步行动 - -| 阶段 | 时间 | 内容 | -|------|------|------| -| 现在 | 2026-02-28 ~ 03-14 | 模拟盘跑两周,积累200-300笔交易数据 | -| Phase 1 | 03-14 | 统计分析各层贡献,初步调权重 | -| Phase 2 | 03-21 | 加入资金费率+清算数据(第一批新信号源) | -| Phase 3 | 04-01 | 回归分析自动优化权重 | -| Phase 4 | 04-15 | 小仓实盘验证 | -| 持续 | 长期 | 不断加入新信号源,数据驱动迭代 | - ---- - -## 七、行业竞品与信号源调研(2026-02-28) - -### 与我们思路相似的项目 - -#### 1. Hyper-Alpha-Arena(GitHub开源)— 相似度85% -- **地址**:https://github.com/HammerGPT/Hyper-Alpha-Arena -- **核心**:监控Order Flow + OI变化 + Funding Rate极端值,触发自动交易 -- 支持币安合约+Hyperliquid,用LLM(GPT-5/Claude/DeepSeek)做策略决策 -- **区别**:他们用LLM自然语言描述策略做决策,我们用评分模型 -- **我们的优势**:5层评分+权重训练更可量化、可回测、可优化 - -#### 2. FinRL-AlphaSeek(哥伦比亚大学,ACM竞赛)— 相似度80% -- **地址**:https://github.com/Open-Finance-Lab/FinRL_Contest_2025 -- **论文**:https://arxiv.org/html/2504.02281v4 -- **核心**:因子挖掘(Factor Mining) + 集成学习(Ensemble Learning) -- 两阶段:① 特征工程+因子选择 ② 多模型集成 -- **映射关系**:他们的"因子"=我们的"信号源",他们的"集成权重"=我们的"评分权重" -- **区别**:他们用强化学习(RL)训练Agent,我们用统计回归优化权重 -- **可借鉴**:遗传算法优化权重(比逻辑回归更强) - -#### 3. ACM论文:ML驱动多因子量化模型(ETH市场)— 相似度75% -- **地址**:https://dl.acm.org/doi/10.1145/3766918.3766922 -- **核心**:把交易因子分三类 — 传统技术因子 + 链上因子 + ML生成因子 -- 用IC值(信息系数)衡量每个因子的预测力 -- 用**遗传算法**自动优化因子权重 -- 信号用Z-score阈值触发(>1买入,<-1卖出) -- **启发**:我们也可以用IC值来量化每个信号源的贡献 - -#### 4. CoinGlass CDRI(衍生品风险指数)— 相似度70% -- **地址**:https://www.coinglass.com/pro/i/CDRI -- **核心**:综合OI/FR/清算/CVD等多指标打分,评分>80或<20触发信号 -- **区别**:他们只做风险预警展示,不做自动交易 - -### 行业信号源使用频率排名 - -| 排名 | 信号源 | 行业使用频率 | 我们状态 | 数据源 | 获取难度 | -|------|--------|------------|---------|--------|---------| -| 1 | CVD/Order Flow | ⭐⭐⭐⭐⭐ | ✅ 已有 | agg_trades | - | -| 2 | Open Interest | ⭐⭐⭐⭐⭐ | ✅ 已有 | 币安API | - | -| 3 | Funding Rate | ⭐⭐⭐⭐⭐ | ⬜ 待加 | 币安API(免费) | ⭐ | -| 4 | 清算数据 | ⭐⭐⭐⭐ | ⬜ 待加 | 币安WS forceOrder | ⭐⭐ | -| 5 | 多空比 | ⭐⭐⭐⭐ | ✅ 已有 | 币安API | - | -| 6 | 链上净流入/流出 | ⭐⭐⭐⭐ | ⬜ 待加 | CryptoQuant(付费) | ⭐⭐⭐ | -| 7 | Coinbase Premium | ⭐⭐⭐ | ✅ 已有 | 价差计算 | - | -| 8 | 社交情绪 | ⭐⭐⭐ | ⬜ 待加 | Santiment/LLM | ⭐⭐⭐ | -| 9 | 期权PCR/DVOL | ⭐⭐⭐ | ⬜ 待加 | Deribit API | ⭐⭐ | -| 10 | 鲸鱼钱包追踪 | ⭐⭐⭐ | ⬜ 待加 | Nansen(付费) | ⭐⭐⭐ | -| 11 | 清算热力图 | ⭐⭐ | ⬜ 待加 | CoinGlass API(付费) | ⭐⭐ | -| 12 | 订单簿深度 | ⭐⭐ | ⬜ 待加 | 币安WS depth | ⭐⭐ | - -### 关键结论 - -1. **没有人做得和我们完全一样** — 大多数用传统技术指标或纯ML黑箱,用CVD+多空比+OI做多层评分的几乎没有 -2. **行业趋势明确** — 多因子 + ML权重优化,和范总定的方向完全一致 -3. **Funding Rate是最高优先级** — 行业使用率最高、免费获取、我们还没加 -4. **权重优化可升级** — 从逻辑回归→IC值+遗传算法,参考ACM论文方法 - -### 数据供应商参考 - -| 平台 | 核心能力 | 价格 | 适合 | -|------|---------|------|------| -| CoinGlass | 衍生品数据(OI/FR/清算/热力图) | 免费基础+付费API | 清算数据 | -| CryptoQuant | 链上数据(净流入/矿工/交易所储备) | $29/月起 | 链上因子 | -| Santiment | 社交情绪+链上+开发活跃度 | 免费基础+付费 | 情绪因子 | -| Glassnode | 链上高级指标(SOPR/NUPL/STH-LTH) | $39/月起 | 深度链上 | -| Nansen | 鲸鱼钱包追踪+Smart Money | $100/月起 | 鲸鱼行为 | diff --git a/docs/arbitrage-engine/v52-performance-analysis.md b/docs/arbitrage-engine/v52-performance-analysis.md deleted file mode 100644 index d79d1e9..0000000 --- a/docs/arbitrage-engine/v52-performance-analysis.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -title: V5.2 模拟盘执行分析报告 -date: 2026-03-03 ---- - -# V5.2 模拟盘执行分析报告 - -> 数据口径:真实成交价(agg_trades)+ 手续费扣除,calc_version=2 -> 分析日期:2026-03-03 -> 策略名称:v52_8signals(8信号源) -> 参与分析:露露(Sonnet 4.6)、小范(GPT-5.3-Codex) - ---- - -## 一、总体概况 - -| 指标 | 数值 | -|------|------| -| 总交易笔数 | 156笔(含4笔活跃/tp1_hit) | -| 已闭合笔数 | 152笔 | -| 净R(含手续费)| **-25.07R** | -| 毛R(不含手续费)| **-3.27R** | -| 总手续费 | **21.80R** | -| 平均单笔手续费 | 0.143R | -| 胜率 | 51.3% | -| 平均每笔净R | -0.165R | - -> 本金10,000 USD,1R=200 USD → 净亏损约5,014 USD -> 与V5.1对比:笔数少(152 vs 500),手续费率低(0.143R vs 0.218R),**但毛R为负(-3.27R)**,信号层本身无优势 - ---- - -## 二、V5.2 vs V5.1 对比 - -| 指标 | V5.1 | V5.2 | -|------|------|------| -| 已闭合笔数 | 500 | 152 | -| 净R | -99.73R | -25.07R | -| 毛R | **+10.73R** | **-3.27R** | -| 总手续费 | 110.46R | 21.80R | -| 平均单笔费 | 0.218R | 0.143R | -| 胜率 | 55.4% | 51.3% | -| 平均净R | -0.193R | -0.165R | - -**关键差异:V5.1毛R为正(+11R),V5.2毛R为负(-3R)。V5.2交易频率低但信号质量更差。** - ---- - -## 三、出场类型分布 - -| 状态 | 笔数 | 平均R(净) | 合计R | 平均手续费 | -|------|------|-----------|-------|----------| -| sl(止损)| 43 | -1.161R | **-49.90R** | 0.161R | -| timeout(超时)| 32 | +0.055R | +1.74R | 0.132R | -| sl_be(保本止损)| 30 | +0.179R | +5.38R | 0.154R | -| tp(止盈)| 29 | +0.964R | +27.97R | 0.119R | -| signal_flip(翻转)| 18 | -0.570R | -10.25R | 0.145R | - -**关键发现**: -- SL 43笔亏49.90R,TP只29笔赚27.97R,差额-21.93R -- signal_flip出场损耗大(-0.570R均值),说明信号方向频繁切换 -- **SL均值-1.161R,低于V5.1的-1.232R,单笔止损更小(SL更宽)** - -### SL均值拆解 - -| 组成 | 数值 | -|------|------| -| SL基础R | -1.000R | -| 手续费 | -0.161R | -| 净SL | -1.161R | - ---- - -## 四、方向分析 - -| 方向 | 笔数 | 胜率 | 合计R | -|------|------|------|-------| -| LONG | 103 | 49.5% | -13.18R | -| SHORT | 53 | 54.9% | -11.88R | - -**结论**:LONG胜率仅49.5%(低于随机),多空均亏。 - ---- - -## 五、币种分析 - -| 币种 | 笔数 | 胜率 | 合计R | -|------|------|------|-------| -| BTCUSDT | 40 | **35.9%** | **-13.24R** | -| XRPUSDT | 37 | 52.8% | -9.17R | -| ETHUSDT | 37 | 58.3% | -3.40R | -| SOLUSDT | 42 | 58.5% | **+0.74R** | - -**关键发现**: -- BTC胜率35.9%,严重低于随机,V5.2比V5.1更差(V5.1是49.3%) -- SOL是唯一正R币种(+0.74R),胜率58.5% -- ETH胜率58.3%但仍净亏(手续费拖累) - ---- - -## 六、信号分数段分析 - -| 分数段 | 笔数 | 胜率 | 合计R | -|--------|------|------|-------| -| 75-79 | 105 | 50.5% | -21.33R | -| 80-84 | 42 | 45.2% | -9.51R | -| 85+ | 9 | **88.9%** | **+5.77R** | - -**重要发现**: -- V5.2的85+高分段胜率88.9%,合计+5.77R,**与V5.1完全相反**(V5.1高分无效) -- 但样本量太少(只有9笔),统计意义有限 -- 75-84分段表现极差,80-84甚至只有45.2%胜率 - ---- - -## 七、时段分析(北京时间) - -### 盈利时段(合计R>0) -| 时段 | R | 胜率 | -|------|---|------| -| 05:00 | +0.94R | 75.0% | -| 08:00 | +2.93R | 85.7% | -| 22:00 | +4.76R | 80.0% | -| 23:00 | +7.62R | 85.7% | - -### 重度亏损时段(胜率<30%) -| 时段 | R | 胜率 | -|------|---|------| -| 00:00 | -4.97R | 14.3% | -| 09:00 | -5.88R | 14.3% | -| 12:00 | -4.38R | 33.3% | -| 13:00 | -5.22R | 40.0% | - -**V5.1和V5.2共同亏损时段**:09:00、13:00(两个策略均在这两个时段表现极差) - ---- - -## 八、持仓时间分析 - -| 出场类型 | 平均持仓 | -|----------|---------| -| timeout | 60.0分钟 | -| sl_be | 26.8分钟 | -| tp | 27.0分钟 | -| sl | **28.1分钟** | -| flip | 27.0分钟 | - -**与V5.1对比**:V5.2的SL持仓时间更长(28min vs 18min),说明SL空间更宽(sl=2.1×ATR vs 1.4×ATR),但仍被打出。 - ---- - -## 九、风险统计 - -| 指标 | 数值 | -|------|------| -| 单笔最大亏损 | -1.29R | -| 单笔最大盈利 | +1.02R | -| 标准差 | 0.796R | -| 中位数 | +0.079R | - ---- - -## 十、核心结论 - -### V5.2 vs V5.1 关键差异 - -| 维度 | V5.1 | V5.2 | 解读 | -|------|------|------|------| -| 毛R | +10.73R | -3.27R | V5.2信号质量更差 | -| 胜率 | 55.4% | 51.3% | V5.2信号未改善胜率 | -| 单笔费 | 0.218R | 0.143R | V5.2手续费率更低(SL更宽) | -| BTC胜率 | 49.3% | 35.9% | V5.2在BTC上更差 | -| 85+分段 | 无效 | 88.9%(9笔)| 样本太少,不可靠 | - -### V5.2失败原因 -1. **信号质量比V5.1更差**:毛R从+11R变成-3R,8个信号源的叠加没有提升预测能力,反而带来更多噪声 -2. **BTC更差**:35.9%胜率说明额外信号源对BTC的预测无帮助 -3. **signal_flip损耗大**:-0.570R均值,方向频繁切换,每次翻转都有损耗 -4. **统计样本不足**:152笔相对于策略评估太少,结论不确定性高 - -### 与Gemini分析对照 -- CVD双重计分问题在V5.2同样存在 -- V5.2增加的8个信号源(相比V5.1的6个)未能提升正交性 -- V5.3应从根本上解决因子多重共线性问题 - ---- - -## 十一、对V5.3设计的启示 - -1. **V5.1有微弱信号(毛R正),V5.2没有**:V5.3应保留V5.1的核心因子,不是简单增加信号 -2. **BTC在两个版本都表现差**:V5.3可考虑完全不交易BTC,专注ETH/XRP/SOL -3. **08:00、22:00、23:00是两个策略共同盈利时段**:这些时段可能有结构性因素(美盘/亚盘交替) -4. **删除确认层(CVD重复)是最优先改动**:在V5.1和V5.2中均可验证这是评分失真的根源 diff --git a/docs/arbitrage-engine/v52-signal-system.md b/docs/arbitrage-engine/v52-signal-system.md deleted file mode 100644 index 539be5a..0000000 --- a/docs/arbitrage-engine/v52-signal-system.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -title: V5.2 信号系统文档 ---- - -# V5.2 八信号源系统(v52_8signals) - -## 概述 - -V5.2 在 V5.1 基础上新增 **资金费率(Funding Rate)** 和 **清算数据(Liquidation)** 两个信号源,形成 7 层 100 分评分体系。目标是提高信号精准度,减少无效开仓。 - -## 与 V5.1 的核心差异 - -| 对比项 | V5.1 | V5.2 | -|--------|------|------| -| 信号源 | 6个 | 8个(+FR, +清算) | -| 评分层 | 5层 | 7层(+FR层, +清算层) | -| 方向权重 | 45分 | 40分 | -| TP/SL | SL=2.0×ATR | SL=3.0×ATR(更宽) | -| 盈亏比目标 | 0.72 | 0.84+ | - -## 评分体系(7层100分) - -| 层级 | 权重 | 信号源 | 说明 | -|------|------|--------|------| -| 方向层 | 40分 | CVD_fast + CVD_mid + P99大单 | 同V5.1但权重降低 | -| 拥挤层 | 18分 | 多空比 + 大户持仓 | 结构性仓位判断 | -| **FR层** | **5分** | **资金费率** | **持仓成本顺风度** | -| 环境层 | 12分 | OI变化率 | 同V5.1但权重降低 | -| 确认层 | 15分 | CVD双周期共振 | 同V5.1 | -| **清算层** | **5分** | **清算比率** | **市场清洗力度** | -| 辅助层 | 5分 | Coinbase Premium | 同V5.1 | - -### FR层详解(5分)— 线性评分 - -**数据源**:Binance fundingRate,每5分钟采集,每8小时结算。 - -**评分公式**: -``` -raw_score = (|当前FR| / 历史最大FR) × 5,上限5分 - -有利方向 → fr_score = raw_score(0~5) -不利方向 → fr_score = 0 -``` - -**方向判断**: -- 做多 + FR为负(空头付费给多头)= 有利 → 给分 -- 做空 + FR为正(多头付费给空头)= 有利 → 给分 -- 其他 = 不利 → 0分(不扣分) - -**历史最大FR**:从数据库实时计算,每小时缓存一次。数据越多越精准。 - -| 币种 | 历史最大FR | 说明 | -|------|-----------|------| -| BTC | ~0.0046% | 波动最小 | -| ETH | ~0.0095% | 中等 | -| XRP | ~0.022% | 波动大 | -| SOL | ~0.022% | 波动大 | - -**设计原则**: -- 信号方向已确定,FR只评估"有多顺风" -- 不利方向不扣分(方向决策不由FR层负责) -- 线性映射,不分层不设阈值,灵活精准 -- 分数带小数(如 +2.27分、+0.33分) - -### 清算层详解(5分)— 梯度评分 - -**数据源**:liq-collector 实时采集清算事件。 - -**评分逻辑**: -``` -ratio = 对手方清算USD / 己方清算USD - -做多 → ratio = 空头清算 / 多头清算 -做空 → ratio = 多头清算 / 空头清算 - -ratio ≥ 2.0 → 5分 -ratio ≥ 1.5 → 3分 -ratio ≥ 1.2 → 1分 -ratio < 1.2 → 0分 -``` - -**逻辑**:对手方爆仓越多,说明市场正在朝我们的方向清洗,有利信号。 - -## TP/SL 设置 - -| 参数 | V5.1 | V5.2 | 变化原因 | -|------|------|------|----------| -| SL | 2.0 × ATR | 3.0 × ATR | 更宽止损,减少噪声出局 | -| TP1 | 1.5 × ATR | 2.0 × ATR | 更远止盈,提高盈亏比 | -| TP2 | 3.0 × ATR | 4.5 × ATR | 大幅提升盈亏比目标 | - -理论盈亏比从 0.72 提升到 0.84。 - -## 开仓规则 - -与 V5.1 相同: -- 阈值:75分(标准),85分(加仓) -- 冷却:10分钟 -- 最大持仓:4笔 -- 反向翻转:反向≥75分 → 平旧开新 - -## 策略配置 - -```json -{ - "name": "v52_8signals", - "version": "5.2", - "threshold": 75, - "weights": { - "direction": 40, - "crowding": 18, - "funding_rate": 5, - "environment": 12, - "confirmation": 15, - "liquidation": 5, - "auxiliary": 5 - }, - "accel_bonus": 0, - "tp_sl": { - "sl_multiplier": 3.0, - "tp1_multiplier": 2.0, - "tp2_multiplier": 4.5 - }, - "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium", "funding_rate", "liquidation"] -} -``` - -## AB测试观测清单(2026-03-02 ~ 03-16) - -### 冻结期规则 -- 不改权重、不改阈值、不改评分逻辑 -- 单写入源(小周生产环境) -- 目标:V5.1 500+笔,V5.2 200+笔 - -### 两周后评审项 -1. **确认层重复计分审计** — 方向层和确认层同源,看区分度 -2. **拥挤层 vs FR相关性** — `corr(FR_score, crowd_score)`,>0.7则降一层 -3. **OI持续性审计** — `oi_persist_n=1` vs `>=2` 胜率差异 -4. **清算触发率审计** — 按币种,避免触发不均衡 -5. **config_hash落库** — 权重调整前补版本标识 - -### 权重优化路径 -- 200+笔 → 统计分析(各层分布+胜率关联) -- 500+笔 → 回归分析(哪些层对盈亏贡献最大) -- 1000+笔 → ML(XGBoost等) - -## 已知问题 - -1. **方向层与确认层同源重复** — 等数据验证 -2. **清算样本少** — 才积累2天,ratio波动大 -3. **币种间FR基准差异大** — BTC max=0.0046% vs SOL=0.022%,线性映射已自动处理 diff --git a/docs/arbitrage-engine/v53-design.md b/docs/arbitrage-engine/v53-design.md deleted file mode 100644 index 2bfdee9..0000000 --- a/docs/arbitrage-engine/v53-design.md +++ /dev/null @@ -1,304 +0,0 @@ ---- -title: V5.3 统一信号系统设计案 -date: 2026-03-03 -updated: 2026-03-03 ---- - -# V5.3 统一信号系统设计案 - -> 目标:让策略从"手工打分规则"升级为"可持续训练和迭代的小模型系统"。统一架构覆盖 BTC/ETH/XRP/SOL,per-symbol 参数化门控,消除双轨维护成本。 - -## 1. 设计原则 - -1. **统一评分、差异化门控**:四层评分逻辑完全一致,通过 `symbol_gates` 参数化各币种的门控阈值。 -2. **先数据、后调参**:先补齐特征与标签落库,再做参数优化。 -3. **反过拟合优先**:任何优化必须先过样本外验证(OOS)。 -4. **信号与执行解耦**:Alpha(信号)与成本(执行)分开归因。 -5. **版本可追溯**:每次信号和交易都可回溯到 `strategy_version + config_hash + engine_instance`。 - -## 2. 现状问题归纳(V5.1/V5.2) - -- V5.1:毛R为正、净R为负,说明有 Alpha 但被手续费和执行摩擦吞噬。 -- V5.2:交易频率下降但毛R转负,说明新增因子未提升预测力,存在噪声与冗余。 -- 评分结构存在共线性:方向层与确认层同源(重复使用 CVD_fast/CVD_mid)。 -- BTC 与 ALT 使用同构逻辑,忽略了市场微结构差异。 - -## 3. V5.3 总体架构 - -``` -Market Data → Feature Snapshot → _evaluate_v53() → Gate Check → Signal Decision → Execution → Label Backfill → Walk-Forward Eval -``` - -### 3.1 统一策略(v53) - -单一策略文件 `backend/strategies/v53.json`,覆盖 BTC/ETH/XRP/SOL。 - -**四层评分(总分100)** - -| 层 | 权重 | 子项 | -|---|---|---| -| Direction | 55 | CVD共振(30) + P99大单对齐(20) + 加速奖励(5) | -| Crowding | 25 | LSR反向(15) + 大户持仓(10) | -| Environment | 15 | OI变化率 | -| Auxiliary | 5 | Coinbase Premium | - -**Per-symbol 四门控制(symbol_gates)** - -| 门 | BTC | ETH | XRP | SOL | -|---|---|---|---|---| -| 波动率下限 | 0.2% | 0.3% | 0.4% | 0.6% | -| 鲸鱼阈值/逻辑 | whale_cvd_ratio >$100k | 大单否决 $50k | 大单否决 $30k | 大单否决 $20k | -| OBI否决 | ±0.30 | ±0.35 | ±0.40 | ±0.45 | -| 期现背离否决 | ±0.3% | ±0.5% | ±0.6% | ±0.8% | - -**开仓档位** -- < 75分:不开仓 -- 75–84分:标准仓(1×R) -- ≥ 85分:加仓档(1.5×R) -- 冷却期:10分钟 - -### 3.2 实时数据流 - -| 数据 | 来源 | 频率 | 覆盖币种 | -|---|---|---|---| -| OBI(订单簿失衡) | `@depth10@100ms` perp WS | 100ms | BTC/ETH/XRP/SOL | -| 期现背离 | `@bookTicker` spot + `@markPrice@1s` perp | 1s | BTC/ETH/XRP/SOL | -| 巨鲸CVD | aggTrades 流内计算(>$100k) | 实时 | BTC | -| 大单方向 | aggTrades 流内计算 | 实时 | ETH/XRP/SOL | - -## 4. 训练数据飞轮(Phase 3) - -``` -signal_feature_events (raw features, 每轮评分写入) - ↓ label_backfill.py (T+60m打标签) -signal_label_events (y_binary_60m, mfe_r_60m, mae_r_60m) - ↓ walk_forward.py -权重优化 → v53.json 更新 -``` - -**Walk-Forward 规则(严防过拟合)** -- 训练窗口:30天,步长:7天 -- 验证集:永远在训练集之后,不交叉 -- 评估指标:OOS 净R、胜率、MDD - -## 5. 版本演进记录 - -| 版本 | 时间 | 变更摘要 | -|---|---|---| -| V5.1 | 2026-02 | 基础CVD评分,有毛Alpha,净R为负 | -| V5.2 | 2026-02 | 新增8信号层,频率下降但净R未改善 | -| V5.3 Phase0 | 2026-03-03 | 建立feature/label落库表,ATR列 | -| V5.3 Phase1 | 2026-03-03 | 四层评分+双轨(alt/btc),删确认层 | -| V5.3 Phase2 | 2026-03-03 | RT-WS接入(OBI+期现背离),覆盖所有symbol | -| V5.3 统一版 | 2026-03-03 | 合并alt/btc为单一v53策略,per-symbol门控 | - -- **Feature Snapshot**:每次评估时落库原始特征和中间分数(含 `atr_value` 快照)。 -- **Track Router**:按 symbol 路由到 ALT/BTC 模型。 -- **Signal Decision**:输出开仓/不开仓/翻转决策和原因。 -- **Execution**:独立处理 maker/taker、TP/SL、BE、flip。 -- **Label Backfill**:按 15/30/60 分钟回填标签。 -- **Walk-Forward Eval**:滚动训练与验证,驱动版本迭代。 - -## 4. 双轨模型定义 - -## 4.1 ALT 轨(ETH/XRP/SOL) - -- 目标:保留 V5.1 有效微观结构 Alpha,去除冗余。 -- 关键变更:取消独立 Confirmation 层(避免与 Direction 共线性重复计分)。 -- 决策机制:线性加权评分(总分 100)+ 门控 + 阈值。 - -### 4.1.1 ALT 权重总表(V5.3 初版) - -| 层级 | 权重 | 子特征 | 子特征权重 | 说明 | -|------|------|--------|------------|------| -| Direction | 55 | `cvd_resonance` | 30 | `cvd_fast` 与 `cvd_mid` 同向共振 | -| Direction | 55 | `p99_flow_alignment` | 20 | P99 大单方向与主方向一致 | -| Direction | 55 | `cvd_accel_bonus` | 5 | CVD 加速度同向奖励 | -| Crowding | 25 | `lsr_contrarian` | 15 | 多空比反向拥挤 | -| Crowding | 25 | `top_trader_position` | 10 | 大户持仓方向确认 | -| Environment | 15 | `oi_delta_regime` | 15 | OI 变化状态 | -| Auxiliary | 5 | `coinbase_premium` | 5 | 美系现货溢价辅助 | - -### 4.1.2 ALT 子特征评分函数(标准化) - -- `cvd_resonance`(0/30): - - LONG:`cvd_fast > 0 && cvd_mid > 0` - - SHORT:`cvd_fast < 0 && cvd_mid < 0` - - 否则 `0` 且 `gate_no_direction=true` -- `p99_flow_alignment`(0/10/20): - - 强同向净流:20 - - 无明显反向压制:10 - - 明显反向:0 -- `cvd_accel_bonus`(0/5): - - 加速度方向与主方向一致:5 -- `lsr_contrarian`(0~15): - - LONG:`lsr<=0.5` 高分,`0.5=2.0` 高分,`1.0= 75 and no veto -flip if reverse_score_alt >= 85 and no veto -``` - -## 4.2 BTC 轨(独立模型) - -- 目标:针对机构主导盘口,提升信号有效性。 -- 决策方式:先用"条件门控 + 否决条件",不与 ALT 共用线性总分。 - -### 4.2.1 BTC 核心特征 - -- `atr_percent_1h`:1小时 ATR 占当前价格百分比(波动率门控) -- `tiered_cvd_whale`:按成交额分层的大单净流(建议主桶 `>100k`) -- `obi_depth_10`:盘口前 10 档失衡 -- `spot_perp_divergence`:现货与永续价量背离 - -### 4.2.2 BTC 门控与否决逻辑 - -- 波动率门控:`atr_percent_1h < min_vol_threshold` -> veto -- 方向门控:巨鲸净流未达阈值 -> veto -- 挂单墙否决:方向与 OBI 显著冲突 -> veto -- 期现背离否决:perp 强多但 spot 弱(或反向)-> veto - -### 4.2.3 BTC 参数(初始值,可配置) - -- `min_vol_threshold = 0.002`(0.2%) -- `obi_veto_threshold = 0.30` -- `whale_flow_threshold`:按币价与流动性分档配置 -- 以上参数均定义为配置项,禁止散落硬编码。 - -### 4.2.4 BTC 决策伪代码 - -```text -if missing_any_feature: - block("missing_feature") -if atr_percent_1h < min_vol_threshold: - block("low_vol_regime") -if abs(tiered_cvd_whale) < whale_flow_threshold: - block("weak_whale_flow") -if direction_conflict_with_obi: - block("obi_imbalance_veto") -if spot_perp_divergence_is_trap: - block("spot_perp_divergence_veto") -otherwise: - allow_open -``` - -## 5. 数据基建(ML Ready) - -## 5.1 表设计 - -### `signal_feature_events` - -- 用途:每次信号评估快照(无论是否开仓)。 -- 关键字段: - - 元数据:`event_id, ts, symbol, track, side` - - 版本:`strategy, strategy_version, config_hash, engine_instance` - - 原始特征:`cvd_fast_raw, cvd_mid_raw, p99_flow_raw, accel_raw, ls_ratio_raw, top_pos_raw, oi_delta_raw, coinbase_premium_raw, fr_raw, liq_raw, obi_raw, tiered_cvd_whale_raw, atr_value` - - 决策:`score_total, score_direction, score_crowding, score_environment, score_aux, gate_passed, block_reason` - -### `signal_label_events` - -- 用途:延迟回填标签,评估信号纯预测能力。 -- 字段:`event_id, y_binary_30m, y_binary_60m, y_return_15m, y_return_30m, y_return_60m, mfe_r_60m, mae_r_60m` - -### `execution_cost_events` - -- 用途:独立归因执行成本。 -- 字段:`trade_id, entry_type, exit_type, fee_bps, slippage_bps, maker_ratio, flip_flag, hold_seconds, friction_cost_r` - -## 5.2 标签定义 - -- `Y_binary_60m`(严格定义):从信号触发时间 `ts` 起 60 分钟内,使用 `Mark Price` 序列判定,若价格先触及 `+2.0 * atr_value`,且在该触发时刻之前从未触及 `-1.0 * atr_value`,则记为 `1`,否则记为 `0`。 -- 时间顺序要求(Chronological Order):若 60 分钟窗口内先触及 `-1.0 * atr_value`,即使后续再触及 `+2.0 * atr_value`,也必须记为 `0`。 -- `Y_return_t`:固定时间窗(15m/30m/60m)净收益率(含成本估计)。 - -说明: -- 标签优先评价"信号有效性",而不是被具体 TP/SL 参数污染的最终交易结果。 -- 统一使用 `Mark Price` + `atr_value` 快照,避免插针和重算偏差。 - -## 6. 执行引擎改造 - -1. **TP 优先 Maker + Taker 兜底**:入场后预挂 TP1/TP2 限价单;若价格已越过 TP 触发价且挂单在超时窗口(如 2 秒)内仍未成交,立即撤单并用 Taker 市价平仓兜底。 -2. **部分成交分支**:兜底前查询成交量,仅对剩余仓位执行 `Cancel -> Taker Close`。 -3. **Break-Even 费用感知**:BE 触发价需覆盖手续费与滑点缓冲,避免"名义保本、账户实亏"。 -4. **Flip 双门槛**:开仓阈值 `75`,翻转阈值 `85`。 -5. **并发和幂等**:`Cancel -> Market` 需要状态锁和幂等键,防止重复平仓。 -6. **执行质量指标化**:持续监控 `maker_ratio / avg_friction_cost_r / flip_loss_r`。 - -## 7. 反过拟合协议(强制) - -1. **Walk-Forward Optimization**:训练窗与验证窗严格时间隔离。 -2. **参数冻结**:一个评估周期内禁止改权重、阈值。 -3. **特征预算**:样本不足时严格限制特征数量,新增特征先 shadow 记录。 -4. **升级门槛**:样本外结果不达标不得进入下一阶段。 -5. **可解释性检查**:无金融逻辑支撑的"高胜率规则"禁止上线。 - -## 8. 模型权重训练与更新机制(新增) - -## 8.1 参数分层 - -- `static_params`:交易风控硬约束(如最大仓位、最大回撤阈值) -- `tunable_params`:可训练参数(ALT 子特征权重、阈值、BTC 门控阈值) -- `release_params`:版本发布参数(`strategy_version`, `config_hash`) - -## 8.2 ALT 权重训练流程 - -1. 用 `signal_feature_events + signal_label_events` 生成训练集。 -2. 先做单变量稳定性审计(IC、分箱胜率、PSI)。 -3. 再做有约束优化: - - 权重非负 - - 总和固定为 100 - - 单层权重变化设上限(如不超过上版的 30%) -4. 在 OOS 上评估,未达标不发布。 - -## 8.3 BTC 阈值训练流程 - -1. 针对每个门控特征做阈值网格搜索。 -2. 以 OOS `net_r + drawdown` 共同评分。 -3. 选择 Pareto 最优点,不追单一胜率最优。 - -## 8.4 参数更新节奏 - -- 建议频率:每 1-2 周滚动一次。 -- 每次仅允许小步更新,避免参数跳变。 -- 每次更新必须附带变更记录:`old -> new`、样本窗口、验证结果。 - -## 9. 发布与回滚机制 - -- 每次策略升级必须生成新 `strategy_version` 和 `config_hash`。 -- 发版前必须附带:训练窗结果 + 验证窗(OOS)结果 + 执行成本变化。 -- 任一核心指标触发阈值告警(如净R断崖、回撤超限)立即回滚到上一稳定版本。 - -## 10. 版本验收标准(V5.3) - -- ALT 轨:样本外连续两个窗口净R为正。 -- BTC 轨:样本外净R非负,且胜率不低于随机基线。 -- 执行层:`maker_ratio >= 40%`,且 `avg_friction_cost_r`(滑点+手续费)较 V5.1 基线下降 >= 30%。 -- 稳定性:最大回撤不显著劣化。 - -## 11. 里程碑 - -- M1:完成三张新表与事件落库。 -- M2:完成 ALT/BTC 路由与首版决策逻辑。 -- M3:完成执行成本改造(maker/BE/flip)。 -- M4:跑通首轮 Walk-Forward 并产出 V5.3 首次评估报告。 diff --git a/docs/arbitrage-engine/v53-implementation-checklist.md b/docs/arbitrage-engine/v53-implementation-checklist.md deleted file mode 100644 index 05ab164..0000000 --- a/docs/arbitrage-engine/v53-implementation-checklist.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: V5.3 实施清单 -status: draft -updated: 2026-03-03 ---- - -# V5.3 实施清单 - -## Phase 0 - 数据与追溯基建(P0) - -- [ ] 新增 `signal_feature_events` 表(含索引) -- [ ] 新增 `signal_label_events` 表(含索引) -- [ ] 新增 `execution_cost_events` 表(含索引) -- [ ] `signal_feature_events` 增加 `atr_value` 字段(信号触发时 ATR 绝对值快照) -- [ ] 在信号评估循环中落库 feature snapshot(每次评估都写) -- [ ] 打通统一追溯字段:`strategy_version/config_hash/engine_instance` -- [ ] 标签回填任务上线(15m/30m/60m) -- [ ] 标签回填强制使用 Mark Price,并按时间顺序判定先触发条件 -- [ ] 标签计算强制使用快照 `atr_value`(禁止回填时重算 ATR) - -## Phase 1 - 决策引擎重构(P0) - -- [ ] 实现 ALT/BTC 路由器(按 symbol 分流) -- [ ] ALT 轨移除独立 confirmation 层,改为四层结构 -- [ ] ALT 轨实现权重分配:`55/25/15/5` -- [ ] ALT 轨实现阈值:`open=75`, `flip=85` -- [ ] BTC 轨首版特征:`tiered_cvd_whale`, `obi_depth_10`, `spot_perp_divergence`, `atr_percent_1h` -- [ ] BTC 轨门控逻辑上线(含 veto 条件) -- [ ] BTC 轨缺失特征默认 `BLOCK_SIGNAL`,并写入 `block_reason` -- [ ] BTC 轨阈值配置化:`min_vol_threshold`, `obi_veto_threshold`, `whale_flow_threshold` - -## Phase 2 - 执行层与摩擦成本优化(P0) - -- [ ] 执行层支持 TP 预挂单(maker 优先) -- [ ] 增加 TP 未成交兜底:越价+超时后撤 maker 改 taker 强平 -- [ ] 增加“部分成交”分支:仅对剩余仓位执行兜底 -- [ ] Break-Even 改为费用感知(含手续费+滑点缓冲) -- [ ] `Cancel -> Market` 增加状态锁与幂等键 -- [ ] 新增执行成本统计任务(fee/slippage/maker_ratio/friction_cost_r) - -## Phase 3 - 评估与发布闸门(P1) - -- [ ] 新增按 `config_hash` 分组报表接口 -- [ ] 新增按 `track` 分组报表接口(ALT/BTC 分开看) -- [ ] 建立 Walk-Forward 评估脚本(训练窗+验证窗) -- [ ] 产出首版 V5.3 OOS 报告模板 -- [ ] 新增参数变更记录模板(`old -> new` + 样本窗口 + OOS结果) - -## Phase 4 - 持续优化(P2) - -- [ ] 特征 shadow 机制(新因子先记录不参与决策) -- [ ] 自动化回滚钩子(核心KPI超阈值触发) -- [ ] 分层 CVD 桶参数自动校准 -- [ ] OBI 深度档位自适应(5档/10档切换) -- [ ] 评估 XGBoost/LightGBM 离线实验管道 - -## 数据库建议(草案) - -## `signal_feature_events` - -- 主键:`event_id` -- 必要索引: - - `(ts)` - - `(symbol, ts DESC)` - - `(track, ts DESC)` - - `(strategy_version, config_hash, ts DESC)` - -## `signal_label_events` - -- 主键:`event_id` -- 必要索引: - - `(y_binary_60m, ts)` - - `(symbol, ts DESC)` - -## `execution_cost_events` - -- 主键:`trade_id` -- 必要索引: - - `(ts)` - - `(symbol, ts DESC)` - - `(entry_type, exit_type, ts DESC)` - -## 上线前验证清单 - -- [ ] feature 事件写入无丢失,延迟可接受 -- [ ] label 回填任务无时间错位 -- [ ] ALT/BTC 路由正确(BTC 不落入 ALT) -- [ ] BTC 缺失特征不会静默放行 -- [ ] maker 优先在真实成交中可观测 -- [ ] TP 兜底分支含部分成交路径可复现 -- [ ] BE 逻辑覆盖成本后,不再出现“保本但净亏”异常 -- [ ] flip 频次和 flip 损耗下降 -- [ ] OOS 报告通过预设阈值 - -## 发布闸门(量化指标) - -- [ ] ALT:连续两个 OOS 窗口净R > 0 -- [ ] BTC:OOS 净R >= 0 且胜率 >= 随机基线 -- [ ] `maker_ratio >= 40%` -- [ ] `avg_friction_cost_r`(滑点+手续费)较 V5.1 下降 >= 30% -- [ ] 最大回撤不高于风险红线 - -## 任务分配建议 - -- 后端核心:`signal_engine.py`, `paper_monitor.py`, `main.py`, `db.py` -- 数据任务:新增 migration + 回填 job -- 评估任务:新增 `scripts/train_eval_walkforward.py` -- 文档任务:每次发版补充 `strategy_version` 变更记录 diff --git a/docs/arbitrage-engine/v54-requirements.md b/docs/arbitrage-engine/v54-requirements.md deleted file mode 100644 index ae076d4..0000000 --- a/docs/arbitrage-engine/v54-requirements.md +++ /dev/null @@ -1,265 +0,0 @@ -# V5.4 Strategy Factory 需求文档 - -**版本**:v1.0 -**日期**:2026-03-11 -**作者**:露露 -**状态**:待范总 + 小范 Review - ---- - -## 1. 背景与目标 - -当前系统(V5.3)使用单体 `signal_engine.py`,所有策略逻辑耦合在一起,存在以下问题: - -- 修改任意策略参数需重启整个引擎,中断数据采集 -- 不同策略无法独立运行和对比,A/B 测试成本高 -- 参数配置分散在 JSON 文件中,无法通过前端界面管理 -- 无法按币种独立优化权重 - -V5.4 目标:构建 **Strategy Factory(策略工厂)**,将信号引擎解耦为数据总线 + 独立策略 Worker,支持前端可视化管理策略生命周期和参数配置。 - ---- - -## 2. 核心架构 - -### 2.1 整体架构 - -``` -Signal Engine(数据总线) - ├── 采集原始数据(aggTrades / OBI / 清算 / 市场数据) - ├── 计算基础 Feature(CVD / ATR / VWAP / whale flow / OBI / spot-perp div) - └── 广播 feature_event(每15秒一次) - -Strategy Workers(策略工厂) - ├── Worker-1:我的BTC策略01(BTCUSDT,asyncio协程) - ├── Worker-2:我的ETH策略01(ETHUSDT,asyncio协程) - ├── Worker-N:... - └── 每个 Worker 订阅 feature_event,独立打分、开仓、管仓 -``` - -### 2.2 关键设计原则 - -- **同一进程 + asyncio 协程**:所有 Worker 共享同一 Python 进程,feature_event 内存传递,省资源 -- **独立资金池**:每个 Worker 有独立的 paper trading 余额,互不影响 -- **15秒内热生效**:前端修改参数后,Worker 在下一个评估周期(≤15秒)自动从 DB 读取新参数 -- **配置存 DB**:所有策略配置存入数据库,JSON 文件废弃 -- **直接切换**:V5.4 上线后直接替换 V5.3 单体,不并行 - ---- - -## 3. 策略生命周期 - -### 3.1 状态定义 - -``` -created(已创建)→ running(运行中)→ paused(已暂停)→ running - ↓ - deprecated(已废弃)→ running(重新启用) -``` - -- **只有「废弃」,没有「删除」** -- 废弃的策略数据永久保留,可在废弃列表中检索 -- 废弃策略可重新启用,继续使用原有余额和历史数据 - -### 3.2 策略标识 - -- 用户填写**显示名称**(自由命名,如"我的BTC激进策略") -- 后台自动生成**UUID**作为唯一标识,用户不感知 -- `paper_trades` 等表通过 strategy_id(UUID)关联 - -### 3.3 余额管理 - -- 创建时设置初始资金(默认 10,000 USDT) -- 支持**追加余额**:追加后,`initial_balance` 同步增加,`current_balance` 同步增加 -- 废弃后重新启用,继续使用废弃时的余额和历史数据 - ---- - -## 4. 策略配置参数 - -每个策略实例(每个币种独立)包含以下可配置参数,均存入数据库,前端可编辑。 - -### 4.1 基础信息 - -| 参数 | 说明 | 类型 | 默认值 | 范围 | -|------|------|------|--------|------| -| display_name | 策略显示名称 | string | — | 1-50字符 | -| symbol | 交易对 | enum | BTCUSDT | BTCUSDT / ETHUSDT / SOLUSDT / XRPUSDT | -| direction | 交易方向 | enum | both | long_only / short_only / both | -| initial_balance | 初始资金(USDT) | float | 10000 | 1000-1000000 | - -### 4.2 CVD 窗口配置 - -| 参数 | 说明 | 类型 | 默认值 | 可选项 | -|------|------|------|--------|--------| -| cvd_fast_window | 快线CVD窗口 | enum | 30m | 5m / 15m / 30m | -| cvd_slow_window | 慢线CVD窗口 | enum | 4h | 1h / 4h | - -### 4.3 四层权重(合计必须 = 100) - -| 参数 | 说明 | 默认值 | 范围 | -|------|------|--------|------| -| weight_direction | 方向得分权重 | 55 | 10-80 | -| weight_env | 环境得分权重 | 25 | 5-60 | -| weight_aux | 辅助因子权重 | 15 | 0-40 | -| weight_momentum | 动量权重 | 5 | 0-20 | - -> 前端校验:四项之和必须 = 100,否则不允许保存 - -### 4.4 入场阈值 - -| 参数 | 说明 | 默认值 | 范围 | -|------|------|--------|------| -| entry_score | 入场最低总分 | 75 | 60-95 | - -### 4.5 四道 Gate(过滤门) - -每道 Gate 有独立开关和阈值: - -| Gate | 说明 | 开关默认 | 阈值参数 | 默认值 | 范围 | -|------|------|----------|----------|--------|------| -| gate_obi | 订单簿失衡门 | ON | obi_threshold | 0.3 | 0.1-0.9 | -| gate_whale_cvd | 大单CVD门 | ON | whale_cvd_threshold | 0.0 | -1.0-1.0 | -| gate_vol_atr | 波动率ATR门 | ON | atr_percentile_min | 20 | 5-80 | -| gate_spot_perp | 现货/永续溢价门 | OFF | spot_perp_threshold | 0.002 | 0.0005-0.01 | - -### 4.6 风控参数 - -| 参数 | 说明 | 默认值 | 范围 | -|------|------|--------|------| -| sl_atr_multiplier | SL宽度(×ATR) | 1.5 | 0.5-3.0 | -| tp1_ratio | TP1(×RD) | 0.75 | 0.3-2.0 | -| tp2_ratio | TP2(×RD) | 1.5 | 0.5-4.0 | -| timeout_minutes | 持仓超时(分钟) | 240 | 30-1440 | -| flip_threshold | 反转平仓阈值(分) | 80 | 60-95 | - ---- - -## 5. 前端功能需求 - -### 5.1 策略广场列表页(已有基础,新增以下) - -- **右上角「+ 新增策略」按钮**:点击进入参数配置创建界面 -- **每个卡片新增「调整参数」按钮**:点击进入参数配置编辑界面(预填当前参数) -- **每个卡片新增「废弃」按钮**:点击弹出二次确认,确认后策略进入废弃状态 -- **每个卡片新增「追加余额」功能**:输入追加金额,确认后更新余额 - -### 5.2 参数配置界面(新建/编辑共用) - -- 基础信息:名称、币种、交易方向、初始资金 -- CVD窗口:快线/慢线各独立选择 -- 四层权重:滑块或数字输入,实时显示合计,合计≠100时禁止保存 -- 四道Gate:开关 + 阈值输入,各有说明文字和合理范围提示 -- 风控参数:数字输入,有最小/最大值限制 -- 底部:「保存并启动」(新建)/ 「保存」(编辑)按钮 - -### 5.3 策略详情页(已有基础,新增以下) - -- 新增第三个 Tab:**「参数配置」** - - 展示当前所有参数,可点击编辑跳转到编辑界面 - -### 5.4 侧边栏新增入口 - -- **「废弃策略」**:点击进入废弃策略列表 - - 展示格式与正常卡片相同,额外显示废弃时间 - - 每个废弃策略有「重新启用」按钮 - - 重新启用后恢复至运行中状态,继续原余额和数据 - -### 5.5 权限 - -- 所有页面需登录才能访问(复用现有 JWT) -- 登录后有全部操作权限,无角色区分 - ---- - -## 6. 后端架构需求 - -### 6.1 数据库新增表 - -**`strategies` 表**(策略配置主表): -```sql -strategy_id UUID PRIMARY KEY -display_name TEXT NOT NULL -symbol TEXT NOT NULL -direction TEXT NOT NULL DEFAULT 'both' -status TEXT NOT NULL DEFAULT 'running' -- running/paused/deprecated -initial_balance FLOAT NOT NULL DEFAULT 10000 -current_balance FLOAT NOT NULL DEFAULT 10000 -cvd_fast_window TEXT NOT NULL DEFAULT '30m' -cvd_slow_window TEXT NOT NULL DEFAULT '4h' -weight_direction INT NOT NULL DEFAULT 55 -weight_env INT NOT NULL DEFAULT 25 -weight_aux INT NOT NULL DEFAULT 15 -weight_momentum INT NOT NULL DEFAULT 5 -entry_score INT NOT NULL DEFAULT 75 -gate_obi_enabled BOOL NOT NULL DEFAULT TRUE -obi_threshold FLOAT NOT NULL DEFAULT 0.3 -gate_whale_enabled BOOL NOT NULL DEFAULT TRUE -whale_cvd_threshold FLOAT NOT NULL DEFAULT 0.0 -gate_vol_enabled BOOL NOT NULL DEFAULT TRUE -atr_percentile_min INT NOT NULL DEFAULT 20 -gate_spot_perp_enabled BOOL NOT NULL DEFAULT FALSE -spot_perp_threshold FLOAT NOT NULL DEFAULT 0.002 -sl_atr_multiplier FLOAT NOT NULL DEFAULT 1.5 -tp1_ratio FLOAT NOT NULL DEFAULT 0.75 -tp2_ratio FLOAT NOT NULL DEFAULT 1.5 -timeout_minutes INT NOT NULL DEFAULT 240 -flip_threshold INT NOT NULL DEFAULT 80 -deprecated_at TIMESTAMP -created_at TIMESTAMP DEFAULT NOW() -updated_at TIMESTAMP DEFAULT NOW() -``` - -### 6.2 现有表调整 - -- `paper_trades`:`strategy` 字段改为存 `strategy_id`(UUID),兼容现有数据(v53/v53_middle/v53_fast 保留字符串形式) -- `signal_indicators`:同上 - -### 6.3 API 新增端点 - -``` -POST /api/strategies 创建策略 -GET /api/strategies 获取所有策略列表(含废弃) -GET /api/strategies/{id} 获取单个策略详情 -PATCH /api/strategies/{id} 更新策略参数 -POST /api/strategies/{id}/pause 暂停策略 -POST /api/strategies/{id}/resume 恢复策略 -POST /api/strategies/{id}/deprecate 废弃策略 -POST /api/strategies/{id}/restore 重新启用 -POST /api/strategies/{id}/add-balance 追加余额 -``` - -### 6.4 Signal Engine 改造 - -- Signal Engine 只负责 feature 计算和广播,不再包含评分/开仓逻辑 -- 每个 Strategy Worker 作为 asyncio 协程,订阅 feature_event -- Worker 启动时从 DB 读取配置,每15秒评估时重新读取(捕获参数变更) - ---- - -## 7. 迁移计划 - -V5.4 上线时: -1. 将现有 v53、v53_middle、v53_fast 三个策略迁移为 `strategies` 表中的三条记录 -2. 历史 `paper_trades` 数据通过 strategy 名称映射到对应 strategy_id -3. 直接切换,不保留 V5.3 单体并行 - ---- - -## 8. 不在本期范围内 - -- 实盘交易(本期只做 paper trading) -- 多用户/多账户体系 -- 策略算法类型选择(本期只支持四层评分算法) -- 自动化参数优化(Optuna 集成) - ---- - -## 9. Review 检查清单 - -- [ ] 范总确认需求无遗漏 -- [ ] 小范审阅数据结构合理性 -- [ ] 确认 `strategies` 表字段完整性 -- [ ] 确认 API 端点覆盖所有前端操作 -- [ ] 确认迁移方案不丢失历史数据 -- [ ] 需求文档 Review 通过后,再开始写数据合约文档 diff --git a/signal-engine.log b/signal-engine.log deleted file mode 100644 index fd36f3e..0000000 --- a/signal-engine.log +++ /dev/null @@ -1,196 +0,0 @@ -2026-03-01 23:06:35,118 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals -2026-03-01 23:06:37,990 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载425,636条历史数据 (窗口=4h) -2026-03-01 23:06:41,185 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载474,707条历史数据 (窗口=4h) -2026-03-01 23:06:41,583 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载63,246条历史数据 (窗口=4h) -2026-03-01 23:06:42,041 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载70,472条历史数据 (窗口=4h) -2026-03-01 23:06:42,041 [INFO] signal-engine: === Signal Engine (PG) 启动完成 === -2026-03-01 23:06:42,270 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=65362.8 -2026-03-01 23:06:42,270 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=65362.8 -2026-03-01 23:06:42,726 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=82.8 -2026-03-01 23:06:42,726 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=92 price=82.8 -2026-03-01 23:07:14,105 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 -2026-03-01 23:14:12,791 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals -2026-03-01 23:14:15,845 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载446,015条历史数据 (窗口=4h) -2026-03-01 23:14:19,122 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载486,995条历史数据 (窗口=4h) -2026-03-01 23:14:19,543 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载64,234条历史数据 (窗口=4h) -2026-03-01 23:14:20,034 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载72,230条历史数据 (窗口=4h) -2026-03-01 23:14:20,034 [INFO] signal-engine: === Signal Engine (PG) 启动完成 === -2026-03-01 23:14:20,277 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=65517.8 -2026-03-01 23:14:20,277 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=65517.8 -2026-03-01 23:14:20,717 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=83.2 -2026-03-01 23:14:20,717 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=97 price=83.2 -2026-03-01 23:14:52,024 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 -2026-03-01 23:23:48,162 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals -2026-03-01 23:23:51,129 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载454,017条历史数据 (窗口=4h) -2026-03-01 23:23:54,321 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载489,974条历史数据 (窗口=4h) -2026-03-01 23:23:54,744 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载65,115条历史数据 (窗口=4h) -2026-03-01 23:23:55,256 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载72,235条历史数据 (窗口=4h) -2026-03-01 23:23:55,257 [INFO] signal-engine: === Signal Engine (PG) 启动完成 === -2026-03-01 23:23:55,497 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=95 price=65616.4 -2026-03-01 23:23:55,497 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=95 price=65616.4 -2026-03-01 23:23:55,960 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=83.4 -2026-03-01 23:23:55,961 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=92 price=83.4 -2026-03-01 23:24:27,328 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 -2026-03-01 23:32:18,630 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: SHORT score=82 price=1.4 -2026-03-01 23:32:18,630 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: SHORT score=82 price=1.4 -2026-03-01 23:32:18,652 [INFO] signal-engine: [XRPUSDT] 📝 模拟开仓: SHORT @ 1.35 score=82 tier=standard strategy=v52_8signals TP1=1.34 TP2=1.33 SL=1.36 -2026-03-01 23:34:08,143 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=65781.9 -2026-03-01 23:34:08,144 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=85 price=65781.9 -2026-03-01 23:34:08,165 [INFO] signal-engine: [BTCUSDT] 📝 模拟开仓: LONG @ 65781.85 score=85 tier=heavy strategy=v52_8signals TP1=66122.21 TP2=66547.66 SL=65271.31 -2026-03-01 23:34:08,664 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=83.5 -2026-03-01 23:34:08,664 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=87 price=83.5 -2026-03-01 23:34:08,686 [INFO] signal-engine: [SOLUSDT] 📝 模拟开仓: LONG @ 83.54 score=87 tier=heavy strategy=v52_8signals TP1=84.10 TP2=84.80 SL=82.70 -2026-03-01 23:35:46,904 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals -2026-03-01 23:35:50,048 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载459,950条历史数据 (窗口=4h) -2026-03-01 23:35:53,307 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载486,670条历史数据 (窗口=4h) -2026-03-01 23:35:53,760 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载64,640条历史数据 (窗口=4h) -2026-03-01 23:35:54,229 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载71,505条历史数据 (窗口=4h) -2026-03-01 23:35:54,229 [INFO] signal-engine: === Signal Engine (PG) 启动完成 === -2026-03-01 23:35:54,463 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=95 price=65794.8 -2026-03-01 23:35:54,464 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=95 price=65794.8 -2026-03-01 23:35:54,823 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: SHORT score=92 price=1.4 -2026-03-01 23:35:54,823 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: SHORT score=92 price=1.4 -2026-03-01 23:35:54,901 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=83.6 -2026-03-01 23:35:54,901 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=92 price=83.6 -2026-03-01 23:36:26,291 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 -2026-03-01 23:45:20,597 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: SHORT score=85 price=1937.2 -2026-03-01 23:45:20,598 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: SHORT score=90 price=1937.2 -2026-03-01 23:45:20,621 [INFO] signal-engine: [ETHUSDT] 📝 模拟开仓: SHORT @ 1937.23 score=90 tier=heavy strategy=v52_8signals TP1=1923.04 TP2=1905.29 SL=1958.53 -2026-03-01 23:46:07,484 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=65708.5 -2026-03-01 23:46:07,484 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=85 price=65708.5 -2026-03-01 23:46:07,850 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: SHORT score=82 price=1.3 -2026-03-01 23:46:07,850 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: SHORT score=82 price=1.3 -2026-03-01 23:46:07,939 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=83.5 -2026-03-01 23:46:07,939 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=87 price=83.5 -2026-03-01 23:51:22,298 [INFO] signal-engine: [BTCUSDT] 状态: CVD_fast=417.5 CVD_mid=3139.9 ATR=262.21 (11%) VWAP=65702.8 -2026-03-01 23:51:22,298 [INFO] signal-engine: [ETHUSDT] 状态: CVD_fast=-892.3 CVD_mid=-33690.7 ATR=8.38 (11%) VWAP=1937.7 -2026-03-01 23:51:22,298 [INFO] signal-engine: [XRPUSDT] 状态: CVD_fast=-517373.6 CVD_mid=-3295080.7 ATR=0.01 (11%) VWAP=1.4 -2026-03-01 23:51:22,298 [INFO] signal-engine: [SOLUSDT] 状态: CVD_fast=829.6 CVD_mid=204333.9 ATR=0.39 (11%) VWAP=83.6 -2026-03-01 23:55:33,622 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: SHORT score=80 price=1937.9 -2026-03-01 23:55:33,622 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: SHORT score=80 price=1937.9 -2026-03-01 23:56:20,475 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=65700.6 -2026-03-01 23:56:20,475 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=85 price=65700.6 -2026-03-01 23:56:20,867 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: SHORT score=82 price=1.4 -2026-03-01 23:56:20,867 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: SHORT score=82 price=1.4 -2026-03-01 23:56:20,955 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=83.6 -2026-03-01 23:56:20,955 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=87 price=83.6 -2026-03-01 23:57:56,842 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals -2026-03-01 23:58:00,054 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载469,368条历史数据 (窗口=4h) -2026-03-01 23:58:03,491 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载480,675条历史数据 (窗口=4h) -2026-03-01 23:58:03,940 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载64,627条历史数据 (窗口=4h) -2026-03-01 23:58:04,405 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载69,923条历史数据 (窗口=4h) -2026-03-01 23:58:04,405 [INFO] signal-engine: === Signal Engine (PG) 启动完成 === -2026-03-01 23:58:04,670 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=65701.1 -2026-03-01 23:58:04,671 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=65701.1 -2026-03-01 23:58:05,068 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=83.6 -2026-03-01 23:58:05,069 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=92 price=83.6 -2026-03-01 23:58:36,358 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 -2026-03-02 00:02:15,179 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: SHORT score=87 price=1.4 -2026-03-02 00:02:15,180 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: SHORT score=87 price=1.4 -2026-03-02 00:08:14,751 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=65785.2 -2026-03-02 00:08:14,751 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=65785.2 -2026-03-02 00:08:15,137 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=82 price=83.6 -2026-03-02 00:08:15,138 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=82 price=83.6 -2026-03-02 00:12:25,824 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=82 price=1.4 -2026-03-02 00:12:25,824 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=87 price=1.4 -2026-03-02 00:12:57,084 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=1942.8 -2026-03-02 00:12:57,085 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=95 price=1942.8 -2026-03-02 00:13:28,619 [INFO] signal-engine: [BTCUSDT] 状态: CVD_fast=492.8 CVD_mid=4524.8 ATR=344.15 (100%) VWAP=65886.1 -2026-03-02 00:13:28,619 [INFO] signal-engine: [ETHUSDT] 状态: CVD_fast=12835.6 CVD_mid=5292.2 ATR=10.87 (100%) VWAP=1942.9 -2026-03-02 00:13:28,619 [INFO] signal-engine: [XRPUSDT] 状态: CVD_fast=561858.0 CVD_mid=1078138.6 ATR=0.01 (100%) VWAP=1.4 -2026-03-02 00:13:28,620 [INFO] signal-engine: [SOLUSDT] 状态: CVD_fast=61604.1 CVD_mid=357198.7 ATR=0.51 (100%) VWAP=83.7 -2026-03-02 00:18:25,810 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=82 price=65894.7 -2026-03-02 00:18:25,810 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=82 price=65894.7 -2026-03-02 00:18:25,829 [INFO] signal-engine: [BTCUSDT] 📝 模拟开仓: LONG @ 65894.67 score=82 tier=standard strategy=v52_8signals TP1=66258.64 TP2=66713.60 SL=65348.71 -2026-03-02 00:18:26,217 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=83.7 -2026-03-02 00:18:26,217 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=87 price=83.7 -2026-03-02 00:18:26,235 [INFO] signal-engine: [SOLUSDT] 📝 模拟开仓: LONG @ 83.72 score=87 tier=heavy strategy=v52_8signals TP1=84.44 TP2=85.34 SL=82.65 -2026-03-02 00:22:37,394 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=77 price=1.4 -2026-03-02 00:22:37,395 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=77 price=1.4 -2026-03-02 00:23:08,756 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=1943.5 -2026-03-02 00:23:08,756 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=1943.5 -2026-03-02 00:25:13,809 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals -2026-03-02 00:25:17,084 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载467,836条历史数据 (窗口=4h) -2026-03-02 00:25:20,192 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载457,043条历史数据 (窗口=4h) -2026-03-02 00:25:20,634 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载65,387条历史数据 (窗口=4h) -2026-03-02 00:25:21,081 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载67,363条历史数据 (窗口=4h) -2026-03-02 00:25:21,081 [INFO] signal-engine: === Signal Engine (PG) 启动完成 === -2026-03-02 00:25:21,333 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=65913.4 -2026-03-02 00:25:21,334 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=65913.4 -2026-03-02 00:25:21,612 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=1943.7 -2026-03-02 00:25:21,612 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=1943.7 -2026-03-02 00:25:21,693 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=1.4 -2026-03-02 00:25:21,693 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=82 price=1.4 -2026-03-02 00:25:21,780 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=83.8 -2026-03-02 00:25:21,780 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=87 price=83.8 -2026-03-02 00:25:53,073 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 -2026-03-02 00:34:24,612 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals -2026-03-02 00:34:27,756 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载459,517条历史数据 (窗口=4h) -2026-03-02 00:34:30,633 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载438,434条历史数据 (窗口=4h) -2026-03-02 00:34:31,056 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载64,941条历史数据 (窗口=4h) -2026-03-02 00:34:31,510 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载66,780条历史数据 (窗口=4h) -2026-03-02 00:34:31,511 [INFO] signal-engine: === Signal Engine (PG) 启动完成 === -2026-03-02 00:34:31,763 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=65953.9 -2026-03-02 00:34:31,763 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=65953.9 -2026-03-02 00:34:32,046 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=95 price=1945.0 -2026-03-02 00:34:32,046 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=87 price=1945.0 -2026-03-02 00:34:32,138 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=1.4 -2026-03-02 00:34:32,235 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=83.8 -2026-03-02 00:34:32,236 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=84 price=83.8 -2026-03-02 00:35:03,725 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 -2026-03-02 00:38:28,367 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=75 price=1.4 -2026-03-02 00:44:45,649 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=66018.3 -2026-03-02 00:44:45,649 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=66018.3 -2026-03-02 00:44:45,939 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=1947.1 -2026-03-02 00:44:45,940 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=1947.1 -2026-03-02 00:44:46,036 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=82 price=1.4 -2026-03-02 00:44:46,128 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=84.0 -2026-03-02 00:44:46,129 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=80 price=84.0 -2026-03-02 00:48:42,855 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=75 price=1.4 -2026-03-02 00:50:01,898 [INFO] signal-engine: [BTCUSDT] 状态: CVD_fast=587.6 CVD_mid=5583.3 ATR=230.66 (69%) VWAP=66328.7 -2026-03-02 00:50:01,898 [INFO] signal-engine: [ETHUSDT] 状态: CVD_fast=13374.7 CVD_mid=74135.1 ATR=7.58 (69%) VWAP=1953.2 -2026-03-02 00:50:01,899 [INFO] signal-engine: [XRPUSDT] 状态: CVD_fast=686323.2 CVD_mid=4294786.9 ATR=0.00 (66%) VWAP=1.4 -2026-03-02 00:50:01,899 [INFO] signal-engine: [SOLUSDT] 状态: CVD_fast=159663.5 CVD_mid=524572.1 ATR=0.37 (38%) VWAP=84.4 -2026-03-02 00:55:01,241 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=66391.1 -2026-03-02 00:55:01,241 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=66391.1 -2026-03-02 00:55:01,565 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=1954.9 -2026-03-02 00:55:01,565 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=1954.9 -2026-03-02 00:55:01,681 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=77 price=1.4 -2026-03-02 00:55:01,797 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=84.5 -2026-03-02 00:55:01,797 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=75 price=84.5 -2026-03-02 00:56:24,512 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals -2026-03-02 00:56:27,834 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载487,314条历史数据 (窗口=4h) -2026-03-02 00:56:30,636 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载423,049条历史数据 (窗口=4h) -2026-03-02 00:56:31,186 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载63,935条历史数据 (窗口=4h) -2026-03-02 00:56:31,669 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载67,314条历史数据 (窗口=4h) -2026-03-02 00:56:31,669 [INFO] signal-engine: === Signal Engine (PG) 启动完成 === -2026-03-02 00:56:31,916 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=95 price=66401.1 -2026-03-02 00:56:31,916 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=87.0 price=66401.1 -2026-03-02 00:56:32,159 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=95 price=1955.3 -2026-03-02 00:56:32,159 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=87.0 price=1955.3 -2026-03-02 00:56:32,227 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=1.4 -2026-03-02 00:56:32,296 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=84.6 -2026-03-02 00:56:32,296 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=79.0 price=84.6 -2026-03-02 00:57:03,538 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 -2026-03-02 01:06:43,805 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=66469.7 -2026-03-02 01:06:43,805 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=83.0 price=66469.7 -2026-03-02 01:06:44,079 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=1959.2 -2026-03-02 01:06:44,079 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=83.0 price=1959.2 -2026-03-02 01:06:44,158 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=82 price=1.4 -2026-03-02 01:06:44,232 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=82 price=84.8 -2026-03-02 01:06:44,232 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=80.0 price=84.8 -2026-03-02 01:06:44,253 [INFO] signal-engine: [SOLUSDT] 📝 模拟开仓: LONG @ 84.81 score=80.0 tier=standard strategy=v52_8signals TP1=85.30 TP2=85.91 SL=84.08 -2026-03-02 01:10:55,723 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=75.0 price=1.4 -2026-03-02 01:11:58,908 [INFO] signal-engine: [BTCUSDT] 状态: CVD_fast=952.8 CVD_mid=5720.3 ATR=235.00 (100%) VWAP=66586.5 -2026-03-02 01:11:58,908 [INFO] signal-engine: [ETHUSDT] 状态: CVD_fast=31499.2 CVD_mid=101911.4 ATR=7.67 (100%) VWAP=1966.7 -2026-03-02 01:11:58,908 [INFO] signal-engine: [XRPUSDT] 状态: CVD_fast=763930.9 CVD_mid=3518537.2 ATR=0.01 (100%) VWAP=1.4 -2026-03-02 01:11:58,908 [INFO] signal-engine: [SOLUSDT] 状态: CVD_fast=173330.9 CVD_mid=509498.7 ATR=0.38 (46%) VWAP=85.1 -2026-03-02 01:16:57,856 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=66677.4 -2026-03-02 01:16:57,856 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=78.0 price=66677.4 -2026-03-02 01:16:58,164 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=1971.5 -2026-03-02 01:16:58,164 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=78.0 price=1971.5 -2026-03-02 01:16:58,185 [INFO] signal-engine: [ETHUSDT] 📝 模拟开仓: LONG @ 1971.50 score=78.0 tier=standard strategy=v52_8signals TP1=1984.82 TP2=2001.47 SL=1951.52 -2026-03-02 01:16:58,264 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=77 price=1.4 -2026-03-02 01:16:58,346 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=82 price=85.3 -2026-03-02 01:16:58,346 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=75.0 price=85.3 -2026-03-02 01:16:58,368 [INFO] signal-engine: [SOLUSDT] 📝 模拟开仓: LONG @ 85.32 score=75.0 tier=standard strategy=v52_8signals TP1=85.98 TP2=86.80 SL=84.34