feat: 所有参数移至config.json, 重构退出逻辑, 清理遗留接口

- 将所有硬编码参数迁移到 config.json (手续费率、止盈/止损阈值、
  超时、腿间隔、加仓步进等)
- 退出条件: 净利 >= take_profit_pct 止盈, 价差 <= 0 平仓
- 删除 Binance/dYdX 遗留代码
- 更新 README 文档
- Dashboard: 双交易所价格表、黑名单UI、按币名排序持仓
- Bitget WS: 文本ping保活
- 数据库: 重置, 无历史仓位
This commit is contained in:
jackyu66git
2026-05-04 01:46:17 +08:00
parent 2ed6ffc747
commit 21a3f9a962
10 changed files with 450 additions and 206 deletions
+32 -10
View File
@@ -38,8 +38,8 @@ function updateClock() {
setInterval(updateClock, 1000);
updateClock();
const EXCHANGES = ['Binance', 'HyperLiquid', 'Bitget', 'dYdX'];
const COINS = ['DOGE', 'LINK', 'ONDO', 'OP', 'WIF', 'ARB'];
const EXCHANGES = ['HyperLiquid', 'Bitget'];
const COINS = []; // populated dynamically from SSE data
function formatPrice(p) {
if (p == null || p <= 0) return '-';
@@ -97,6 +97,13 @@ const eventHandlers = {};
eventHandlers.prices = (prices) => {
if (!prices || prices.length === 0) return;
// Dynamically populate COINS list on first data
if (COINS.length === 0) {
for (const row of prices) {
COINS.push(row.coin);
}
}
let html = '';
let coinsOnline = 0;
@@ -110,7 +117,6 @@ eventHandlers.prices = (prices) => {
const cells = EXCHANGES.map(ex => {
const p = row[ex];
const sp = row[ex + '_spread'];
const key = coin + '.' + ex;
const prev = priceCache[key];
const curP = p || 0;
@@ -131,9 +137,6 @@ eventHandlers.prices = (prices) => {
}
let display = formatPrice(p);
if (sp && sp > 0.01) {
display += `<span class="text-dim" style="font-size:10px"> (${sp.toFixed(3)}%)</span>`;
}
return `<td class="${cls}">${display}</td>`;
});
@@ -171,14 +174,17 @@ eventHandlers.arb = (opps) => {
els.arbBody.innerHTML = html;
};
// P3-3: Positions with live PnL
// P3-3: Positions with live PnL — sorted by time (oldest first)
eventHandlers.positions = (positions) => {
if (!positions || positions.length === 0) {
els.posBody.innerHTML = '<tr><td colspan="8" class="text-dim">无持仓</td></tr>';
return;
}
const html = positions.map(p => {
// Sort by coin name (stable, deterministic)
const sorted = [...positions].sort((a, b) => a.coin.localeCompare(b.coin));
const html = sorted.map(p => {
const pnl = p.pnl_est;
const pnlStr = pnl != null ? pnl.toFixed(4) + '%' : '-';
const curSpread = p.current_spread != null ? p.current_spread.toFixed(4) + '%' : '-';
@@ -209,8 +215,12 @@ eventHandlers.stats = (stats) => {
// Detailed PnL stats
if (stats.detail) {
const d = stats.detail;
$('stat-total-pnl').textContent = (d.total_pnl != null) ? d.total_pnl.toFixed(2) + '%' : '—';
$('stat-avg-pnl').textContent = (d.avg_pnl != null) ? d.avg_pnl.toFixed(2) + '%' : '—';
// Total PnL: show both USD and percentage of capital
const usdStr = (d.total_pnl_usd != null) ? '$' + d.total_pnl_usd.toFixed(2) : '—';
const pctStr = (d.capital_pnl != null) ? d.capital_pnl.toFixed(4) + '%' : '—';
$('stat-total-pnl').textContent = usdStr + ' (' + pctStr + ')';
$('stat-total-pnl').className = pnlClass(d.capital_pnl);
$('stat-capital').textContent = (stats.capital != null) ? '$' + stats.capital.toFixed(0) : '—';
$('stat-win-rate').textContent = (d.win_rate != null) ? d.win_rate.toFixed(1) + '%' : '—';
$('stat-max-profit').textContent = (d.max_profit != null) ? '+' + d.max_profit.toFixed(2) + '%' : '—';
$('stat-max-loss').textContent = (d.max_loss != null) ? d.max_loss.toFixed(2) + '%' : '—';
@@ -225,6 +235,18 @@ eventHandlers.stats = (stats) => {
}).join(' ');
els.connDetail.innerHTML = dots;
}
// Blacklist — stale spread coins
if (stats.blacklist && stats.blacklist.length > 0) {
const html = stats.blacklist.map(b => {
const minLeft = Math.floor(b.remaining_sec / 60);
const secLeft = b.remaining_sec % 60;
return `<span style="color:#f85149;margin-right:12px;font-size:13px">⛔ ${b.coin} (${b.since} 剩余 ${minLeft}:${secLeft.toString().padStart(2,'0')})</span>`;
}).join('');
$('bl-body').innerHTML = html;
} else {
$('bl-body').innerHTML = '<span class="text-dim">暂无</span>';
}
};
// P3-4: Real-time trade events
+12 -4
View File
@@ -27,14 +27,14 @@
<div class="stat"><label>收敛</label><span id="stat-converged" class="pct-green">0</span></div>
<div class="stat"><label>发散</label><span id="stat-diverged" class="pct-red">0</span></div>
<div class="stat"><label>持平</label><span id="stat-flat" class="pct-gray">0</span></div>
<div class="stat"><label>持仓</label><span id="stat-positions" class="pct-yellow">0</span></div>
<div class="stat"><label>持仓</label><span id="stat-positions" class="pct-yellow">0 / <span id="stat-max-pos">5</span></span></div>
<div class="stat"><label>币种</label><span id="stat-coins" class="pct-blue">0</span></div>
<div class="stat" id="conn-stats"><label>连接</label><span id="conn-detail"></span></div>
</div>
<!-- Detailed PnL stats -->
<div class="stats-row detail-stats" style="margin-top:4px;font-size:12px;opacity:0.85">
<div class="stat"><label>总PnL</label><span id="stat-total-pnl"></span></div>
<div class="stat"><label>平均PnL</label><span id="stat-avg-pnl"></span></div>
<div class="stat"><label>本金</label><span id="stat-capital"></span></div>
<div class="stat"><label>胜率</label><span id="stat-win-rate"></span></div>
<div class="stat"><label>最多盈利</label><span id="stat-max-profit"></span></div>
<div class="stat"><label>最多亏损</label><span id="stat-max-loss"></span></div>
@@ -42,16 +42,24 @@
</div>
</section>
<!-- Blacklist -->
<section class="card" id="bl-card">
<h2>⛔ 黑名单</h2>
<div class="stats-row" id="bl-body">
<span class="text-dim">暂无</span>
</div>
</section>
<!-- Price Table -->
<section class="card" id="prices-card">
<h2>💰 实时价格 <span id="prices-age" class="text-dim" style="font-size:11px"></span></h2>
<div class="table-wrap">
<table id="price-table">
<thead>
<tr><th>币种</th><th>Binance</th><th>HyperLiquid</th><th>Bitget</th><th>dYdX</th><th>BG↔HL价差</th></tr>
<tr><th>币种</th><th>HyperLiquid</th><th>Bitget</th><th>BG↔HL价差</th></tr>
</thead>
<tbody id="price-body">
<tr><td colspan="6" class="loading">等待数据...</td></tr>
<tr><td colspan="4" class="loading">等待数据...</td></tr>
</tbody>
</table>
</div>