add ETH/XRP/SOL factory strategies and plaza filters
This commit is contained in:
parent
e0e203bbcc
commit
cd1d41cff5
@ -317,6 +317,16 @@ export default function StrategyPlazaPage() {
|
|||||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
|
||||||
const [addBalanceTarget, setAddBalanceTarget] = useState<StrategyCard | null>(null);
|
const [addBalanceTarget, setAddBalanceTarget] = useState<StrategyCard | null>(null);
|
||||||
|
|
||||||
|
type SymbolFilter = "ALL" | "BTCUSDT" | "ETHUSDT" | "XRPUSDT" | "SOLUSDT";
|
||||||
|
type StatusFilter = "all" | "running" | "paused" | "error";
|
||||||
|
type PnlFilter = "all" | "positive" | "negative";
|
||||||
|
type SortKey = "recent" | "net_usdt_desc" | "net_usdt_asc" | "pnl24h_desc" | "pnl24h_asc";
|
||||||
|
|
||||||
|
const [symbolFilter, setSymbolFilter] = useState<SymbolFilter>("ALL");
|
||||||
|
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
|
||||||
|
const [pnlFilter, setPnlFilter] = useState<PnlFilter>("all");
|
||||||
|
const [sortKey, setSortKey] = useState<SortKey>("net_usdt_desc");
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
const fetchData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await authFetch("/api/strategies");
|
const res = await authFetch("/api/strategies");
|
||||||
@ -350,6 +360,33 @@ export default function StrategyPlazaPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filteredStrategies = strategies
|
||||||
|
.filter((s) => {
|
||||||
|
if (symbolFilter !== "ALL" && s.symbol !== symbolFilter) return false;
|
||||||
|
if (statusFilter !== "all" && s.status !== statusFilter) return false;
|
||||||
|
if (pnlFilter === "positive" && s.net_usdt <= 0) return false;
|
||||||
|
if (pnlFilter === "negative" && s.net_usdt >= 0) return false;
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
switch (sortKey) {
|
||||||
|
case "net_usdt_desc":
|
||||||
|
return b.net_usdt - a.net_usdt;
|
||||||
|
case "net_usdt_asc":
|
||||||
|
return a.net_usdt - b.net_usdt;
|
||||||
|
case "pnl24h_desc":
|
||||||
|
return b.pnl_usdt_24h - a.pnl_usdt_24h;
|
||||||
|
case "pnl24h_asc":
|
||||||
|
return a.pnl_usdt_24h - b.pnl_usdt_24h;
|
||||||
|
case "recent":
|
||||||
|
default: {
|
||||||
|
const aTs = a.last_trade_at ?? a.started_at ?? 0;
|
||||||
|
const bTs = b.last_trade_at ?? b.started_at ?? 0;
|
||||||
|
return bTs - aTs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-64">
|
<div className="flex items-center justify-center min-h-64">
|
||||||
@ -383,9 +420,101 @@ export default function StrategyPlazaPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Filters & Sorting */}
|
||||||
|
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||||||
|
{/* 左侧:币种 + 盈亏过滤 */}
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<div className="flex items-center gap-1 text-[11px] text-slate-400">
|
||||||
|
<span>币种:</span>
|
||||||
|
{(["ALL", "BTCUSDT", "ETHUSDT", "XRPUSDT", "SOLUSDT"] as SymbolFilter[]).map((sym) => {
|
||||||
|
const label = sym === "ALL" ? "全部" : sym.replace("USDT", "");
|
||||||
|
const active = symbolFilter === sym;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={sym}
|
||||||
|
onClick={() => setSymbolFilter(sym)}
|
||||||
|
className={`px-2 py-0.5 rounded-full border text-[11px] ${
|
||||||
|
active
|
||||||
|
? "border-blue-500 bg-blue-50 text-blue-600"
|
||||||
|
: "border-slate-200 text-slate-500 hover:bg-slate-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 text-[11px] text-slate-400">
|
||||||
|
<span>盈亏:</span>
|
||||||
|
{[
|
||||||
|
{ key: "all" as PnlFilter, label: "全部" },
|
||||||
|
{ key: "positive" as PnlFilter, label: "仅盈利" },
|
||||||
|
{ key: "negative" as PnlFilter, label: "仅亏损" },
|
||||||
|
].map((opt) => {
|
||||||
|
const active = pnlFilter === opt.key;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={opt.key}
|
||||||
|
onClick={() => setPnlFilter(opt.key)}
|
||||||
|
className={`px-2 py-0.5 rounded-full border text-[11px] ${
|
||||||
|
active
|
||||||
|
? "border-emerald-500 bg-emerald-50 text-emerald-600"
|
||||||
|
: "border-slate-200 text-slate-500 hover:bg-slate-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧:状态 + 排序 */}
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<div className="flex items-center gap-1 text-[11px] text-slate-400">
|
||||||
|
<span>状态:</span>
|
||||||
|
{[
|
||||||
|
{ key: "all" as StatusFilter, label: "全部" },
|
||||||
|
{ key: "running" as StatusFilter, label: "运行中" },
|
||||||
|
{ key: "paused" as StatusFilter, label: "已暂停" },
|
||||||
|
{ key: "error" as StatusFilter, label: "异常" },
|
||||||
|
].map((opt) => {
|
||||||
|
const active = statusFilter === opt.key;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={opt.key}
|
||||||
|
onClick={() => setStatusFilter(opt.key)}
|
||||||
|
className={`px-2 py-0.5 rounded-full border text-[11px] ${
|
||||||
|
active
|
||||||
|
? "border-slate-700 bg-slate-800 text-white"
|
||||||
|
: "border-slate-200 text-slate-500 hover:bg-slate-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 text-[11px] text-slate-400">
|
||||||
|
<span>排序:</span>
|
||||||
|
<select
|
||||||
|
value={sortKey}
|
||||||
|
onChange={(e) => setSortKey(e.target.value as SortKey)}
|
||||||
|
className="border border-slate-200 rounded-lg px-2 py-1 text-[11px] text-slate-700 bg-white"
|
||||||
|
>
|
||||||
|
<option value="net_usdt_desc">总盈亏从高到低</option>
|
||||||
|
<option value="net_usdt_asc">总盈亏从低到高</option>
|
||||||
|
<option value="pnl24h_desc">24h 盈亏从高到低</option>
|
||||||
|
<option value="pnl24h_asc">24h 盈亏从低到高</option>
|
||||||
|
<option value="recent">最近有交易优先</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Strategy Cards */}
|
{/* Strategy Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
{strategies.map((s) => (
|
{filteredStrategies.map((s) => (
|
||||||
<StrategyCardComponent
|
<StrategyCardComponent
|
||||||
key={s.strategy_id}
|
key={s.strategy_id}
|
||||||
s={s}
|
s={s}
|
||||||
@ -395,7 +524,7 @@ export default function StrategyPlazaPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{strategies.length === 0 && (
|
{filteredStrategies.length === 0 && (
|
||||||
<div className="text-center text-slate-400 text-sm py-16">
|
<div className="text-center text-slate-400 text-sm py-16">
|
||||||
<p className="mb-3">暂无运行中的策略</p>
|
<p className="mb-3">暂无运行中的策略</p>
|
||||||
<button
|
<button
|
||||||
|
|||||||
244
scripts/bootstrap_eth_xrp_sol_strategies.py
Normal file
244
scripts/bootstrap_eth_xrp_sol_strategies.py
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
bootstrap_eth_xrp_sol_strategies.py
|
||||||
|
|
||||||
|
基于 v5.3 Optuna 结果 + v53.json 的 symbol_gates,为策略工厂批量创建
|
||||||
|
ETH/XRP/SOL 的 54 条单币种策略配置。
|
||||||
|
|
||||||
|
约定:
|
||||||
|
- 仍沿用 BTC 工厂当前的 CVD 组合与 TP 档位:
|
||||||
|
* CVD 窗口: (5m,30m), (5m,1h), (15m,1h), (15m,4h), (30m,1h), (30m,4h)
|
||||||
|
* TP 档位: 保守 / 平衡 / 激进(sl=2ATR,TP=0.75/1.0/1.5R 与 1.5/2.0/2.5R)
|
||||||
|
- 每个 symbol 的门控阈值复刻 v53.json 的 symbol_gates:
|
||||||
|
* ETHUSDT: min_vol=0.003, whale_usd=50000, obi=0.35, spd=0.005
|
||||||
|
* XRPUSDT: min_vol=0.0025, whale_usd=30000, obi=0.40, spd=0.006
|
||||||
|
* SOLUSDT: min_vol=0.004, whale_usd=20000, obi=0.45, spd=0.008
|
||||||
|
- 五门开关与 BTC 工厂当前配置保持一致:
|
||||||
|
* 波动率门 / CVD 门 / 巨鲸门 / OBI 门:启用
|
||||||
|
* 期现门:关闭(仅写入阈值,保留以后启用的空间)
|
||||||
|
- 权重与开仓阈值取自 optuna_results_v3_cn.xlsx 中各 symbol 的 v53/v53_fast Top1:
|
||||||
|
* ETHUSDT (v53): dir=51, env=18, aux=28, mom=3, threshold=75
|
||||||
|
* XRPUSDT (v53): dir=58, env=8, aux=32, mom=2, threshold=80
|
||||||
|
* SOLUSDT (v53_fast): dir=38, env=42, aux=8, mom=12, threshold=65
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 如果某个 display_name 已存在于 strategies 表,将跳过,不会重复插入。
|
||||||
|
- 连接参数走 backend.db.get_sync_conn(),运行脚本时请设置:
|
||||||
|
PG_HOST=127.0.0.1 PG_PORT=9470 PG_DB=arb_engine PG_USER=arb PG_PASS=...
|
||||||
|
或在服务器上直接使用 Cloud SQL 内网地址。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
# 确保可以从 backend 导入 db.get_sync_conn
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
|
||||||
|
from db import get_sync_conn # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SymbolProfile:
|
||||||
|
symbol: str
|
||||||
|
weight_direction: int
|
||||||
|
weight_env: int
|
||||||
|
weight_aux: int
|
||||||
|
weight_momentum: int
|
||||||
|
entry_score: int
|
||||||
|
min_vol: float
|
||||||
|
whale_usd_threshold: float
|
||||||
|
whale_flow_pct: float
|
||||||
|
obi_threshold: float
|
||||||
|
spot_perp_threshold: float
|
||||||
|
|
||||||
|
|
||||||
|
# 基于 optuna_results_v3_cn.xlsx Top1 Summary + v53.json symbol_gates
|
||||||
|
SYMBOL_PROFILES: list[SymbolProfile] = [
|
||||||
|
SymbolProfile(
|
||||||
|
symbol="ETHUSDT",
|
||||||
|
weight_direction=51,
|
||||||
|
weight_env=18,
|
||||||
|
weight_aux=28,
|
||||||
|
weight_momentum=3,
|
||||||
|
entry_score=75,
|
||||||
|
min_vol=0.003,
|
||||||
|
whale_usd_threshold=50_000,
|
||||||
|
whale_flow_pct=0.5, # ALT 分支主要用 whale_usd_threshold,此处保持默认
|
||||||
|
obi_threshold=0.35,
|
||||||
|
spot_perp_threshold=0.005,
|
||||||
|
),
|
||||||
|
SymbolProfile(
|
||||||
|
symbol="XRPUSDT",
|
||||||
|
weight_direction=58,
|
||||||
|
weight_env=8,
|
||||||
|
weight_aux=32,
|
||||||
|
weight_momentum=2,
|
||||||
|
entry_score=80,
|
||||||
|
min_vol=0.0025,
|
||||||
|
whale_usd_threshold=30_000,
|
||||||
|
whale_flow_pct=0.5,
|
||||||
|
obi_threshold=0.40,
|
||||||
|
spot_perp_threshold=0.006,
|
||||||
|
),
|
||||||
|
SymbolProfile(
|
||||||
|
symbol="SOLUSDT",
|
||||||
|
weight_direction=38,
|
||||||
|
weight_env=42,
|
||||||
|
weight_aux=8,
|
||||||
|
weight_momentum=12,
|
||||||
|
entry_score=65,
|
||||||
|
min_vol=0.004,
|
||||||
|
whale_usd_threshold=20_000,
|
||||||
|
whale_flow_pct=0.5,
|
||||||
|
obi_threshold=0.45,
|
||||||
|
spot_perp_threshold=0.008,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# 与 BTC 工厂一致的 CVD 组合
|
||||||
|
CVD_COMBOS: list[tuple[str, str]] = [
|
||||||
|
("5m", "30m"),
|
||||||
|
("5m", "1h"),
|
||||||
|
("15m", "1h"),
|
||||||
|
("15m", "4h"),
|
||||||
|
("30m", "1h"),
|
||||||
|
("30m", "4h"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# TP 档位:保守 / 平衡 / 激进(统一 sl_multiplier=2.0)
|
||||||
|
TP_PROFILES: dict[str, dict[str, float]] = {
|
||||||
|
"保守": {"sl_atr_multiplier": 2.0, "tp1_ratio": 0.75, "tp2_ratio": 1.5},
|
||||||
|
"平衡": {"sl_atr_multiplier": 2.0, "tp1_ratio": 1.0, "tp2_ratio": 2.0},
|
||||||
|
"激进": {"sl_atr_multiplier": 2.0, "tp1_ratio": 1.5, "tp2_ratio": 2.5},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_display_name(symbol: str, fast_win: str, slow_win: str, tp_label: str) -> str:
|
||||||
|
"""
|
||||||
|
生成与 BTC 工厂一致的 display_name,例如:
|
||||||
|
BTC_CVD5x30m_TP保守 → ETH_CVD5x30m_TP保守
|
||||||
|
"""
|
||||||
|
base = symbol.replace("USDT", "")
|
||||||
|
fast_label = fast_win.replace("m", "") # "5m" → "5"
|
||||||
|
return f"{base}_CVD{fast_label}x{slow_win}_TP{tp_label}"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
created = 0
|
||||||
|
skipped = 0
|
||||||
|
|
||||||
|
with get_sync_conn() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
for profile in SYMBOL_PROFILES:
|
||||||
|
sym = profile.symbol
|
||||||
|
for fast_win, slow_win in CVD_COMBOS:
|
||||||
|
for tp_label, tp_cfg in TP_PROFILES.items():
|
||||||
|
display_name = build_display_name(sym, fast_win, slow_win, tp_label)
|
||||||
|
|
||||||
|
# 避免重复插入:按 display_name 检查
|
||||||
|
cur.execute(
|
||||||
|
"SELECT 1 FROM strategies WHERE display_name=%s",
|
||||||
|
(display_name,),
|
||||||
|
)
|
||||||
|
if cur.fetchone():
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO strategies (
|
||||||
|
display_name,
|
||||||
|
symbol,
|
||||||
|
direction,
|
||||||
|
cvd_fast_window,
|
||||||
|
cvd_slow_window,
|
||||||
|
weight_direction,
|
||||||
|
weight_env,
|
||||||
|
weight_aux,
|
||||||
|
weight_momentum,
|
||||||
|
entry_score,
|
||||||
|
gate_vol_enabled,
|
||||||
|
vol_atr_pct_min,
|
||||||
|
gate_cvd_enabled,
|
||||||
|
gate_whale_enabled,
|
||||||
|
whale_usd_threshold,
|
||||||
|
whale_flow_pct,
|
||||||
|
gate_obi_enabled,
|
||||||
|
obi_threshold,
|
||||||
|
gate_spot_perp_enabled,
|
||||||
|
spot_perp_threshold,
|
||||||
|
sl_atr_multiplier,
|
||||||
|
tp1_ratio,
|
||||||
|
tp2_ratio,
|
||||||
|
timeout_minutes,
|
||||||
|
flip_threshold
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
%s, -- display_name
|
||||||
|
%s, -- symbol
|
||||||
|
%s, -- direction
|
||||||
|
%s, -- cvd_fast_window
|
||||||
|
%s, -- cvd_slow_window
|
||||||
|
%s, -- weight_direction
|
||||||
|
%s, -- weight_env
|
||||||
|
%s, -- weight_aux
|
||||||
|
%s, -- weight_momentum
|
||||||
|
%s, -- entry_score
|
||||||
|
%s, -- gate_vol_enabled
|
||||||
|
%s, -- vol_atr_pct_min
|
||||||
|
%s, -- gate_cvd_enabled
|
||||||
|
%s, -- gate_whale_enabled
|
||||||
|
%s, -- whale_usd_threshold
|
||||||
|
%s, -- whale_flow_pct
|
||||||
|
%s, -- gate_obi_enabled
|
||||||
|
%s, -- obi_threshold
|
||||||
|
%s, -- gate_spot_perp_enabled
|
||||||
|
%s, -- spot_perp_threshold
|
||||||
|
%s, -- sl_atr_multiplier
|
||||||
|
%s, -- tp1_ratio
|
||||||
|
%s, -- tp2_ratio
|
||||||
|
%s, -- timeout_minutes
|
||||||
|
%s -- flip_threshold
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
display_name,
|
||||||
|
sym,
|
||||||
|
"both", # 方向:多空双向
|
||||||
|
fast_win,
|
||||||
|
slow_win,
|
||||||
|
profile.weight_direction,
|
||||||
|
profile.weight_env,
|
||||||
|
profile.weight_aux,
|
||||||
|
profile.weight_momentum,
|
||||||
|
profile.entry_score,
|
||||||
|
True, # gate_vol_enabled
|
||||||
|
profile.min_vol,
|
||||||
|
True, # gate_cvd_enabled
|
||||||
|
True, # gate_whale_enabled
|
||||||
|
profile.whale_usd_threshold,
|
||||||
|
profile.whale_flow_pct,
|
||||||
|
True, # gate_obi_enabled
|
||||||
|
profile.obi_threshold,
|
||||||
|
False, # gate_spot_perp_enabled(与当前 BTC 工厂一致,先关闭)
|
||||||
|
profile.spot_perp_threshold,
|
||||||
|
tp_cfg["sl_atr_multiplier"],
|
||||||
|
tp_cfg["tp1_ratio"],
|
||||||
|
tp_cfg["tp2_ratio"],
|
||||||
|
240, # timeout_minutes,沿用 BTC 工厂
|
||||||
|
80, # flip_threshold,沿用 v5.4 设计
|
||||||
|
),
|
||||||
|
)
|
||||||
|
created += 1
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print(f"[bootstrap] created={created}, skipped={skipped}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user