172 lines
5.9 KiB
Python
172 lines
5.9 KiB
Python
from app.core.market_regime import classify_market_regime
|
|
from app.core.global_risk import evaluate_global_risk
|
|
|
|
|
|
def _overview(**overrides):
|
|
data = {
|
|
"sample_count": 200,
|
|
"benchmarks": {
|
|
"BTC/USDT": {"change_24h": 0.5},
|
|
"ETH/USDT": {"change_24h": 0.4},
|
|
},
|
|
"advance_decline_ratio": 1.0,
|
|
"avg_change_24h": 0.1,
|
|
"hot_count_5pct": 8,
|
|
"crash_count_5pct": 4,
|
|
"funding": {"avg_funding_rate": 0.0001, "extreme_positive_count": 2},
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
|
|
def test_market_regime_reduces_size_for_clear_risk_off():
|
|
result = classify_market_regime(
|
|
_overview(
|
|
benchmarks={"BTC/USDT": {"change_24h": -3.4}, "ETH/USDT": {"change_24h": -4.2}},
|
|
advance_decline_ratio=0.4,
|
|
crash_count_5pct=35,
|
|
)
|
|
)
|
|
|
|
assert result["regime"] == "risk_off"
|
|
assert result["risk_level"] == "critical"
|
|
assert result["position_multiplier"] == 0.25
|
|
|
|
|
|
def test_market_regime_detects_altcoin_rotation():
|
|
result = classify_market_regime(
|
|
_overview(
|
|
benchmarks={"BTC/USDT": {"change_24h": 0.6}, "ETH/USDT": {"change_24h": 1.1}},
|
|
advance_decline_ratio=1.4,
|
|
avg_change_24h=1.2,
|
|
hot_count_5pct=22,
|
|
crash_count_5pct=3,
|
|
)
|
|
)
|
|
|
|
assert result["regime"] == "altcoin_rotation"
|
|
assert result["risk_level"] == "medium"
|
|
|
|
|
|
def test_global_risk_blocks_same_sector_concentration(pg_conn, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"app.core.global_risk.get_crypto_market_overview",
|
|
lambda allow_live_fallback=False: _overview(),
|
|
)
|
|
pg_conn.execute(
|
|
"""
|
|
INSERT INTO paper_trades (
|
|
recommendation_id, symbol, side, status, opened_at, entry_price, qty,
|
|
notional_usdt, current_price, pnl_pct, created_at, updated_at
|
|
) VALUES
|
|
(1, 'DOGE/USDT', 'long', 'open', '2026-05-23T10:00:00', 1, 100, 1000, 1, 0, '2026-05-23T10:00:00', '2026-05-23T10:00:00'),
|
|
(2, 'SHIB/USDT', 'long', 'open', '2026-05-23T10:00:00', 1, 100, 1000, 1, 0, '2026-05-23T10:00:00', '2026-05-23T10:00:00')
|
|
"""
|
|
)
|
|
pg_conn.commit()
|
|
|
|
result = evaluate_global_risk(
|
|
conn=pg_conn,
|
|
config={
|
|
"account_equity_usdt": 20000,
|
|
"global_risk_max_same_sector_positions": 2,
|
|
"global_risk_max_same_direction_positions": 10,
|
|
},
|
|
rec={"symbol": "PEPE/USDT", "sector": "MEME"},
|
|
additional_notional=1000,
|
|
)
|
|
|
|
assert result["allow_new_entries"] is False
|
|
assert result["decision"] == "block_same_sector_concentration"
|
|
|
|
|
|
def test_global_risk_off_is_favorable_for_short_entries(pg_conn, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"app.core.global_risk.get_crypto_market_overview",
|
|
lambda allow_live_fallback=False: _overview(
|
|
benchmarks={"BTC/USDT": {"change_24h": -3.4}, "ETH/USDT": {"change_24h": -4.2}},
|
|
advance_decline_ratio=0.4,
|
|
crash_count_5pct=35,
|
|
),
|
|
)
|
|
|
|
result = evaluate_global_risk(
|
|
conn=pg_conn,
|
|
config={
|
|
"account_equity_usdt": 20000,
|
|
"global_risk_gate_enabled": True,
|
|
"global_risk_block_critical": False,
|
|
"global_risk_critical_min_rec_score": 80,
|
|
},
|
|
rec={"symbol": "SHORT/USDT", "side": "short", "rec_score": 55},
|
|
additional_notional=1000,
|
|
)
|
|
|
|
assert result["allow_new_entries"] is True
|
|
assert result["side"] == "short"
|
|
assert result["risk_level"] == "medium"
|
|
assert result["directional_market_bias"]["market_bias"] == "favorable"
|
|
assert "risk_off 对空头属于顺风环境" in " ".join(result["reasons"])
|
|
|
|
|
|
def test_intraday_global_risk_softens_risk_off_for_low_score_long(pg_conn, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"app.core.global_risk.get_crypto_market_overview",
|
|
lambda allow_live_fallback=False: _overview(
|
|
benchmarks={"BTC/USDT": {"change_24h": -3.4}, "ETH/USDT": {"change_24h": -4.2}},
|
|
advance_decline_ratio=0.4,
|
|
crash_count_5pct=35,
|
|
),
|
|
)
|
|
|
|
result = evaluate_global_risk(
|
|
conn=pg_conn,
|
|
config={
|
|
"trading_mode": "intraday_trading",
|
|
"account_equity_usdt": 20000,
|
|
"global_risk_gate_enabled": True,
|
|
"global_risk_block_critical": False,
|
|
"global_risk_score_blocks_intraday": False,
|
|
"global_risk_critical_min_rec_score": 80,
|
|
"global_risk_high_min_rec_score": 70,
|
|
},
|
|
rec={"symbol": "LONG/USDT", "side": "long", "rec_score": 28},
|
|
additional_notional=1000,
|
|
)
|
|
|
|
assert result["allow_new_entries"] is True
|
|
assert result["decision"] == "allow_reduced_size"
|
|
assert result["risk_level"] == "critical"
|
|
assert result["intraday_soft_risk"] is True
|
|
assert result["position_multiplier"] == 0.25
|
|
assert "日内模式下 critical 市场风险只做仓位调节" in " ".join(result["reasons"])
|
|
|
|
|
|
def test_altcoin_rotation_is_adverse_for_short_entries(pg_conn, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"app.core.global_risk.get_crypto_market_overview",
|
|
lambda allow_live_fallback=False: _overview(
|
|
benchmarks={"BTC/USDT": {"change_24h": 0.6}, "ETH/USDT": {"change_24h": 1.1}},
|
|
advance_decline_ratio=1.4,
|
|
avg_change_24h=1.2,
|
|
hot_count_5pct=22,
|
|
crash_count_5pct=3,
|
|
),
|
|
)
|
|
|
|
result = evaluate_global_risk(
|
|
conn=pg_conn,
|
|
config={
|
|
"account_equity_usdt": 20000,
|
|
"global_risk_gate_enabled": True,
|
|
"global_risk_high_min_rec_score": 70,
|
|
},
|
|
rec={"symbol": "SQUEEZE/USDT", "side": "short", "rec_score": 55},
|
|
additional_notional=1000,
|
|
)
|
|
|
|
assert result["allow_new_entries"] is False
|
|
assert result["risk_level"] == "high"
|
|
assert result["decision"] == "block_high_weak_score"
|
|
assert result["directional_market_bias"]["market_bias"] == "adverse"
|