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)