2025-12-19 19:16:41 +08:00
|
|
|
|
import csv
|
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 配置 ----------------
|
|
|
|
|
|
CSV_FILE = "bitmart/kline_3.csv" # 你的 CSV 文件路径
|
|
|
|
|
|
LEVERAGE = 100
|
|
|
|
|
|
CAPITAL = 10000
|
|
|
|
|
|
POSITION_RATIO = 0.01
|
|
|
|
|
|
FEE_RATE = 0.00015
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 读取 CSV ----------------
|
|
|
|
|
|
data = []
|
|
|
|
|
|
with open(CSV_FILE, 'r') as f:
|
|
|
|
|
|
reader = csv.DictReader(f)
|
|
|
|
|
|
for row in reader:
|
|
|
|
|
|
ts = int(row['id'])
|
|
|
|
|
|
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
|
|
|
|
|
|
if dt.year == 2025 and dt.month == 1:
|
|
|
|
|
|
data.append({
|
|
|
|
|
|
'time': dt,
|
|
|
|
|
|
'open': float(row['open']),
|
|
|
|
|
|
'high': float(row['high']),
|
|
|
|
|
|
'low': float(row['low']),
|
|
|
|
|
|
'close': float(row['close']),
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 识别交易信号 ----------------
|
|
|
|
|
|
trades = []
|
|
|
|
|
|
position_usdt = CAPITAL * POSITION_RATIO
|
|
|
|
|
|
leveraged_position = position_usdt * LEVERAGE
|
|
|
|
|
|
|
|
|
|
|
|
for i in range(len(data)-1):
|
|
|
|
|
|
k1 = data[i]
|
|
|
|
|
|
k2 = data[i+1]
|
|
|
|
|
|
|
|
|
|
|
|
# 刺透形态(Piercing Line,多头)
|
|
|
|
|
|
if k1['close'] < k1['open'] and k2['close'] > k2['open']:
|
|
|
|
|
|
midpoint = (k1['open'] + k1['close']) / 2
|
|
|
|
|
|
if k2['open'] < k1['close'] and k2['close'] > midpoint:
|
|
|
|
|
|
trades.append({
|
|
|
|
|
|
'方向': '多',
|
|
|
|
|
|
'开仓时间': k2['time'],
|
|
|
|
|
|
'开仓价格': k2['open'],
|
|
|
|
|
|
'平仓时间': k2['time'],
|
|
|
|
|
|
'平仓价格': k2['close']
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# 乌云盖顶(Dark Cloud Cover,空头)
|
|
|
|
|
|
elif k1['close'] > k1['open'] and k2['close'] < k2['open']:
|
|
|
|
|
|
midpoint = (k1['open'] + k1['close']) / 2
|
|
|
|
|
|
if k2['open'] > k1['close'] and k2['close'] < midpoint:
|
|
|
|
|
|
trades.append({
|
|
|
|
|
|
'方向': '空',
|
|
|
|
|
|
'开仓时间': k2['time'],
|
|
|
|
|
|
'开仓价格': k2['open'],
|
|
|
|
|
|
'平仓时间': k2['time'],
|
|
|
|
|
|
'平仓价格': k2['close']
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 绘制 K 线图 ----------------
|
|
|
|
|
|
plt.figure(figsize=(16,6))
|
|
|
|
|
|
|
|
|
|
|
|
times = [k['time'] for k in data]
|
|
|
|
|
|
opens = [k['open'] for k in data]
|
|
|
|
|
|
closes = [k['close'] for k in data]
|
|
|
|
|
|
highs = [k['high'] for k in data]
|
|
|
|
|
|
lows = [k['low'] for k in data]
|
|
|
|
|
|
|
|
|
|
|
|
# 绘制 K 线(用竖线表示最高最低价,用矩形表示开收盘价)
|
|
|
|
|
|
for i in range(len(data)):
|
|
|
|
|
|
color = 'green' if closes[i] >= opens[i] else 'red'
|
|
|
|
|
|
plt.plot([times[i], times[i]], [lows[i], highs[i]], color='black') # 高低价
|
|
|
|
|
|
plt.plot([times[i]-0.0005, times[i]+0.0005], [opens[i], opens[i]], color=color, linewidth=5) # 开盘价
|
|
|
|
|
|
plt.plot([times[i]-0.0005, times[i]+0.0005], [closes[i], closes[i]], color=color, linewidth=5) # 收盘价
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 标注交易信号 ----------------
|
|
|
|
|
|
for t in trades:
|
|
|
|
|
|
if t['方向'] == '多':
|
|
|
|
|
|
plt.scatter(t['开仓时间'], t['开仓价格'], color='green', marker='^', s=100, label='多开' if '多开' not in plt.gca().get_legend_handles_labels()[1] else "")
|
|
|
|
|
|
plt.scatter(t['平仓时间'], t['平仓价格'], color='red', marker='v', s=100, label='多平' if '多平' not in plt.gca().get_legend_handles_labels()[1] else "")
|
|
|
|
|
|
else:
|
|
|
|
|
|
plt.scatter(t['开仓时间'], t['开仓价格'], color='red', marker='v', s=100, label='空开' if '空开' not in plt.gca().get_legend_handles_labels()[1] else "")
|
|
|
|
|
|
plt.scatter(t['平仓时间'], t['平仓价格'], color='green', marker='^', s=100, label='空平' if '空平' not in plt.gca().get_legend_handles_labels()[1] else "")
|
|
|
|
|
|
|
|
|
|
|
|
plt.xlabel('时间')
|
|
|
|
|
|
plt.ylabel('价格(USDT)')
|
|
|
|
|
|
plt.title('ETH 永续合约 1 月交易回测(刺透 & 乌云形态)')
|
|
|
|
|
|
plt.legend()
|
|
|
|
|
|
plt.grid(True)
|
|
|
|
|
|
plt.gcf().autofmt_xdate()
|
|
|
|
|
|
plt.show()
|