alphax/tests/test_screener_optimizations.py
2026-05-13 22:49:47 +08:00

242 lines
9.6 KiB
Python

import os
import sys
import pandas as pd
PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if PROJECT_DIR not in sys.path:
sys.path.insert(0, PROJECT_DIR)
from app.services import altcoin_screener
def test_fetch_all_tickers_filters_stable_and_fiat_suffixes(monkeypatch):
monkeypatch.setattr(
altcoin_screener.exchange,
"fetch_tickers",
lambda: {
"BTC/USDT": {"last": 1, "percentage": 1, "quoteVolume": 100},
"RLUSD/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"BFUSD/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"EUR/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"AI/USDT": {"last": 1, "percentage": 5, "quoteVolume": 1000},
"USD1/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"U/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"XUSD/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"FRAX/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"LUSD/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"GUSD/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"SUSD/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"USDD/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"EURS/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
"AUD/USDT": {"last": 1, "percentage": 0.1, "quoteVolume": 100},
},
)
pairs = altcoin_screener.fetch_all_tickers()
assert "AI/USDT" in pairs
assert "RLUSD/USDT" not in pairs
assert "BFUSD/USDT" not in pairs
assert "EUR/USDT" not in pairs
assert "USD1/USDT" not in pairs
assert "U/USDT" not in pairs
assert "XUSD/USDT" not in pairs
assert "FRAX/USDT" not in pairs
assert "LUSD/USDT" not in pairs
assert "GUSD/USDT" not in pairs
assert "SUSD/USDT" not in pairs
assert "USDD/USDT" not in pairs
assert "EURS/USDT" not in pairs
assert "AUD/USDT" not in pairs
assert "BTC/USDT" not in pairs
def _mock_weights():
return {
"量价齐飞": 5,
"N倍放量": 5,
"连续3x放量": 4,
"布林收窄": 3,
"静K蓄力": 2,
"Q≥7供给区突破": 4,
"Q7供给区突破": 4,
"动K(阳)+量递增": 3,
"动K阳量递增": 3,
"连续K加速": 3,
"板块联动": 3,
"大户偏多": 1,
"静K→动K转折": 4,
"静K动K转折": 4,
"1H放量(量价背离)": 1,
}
def test_volume_price_fly_accepts_two_consecutive_4x_bars(monkeypatch):
monkeypatch.setattr(
altcoin_screener,
"vp_fly_params",
lambda: {"vol_ratio_min": 5.0, "body_ratio_min": 0.70, "consecutive_relaxed_vol_ratio_min": 4.0},
)
rows = []
for i in range(20):
rows.append({"open": 1.0, "high": 1.03, "low": 0.99, "close": 1.01, "volume": 100.0})
rows.extend([
{"open": 1.00, "high": 1.10, "low": 0.99, "close": 1.09, "volume": 650.0},
{"open": 1.09, "high": 1.20, "low": 1.08, "close": 1.18, "volume": 670.0},
])
df = pd.DataFrame(rows)
vp = altcoin_screener.detect_volume_price_fly(df)
assert vp["vp_fly_count"] == 2
assert len(vp["vp_fly_details"]) == 2
def test_layer1_keeps_high_momentum_breakout_without_5x_vp(monkeypatch):
monkeypatch.setattr(altcoin_screener, "fetch_all_tickers", lambda: {
"CREAM/USDT": {"price": 2.1, "change_24h": 12.0, "volume_24h": 8000000},
})
monkeypatch.setattr(altcoin_screener, "fetch_funding_rates", lambda: {})
monkeypatch.setattr(altcoin_screener, "is_meme_coin", lambda symbol: False)
monkeypatch.setattr(altcoin_screener, "get_burst_threshold", lambda symbol: 20)
monkeypatch.setattr(altcoin_screener, "funding_rate_params", lambda: {"long_extreme": 0.001, "short_extreme": -0.0005})
monkeypatch.setattr(altcoin_screener, "detect_bollinger_squeeze", lambda df: None)
monkeypatch.setattr(altcoin_screener, "vp_fly_params", lambda: {"vol_ratio_min": 5.0, "body_ratio_min": 0.70, "consecutive_relaxed_vol_ratio_min": 4.0})
monkeypatch.setattr(altcoin_screener, "get_dynamic_weights", lambda: {
"量价齐飞": 5,
"N倍放量(≥10x)": 6,
"连续3x放量(≥3根)": 4,
"布林收窄": 3,
"静K蓄力": 2,
"1H放量(量价背离)": 1,
})
h1_rows = []
for _ in range(20):
h1_rows.append([0, 1.0, 1.03, 0.99, 1.01, 100.0])
h1_rows.extend([
[0, 1.00, 1.10, 0.99, 1.09, 650.0],
[0, 1.09, 1.20, 1.08, 1.18, 670.0],
])
h4_rows = []
price = 1.0
for _ in range(30):
h4_rows.append([0, price, price * 1.01, price * 0.99, price * 1.002, 100.0])
price *= 1.001
def fake_fetch_klines(symbol, timeframe, limit=100):
data = h1_rows if timeframe == '1h' else h4_rows
return pd.DataFrame(data, columns=['timestamp','open','high','low','close','volume'])
monkeypatch.setattr(altcoin_screener, "fetch_klines", fake_fetch_klines)
candidates = altcoin_screener.layer1_coarse_filter()
vp = altcoin_screener.detect_volume_price_fly(fake_fetch_klines('CREAM/USDT', '1h'))
assert vp["vp_fly_count"] == 2
assert "CREAM/USDT" in candidates
assert any("量价齐飞" in s for s in candidates["CREAM/USDT"]["anomalies"])
assert any("连续2根量价齐飞K" in s for s in candidates["CREAM/USDT"]["anomalies"])
def test_static_accumulation_bypass_promotes_expired_to_accumulate(monkeypatch):
monkeypatch.setattr(altcoin_screener, "get_dynamic_weights", _mock_weights)
monkeypatch.setattr(altcoin_screener, "state_score_thresholds", lambda: (8, 10, 3))
monkeypatch.setattr(
altcoin_screener,
"get_screener_section",
lambda name=None: {
"sector_rotation": {
"bonus_weight": 0,
"min_non_sector_signals_for_accelerate": 2,
"sector_only_max_state": "蓄力",
},
"static_accumulation_bypass": {
"min_score": 2,
"min_vol_ratio": 1.0,
"min_static_count": 3,
},
}.get(name, {}),
)
monkeypatch.setattr(altcoin_screener, "fetch_top_trader_ratio", lambda symbol: None)
monkeypatch.setattr(altcoin_screener, "log_screening", lambda **kwargs: None)
monkeypatch.setattr(altcoin_screener, "create_recommendation", lambda **kwargs: 456)
monkeypatch.setattr(altcoin_screener, "get_sector_for_coin", lambda symbol: [])
monkeypatch.setattr(altcoin_screener, "dynamic_leader_detection", lambda perf: {})
monkeypatch.setattr(altcoin_screener, "SECTOR_MEMBERS", {})
qualified, _, _ = altcoin_screener.layer2_fine_filter({
"PNT/USDT": {
"anomaly_score": 2,
"price": 1.0,
"change_24h": 4.0,
"funding_rate": 0.0,
"is_meme": False,
"vp_data": None,
"bb_data": None,
"static_accumulation": {"static_count": 5, "vol_ratio": 1.1},
"h4_df": None,
}
})
assert qualified["PNT/USDT"]["state"] == "蓄力"
assert qualified["PNT/USDT"]["base_state"] == "过期"
assert qualified["PNT/USDT"]["force_reason"] == "静K蓄力旁路"
assert any("静K蓄力旁路入池" in s for s in qualified["PNT/USDT"]["signals"])
def test_strong_static_accumulation_can_promote_to_accelerate(monkeypatch):
monkeypatch.setattr(altcoin_screener, "get_dynamic_weights", _mock_weights)
monkeypatch.setattr(altcoin_screener, "state_score_thresholds", lambda: (8, 10, 3))
monkeypatch.setattr(
altcoin_screener,
"get_screener_section",
lambda name=None: {
"sector_rotation": {
"bonus_weight": 0,
"min_non_sector_signals_for_accelerate": 2,
"sector_only_max_state": "蓄力",
},
"static_accumulation_bypass": {
"min_score": 2,
"min_vol_ratio": 1.0,
"min_static_count": 3,
"direct_accelerate": {
"enabled": True,
"min_static_count": 10,
"min_vol_ratio": 1.25,
"min_score": 5,
},
},
}.get(name, {}),
)
monkeypatch.setattr(altcoin_screener, "fetch_top_trader_ratio", lambda symbol: None)
monkeypatch.setattr(altcoin_screener, "log_screening", lambda **kwargs: None)
created = []
monkeypatch.setattr(altcoin_screener, "create_recommendation", lambda **kwargs: created.append(kwargs) or 789)
monkeypatch.setattr(altcoin_screener, "get_sector_for_coin", lambda symbol: [])
monkeypatch.setattr(altcoin_screener, "dynamic_leader_detection", lambda perf: {})
monkeypatch.setattr(altcoin_screener, "SECTOR_MEMBERS", {})
qualified, _, _ = altcoin_screener.layer2_fine_filter({
"PNT/USDT": {
"anomaly_score": 5,
"price": 1.0,
"change_24h": 4.0,
"funding_rate": 0.0,
"is_meme": False,
"vp_data": None,
"bb_data": None,
"static_accumulation": {"static_count": 12, "vol_ratio": 1.4},
"h4_df": None,
}
})
assert qualified["PNT/USDT"]["state"] == "加速"
assert qualified["PNT/USDT"]["base_state"] == "蓄力"
assert qualified["PNT/USDT"]["force_reason"] == "强静K蓄力直升加速"
assert any("强静K蓄力直升加速" in s for s in qualified["PNT/USDT"]["signals"])
assert created and created[0]["force_reason"] == "强静K蓄力直升加速"