arbitrage-engine/V52_TASK.md
root ee90b8dcfa feat: sidebar navigation with V5.1/V5.2 separate entries
- Sidebar: 信号/模拟盘 section headers
- Three paper trade entries: 全部持仓, V5.1模拟盘, V5.2模拟盘 (NEW badge)
- Paper page reads strategy from URL query params
- Suspense boundary for useSearchParams
2026-03-01 12:25:40 +00:00

7.4 KiB

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:

{
  "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:

{
  "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:

# 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

# 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

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:

for ind_type in ["long_short_ratio", "top_trader_position", "open_interest_hist", "coinbase_premium", "funding_rate"]:

And the extraction:

elif ind_type == "funding_rate":
    indicators[ind_type] = float(val.get("lastFundingRate", 0))

2e. Update total_score calculation

Currently:

total_score = direction_score + accel_bonus + crowding_score + environment_score + confirmation_score + aux_score

Change to:

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:

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:

# 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:

# 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:

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