feat(crypto-tail): 策略最小价差(无/固定/自动) + 前端默认与文案
- 后端: 最小价差 DB/Entity/DTO、Binance K线 REST+WS、自动价差 IQR 预计算与执行时校验 - 前端: 最小价差(自动-固定-无),默认自动,label 旁 info 说明,选择自动不展示建议约 - i18n: minSpreadModeTip 说明不写死标的 - 文档: crypto-tail-strategy-min-spread-flow.md - scripts: Binance K线拉取与 WS 示例 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 通过币安 WebSocket 订阅 BTC/USDC 15 分钟 K 线推送。
|
||||
*
|
||||
* 文档: https://developers.binance.com/docs/binance-spot-api-docs/web-socket-streams
|
||||
* - 现货 K 线流: wss://stream.binance.com:9443/ws/btcusdc@kline_15m
|
||||
* - 服务端约每 20 秒发 ping,ws 库会自动回 pong
|
||||
* - K 线推送频率: 15m 约每 2 秒更新一次;x=true 表示该根 K 线已收盘
|
||||
*
|
||||
* 依赖: npm install ws(在 scripts 目录下执行)
|
||||
*
|
||||
* 使用方法:
|
||||
* node scripts/ws_binance_btc_usdc_klines.js
|
||||
* node scripts/ws_binance_btc_usdc_klines.js --interval 1m
|
||||
* node scripts/ws_binance_btc_usdc_klines.js --url "wss://stream.binance.com:9443/ws/btcusdc@kline_15m"
|
||||
* Ctrl+C 退出
|
||||
*/
|
||||
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const BINANCE_WS_BASE = 'wss://stream.binance.com:9443';
|
||||
|
||||
function parseArgs() {
|
||||
const args = process.argv.slice(2);
|
||||
const out = { symbol: 'btcusdc', interval: '15m', url: null };
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--symbol' && args[i + 1]) {
|
||||
out.symbol = String(args[i + 1]).toLowerCase();
|
||||
i++;
|
||||
} else if (args[i] === '--interval' && args[i + 1]) {
|
||||
out.interval = args[i + 1];
|
||||
i++;
|
||||
} else if (args[i] === '--url' && args[i + 1]) {
|
||||
out.url = args[i + 1];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function formatKline(msg) {
|
||||
if (msg.e !== 'kline') {
|
||||
return JSON.stringify(msg);
|
||||
}
|
||||
const k = msg.k || {};
|
||||
const tMs = Number(k.t) || 0;
|
||||
const tsStr = new Date(tMs).toISOString().replace('T', ' ').slice(0, 19);
|
||||
const closed = k.x ? ' [CLOSED]' : '';
|
||||
return ` ${tsStr} O:${k.o} H:${k.h} L:${k.l} C:${k.c} V:${k.v}${closed}`;
|
||||
}
|
||||
|
||||
function run(wsUrl) {
|
||||
console.log(`Connecting: ${wsUrl}`);
|
||||
console.log('(Ctrl+C to exit)\n');
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.on('open', () => {
|
||||
// ws 库收到 ping 会自动回 pong,无需手动处理
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
try {
|
||||
const msg = JSON.parse(data.toString());
|
||||
if (msg.result !== undefined && msg.id !== undefined) return;
|
||||
if (msg.code !== undefined && msg.code !== 0) {
|
||||
console.error('Error:', msg);
|
||||
return;
|
||||
}
|
||||
console.log(formatKline(msg));
|
||||
} catch {
|
||||
console.log(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (err) => {
|
||||
console.error('WebSocket error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
ws.on('close', (code, reason) => {
|
||||
if (code !== 1000) {
|
||||
console.error(`Connection closed: ${code} ${reason?.toString() || ''}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const args = parseArgs();
|
||||
const wsUrl = args.url || `${BINANCE_WS_BASE}/ws/${args.symbol}@kline_${args.interval}`;
|
||||
run(wsUrl);
|
||||
Reference in New Issue
Block a user