feat: 备份 crypto/ + stocks/ 子目录到 skill 仓库

【备份】cron 已迁到 ~/.hermes/scripts/symlink, 旧副本 ~/.hermes/scripts/crypto/ 和 stocks/ 即将删, 先备份
- crypto-t-monitor/scripts/backtest.py + okx_t_monitor.py
- intraday-trading/scripts/{hk,us}_intraday_cli.py + hk_intraday_cli_runner.sh
- strategy-management/scripts/backtest.py (与 crypto-t-monitor 重复, 备份占位)

【未删本地】等用户确认
This commit is contained in:
2026-07-24 12:06:22 +08:00
parent 80063a1cac
commit d331b4e682
6 changed files with 1877 additions and 0 deletions
+216
View File
@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""
OKX 币圈做T 回测工具 (v2.0.0)
基于历史 K 线模拟策略, 验证 buy/sell 价位参数
"""
import os, json, sys, argparse, datetime
sys.path.insert(0, os.path.dirname(__file__))
# 加载凭证
okx_creds = {}
with open(os.path.expanduser('~/.bashrc')) as f:
import re
for line in f:
m = re.match(r'export\s+(OKX_\w+)=(.*)', line.strip())
if m:
okx_creds[m.group(1)] = m.group(2).strip().strip('"').strip("'")
def fetch_history_klines(sym, bar='1H', days=30):
"""拉 OKX 历史 K 线 (OKX 限制单次 100 根, 多页拉)
用 OKX 的 'after' 参数翻页 (传毫秒时间戳)
"""
import subprocess
import hmac, base64, hashlib
all_data = []
# OKX 时间戳 (毫秒)
cur_ts = int(datetime.datetime.utcnow().timestamp() * 1000)
# 计算需要多少页 (1H K线, 24 根/天)
pages = max(1, (days * 24 + 99) // 100)
for page in range(pages):
path = f"/api/v5/market/history-candles?instId={sym}-USDT-SWAP&bar={bar}&limit=100&after={cur_ts}"
msg = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.') + \
f"{datetime.datetime.utcnow().microsecond // 1000:03d}Z" + 'GET' + path
signature = base64.b64encode(
hmac.new(okx_creds['OKX_SECRET'].encode(), msg.encode(), hashlib.sha256).digest()
).decode()
ts_str = msg[:30] # YYYY-MM-DDTHH:MM:SS.sssZ (但实际上 ms 只有 3 位 + Z)
# 修正: 用 'Z' 结尾的后 24 字节
curl_cmd = [
'curl', '-s', '--proxy', 'http://127.0.0.1:7890',
'-H', f'OK-ACCESS-KEY: {okx_creds["OKX_API_KEY"]}',
'-H', f'OK-ACCESS-SIGN: {signature}',
'-H', f'OK-ACCESS-TIMESTAMP: {ts_str}',
'-H', f'OK-ACCESS-PASSPHRASE: {okx_creds["OKX_PASSPHRASE"]}',
f'https://www.okx.com{path}'
]
try:
r = subprocess.run(curl_cmd, capture_output=True, text=True, timeout=20)
data = json.loads(r.stdout)
if data.get('code') == '0':
klines = data.get('data', [])
if not klines:
break
all_data.extend(klines)
# 翻页: after 是上一个数据最小时间戳 - 1
cur_ts = int(klines[-1][0]) - 1
if len(klines) < 100:
break
else:
print(f"⚠️ Page {page} code={data.get('code')} msg={data.get('msg')}")
break
except Exception as e:
print(f"⚠️ Page {page} failed: {e}")
break
print(f"📥 拉到 {len(all_data)} 根 K 线")
return all_data
def calc_atr(klines, period=14):
"""ATR 计算"""
if len(klines) < period + 1:
return None
closes = [float(k[4]) for k in klines]
highs = [float(k[2]) for k in klines]
lows = [float(k[3]) for k in klines]
trs = []
for i in range(1, len(closes)):
tr = max(highs[i] - lows[i],
abs(highs[i] - closes[i-1]),
abs(lows[i] - closes[i-1]))
trs.append(tr)
return sum(trs[-period:]) / period
def simulate_strategy(klines, atr_multiplier=0.5, t_qty=0.05, leverage=25, ct_val=0.1, initial_usdt=1000, threshold=0.003):
"""基于历史 K 线模拟做T策略
每小时检查价位:
- 跌到 buy1/buy2 → 买入
- 涨到 sell1/sell2 → 卖出
持仓同步变化 (跟 okx_t_monitor 一致)
"""
trades = []
position = 0
avg_cost = 0
last_trade_ts = None
for i in range(20, len(klines)):
row = klines[i]
ts = row[0]
high = float(row[2])
low = float(row[3])
close = float(row[4])
# 计算过去 14 根 K 线的 ATR
past = klines[i-20:i]
atr = calc_atr(past, 14)
if not atr:
continue
buy1 = close - atr * atr_multiplier * 0.5
buy2 = close - atr * atr_multiplier
sell1 = close + atr * atr_multiplier * 0.5
sell2 = close + atr * atr_multiplier
# 检查是否触及价位 (用 high/low 比对 close)
if last_trade_ts == ts:
continue
# 优先 sell1 > buy1 (趋势方向)
if position > 0 and (high >= sell2 or (high >= sell1 and position > 0)):
# 卖出
sell_price = sell2 if high >= sell2 else sell1
pnl = (sell_price - avg_cost) * position
trades.append(('sell', sell_price, position, pnl, ts))
position = 0
avg_cost = 0
last_trade_ts = ts
elif position == 0 and (low <= buy2 or low <= buy1):
buy_price = buy2 if low <= buy2 else buy1
position = t_qty
avg_cost = buy_price
trades.append(('buy', buy_price, position, None, ts))
last_trade_ts = ts
# 统计
total_pnl = sum(t[3] for t in trades if t[3] is not None)
buy_count = sum(1 for t in trades if t[0] == 'buy')
sell_count = sum(1 for t in trades if t[0] == 'sell')
win_trades = [t for t in trades if t[3] and t[3] > 0]
win_rate = len(win_trades) / sell_count * 100 if sell_count > 0 else 0
return {
'trades': trades,
'total_pnl': total_pnl,
'buy_count': buy_count,
'sell_count': sell_count,
'win_rate': win_rate,
'final_position': position,
'final_avg_cost': avg_cost,
}
def main():
parser = argparse.ArgumentParser(description='币圈做T回测 (v2.0.0)')
parser.add_argument('symbol', help='币种 (如 ETH)')
parser.add_argument('--mode', choices=['short', 'trend'], default='trend',
help='short=日内(1H,默认) / trend=趋势(4H,默认短期)')
parser.add_argument('--days', type=int, default=30, help='回测天数 (short=7, trend=30)')
parser.add_argument('--bar', default=None, help='K 线周期 (覆盖 mode 默认)')
parser.add_argument('--atr-multiplier', type=float, default=None, help='ATR 倍数')
parser.add_argument('--t-qty', type=float, default=0.05, help='每笔数量 (默认 0.05)')
parser.add_argument('--leverage', type=int, default=25, help='杠杆 (默认 25)')
parser.add_argument('--ct-val', type=float, default=0.1, help='合约面值 (默认 0.1)')
args = parser.parse_args()
# Mode-based defaults
if args.bar is None:
args.bar = '1H' if args.mode == 'short' else '4H'
if args.atr_multiplier is None:
# Trend: 更宽价位 (ATR × 1.5), 避免被洗
args.atr_multiplier = 0.5 if args.mode == 'short' else 1.5
if args.days == 30: # 如果用户没指定,按 mode
args.days = 7 if args.mode == 'short' else 30
print(f"📊 {args.symbol} {args.bar} 回测 ({args.days} 天, mode={args.mode})")
print(f" ATR={args.atr_multiplier} t_qty={args.t_qty} lev={args.leverage}x")
print()
# 拉数据
klines = fetch_history_klines(args.symbol, args.bar, args.days)
if not klines:
print("❌ 没拉到数据")
sys.exit(1)
print(f"✅ 拉到 {len(klines)} 根 K 线")
print()
# 模拟
result = simulate_strategy(klines, args.atr_multiplier, args.t_qty,
args.leverage, args.ct_val)
# 报告
print(f"📈 回测结果:")
print(f" 买入: {result['buy_count']}")
print(f" 卖出: {result['sell_count']}")
print(f" 胜率: {result['win_rate']:.1f}%")
print(f" 总盈亏: ${result['total_pnl']:.2f}")
print(f" 最终仓位: {result['final_position']}张 @ ${result['final_avg_cost']:.2f}" if result['final_position'] > 0 else " 最终仓位: 0 (全平)")
# Top 5 交易
closed = [t for t in result['trades'] if t[3] is not None]
if closed:
print()
print(f" Top 5 盈利交易:")
for t in sorted(closed, key=lambda x: -x[3])[:5]:
print(f" ${t[1]:.2f} | pnl ${t[3]:.2f} | {t[4]}")
if __name__ == '__main__':
main()