212 lines
7.2 KiB
Python
212 lines
7.2 KiB
Python
import datetime
|
|
import json
|
|
import time
|
|
import requests
|
|
from loguru import logger
|
|
|
|
# ================== 通用请求头 ==================
|
|
headers = {
|
|
'accept': 'application/json, text/plain, */*',
|
|
'accept-language': 'zh,zh-CN;q=0.9,zh-HK;q=0.8,en;q=0.7',
|
|
'cache-control': 'no-cache',
|
|
'origin': 'https://www.okx.com',
|
|
'pragma': 'no-cache',
|
|
'priority': 'u=1, i',
|
|
'referer': 'https://www.okx.com/',
|
|
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"Windows"',
|
|
'sec-fetch-dest': 'empty',
|
|
'sec-fetch-mode': 'cors',
|
|
'sec-fetch-site': 'same-site',
|
|
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
|
|
}
|
|
|
|
|
|
# ================== 获取 OKX K线数据 ==================
|
|
def fetch_kline_okx(day: int, year: int = 2025, month: int = 9, period='15m', symbol="ETH-USDT"):
|
|
"""
|
|
从 OKX 获取指定日期的K线数据
|
|
:param day: 日期
|
|
:param year: 年份
|
|
:param month: 月份
|
|
:param period: K线周期 (如 '1m', '5m', '15m', '1H')
|
|
:param symbol: 交易对
|
|
"""
|
|
time_ser = datetime.datetime(year, month, day)
|
|
start_of_day = int(time_ser.replace(hour=0, minute=0, second=0, microsecond=0).timestamp() * 1000)
|
|
end_of_day = int(time_ser.replace(hour=23, minute=59, second=59, microsecond=0).timestamp() * 1000)
|
|
|
|
url = "https://www.okx.com/api/v5/market/candles"
|
|
params = {
|
|
"instId": symbol,
|
|
"bar": period,
|
|
"limit": "1000"
|
|
}
|
|
|
|
all_data = []
|
|
next_start = end_of_day
|
|
|
|
while True:
|
|
params["after"] = next_start
|
|
resp = requests.get(url, params=params, headers=headers)
|
|
data = resp.json().get("data", [])
|
|
|
|
if not data:
|
|
break
|
|
|
|
for d in data:
|
|
candle = {
|
|
"id": int(int(d[0]) / 1000), # 秒级时间戳
|
|
"open": d[1],
|
|
"high": d[2],
|
|
"low": d[3],
|
|
"close": d[4],
|
|
"volume": d[5]
|
|
}
|
|
all_data.append(candle)
|
|
|
|
next_start = int(data[-1][0])
|
|
if len(data) < 1000:
|
|
break
|
|
|
|
# OKX返回数据按时间倒序排列
|
|
return sorted(all_data, key=lambda x: x['id'])
|
|
|
|
|
|
# ================== 辅助函数 ==================
|
|
def is_bullish(candle):
|
|
"""判断是否是阳线(涨)"""
|
|
return float(candle['open']) < float(candle['close'])
|
|
|
|
|
|
def is_bearish(candle):
|
|
"""判断是否是阴线(跌)"""
|
|
return float(candle['open']) > float(candle['close'])
|
|
|
|
|
|
def check_signal(prev, curr):
|
|
"""
|
|
判断包住形态信号:
|
|
1. 前跌后涨包住 -> 做多信号 (bear_bull_engulf)
|
|
2. 前涨后跌包住 -> 做空信号 (bull_bear_engulf)
|
|
"""
|
|
p_open, p_close = float(prev['open']), float(prev['close'])
|
|
c_open, c_close = float(curr['open']), float(curr['close'])
|
|
|
|
# 前跌后涨包住 -> 做多
|
|
if is_bullish(curr) and is_bearish(prev):
|
|
if c_open < p_close and c_close > p_open:
|
|
return "long", "bear_bull_engulf"
|
|
|
|
# 前涨后跌包住 -> 做空
|
|
if is_bearish(curr) and is_bullish(prev):
|
|
if c_open > p_close and c_close < p_open:
|
|
return "short", "bull_bear_engulf"
|
|
|
|
return None, None
|
|
|
|
|
|
def simulate_trade(direction, entry_price, future_candles, take_profit_diff=30, stop_loss_diff=-10):
|
|
"""
|
|
模拟逐根K线的止盈止损
|
|
"""
|
|
for candle in future_candles:
|
|
high, low, close = float(candle['high']), float(candle['low']), float(candle['close'])
|
|
|
|
if direction == "long":
|
|
if high >= entry_price + take_profit_diff:
|
|
return entry_price + take_profit_diff, take_profit_diff, candle['id'] # 止盈
|
|
if low <= entry_price + stop_loss_diff:
|
|
return entry_price + stop_loss_diff, stop_loss_diff, candle['id'] # 止损
|
|
|
|
elif direction == "short":
|
|
if low <= entry_price - take_profit_diff:
|
|
return entry_price - take_profit_diff, take_profit_diff, candle['id']
|
|
if high >= entry_price - stop_loss_diff:
|
|
return entry_price - stop_loss_diff, stop_loss_diff, candle['id']
|
|
|
|
final_price = float(future_candles[-1]['close'])
|
|
diff_money = (final_price - entry_price) if direction == "long" else (entry_price - final_price)
|
|
return final_price, diff_money, future_candles[-1]['id']
|
|
|
|
|
|
# ================== 主程序 ==================
|
|
if __name__ == '__main__':
|
|
all_trades = []
|
|
signal_stats = {
|
|
"bear_bull_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "涨包跌"},
|
|
"bull_bear_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "跌包涨"}
|
|
}
|
|
|
|
datas = []
|
|
for i in range(1, 31):
|
|
logger.info(f"Fetching 2025-09-{i}")
|
|
sorted_data = fetch_kline_okx(year=2025, month=1, day=i, period='15m', symbol="ETH-USDT")
|
|
datas.extend(sorted_data)
|
|
time.sleep(0.3)
|
|
|
|
# # 可选:保存数据方便离线分析
|
|
# with open("okx_eth_15m_sep2025.json", "w", encoding="utf-8") as f:
|
|
# json.dump(datas, f, ensure_ascii=False, indent=2)
|
|
|
|
datas = sorted(datas, key=lambda x: x["id"])
|
|
|
|
daily_signals = daily_wins = daily_profit = 0
|
|
|
|
for idx in range(1, len(datas) - 2):
|
|
prev, curr = datas[idx - 1], datas[idx]
|
|
entry_candle = datas[idx + 1]
|
|
future_candles = datas[idx + 2:]
|
|
|
|
entry_open = float(entry_candle['open'])
|
|
direction, signal_type = check_signal(prev, curr)
|
|
|
|
if direction and signal_type:
|
|
daily_signals += 1
|
|
exit_price, diff, exit_time = simulate_trade(direction, entry_open, future_candles,
|
|
take_profit_diff=30, stop_loss_diff=-2)
|
|
|
|
signal_stats[signal_type]["count"] += 1
|
|
signal_stats[signal_type]["total_profit"] += diff
|
|
if diff > 0:
|
|
signal_stats[signal_type]["wins"] += 1
|
|
daily_wins += 1
|
|
|
|
daily_profit += diff
|
|
|
|
local_time = datetime.datetime.fromtimestamp(entry_candle['id'])
|
|
formatted_time = local_time.strftime("%Y-%m-%d %H:%M")
|
|
|
|
all_trades.append((
|
|
formatted_time,
|
|
"做多" if direction == "long" else "做空",
|
|
signal_stats[signal_type]["name"],
|
|
entry_open,
|
|
exit_price,
|
|
diff,
|
|
exit_time
|
|
))
|
|
|
|
# ================= 输出交易详情 =================
|
|
logger.info("===== 每笔交易详情 =====")
|
|
n = n1 = 0
|
|
for date, direction, signal_name, entry, exit, diff, end_time in all_trades:
|
|
profit_amount = diff / entry * 10000
|
|
close_fee = 10000 / entry * exit * 0.0005
|
|
|
|
logger.info(
|
|
f"{date} {direction}({signal_name}) 入场={entry:.2f} 出场={exit:.2f} 出场时间={end_time} "
|
|
f"差价={diff:.2f} 盈利={profit_amount:.2f} "
|
|
f"开仓手续费=5u 平仓手续费={close_fee:.2f}"
|
|
)
|
|
n1 += 5 + close_fee
|
|
n += profit_amount
|
|
|
|
print("=" * 60)
|
|
print(f"交易总数:{len(all_trades)}")
|
|
print(f"总盈利:{n:.2f} u")
|
|
print(f"总手续费:{n1:.2f} u")
|
|
print(f"净利润:{n - n1:.2f} u")
|
|
print("=" * 60)
|