hahaa
This commit is contained in:
79
adaptive_third_strategy/README.md
Normal file
79
adaptive_third_strategy/README.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 自适应三分位趋势策略
|
||||
|
||||
优化版实盘策略:趋势过滤 + 动态阈值 + 信号确认 + 完整风险管理。
|
||||
|
||||
## 目录结构
|
||||
|
||||
- `config.py` - 策略参数(趋势模式、止损止盈、确认条件等)
|
||||
- `indicators.py` - EMA、ATR 指标
|
||||
- `data_fetcher.py` - **Bitmart 方式**拉取 5/15/60 分钟 K 线(API 或 CSV)
|
||||
- `strategy_core.py` - 趋势判断、有效 K 线、动态触发价、信号确认
|
||||
- `backtest.py` - 回测引擎(硬止损、止盈、移动止损、时间止损)
|
||||
- `trade.py` - 实盘交易入口(拉数据 + 信号,开平仓需对接现有 Bitmart 下单逻辑)
|
||||
- `data/` - 回测用 K 线 CSV(可选,无则 API 拉取)
|
||||
|
||||
## 回测(先出数据再回测)
|
||||
|
||||
在**项目根目录** `lm_code` 下执行:
|
||||
|
||||
```bash
|
||||
# 使用 API 拉取数据并回测(会请求 Bitmart 接口)
|
||||
python adaptive_third_strategy/backtest.py --start 2025-01-01 --end 2025-01-30 --capital 10000
|
||||
|
||||
# 仅用本地已有 CSV 回测(需先抓过数据)
|
||||
python adaptive_third_strategy/backtest.py --start 2025-01-01 --end 2025-01-30 --no-api
|
||||
```
|
||||
|
||||
数据会保存到 `adaptive_third_strategy/data/kline_5m.csv`、`kline_15m.csv`、`kline_60m.csv`。
|
||||
回测结果输出到控制台,交易明细保存到 `adaptive_third_strategy/backtest_trades.csv`。
|
||||
|
||||
## 仅抓取数据(Bitmart 方式)
|
||||
|
||||
与 `bitmart/回测.py`、`bitmart/抓取多周期K线.py` 相同的 API 调用方式:
|
||||
|
||||
```bash
|
||||
cd adaptive_third_strategy && python -c "
|
||||
from data_fetcher import fetch_multi_timeframe, save_klines_csv
|
||||
import time, os
|
||||
end_ts = int(time.time())
|
||||
start_ts = end_ts - 30*24*3600
|
||||
data = fetch_multi_timeframe(start_ts, end_ts, [5, 15, 60])
|
||||
os.makedirs('data', exist_ok=True)
|
||||
for step, klines in data.items():
|
||||
save_klines_csv(klines, f'data/kline_{step}m.csv')
|
||||
"
|
||||
```
|
||||
|
||||
需在项目根目录 `lm_code` 下执行时,先 `import sys; sys.path.insert(0, '..')` 或使用:
|
||||
|
||||
```bash
|
||||
python -c "
|
||||
import sys, os
|
||||
sys.path.insert(0, os.getcwd())
|
||||
from adaptive_third_strategy.data_fetcher import fetch_multi_timeframe, save_klines_csv
|
||||
import time
|
||||
end_ts = int(time.time())
|
||||
start_ts = end_ts - 30*24*3600
|
||||
data = fetch_multi_timeframe(start_ts, end_ts, [5, 15, 60])
|
||||
os.makedirs('adaptive_third_strategy/data', exist_ok=True)
|
||||
for step, klines in data.items():
|
||||
save_klines_csv(klines, f'adaptive_third_strategy/data/kline_{step}m.csv')
|
||||
"
|
||||
```
|
||||
|
||||
## 实盘
|
||||
|
||||
```bash
|
||||
python adaptive_third_strategy/trade.py
|
||||
```
|
||||
|
||||
当前 `trade.py` 只做:拉 5/15/60 数据、算信号、打日志/钉钉。实际开平仓需在 `run_loop` 里接入你现有的 Bitmart 下单方式(如 `交易/bitmart-三分之一策略交易.py` 的浏览器点击或 API 下单)。
|
||||
|
||||
## 策略要点
|
||||
|
||||
- **数据与周期**:主周期 5 分钟,趋势用 15 分钟 / 1 小时 EMA。
|
||||
- **有效 K 线**:实体 ≥ max(ATR×0.1, 价格×0.05%)。
|
||||
- **趋势过滤**:长期 1h EMA50/200,中期 15m EMA20/50,短期 5m 收盘 vs EMA9;保守模式要求中期+短期一致。
|
||||
- **动态触发**:波动率系数 clamp(实体/ATR, 0.3, 3.0),顺势方向更易触发。
|
||||
- **信号确认**:收盘价、成交量、动量等至少满足 2 项。
|
||||
- **风控**:硬止损 ATR×2、时间止损 3 根 K、移动止损(盈利 1×ATR 后 0.5×ATR 跟踪)、止盈目标 1.5/3/5×ATR。
|
||||
2
adaptive_third_strategy/__init__.py
Normal file
2
adaptive_third_strategy/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""自适应三分位趋势策略"""
|
||||
464
adaptive_third_strategy/backtest.py
Normal file
464
adaptive_third_strategy/backtest.py
Normal file
@@ -0,0 +1,464 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
自适应三分位趋势策略 - 回测引擎
|
||||
使用 Bitmart 方式获取数据(API 或 CSV),含硬止损、分批止盈、移动止损、时间止损
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
import datetime
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
|
||||
# 使用 UTC 时区,避免 utcfromtimestamp 弃用警告
|
||||
def _utc_dt(ts):
|
||||
if hasattr(datetime, "timezone") and hasattr(datetime.timezone, "utc"):
|
||||
return datetime.datetime.fromtimestamp(ts, tz=datetime.timezone.utc)
|
||||
return datetime.datetime.utcfromtimestamp(ts)
|
||||
|
||||
try:
|
||||
from loguru import logger
|
||||
except ImportError:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if ROOT_DIR not in sys.path:
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
from adaptive_third_strategy.config import (
|
||||
STEP_5M,
|
||||
STEP_15M,
|
||||
STEP_60M,
|
||||
ATR_PERIOD,
|
||||
EMA_SHORT,
|
||||
EMA_MID_FAST,
|
||||
EMA_MID_SLOW,
|
||||
EMA_LONG_FAST,
|
||||
EMA_LONG_SLOW,
|
||||
STOP_LOSS_ATR_MULT,
|
||||
TIME_STOP_BARS,
|
||||
TRAIL_START_ATR,
|
||||
TRAIL_ATR_MULT,
|
||||
TP1_ATR,
|
||||
TP2_ATR,
|
||||
TP3_ATR,
|
||||
TP1_RATIO,
|
||||
TP2_RATIO,
|
||||
TP3_RATIO,
|
||||
MIN_BARS_SINCE_ENTRY,
|
||||
SAME_KLINE_NO_REVERSE,
|
||||
REVERSE_BREAK_MULT,
|
||||
REVERSE_LOSS_ATR,
|
||||
MAX_POSITION_PERCENT,
|
||||
BASE_POSITION_PERCENT,
|
||||
CONTRACT_SIZE,
|
||||
FEE_RATE,
|
||||
FEE_FIXED,
|
||||
FEE_FIXED_BACKTEST,
|
||||
MIN_BARS_BETWEEN_TRADES,
|
||||
SLIPPAGE_POINTS,
|
||||
)
|
||||
from adaptive_third_strategy.indicators import get_ema_atr_from_klines, align_higher_tf_ema
|
||||
from adaptive_third_strategy.strategy_core import (
|
||||
check_trigger,
|
||||
get_body_size,
|
||||
build_volume_ma,
|
||||
)
|
||||
from adaptive_third_strategy.data_fetcher import (
|
||||
fetch_multi_timeframe,
|
||||
load_klines_csv,
|
||||
save_klines_csv,
|
||||
)
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def ensure_data(
|
||||
start_time: int,
|
||||
end_time: int,
|
||||
data_dir: str,
|
||||
use_api: bool = True,
|
||||
api_key: str = "",
|
||||
secret_key: str = "",
|
||||
) -> Tuple[List[Dict], List[Dict], List[Dict]]:
|
||||
"""
|
||||
确保有 5/15/60 分钟数据。若 data_dir 下已有 CSV 则加载,否则用 API 拉取并保存。
|
||||
use_api=False 时仅从 CSV 加载(需事先抓取)。
|
||||
"""
|
||||
data_dir = data_dir or os.path.join(SCRIPT_DIR, "data")
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
paths = {
|
||||
5: os.path.join(data_dir, "kline_5m.csv"),
|
||||
15: os.path.join(data_dir, "kline_15m.csv"),
|
||||
60: os.path.join(data_dir, "kline_60m.csv"),
|
||||
}
|
||||
k5 = load_klines_csv(paths[5])
|
||||
k15 = load_klines_csv(paths[15])
|
||||
k60 = load_klines_csv(paths[60])
|
||||
need_fetch = not k5 or not k15 or not k60
|
||||
if need_fetch and use_api:
|
||||
from adaptive_third_strategy.data_fetcher import DEFAULT_API_KEY, DEFAULT_SECRET_KEY, DEFAULT_MEMO
|
||||
key = api_key or DEFAULT_API_KEY
|
||||
sec = secret_key or DEFAULT_SECRET_KEY
|
||||
data = fetch_multi_timeframe(start_time, end_time, [5, 15, 60], key, sec, DEFAULT_MEMO)
|
||||
k5, k15, k60 = data[5], data[15], data[60]
|
||||
for step, kl in [(5, k5), (15, k15), (60, k60)]:
|
||||
save_klines_csv(kl, paths[step])
|
||||
# 按时间范围过滤(end_time 若为次日 0 点则用 < 以只含 end 日及之前)
|
||||
k5 = [x for x in k5 if start_time <= x["id"] < end_time]
|
||||
k15 = [x for x in k15 if start_time <= x["id"] < end_time]
|
||||
k60 = [x for x in k60 if start_time <= x["id"] < end_time]
|
||||
k5.sort(key=lambda x: x["id"])
|
||||
k15.sort(key=lambda x: x["id"])
|
||||
k60.sort(key=lambda x: x["id"])
|
||||
return k5, k15, k60
|
||||
|
||||
|
||||
def run_backtest(
|
||||
klines_5m: List[Dict],
|
||||
klines_15m: List[Dict],
|
||||
klines_60m: List[Dict],
|
||||
initial_capital: float = 10000.0,
|
||||
use_stop_loss: bool = True,
|
||||
use_take_profit: bool = True,
|
||||
use_trailing_stop: bool = True,
|
||||
use_time_stop: bool = True,
|
||||
fixed_margin_usdt: Optional[float] = None,
|
||||
leverage: Optional[float] = None,
|
||||
deduct_fee: bool = True,
|
||||
) -> Tuple[List[Dict], Dict, List[Dict]]:
|
||||
"""
|
||||
回测主循环。
|
||||
- 若指定 fixed_margin_usdt 与 leverage,则每笔开仓名义价值 = fixed_margin_usdt * leverage(如 50U 一百倍 = 5000U)。
|
||||
- deduct_fee=False 时不扣手续费,总盈利 = 所有 money_pnl 之和。
|
||||
"""
|
||||
if len(klines_5m) < ATR_PERIOD + 50:
|
||||
logger.warning("5分钟K线数量不足")
|
||||
return [], {}, klines_5m
|
||||
|
||||
ema_5m, atr_5m = get_ema_atr_from_klines(klines_5m, EMA_SHORT, ATR_PERIOD)
|
||||
ema_15m_align = align_higher_tf_ema(klines_5m, klines_15m, EMA_MID_FAST, EMA_MID_SLOW)
|
||||
ema_60m_align = align_higher_tf_ema(klines_5m, klines_60m, EMA_LONG_FAST, EMA_LONG_SLOW)
|
||||
volume_ma = build_volume_ma(klines_5m)
|
||||
|
||||
trades: List[Dict] = []
|
||||
position: Optional[Dict] = None
|
||||
last_trade_bar_idx: Optional[int] = None
|
||||
last_close_bar_idx: Optional[int] = None
|
||||
equity_curve: List[float] = [initial_capital]
|
||||
capital = initial_capital
|
||||
# 每笔固定名义价值(USDT):50U 一百倍 = 5000
|
||||
fixed_notional = (fixed_margin_usdt * leverage) if (fixed_margin_usdt is not None and leverage is not None) else None
|
||||
use_fee = deduct_fee
|
||||
fee_fixed = 0 if not use_fee else (FEE_FIXED_BACKTEST if FEE_FIXED_BACKTEST is not None else FEE_FIXED)
|
||||
|
||||
def _size_usdt(cap: float) -> float:
|
||||
if fixed_notional is not None:
|
||||
return fixed_notional
|
||||
return min(cap * MAX_POSITION_PERCENT, cap * BASE_POSITION_PERCENT)
|
||||
|
||||
def _fee(sz: float) -> float:
|
||||
return 0 if not use_fee else (fee_fixed + sz * FEE_RATE * 2)
|
||||
|
||||
for idx in range(ATR_PERIOD, len(klines_5m)):
|
||||
just_reversed = False
|
||||
curr = klines_5m[idx]
|
||||
bar_id = curr["id"]
|
||||
high, low, close = float(curr["high"]), float(curr["low"]), float(curr["close"])
|
||||
atr_val = atr_5m[idx]
|
||||
if atr_val is None or atr_val <= 0:
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
|
||||
# ---------- 持仓管理:止损 / 止盈 / 移动止损 / 时间止损 ----------
|
||||
if position is not None:
|
||||
pos_dir = position["direction"]
|
||||
entry_price = position["entry_price"]
|
||||
entry_idx = position["entry_bar_idx"]
|
||||
entry_atr = position["entry_atr"]
|
||||
stop_price = position.get("stop_price")
|
||||
trail_activated = position.get("trail_activated", False)
|
||||
exit_reason = None
|
||||
exit_price = close
|
||||
|
||||
if pos_dir == "long":
|
||||
# 硬止损
|
||||
if use_stop_loss and stop_price is not None and low <= stop_price:
|
||||
exit_price = min(stop_price, high)
|
||||
exit_reason = "stop_loss"
|
||||
# 止盈(简化:首次触及任一目标即全平)
|
||||
elif use_take_profit:
|
||||
tp1 = entry_price + entry_atr * TP1_ATR
|
||||
tp2 = entry_price + entry_atr * TP2_ATR
|
||||
tp3 = entry_price + entry_atr * TP3_ATR
|
||||
if high >= tp3:
|
||||
exit_price = tp3
|
||||
exit_reason = "tp3"
|
||||
elif high >= tp2:
|
||||
exit_price = tp2
|
||||
exit_reason = "tp2"
|
||||
elif high >= tp1:
|
||||
exit_price = tp1
|
||||
exit_reason = "tp1"
|
||||
# 移动止损
|
||||
if use_trailing_stop and not exit_reason:
|
||||
if close >= entry_price + entry_atr * TRAIL_START_ATR:
|
||||
trail_activated = True
|
||||
position["trail_activated"] = True
|
||||
trail_stop = close - entry_atr * TRAIL_ATR_MULT
|
||||
if low <= trail_stop:
|
||||
exit_price = trail_stop
|
||||
exit_reason = "trail_stop"
|
||||
# 时间止损
|
||||
if use_time_stop and not exit_reason and (idx - entry_idx) >= TIME_STOP_BARS:
|
||||
if close <= entry_price:
|
||||
exit_price = close
|
||||
exit_reason = "time_stop"
|
||||
else:
|
||||
if use_stop_loss and stop_price is not None and high >= stop_price:
|
||||
exit_price = max(stop_price, low)
|
||||
exit_reason = "stop_loss"
|
||||
elif use_take_profit:
|
||||
tp1 = entry_price - entry_atr * TP1_ATR
|
||||
tp2 = entry_price - entry_atr * TP2_ATR
|
||||
tp3 = entry_price - entry_atr * TP3_ATR
|
||||
if low <= tp3:
|
||||
exit_price = tp3
|
||||
exit_reason = "tp3"
|
||||
elif low <= tp2:
|
||||
exit_price = tp2
|
||||
exit_reason = "tp2"
|
||||
elif low <= tp1:
|
||||
exit_price = tp1
|
||||
exit_reason = "tp1"
|
||||
if use_trailing_stop and not exit_reason:
|
||||
if close <= entry_price - entry_atr * TRAIL_START_ATR:
|
||||
trail_activated = True
|
||||
position["trail_activated"] = True
|
||||
trail_stop = close + entry_atr * TRAIL_ATR_MULT
|
||||
if high >= trail_stop:
|
||||
exit_price = trail_stop
|
||||
exit_reason = "trail_stop"
|
||||
if use_time_stop and not exit_reason and (idx - entry_idx) >= TIME_STOP_BARS:
|
||||
if close >= entry_price:
|
||||
exit_price = close
|
||||
exit_reason = "time_stop"
|
||||
|
||||
if exit_reason:
|
||||
# 平仓
|
||||
if pos_dir == "long":
|
||||
point_pnl = exit_price - entry_price
|
||||
else:
|
||||
point_pnl = entry_price - exit_price
|
||||
size_usdt = position.get("size_usdt", _size_usdt(capital))
|
||||
contract_val = CONTRACT_SIZE / entry_price
|
||||
money_pnl = point_pnl / entry_price * size_usdt
|
||||
fee = _fee(size_usdt)
|
||||
net = money_pnl - fee
|
||||
capital += net
|
||||
trades.append({
|
||||
"direction": "做多" if pos_dir == "long" else "做空",
|
||||
"entry_time": _utc_dt(position["entry_time"]),
|
||||
"exit_time": _utc_dt(bar_id),
|
||||
"entry_price": entry_price,
|
||||
"exit_price": exit_price,
|
||||
"point_pnl": point_pnl,
|
||||
"money_pnl": money_pnl,
|
||||
"fee": fee,
|
||||
"net_profit": net,
|
||||
"exit_reason": exit_reason,
|
||||
"hold_bars": idx - entry_idx,
|
||||
})
|
||||
position = None
|
||||
last_close_bar_idx = idx
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
|
||||
# ---------- 信号检测 ----------
|
||||
direction, trigger_price, valid_prev_idx, valid_prev = check_trigger(
|
||||
klines_5m, idx, atr_5m, ema_5m, ema_15m_align, ema_60m_align, volume_ma, use_confirm=True
|
||||
)
|
||||
if direction is None:
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
if SAME_KLINE_NO_REVERSE and last_trade_bar_idx == idx:
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
if position is not None:
|
||||
if direction == position["direction"]:
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
# 反手条件
|
||||
bars_since = idx - position["entry_bar_idx"]
|
||||
if bars_since < MIN_BARS_SINCE_ENTRY:
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
entry_atr_pos = position.get("entry_atr") or atr_val
|
||||
pos_loss_atr = (position["entry_price"] - close) / entry_atr_pos if position["direction"] == "long" else (close - position["entry_price"]) / entry_atr_pos
|
||||
if pos_loss_atr < REVERSE_LOSS_ATR:
|
||||
# 可选:反向突破幅度 > 实体/2 才反手
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
# 先平仓再开仓(下面统一开仓)
|
||||
# 简化:这里直接平仓记一笔,再开新仓
|
||||
exit_price = close
|
||||
if position["direction"] == "long":
|
||||
point_pnl = exit_price - position["entry_price"]
|
||||
else:
|
||||
point_pnl = position["entry_price"] - exit_price
|
||||
size_usdt = position.get("size_usdt", _size_usdt(capital))
|
||||
money_pnl = point_pnl / position["entry_price"] * size_usdt
|
||||
fee = _fee(size_usdt)
|
||||
net = money_pnl - fee
|
||||
capital += net
|
||||
trades.append({
|
||||
"direction": "做多" if position["direction"] == "long" else "做空",
|
||||
"entry_time": _utc_dt(position["entry_time"]),
|
||||
"exit_time": _utc_dt(bar_id),
|
||||
"entry_price": position["entry_price"],
|
||||
"exit_price": exit_price,
|
||||
"point_pnl": point_pnl,
|
||||
"money_pnl": money_pnl,
|
||||
"fee": fee,
|
||||
"net_profit": net,
|
||||
"exit_reason": "reverse",
|
||||
"hold_bars": idx - position["entry_bar_idx"],
|
||||
})
|
||||
position = None
|
||||
last_close_bar_idx = idx
|
||||
just_reversed = True
|
||||
|
||||
# ---------- 开仓 ----------
|
||||
# 反手后本 K 线允许开仓;否则需间隔 MIN_BARS_BETWEEN_TRADES 根
|
||||
if not just_reversed and last_close_bar_idx is not None and (idx - last_close_bar_idx) < MIN_BARS_BETWEEN_TRADES:
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
just_reversed = False
|
||||
size_usdt = _size_usdt(capital)
|
||||
if size_usdt <= 0:
|
||||
equity_curve.append(capital)
|
||||
continue
|
||||
stop_price = None
|
||||
if direction == "long":
|
||||
stop_price = trigger_price - atr_val * STOP_LOSS_ATR_MULT
|
||||
else:
|
||||
stop_price = trigger_price + atr_val * STOP_LOSS_ATR_MULT
|
||||
position = {
|
||||
"direction": direction,
|
||||
"entry_price": trigger_price,
|
||||
"entry_time": bar_id,
|
||||
"entry_bar_idx": idx,
|
||||
"entry_atr": atr_val,
|
||||
"stop_price": stop_price,
|
||||
"size_usdt": size_usdt,
|
||||
"closed_ratio": 0,
|
||||
"trail_activated": False,
|
||||
}
|
||||
last_trade_bar_idx = idx
|
||||
equity_curve.append(capital)
|
||||
|
||||
# 尾仓
|
||||
if position is not None:
|
||||
last_bar = klines_5m[-1]
|
||||
exit_price = float(last_bar["close"])
|
||||
pos_dir = position["direction"]
|
||||
entry_price = position["entry_price"]
|
||||
if pos_dir == "long":
|
||||
point_pnl = exit_price - entry_price
|
||||
else:
|
||||
point_pnl = entry_price - exit_price
|
||||
size_usdt = position.get("size_usdt", _size_usdt(capital))
|
||||
money_pnl = point_pnl / entry_price * size_usdt
|
||||
fee = _fee(size_usdt)
|
||||
net = money_pnl - fee
|
||||
capital += net
|
||||
trades.append({
|
||||
"direction": "做多" if pos_dir == "long" else "做空",
|
||||
"entry_time": _utc_dt(position["entry_time"]),
|
||||
"exit_time": _utc_dt(last_bar["id"]),
|
||||
"entry_price": entry_price,
|
||||
"exit_price": exit_price,
|
||||
"point_pnl": point_pnl,
|
||||
"money_pnl": money_pnl,
|
||||
"fee": fee,
|
||||
"net_profit": net,
|
||||
"exit_reason": "tail",
|
||||
"hold_bars": len(klines_5m) - 1 - position["entry_bar_idx"],
|
||||
})
|
||||
|
||||
# 统计
|
||||
total_net = sum(t["net_profit"] for t in trades)
|
||||
total_gross = sum(t["money_pnl"] for t in trades)
|
||||
total_fee = sum(t["fee"] for t in trades)
|
||||
win_count = len([t for t in trades if t["net_profit"] > 0])
|
||||
stats = {
|
||||
"total_trades": len(trades),
|
||||
"win_count": win_count,
|
||||
"win_rate": (win_count / len(trades) * 100) if trades else 0,
|
||||
"total_gross_profit": total_gross,
|
||||
"total_fee": total_fee,
|
||||
"total_net_profit": total_net,
|
||||
"final_capital": capital,
|
||||
"max_drawdown": 0,
|
||||
"max_drawdown_pct": 0,
|
||||
}
|
||||
peak = initial_capital
|
||||
for eq in equity_curve:
|
||||
peak = max(peak, eq)
|
||||
dd = peak - eq
|
||||
if peak > 0:
|
||||
stats["max_drawdown"] = max(stats["max_drawdown"], dd)
|
||||
stats["max_drawdown_pct"] = max(stats["max_drawdown_pct"], dd / peak * 100)
|
||||
return trades, stats, klines_5m
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="自适应三分位趋势策略回测")
|
||||
parser.add_argument("--start", default="2025-01-01", help="开始日期 YYYY-MM-DD")
|
||||
parser.add_argument("--end", default="2025-12-31", help="结束日期,默认 2025-12-31")
|
||||
parser.add_argument("--data-dir", default=None, help="数据目录,默认 adaptive_third_strategy/data")
|
||||
parser.add_argument("--no-api", action="store_true", help="不从 API 拉取,仅用本地 CSV")
|
||||
parser.add_argument("--capital", type=float, default=10000, help="初始资金(按比例开仓时用)")
|
||||
parser.add_argument("--fixed-margin", type=float, default=None, help="每笔固定保证金 USDT,如 50")
|
||||
parser.add_argument("--leverage", type=float, default=None, help="杠杆倍数,如 100")
|
||||
parser.add_argument("--no-fee", action="store_true", help="不扣手续费,只算总盈利")
|
||||
args = parser.parse_args()
|
||||
start_dt = datetime.datetime.strptime(args.start, "%Y-%m-%d")
|
||||
if args.end:
|
||||
end_dt = datetime.datetime.strptime(args.end, "%Y-%m-%d")
|
||||
# 包含 end 日全天:取次日 0 点前一刻,这样 id < end_ts 的 K 线都含在内
|
||||
end_ts = int((end_dt + datetime.timedelta(days=1)).timestamp())
|
||||
else:
|
||||
end_ts = int(datetime.datetime.utcnow().timestamp())
|
||||
start_ts = int(start_dt.timestamp())
|
||||
data_dir = args.data_dir or os.path.join(SCRIPT_DIR, "data")
|
||||
k5, k15, k60 = ensure_data(start_ts, end_ts, data_dir, use_api=not args.no_api)
|
||||
if not k5:
|
||||
logger.error("无 5 分钟数据,请先抓取或开启 --api")
|
||||
return
|
||||
logger.info(f"5m={len(k5)} 15m={len(k15)} 60m={len(k60)}")
|
||||
trades, stats, _ = run_backtest(
|
||||
k5, k15, k60,
|
||||
initial_capital=args.capital,
|
||||
fixed_margin_usdt=args.fixed_margin,
|
||||
leverage=args.leverage,
|
||||
deduct_fee=not args.no_fee,
|
||||
)
|
||||
logger.info(f"交易笔数: {stats['total_trades']} 胜率: {stats['win_rate']:.2f}% "
|
||||
f"总盈利(未扣费): {stats['total_gross_profit']:.2f} USDT "
|
||||
f"总手续费: {stats['total_fee']:.2f} 总净利润: {stats['total_net_profit']:.2f} "
|
||||
f"最大回撤: {stats['max_drawdown']:.2f} ({stats['max_drawdown_pct']:.2f}%)")
|
||||
out_csv = os.path.join(SCRIPT_DIR, "backtest_trades.csv")
|
||||
if trades:
|
||||
with open(out_csv, "w", newline="", encoding="utf-8") as f:
|
||||
w = csv.DictWriter(f, fieldnames=["direction", "entry_time", "exit_time", "entry_price", "exit_price", "point_pnl", "money_pnl", "fee", "net_profit", "exit_reason", "hold_bars"])
|
||||
w.writeheader()
|
||||
for t in trades:
|
||||
w.writerow({k: str(v) if isinstance(v, datetime.datetime) else v for k, v in t.items()})
|
||||
logger.info(f"交易记录已保存: {out_csv}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1040
adaptive_third_strategy/backtest_trades.csv
Normal file
1040
adaptive_third_strategy/backtest_trades.csv
Normal file
File diff suppressed because it is too large
Load Diff
97
adaptive_third_strategy/config.py
Normal file
97
adaptive_third_strategy/config.py
Normal file
@@ -0,0 +1,97 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
自适应三分位趋势策略 - 配置参数
|
||||
"""
|
||||
|
||||
# ========== 数据与周期 ==========
|
||||
CONTRACT_SYMBOL = "ETHUSDT"
|
||||
STEP_5M = 5
|
||||
STEP_15M = 15
|
||||
STEP_60M = 60
|
||||
|
||||
# 有效K线标准:实体 >= max(ATR(20)*0.1, 最小波动阈值)
|
||||
MIN_BODY_ATR_RATIO = 0.1
|
||||
MIN_VOLATILITY_PERCENT = 0.0005 # 当前价格 × 0.05%
|
||||
|
||||
# ========== 趋势判断(EMA周期) ==========
|
||||
# 长期:1小时 EMA(50) vs EMA(200)
|
||||
EMA_LONG_FAST = 50
|
||||
EMA_LONG_SLOW = 200
|
||||
# 中期:15分钟 EMA(20) vs EMA(50)
|
||||
EMA_MID_FAST = 20
|
||||
EMA_MID_SLOW = 50
|
||||
# 短期:5分钟 EMA(9)
|
||||
EMA_SHORT = 9
|
||||
|
||||
# 交易模式:aggressive / conservative / strict
|
||||
TREND_MODE = "conservative"
|
||||
|
||||
# ========== 动态触发 ==========
|
||||
ATR_PERIOD = 20
|
||||
VOLATILITY_COEF_CLAMP = (0.3, 3.0) # 波动率系数范围
|
||||
BASE_COEF = 0.33
|
||||
# 趋势方向系数(顺势更易触发)
|
||||
TREND_FAVOR_COEF = 0.8
|
||||
TREND_AGAINST_COEF = 1.2
|
||||
|
||||
# ========== 信号确认(至少满足几项) ==========
|
||||
CONFIRM_REQUIRED = 2
|
||||
VOLUME_MA_PERIOD = 20
|
||||
VOLUME_RATIO_THRESHOLD = 0.8 # 成交量 > 前20根均量 × 0.8
|
||||
|
||||
# ========== 风险管理 ==========
|
||||
MAX_POSITION_PERCENT = 0.10 # 单笔最大 10%
|
||||
BASE_POSITION_PERCENT = 0.02 # 基础仓位 2%
|
||||
STOP_LOSS_ATR_MULT = 2.0 # 硬止损 = ATR × 2.0
|
||||
TIME_STOP_BARS = 3 # 3根K线未盈利平仓
|
||||
TRAIL_START_ATR = 1.0 # 盈利 1×ATR 后启动移动止损
|
||||
TRAIL_ATR_MULT = 0.5 # 移动止损距离 0.5×ATR
|
||||
# 止盈目标(ATR倍数)与分批比例
|
||||
TP1_ATR = 1.5
|
||||
TP2_ATR = 3.0
|
||||
TP3_ATR = 5.0
|
||||
TP1_RATIO = 0.30
|
||||
TP2_RATIO = 0.30
|
||||
TP3_RATIO = 0.40
|
||||
|
||||
# ========== 反手条件 ==========
|
||||
REVERSE_BREAK_MULT = 0.5 # 突破幅度 > 实体/2
|
||||
REVERSE_LOSS_ATR = 0.5 # 当前持仓亏损 > 0.5×ATR 可反手
|
||||
MIN_BARS_SINCE_ENTRY = 2 # 距离上次开仓至少2根K线
|
||||
SAME_KLINE_NO_REVERSE = True # 同一K线不反手
|
||||
|
||||
# ========== 市场状态调整 ==========
|
||||
# 强趋势 / 震荡 / 高波动 时的系数
|
||||
STRONG_TREND_COEF = 0.25
|
||||
STRONG_TREND_STOP_ATR = 3.0
|
||||
RANGE_COEF = 0.4
|
||||
RANGE_STOP_ATR = 1.5
|
||||
HIGH_VOL_POSITION_COEF = 0.5
|
||||
HIGH_VOL_EXTRA_CONFIRM = 1
|
||||
|
||||
# ========== 禁止交易时段(UTC+8 小时:分) ==========
|
||||
FORBIDDEN_PERIODS = [
|
||||
(0, 0, 0, 30), # 00:00-00:30
|
||||
(8, 0, 8, 30), # 08:00-08:30
|
||||
(20, 0, 20, 30), # 20:00-20:30
|
||||
]
|
||||
# 高波动暂停:ATR > 平均ATR × 2 暂停
|
||||
ATR_PAUSE_MULT = 2.0
|
||||
|
||||
# ========== 自适应绩效 ==========
|
||||
CONSECUTIVE_LOSS_PAUSE = 3 # 连续亏损3次仓位减半
|
||||
WIN_RATE_LOOKBACK = 20
|
||||
WIN_RATE_PAUSE_THRESHOLD = 0.40 # 胜率<40% 暂停
|
||||
MAX_DRAWDOWN_PERCENT = 0.10 # 最大回撤10% 观察期
|
||||
MAX_TRADES_PER_DAY = 20
|
||||
MAX_HOLD_BARS = 4 * 12 # 4小时 ≈ 4*12 根5分钟
|
||||
|
||||
# ========== 回测/实盘通用 ==========
|
||||
CONTRACT_SIZE = 10000
|
||||
SLIPPAGE_POINTS = 3
|
||||
FEE_RATE = 0.0005
|
||||
FEE_FIXED = 5
|
||||
# 回测专用:固定手续费(合约多为按比例,可设为 0 观察策略本身)
|
||||
FEE_FIXED_BACKTEST = 0
|
||||
# 两笔开仓之间至少间隔 N 根 5 分钟 K 线,减少过度交易
|
||||
MIN_BARS_BETWEEN_TRADES = 4
|
||||
2785
adaptive_third_strategy/data/kline_15m.csv
Normal file
2785
adaptive_third_strategy/data/kline_15m.csv
Normal file
File diff suppressed because it is too large
Load Diff
8353
adaptive_third_strategy/data/kline_5m.csv
Normal file
8353
adaptive_third_strategy/data/kline_5m.csv
Normal file
File diff suppressed because it is too large
Load Diff
697
adaptive_third_strategy/data/kline_60m.csv
Normal file
697
adaptive_third_strategy/data/kline_60m.csv
Normal file
@@ -0,0 +1,697 @@
|
||||
id,open,high,low,close,volume
|
||||
1735660800,3412.6,3416.0,3390.39,3408.23,102369440.0
|
||||
1735664400,3408.23,3410.38,3329.45,3362.6,239948902.0
|
||||
1735668000,3362.62,3372.14,3349.24,3360.3,87820990.0
|
||||
1735671600,3360.3,3367.9,3342.85,3354.68,42695570.0
|
||||
1735675200,3354.68,3364.47,3336.84,3346.12,54111736.0
|
||||
1735678800,3345.76,3354.28,3331.51,3351.72,48624216.0
|
||||
1735682400,3351.66,3351.66,3327.8,3338.94,44710804.0
|
||||
1735686000,3338.94,3345.03,3327.22,3336.37,37042226.0
|
||||
1735689600,3336.47,3364.49,3334.99,3362.89,57465298.0
|
||||
1735693200,3362.89,3365.08,3341.6,3345.83,34711202.0
|
||||
1735696800,3345.83,3368.02,3345.35,3361.81,34405766.0
|
||||
1735700400,3361.81,3363.0,3350.22,3354.34,21508812.0
|
||||
1735704000,3354.34,3355.78,3338.4,3340.12,45813072.0
|
||||
1735707600,3340.04,3350.57,3339.13,3344.2,29504628.0
|
||||
1735711200,3344.2,3347.33,3335.57,3345.65,26979022.0
|
||||
1735714800,3345.65,3347.77,3338.32,3346.0,19122736.0
|
||||
1735718400,3345.94,3348.0,3330.48,3335.57,39528308.0
|
||||
1735722000,3335.57,3340.16,3313.08,3333.4,129899100.0
|
||||
1735725600,3333.52,3337.99,3325.01,3325.99,32584622.0
|
||||
1735729200,3325.99,3347.73,3322.8,3340.07,45465420.0
|
||||
1735732800,3340.07,3351.21,3337.29,3346.01,39172620.0
|
||||
1735736400,3346.01,3352.78,3331.45,3343.1,51120458.0
|
||||
1735740000,3342.83,3358.89,3327.15,3345.0,82299724.0
|
||||
1735743600,3345.06,3354.24,3332.01,3352.07,51286002.0
|
||||
1735747200,3352.07,3356.14,3331.27,3337.72,49132440.0
|
||||
1735750800,3337.72,3344.37,3322.56,3343.02,49046866.0
|
||||
1735754400,3343.01,3351.47,3332.38,3347.81,36271786.0
|
||||
1735758000,3347.81,3363.82,3338.02,3356.2,55282332.0
|
||||
1735761600,3356.2,3361.76,3351.0,3359.39,38099614.0
|
||||
1735765200,3359.28,3370.87,3352.49,3367.59,51552628.0
|
||||
1735768800,3367.59,3373.97,3357.81,3360.65,43553722.0
|
||||
1735772400,3360.65,3365.99,3352.07,3359.35,33197468.0
|
||||
1735776000,3359.35,3399.41,3354.01,3389.27,123638482.0
|
||||
1735779600,3389.27,3404.76,3377.72,3383.02,72528314.0
|
||||
1735783200,3383.1,3397.9,3376.89,3397.89,35142436.0
|
||||
1735786800,3397.89,3401.77,3386.57,3388.01,28707566.0
|
||||
1735790400,3388.01,3427.38,3387.4,3417.81,124752266.0
|
||||
1735794000,3417.82,3419.18,3404.4,3412.37,64463210.0
|
||||
1735797600,3412.63,3415.73,3401.82,3410.19,37061074.0
|
||||
1735801200,3410.19,3421.77,3408.6,3419.68,42976506.0
|
||||
1735804800,3419.68,3442.8,3414.59,3437.98,81383320.0
|
||||
1735808400,3437.13,3470.72,3433.94,3467.93,117165150.0
|
||||
1735812000,3467.93,3476.09,3457.49,3468.61,95625752.0
|
||||
1735815600,3468.61,3474.99,3457.1,3470.47,55981282.0
|
||||
1735819200,3470.46,3480.37,3465.1,3479.77,71134738.0
|
||||
1735822800,3479.89,3483.94,3459.45,3465.33,135921684.0
|
||||
1735826400,3465.33,3509.87,3439.82,3494.61,298463722.0
|
||||
1735830000,3494.61,3495.56,3455.45,3462.76,137324232.0
|
||||
1735833600,3462.76,3488.75,3447.84,3468.71,102691858.0
|
||||
1735837200,3468.7,3478.07,3429.2,3449.77,130762240.0
|
||||
1735840800,3449.66,3464.67,3439.64,3464.16,96730060.0
|
||||
1735844400,3464.16,3473.57,3458.11,3462.31,52010818.0
|
||||
1735848000,3462.31,3474.61,3450.9,3452.17,41220990.0
|
||||
1735851600,3452.29,3455.39,3442.18,3453.73,48633000.0
|
||||
1735855200,3453.73,3453.75,3419.06,3440.0,62033228.0
|
||||
1735858800,3439.91,3456.41,3437.0,3454.81,33740154.0
|
||||
1735862400,3454.81,3466.23,3445.58,3448.8,49005198.0
|
||||
1735866000,3448.8,3465.02,3442.11,3464.36,64836976.0
|
||||
1735869600,3464.36,3476.32,3455.92,3456.74,51253786.0
|
||||
1735873200,3456.64,3471.96,3455.04,3469.22,41861896.0
|
||||
1735876800,3469.21,3470.11,3455.0,3457.65,33613294.0
|
||||
1735880400,3457.85,3459.99,3443.71,3455.13,57285134.0
|
||||
1735884000,3455.13,3460.9,3441.01,3441.9,36792172.0
|
||||
1735887600,3441.9,3443.62,3431.19,3432.52,64944146.0
|
||||
1735891200,3432.52,3441.96,3422.35,3429.01,71555590.0
|
||||
1735894800,3429.01,3449.99,3428.0,3442.67,45692272.0
|
||||
1735898400,3442.66,3451.63,3437.26,3444.82,34326568.0
|
||||
1735902000,3444.82,3466.53,3444.82,3464.73,51124624.0
|
||||
1735905600,3464.47,3475.49,3460.01,3462.58,57106556.0
|
||||
1735909200,3462.53,3496.26,3462.05,3480.31,136074052.0
|
||||
1735912800,3480.36,3535.47,3479.85,3525.73,252612796.0
|
||||
1735916400,3527.99,3575.99,3518.91,3564.31,266749252.0
|
||||
1735920000,3564.31,3598.54,3564.2,3583.68,236728242.0
|
||||
1735923600,3583.76,3593.0,3572.94,3574.28,77062022.0
|
||||
1735927200,3574.28,3616.82,3573.18,3604.62,145063892.0
|
||||
1735930800,3604.53,3629.68,3601.44,3621.89,136264822.0
|
||||
1735934400,3621.89,3622.32,3599.55,3600.11,82316038.0
|
||||
1735938000,3600.2,3615.64,3594.35,3613.51,60389536.0
|
||||
1735941600,3613.51,3623.2,3609.21,3615.11,41030186.0
|
||||
1735945200,3615.11,3616.87,3603.44,3607.89,55205762.0
|
||||
1735948800,3607.89,3619.9,3601.06,3601.96,52305142.0
|
||||
1735952400,3601.96,3603.52,3581.77,3595.65,60263730.0
|
||||
1735956000,3595.65,3599.45,3582.0,3595.42,35032408.0
|
||||
1735959600,3595.42,3599.48,3590.01,3597.04,28343560.0
|
||||
1735963200,3597.08,3597.18,3583.1,3590.9,31587898.0
|
||||
1735966800,3590.9,3596.15,3585.87,3595.52,27817296.0
|
||||
1735970400,3595.52,3606.6,3594.06,3599.16,34022310.0
|
||||
1735974000,3599.16,3609.0,3591.89,3593.04,43060454.0
|
||||
1735977600,3593.04,3603.11,3584.0,3584.17,82921978.0
|
||||
1735981200,3584.06,3594.99,3582.48,3588.57,39073846.0
|
||||
1735984800,3588.57,3591.98,3570.92,3587.1,48068340.0
|
||||
1735988400,3587.1,3605.05,3583.83,3601.16,55657250.0
|
||||
1735992000,3601.16,3644.09,3599.35,3634.42,152503238.0
|
||||
1735995600,3634.42,3648.59,3626.28,3643.77,105667482.0
|
||||
1735999200,3643.77,3646.0,3631.36,3636.4,63779260.0
|
||||
1736002800,3636.4,3662.37,3592.27,3606.5,238821272.0
|
||||
1736006400,3606.5,3627.64,3600.3,3620.72,76957118.0
|
||||
1736010000,3620.89,3633.52,3618.89,3632.52,40292730.0
|
||||
1736013600,3632.52,3643.7,3618.0,3625.99,47967500.0
|
||||
1736017200,3625.99,3638.39,3623.02,3629.81,24410624.0
|
||||
1736020800,3629.81,3667.58,3629.81,3659.31,70469288.0
|
||||
1736024400,3659.31,3669.95,3653.82,3657.57,60929522.0
|
||||
1736028000,3657.57,3662.13,3645.68,3658.81,34926154.0
|
||||
1736031600,3658.81,3664.88,3649.92,3655.98,35246640.0
|
||||
1736035200,3655.98,3674.56,3646.87,3648.66,55777438.0
|
||||
1736038800,3648.66,3650.3,3630.11,3635.55,52527572.0
|
||||
1736042400,3635.55,3644.01,3633.69,3640.11,24195566.0
|
||||
1736046000,3640.11,3640.9,3628.03,3636.5,20439542.0
|
||||
1736049600,3636.5,3648.0,3635.87,3642.88,29199868.0
|
||||
1736053200,3642.88,3642.9,3629.28,3631.5,26183660.0
|
||||
1736056800,3631.5,3640.66,3626.93,3637.54,20101968.0
|
||||
1736060400,3637.54,3640.13,3628.97,3630.38,19147964.0
|
||||
1736064000,3630.38,3632.11,3607.51,3618.97,102512590.0
|
||||
1736067600,3618.97,3622.41,3604.01,3608.53,47549468.0
|
||||
1736071200,3608.65,3617.6,3601.63,3614.68,40940760.0
|
||||
1736074800,3614.68,3620.13,3608.33,3612.57,30546036.0
|
||||
1736078400,3612.57,3621.77,3603.95,3612.88,43155640.0
|
||||
1736082000,3613.04,3626.65,3611.35,3613.49,37524524.0
|
||||
1736085600,3613.49,3627.36,3592.53,3621.73,135663724.0
|
||||
1736089200,3621.65,3637.67,3618.01,3628.43,75103806.0
|
||||
1736092800,3628.43,3640.01,3623.01,3627.1,60533890.0
|
||||
1736096400,3627.1,3647.35,3621.0,3627.36,52375452.0
|
||||
1736100000,3627.14,3632.37,3611.43,3629.47,43996958.0
|
||||
1736103600,3629.47,3638.96,3625.0,3636.9,25938496.0
|
||||
1736107200,3636.9,3657.0,3627.34,3634.52,61275846.0
|
||||
1736110800,3634.52,3647.21,3632.96,3644.52,23260872.0
|
||||
1736114400,3644.52,3648.89,3633.14,3644.18,23637400.0
|
||||
1736118000,3644.15,3654.47,3631.56,3634.85,35985516.0
|
||||
1736121600,3634.85,3654.52,3620.5,3620.5,61317924.0
|
||||
1736125200,3620.67,3654.84,3608.95,3647.81,86153808.0
|
||||
1736128800,3647.51,3684.08,3644.14,3657.59,182952938.0
|
||||
1736132400,3657.5,3673.87,3652.98,3663.93,46604218.0
|
||||
1736136000,3663.93,3678.2,3659.69,3671.82,46449432.0
|
||||
1736139600,3671.82,3697.99,3666.17,3679.61,99035916.0
|
||||
1736143200,3679.61,3684.04,3664.12,3664.87,52617428.0
|
||||
1736146800,3664.87,3667.1,3641.26,3649.39,93646214.0
|
||||
1736150400,3649.39,3659.4,3635.02,3639.98,82925846.0
|
||||
1736154000,3639.98,3649.36,3639.06,3643.89,28971374.0
|
||||
1736157600,3643.89,3645.87,3631.43,3637.07,42695740.0
|
||||
1736161200,3637.07,3658.44,3635.1,3653.7,56217424.0
|
||||
1736164800,3653.7,3661.82,3640.68,3641.68,59441302.0
|
||||
1736168400,3641.68,3651.94,3623.81,3650.0,96201734.0
|
||||
1736172000,3650.0,3702.32,3621.12,3684.3,289303830.0
|
||||
1736175600,3684.26,3713.01,3670.27,3711.59,260080200.0
|
||||
1736179200,3712.89,3743.7,3690.41,3709.39,272849142.0
|
||||
1736182800,3709.39,3710.16,3654.92,3672.78,176868982.0
|
||||
1736186400,3672.78,3691.03,3661.17,3681.2,68879960.0
|
||||
1736190000,3681.31,3694.94,3674.38,3686.47,39532406.0
|
||||
1736193600,3686.47,3702.68,3671.65,3682.33,59308086.0
|
||||
1736197200,3682.07,3687.78,3665.39,3666.31,51081846.0
|
||||
1736200800,3666.31,3682.56,3666.1,3677.43,38055272.0
|
||||
1736204400,3677.43,3686.36,3667.4,3685.72,28287998.0
|
||||
1736208000,3685.72,3693.4,3672.22,3674.01,48983234.0
|
||||
1736211600,3674.01,3695.78,3661.96,3693.68,48454388.0
|
||||
1736215200,3693.68,3699.99,3675.01,3676.88,41902274.0
|
||||
1736218800,3676.87,3687.99,3672.81,3681.48,34679266.0
|
||||
1736222400,3681.48,3685.53,3672.38,3672.4,22079898.0
|
||||
1736226000,3672.4,3676.15,3657.64,3668.64,45811008.0
|
||||
1736229600,3668.64,3674.79,3663.01,3671.53,29019498.0
|
||||
1736233200,3671.53,3678.99,3661.35,3666.6,29153142.0
|
||||
1736236800,3666.6,3678.49,3659.26,3673.4,41854680.0
|
||||
1736240400,3673.4,3675.58,3652.21,3660.13,57671204.0
|
||||
1736244000,3660.14,3665.0,3650.39,3657.01,42289288.0
|
||||
1736247600,3657.01,3657.99,3625.07,3638.34,132520824.0
|
||||
1736251200,3638.6,3642.32,3625.51,3632.68,67571326.0
|
||||
1736254800,3632.68,3644.9,3630.15,3635.14,48976982.0
|
||||
1736258400,3635.2,3641.18,3523.0,3567.07,333440168.0
|
||||
1736262000,3567.07,3580.49,3410.38,3461.46,1085438710.0
|
||||
1736265600,3461.33,3490.42,3447.53,3456.8,250533314.0
|
||||
1736269200,3456.8,3469.33,3420.12,3441.09,227652302.0
|
||||
1736272800,3440.99,3458.14,3411.8,3420.51,182306610.0
|
||||
1736276400,3420.51,3436.46,3374.36,3384.93,267051616.0
|
||||
1736280000,3384.59,3399.26,3362.16,3395.32,175652002.0
|
||||
1736283600,3395.27,3412.8,3359.81,3361.96,139760754.0
|
||||
1736287200,3362.09,3396.25,3355.13,3395.21,96223134.0
|
||||
1736290800,3395.21,3395.21,3373.61,3379.37,51490032.0
|
||||
1736294400,3379.37,3413.55,3376.91,3410.01,87357960.0
|
||||
1736298000,3410.09,3410.12,3373.39,3389.82,70746830.0
|
||||
1736301600,3389.82,3402.98,3381.2,3381.23,51129254.0
|
||||
1736305200,3381.23,3386.39,3342.71,3359.34,186577674.0
|
||||
1736308800,3359.34,3372.73,3342.07,3347.27,105866768.0
|
||||
1736312400,3347.27,3371.89,3336.08,3350.48,104936154.0
|
||||
1736316000,3350.48,3361.34,3306.03,3307.77,211327564.0
|
||||
1736319600,3307.8,3358.07,3306.71,3353.19,146187366.0
|
||||
1736323200,3353.19,3375.59,3349.83,3364.56,101254772.0
|
||||
1736326800,3364.57,3372.85,3345.0,3345.89,75539896.0
|
||||
1736330400,3345.89,3371.99,3345.16,3360.9,69829694.0
|
||||
1736334000,3360.9,3366.39,3321.76,3348.44,164453210.0
|
||||
1736337600,3348.44,3359.3,3337.6,3338.59,85930986.0
|
||||
1736341200,3338.61,3365.29,3338.61,3357.39,89649230.0
|
||||
1736344800,3357.21,3380.52,3346.3,3369.27,151342344.0
|
||||
1736348400,3368.94,3384.66,3332.0,3345.99,206886030.0
|
||||
1736352000,3345.99,3347.06,3310.44,3313.07,148369138.0
|
||||
1736355600,3313.11,3318.93,3207.46,3262.23,658990620.0
|
||||
1736359200,3262.21,3329.38,3257.5,3281.58,286190748.0
|
||||
1736362800,3281.99,3308.69,3270.47,3279.62,140264688.0
|
||||
1736366400,3279.57,3290.0,3265.01,3284.51,65621974.0
|
||||
1736370000,3284.51,3303.67,3268.87,3297.9,74963226.0
|
||||
1736373600,3297.85,3332.86,3297.28,3331.31,80470748.0
|
||||
1736377200,3331.31,3333.47,3314.73,3325.46,41780554.0
|
||||
1736380800,3325.46,3342.07,3316.67,3340.7,60166482.0
|
||||
1736384400,3340.12,3356.49,3333.9,3344.64,63614256.0
|
||||
1736388000,3344.64,3349.59,3305.94,3339.6,165240584.0
|
||||
1736391600,3339.6,3341.35,3315.56,3324.06,63469448.0
|
||||
1736395200,3323.71,3342.24,3311.0,3339.0,67512222.0
|
||||
1736398800,3338.96,3341.19,3323.42,3327.38,39150924.0
|
||||
1736402400,3327.38,3333.77,3313.68,3314.92,41707280.0
|
||||
1736406000,3314.92,3323.33,3281.3,3288.19,154505570.0
|
||||
1736409600,3288.19,3325.89,3263.38,3308.99,192210656.0
|
||||
1736413200,3309.07,3326.72,3305.94,3316.68,65498146.0
|
||||
1736416800,3316.68,3321.07,3283.42,3310.84,106064710.0
|
||||
1736420400,3310.84,3325.5,3292.07,3300.36,76143062.0
|
||||
1736424000,3300.36,3309.23,3279.89,3294.89,72951552.0
|
||||
1736427600,3294.89,3297.76,3211.02,3219.8,310900906.0
|
||||
1736431200,3219.6,3251.76,3209.02,3249.62,207212678.0
|
||||
1736434800,3249.62,3335.0,3242.45,3318.81,240337982.0
|
||||
1736438400,3318.81,3326.2,3286.7,3300.79,164041718.0
|
||||
1736442000,3300.79,3302.94,3234.38,3249.49,184871012.0
|
||||
1736445600,3249.49,3269.11,3244.47,3258.78,103697430.0
|
||||
1736449200,3259.07,3267.62,3190.01,3193.56,229460404.0
|
||||
1736452800,3193.54,3212.74,3155.96,3194.13,302442148.0
|
||||
1736456400,3194.12,3224.91,3189.44,3206.78,90139568.0
|
||||
1736460000,3205.89,3238.13,3186.39,3222.7,89771046.0
|
||||
1736463600,3222.7,3231.72,3208.6,3217.86,49759112.0
|
||||
1736467200,3218.13,3232.61,3212.86,3221.74,54670720.0
|
||||
1736470800,3221.74,3238.89,3212.52,3235.53,61826016.0
|
||||
1736474400,3235.53,3258.78,3229.27,3239.91,73960624.0
|
||||
1736478000,3240.12,3254.85,3237.3,3253.05,39189052.0
|
||||
1736481600,3253.05,3263.57,3245.37,3252.64,52641914.0
|
||||
1736485200,3252.78,3280.15,3241.27,3262.78,81991616.0
|
||||
1736488800,3262.6,3279.35,3258.56,3276.45,46459632.0
|
||||
1736492400,3276.45,3312.38,3269.49,3294.65,142743074.0
|
||||
1736496000,3294.65,3300.61,3286.36,3294.77,54960336.0
|
||||
1736499600,3294.77,3309.3,3289.02,3301.2,55245834.0
|
||||
1736503200,3301.2,3319.92,3296.47,3316.89,63228784.0
|
||||
1736506800,3316.89,3318.66,3294.49,3309.74,69342416.0
|
||||
1736510400,3309.74,3311.0,3291.98,3309.36,67812524.0
|
||||
1736514000,3309.25,3315.95,3215.36,3242.94,442746528.0
|
||||
1736517600,3242.94,3277.64,3235.03,3255.47,229753402.0
|
||||
1736521200,3253.81,3274.86,3193.21,3243.69,363287258.0
|
||||
1736524800,3243.54,3260.64,3231.0,3246.19,141134442.0
|
||||
1736528400,3246.11,3320.22,3241.43,3313.36,173307620.0
|
||||
1736532000,3313.27,3313.27,3280.14,3285.21,117654418.0
|
||||
1736535600,3285.76,3297.7,3269.11,3293.79,84001780.0
|
||||
1736539200,3293.79,3293.8,3256.27,3258.81,62458836.0
|
||||
1736542800,3258.71,3273.33,3256.01,3264.13,32978326.0
|
||||
1736546400,3264.13,3280.1,3258.48,3272.36,35630396.0
|
||||
1736550000,3272.36,3274.17,3263.04,3265.52,22914060.0
|
||||
1736553600,3265.52,3268.92,3256.51,3264.4,38075382.0
|
||||
1736557200,3264.4,3266.68,3235.13,3244.52,61301216.0
|
||||
1736560800,3244.52,3250.87,3236.17,3246.5,29747936.0
|
||||
1736564400,3246.5,3247.11,3230.52,3235.88,28924308.0
|
||||
1736568000,3235.88,3242.0,3228.51,3237.53,32303354.0
|
||||
1736571600,3237.53,3244.92,3231.48,3244.35,33205960.0
|
||||
1736575200,3244.35,3249.5,3236.31,3236.33,28510900.0
|
||||
1736578800,3236.33,3243.13,3224.17,3234.72,38537850.0
|
||||
1736582400,3234.72,3239.07,3215.12,3223.37,48376530.0
|
||||
1736586000,3223.37,3243.45,3222.27,3243.01,49518806.0
|
||||
1736589600,3243.01,3255.31,3242.22,3255.26,79756560.0
|
||||
1736593200,3255.26,3279.23,3252.9,3272.68,95103116.0
|
||||
1736596800,3272.68,3280.0,3266.71,3271.65,39652500.0
|
||||
1736600400,3271.65,3274.82,3257.51,3266.4,49449588.0
|
||||
1736604000,3266.4,3278.98,3257.26,3277.3,42866688.0
|
||||
1736607600,3277.2,3281.01,3265.16,3270.07,31206676.0
|
||||
1736611200,3270.07,3277.14,3261.0,3274.11,31282780.0
|
||||
1736614800,3274.11,3274.9,3257.7,3270.89,40054362.0
|
||||
1736618400,3270.89,3279.01,3266.11,3277.06,18851470.0
|
||||
1736622000,3276.96,3282.49,3268.28,3277.47,31217294.0
|
||||
1736625600,3277.47,3299.48,3274.61,3298.28,40594708.0
|
||||
1736629200,3298.28,3319.01,3298.05,3305.69,62717802.0
|
||||
1736632800,3305.69,3307.36,3284.5,3287.56,40714280.0
|
||||
1736636400,3287.56,3293.32,3280.33,3281.85,29895820.0
|
||||
1736640000,3281.85,3286.11,3277.98,3282.18,22834780.0
|
||||
1736643600,3282.18,3288.32,3273.02,3287.87,25636570.0
|
||||
1736647200,3287.87,3288.96,3278.26,3288.87,17973236.0
|
||||
1736650800,3288.87,3289.88,3277.02,3285.27,19745114.0
|
||||
1736654400,3285.27,3288.62,3279.52,3282.52,17536690.0
|
||||
1736658000,3282.52,3284.4,3273.11,3278.51,18182922.0
|
||||
1736661600,3278.51,3280.29,3266.38,3269.24,28730928.0
|
||||
1736665200,3269.24,3272.82,3262.31,3265.63,38470516.0
|
||||
1736668800,3265.62,3270.61,3233.02,3238.36,122652126.0
|
||||
1736672400,3238.45,3243.5,3223.13,3236.97,107510624.0
|
||||
1736676000,3236.97,3244.82,3232.45,3243.12,47696088.0
|
||||
1736679600,3243.12,3257.11,3239.67,3251.63,50138698.0
|
||||
1736683200,3251.86,3264.27,3246.51,3249.24,57619188.0
|
||||
1736686800,3249.24,3270.58,3246.94,3266.0,68944450.0
|
||||
1736690400,3266.0,3279.9,3265.0,3267.83,73240184.0
|
||||
1736694000,3267.83,3294.5,3264.0,3284.65,79888684.0
|
||||
1736697600,3284.65,3286.99,3267.61,3280.51,43169660.0
|
||||
1736701200,3280.51,3291.46,3277.19,3287.9,46143554.0
|
||||
1736704800,3287.81,3298.89,3279.33,3283.73,47041518.0
|
||||
1736708400,3283.73,3287.97,3274.66,3279.11,25800656.0
|
||||
1736712000,3279.11,3288.49,3272.07,3280.55,23003516.0
|
||||
1736715600,3280.63,3281.94,3262.59,3265.11,44126248.0
|
||||
1736719200,3265.12,3267.41,3232.85,3239.17,101407078.0
|
||||
1736722800,3239.17,3267.36,3237.68,3265.69,44761358.0
|
||||
1736726400,3265.69,3338.19,3263.9,3307.67,252955806.0
|
||||
1736730000,3307.43,3311.66,3251.27,3260.13,196644388.0
|
||||
1736733600,3260.13,3269.95,3236.0,3254.9,121374346.0
|
||||
1736737200,3254.9,3265.89,3234.02,3241.24,90367330.0
|
||||
1736740800,3241.24,3259.58,3236.46,3237.36,78938608.0
|
||||
1736744400,3237.36,3237.8,3211.68,3224.18,119089336.0
|
||||
1736748000,3224.49,3232.3,3180.01,3190.13,176832932.0
|
||||
1736751600,3190.1,3208.74,3181.93,3200.53,86710800.0
|
||||
1736755200,3200.53,3205.45,3159.9,3168.74,156073064.0
|
||||
1736758800,3168.74,3176.15,3119.18,3155.01,234976700.0
|
||||
1736762400,3155.01,3155.99,3106.07,3117.96,317611886.0
|
||||
1736766000,3118.75,3123.04,3055.09,3056.7,378759676.0
|
||||
1736769600,3056.4,3084.95,3039.54,3045.72,305548852.0
|
||||
1736773200,3045.89,3090.3,3032.3,3059.44,249979524.0
|
||||
1736776800,3059.44,3060.11,2909.6,3033.47,974243346.0
|
||||
1736780400,3033.59,3076.35,3025.13,3047.42,366822882.0
|
||||
1736784000,3047.42,3058.66,3004.86,3008.65,234479378.0
|
||||
1736787600,3007.87,3035.84,3003.01,3019.36,152995168.0
|
||||
1736791200,3019.36,3032.98,2985.02,3006.39,179997976.0
|
||||
1736794800,3006.38,3036.47,2991.8,3023.97,125369102.0
|
||||
1736798400,3023.97,3103.38,3010.35,3090.15,177752432.0
|
||||
1736802000,3090.41,3118.78,3081.97,3112.72,165786786.0
|
||||
1736805600,3112.72,3141.66,3110.97,3130.64,116974672.0
|
||||
1736809200,3130.64,3139.43,3124.38,3136.15,47544220.0
|
||||
1736812800,3136.15,3147.25,3124.06,3132.57,84185166.0
|
||||
1736816400,3132.57,3157.97,3132.19,3138.01,94074596.0
|
||||
1736820000,3137.9,3165.79,3130.36,3155.98,85221592.0
|
||||
1736823600,3155.98,3174.3,3153.45,3157.58,54405210.0
|
||||
1736827200,3157.58,3174.12,3157.58,3171.39,55857890.0
|
||||
1736830800,3171.23,3181.33,3158.29,3160.65,79933988.0
|
||||
1736834400,3160.65,3187.06,3156.26,3182.95,73104704.0
|
||||
1736838000,3182.95,3189.0,3169.86,3170.4,56505048.0
|
||||
1736841600,3170.4,3191.27,3168.36,3181.91,74257186.0
|
||||
1736845200,3182.0,3255.21,3176.59,3242.42,259501370.0
|
||||
1736848800,3242.42,3242.89,3219.7,3233.75,124964948.0
|
||||
1736852400,3233.75,3234.23,3209.7,3216.28,67811012.0
|
||||
1736856000,3216.12,3218.73,3179.51,3182.39,163014218.0
|
||||
1736859600,3182.5,3231.74,3178.0,3207.21,199494486.0
|
||||
1736863200,3207.22,3232.31,3186.1,3190.41,130742936.0
|
||||
1736866800,3190.94,3214.98,3186.11,3197.86,137337984.0
|
||||
1736870400,3197.86,3202.94,3171.03,3189.99,137636112.0
|
||||
1736874000,3189.99,3211.7,3182.72,3211.33,80978048.0
|
||||
1736877600,3211.1,3231.4,3205.89,3223.37,92417500.0
|
||||
1736881200,3223.37,3234.31,3211.09,3212.9,62213378.0
|
||||
1736884800,3212.9,3224.69,3193.52,3221.24,82723252.0
|
||||
1736888400,3221.24,3232.5,3211.25,3214.72,50505536.0
|
||||
1736892000,3214.72,3239.2,3212.11,3233.81,35697584.0
|
||||
1736895600,3233.81,3236.6,3221.18,3223.99,28458870.0
|
||||
1736899200,3223.99,3241.26,3216.09,3220.18,62474718.0
|
||||
1736902800,3220.18,3244.58,3215.73,3228.96,71656158.0
|
||||
1736906400,3228.96,3238.69,3205.0,3211.66,70425488.0
|
||||
1736910000,3211.77,3240.66,3207.26,3239.89,54759212.0
|
||||
1736913600,3239.88,3251.11,3220.79,3225.01,81473270.0
|
||||
1736917200,3224.99,3234.39,3213.86,3228.57,46760866.0
|
||||
1736920800,3228.57,3231.82,3215.17,3229.14,47369732.0
|
||||
1736924400,3229.14,3249.69,3228.61,3231.31,72666604.0
|
||||
1736928000,3231.31,3246.86,3228.52,3230.98,60786620.0
|
||||
1736931600,3231.1,3236.52,3195.34,3199.21,97527064.0
|
||||
1736935200,3199.21,3217.14,3196.79,3208.08,62126134.0
|
||||
1736938800,3208.08,3208.96,3185.53,3198.3,77459828.0
|
||||
1736942400,3198.3,3210.72,3185.4,3200.66,78921674.0
|
||||
1736946000,3200.66,3297.25,3200.4,3297.01,361680060.0
|
||||
1736949600,3297.01,3330.61,3274.57,3328.32,260514000.0
|
||||
1736953200,3328.37,3353.09,3324.53,3333.17,216457060.0
|
||||
1736956800,3333.07,3365.41,3332.56,3338.3,139245078.0
|
||||
1736960400,3338.3,3357.46,3322.99,3324.68,90410118.0
|
||||
1736964000,3324.68,3373.77,3324.68,3370.89,81676352.0
|
||||
1736967600,3370.8,3442.18,3366.66,3433.76,263310966.0
|
||||
1736971200,3433.76,3472.38,3431.66,3432.83,208944138.0
|
||||
1736974800,3432.81,3445.86,3426.0,3431.39,62340338.0
|
||||
1736978400,3431.39,3434.99,3417.53,3429.39,51207524.0
|
||||
1736982000,3429.48,3451.12,3419.23,3449.82,66010384.0
|
||||
1736985600,3449.82,3458.48,3385.69,3397.95,171382692.0
|
||||
1736989200,3397.95,3412.55,3391.65,3402.58,46722624.0
|
||||
1736992800,3402.58,3405.0,3380.39,3384.03,61431958.0
|
||||
1736996400,3384.03,3386.8,3345.74,3362.11,132369822.0
|
||||
1737000000,3362.11,3374.53,3360.45,3373.24,55905782.0
|
||||
1737003600,3373.24,3376.07,3364.58,3366.9,27209092.0
|
||||
1737007200,3366.9,3386.02,3361.78,3378.89,55504360.0
|
||||
1737010800,3378.86,3389.29,3375.89,3382.41,42026166.0
|
||||
1737014400,3382.41,3384.0,3301.88,3315.42,213550818.0
|
||||
1737018000,3315.44,3335.0,3298.01,3334.8,173270654.0
|
||||
1737021600,3334.8,3340.99,3328.0,3332.2,59671834.0
|
||||
1737025200,3332.87,3354.87,3320.07,3349.64,111186100.0
|
||||
1737028800,3349.65,3363.28,3340.11,3355.43,97407238.0
|
||||
1737032400,3355.53,3357.3,3309.0,3327.07,167514778.0
|
||||
1737036000,3327.1,3341.53,3264.28,3276.1,285205966.0
|
||||
1737039600,3276.25,3332.09,3268.53,3331.98,228397622.0
|
||||
1737043200,3331.98,3360.66,3329.4,3339.22,152861984.0
|
||||
1737046800,3338.77,3364.41,3329.91,3342.69,93468544.0
|
||||
1737050400,3342.69,3349.79,3308.93,3324.61,90234800.0
|
||||
1737054000,3324.61,3344.94,3318.01,3343.35,43458370.0
|
||||
1737057600,3343.35,3351.99,3324.68,3335.77,53289036.0
|
||||
1737061200,3335.71,3336.78,3310.16,3319.09,54644580.0
|
||||
1737064800,3319.09,3319.31,3267.03,3296.7,131717852.0
|
||||
1737068400,3296.7,3311.39,3289.02,3306.93,68247846.0
|
||||
1737072000,3306.93,3316.78,3306.59,3312.78,42675412.0
|
||||
1737075600,3312.78,3393.76,3312.72,3384.73,214952370.0
|
||||
1737079200,3384.73,3396.23,3354.05,3372.07,140006948.0
|
||||
1737082800,3372.07,3376.06,3357.4,3359.98,47167210.0
|
||||
1737086400,3359.98,3371.42,3346.44,3369.69,64118896.0
|
||||
1737090000,3369.69,3374.77,3358.19,3365.51,44641394.0
|
||||
1737093600,3365.51,3374.7,3363.39,3366.38,39763556.0
|
||||
1737097200,3366.38,3393.91,3361.3,3371.36,70940042.0
|
||||
1737100800,3371.36,3412.78,3371.0,3405.9,121892422.0
|
||||
1737104400,3405.9,3413.03,3399.07,3402.51,70929662.0
|
||||
1737108000,3402.51,3437.78,3401.1,3426.64,102788236.0
|
||||
1737111600,3426.64,3429.5,3415.53,3422.1,55018198.0
|
||||
1737115200,3422.1,3425.46,3399.89,3406.78,90512196.0
|
||||
1737118800,3406.78,3422.93,3394.96,3400.81,77679768.0
|
||||
1737122400,3400.76,3431.89,3400.75,3427.7,167275558.0
|
||||
1737126000,3427.49,3449.69,3403.1,3420.33,195084666.0
|
||||
1737129600,3420.32,3447.59,3410.53,3436.43,111256436.0
|
||||
1737133200,3436.43,3441.9,3406.02,3413.26,100510326.0
|
||||
1737136800,3413.38,3428.83,3409.07,3410.49,47213594.0
|
||||
1737140400,3410.49,3443.03,3409.73,3430.79,91962986.0
|
||||
1737144000,3431.02,3524.4,3424.26,3514.04,286751750.0
|
||||
1737147600,3514.04,3524.53,3465.44,3472.64,136440486.0
|
||||
1737151200,3472.64,3486.56,3466.01,3468.91,61781218.0
|
||||
1737154800,3468.91,3483.87,3461.49,3472.39,47196678.0
|
||||
1737158400,3472.39,3492.94,3467.34,3485.83,52615116.0
|
||||
1737162000,3485.83,3485.83,3453.85,3464.95,63843980.0
|
||||
1737165600,3464.95,3473.28,3446.9,3450.06,47257378.0
|
||||
1737169200,3450.11,3455.0,3366.79,3382.77,229697520.0
|
||||
1737172800,3382.77,3394.52,3350.01,3364.52,172058940.0
|
||||
1737176400,3364.52,3369.99,3290.01,3292.89,274237194.0
|
||||
1737180000,3292.89,3339.39,3291.66,3312.76,153000770.0
|
||||
1737183600,3312.76,3314.37,3280.01,3286.46,147604794.0
|
||||
1737187200,3286.47,3294.66,3251.0,3291.85,228254978.0
|
||||
1737190800,3291.85,3299.87,3252.31,3269.83,127768426.0
|
||||
1737194400,3269.89,3276.99,3225.14,3265.78,252550352.0
|
||||
1737198000,3265.81,3323.48,3247.76,3310.18,227271112.0
|
||||
1737201600,3310.29,3320.91,3273.3,3288.68,159370596.0
|
||||
1737205200,3288.68,3322.36,3278.33,3321.84,137949644.0
|
||||
1737208800,3321.84,3325.22,3287.76,3295.86,97189442.0
|
||||
1737212400,3295.86,3346.2,3283.95,3305.77,219267024.0
|
||||
1737216000,3305.89,3315.72,3252.51,3252.51,243473644.0
|
||||
1737219600,3252.51,3284.8,3252.12,3257.2,97599068.0
|
||||
1737223200,3256.5,3283.03,3249.0,3263.66,83528110.0
|
||||
1737226800,3263.54,3292.49,3260.39,3289.08,56544316.0
|
||||
1737230400,3289.08,3290.88,3256.74,3264.87,58021088.0
|
||||
1737234000,3264.88,3285.06,3262.08,3273.72,37319280.0
|
||||
1737237600,3273.72,3287.67,3270.02,3279.36,30291248.0
|
||||
1737241200,3279.42,3320.34,3278.22,3306.69,83215168.0
|
||||
1737244800,3306.69,3322.38,3297.72,3315.68,66796494.0
|
||||
1737248400,3315.68,3365.18,3312.9,3352.57,155449194.0
|
||||
1737252000,3352.57,3377.32,3346.9,3352.47,82240816.0
|
||||
1737255600,3352.47,3365.32,3332.52,3334.97,97009222.0
|
||||
1737259200,3334.97,3337.32,3276.02,3282.51,202282652.0
|
||||
1737262800,3282.51,3299.78,3258.18,3298.57,105321972.0
|
||||
1737266400,3298.57,3299.93,3280.91,3282.12,47257870.0
|
||||
1737270000,3282.18,3292.37,3261.31,3275.02,88322326.0
|
||||
1737273600,3275.02,3277.15,3215.0,3221.19,203332442.0
|
||||
1737277200,3221.19,3233.18,3143.11,3226.34,411242284.0
|
||||
1737280800,3225.66,3226.18,3173.02,3174.2,166786984.0
|
||||
1737284400,3174.3,3188.53,3131.0,3149.56,209689408.0
|
||||
1737288000,3150.33,3210.59,3144.26,3205.26,164299906.0
|
||||
1737291600,3205.26,3277.31,3193.14,3273.7,251270350.0
|
||||
1737295200,3275.24,3395.99,3258.42,3380.19,863763538.0
|
||||
1737298800,3380.11,3424.81,3352.82,3383.79,333831610.0
|
||||
1737302400,3383.77,3424.04,3377.57,3410.83,168812328.0
|
||||
1737306000,3410.88,3448.14,3385.96,3442.35,194255198.0
|
||||
1737309600,3442.35,3449.0,3406.9,3434.0,106325700.0
|
||||
1737313200,3433.4,3434.0,3407.59,3419.94,57312166.0
|
||||
1737316800,3419.94,3432.95,3351.04,3378.22,144020792.0
|
||||
1737320400,3378.22,3379.4,3207.05,3235.01,501866746.0
|
||||
1737324000,3232.5,3311.84,3156.18,3265.77,478996130.0
|
||||
1737327600,3265.76,3265.76,3160.07,3213.52,466762210.0
|
||||
1737331200,3214.05,3225.31,3142.73,3170.28,407986910.0
|
||||
1737334800,3170.27,3219.48,3165.19,3200.27,229060286.0
|
||||
1737338400,3199.96,3273.76,3196.01,3243.54,218587436.0
|
||||
1737342000,3244.04,3279.99,3227.01,3271.39,119161842.0
|
||||
1737345600,3271.35,3298.81,3249.6,3269.87,100450292.0
|
||||
1737349200,3269.87,3318.14,3266.79,3290.91,124554022.0
|
||||
1737352800,3290.91,3454.72,3285.41,3417.53,388228564.0
|
||||
1737356400,3417.41,3441.9,3351.89,3383.02,374577926.0
|
||||
1737360000,3382.95,3412.04,3380.02,3392.15,127096766.0
|
||||
1737363600,3392.15,3411.93,3352.99,3364.15,186871672.0
|
||||
1737367200,3364.15,3391.99,3341.78,3354.39,126612530.0
|
||||
1737370800,3354.39,3388.33,3334.21,3375.69,172373108.0
|
||||
1737374400,3375.69,3379.6,3256.4,3303.82,367009686.0
|
||||
1737378000,3303.93,3354.87,3295.01,3342.28,210560350.0
|
||||
1737381600,3342.23,3361.6,3329.61,3358.56,154930784.0
|
||||
1737385200,3358.55,3378.98,3315.21,3335.57,241523322.0
|
||||
1737388800,3335.93,3390.0,3276.81,3380.16,274158310.0
|
||||
1737392400,3379.9,3384.07,3205.0,3292.24,795072372.0
|
||||
1737396000,3292.24,3341.58,3283.72,3330.16,213855650.0
|
||||
1737399600,3330.04,3364.11,3308.4,3342.92,128630890.0
|
||||
1737403200,3342.92,3344.32,3313.98,3320.27,77237838.0
|
||||
1737406800,3320.27,3337.16,3280.51,3284.42,82249858.0
|
||||
1737410400,3284.36,3324.16,3255.28,3318.25,103096784.0
|
||||
1737414000,3318.14,3320.28,3260.59,3283.21,98151492.0
|
||||
1737417600,3283.21,3289.47,3220.47,3225.66,167574272.0
|
||||
1737421200,3225.9,3260.77,3203.89,3250.4,181075574.0
|
||||
1737424800,3250.4,3272.75,3227.81,3256.73,97124780.0
|
||||
1737428400,3256.73,3269.67,3248.01,3262.23,63734320.0
|
||||
1737432000,3262.23,3262.25,3212.81,3246.37,95284392.0
|
||||
1737435600,3246.37,3253.9,3216.03,3224.39,73892594.0
|
||||
1737439200,3224.65,3253.97,3212.82,3241.15,98098922.0
|
||||
1737442800,3241.15,3266.0,3234.55,3254.01,67830494.0
|
||||
1737446400,3254.0,3262.97,3233.55,3253.27,67584610.0
|
||||
1737450000,3253.27,3276.4,3249.82,3275.74,77660896.0
|
||||
1737453600,3275.74,3308.8,3270.41,3302.06,104765974.0
|
||||
1737457200,3302.06,3313.19,3291.81,3304.39,91283102.0
|
||||
1737460800,3304.39,3325.85,3293.07,3306.62,128372258.0
|
||||
1737464400,3306.62,3314.49,3288.96,3308.85,80976134.0
|
||||
1737468000,3308.71,3333.73,3279.0,3288.51,211256600.0
|
||||
1737471600,3288.51,3300.99,3261.77,3294.1,128559286.0
|
||||
1737475200,3294.1,3349.14,3289.36,3313.34,197211854.0
|
||||
1737478800,3313.15,3356.04,3307.09,3350.37,90531198.0
|
||||
1737482400,3350.37,3367.88,3329.87,3345.17,100399176.0
|
||||
1737486000,3345.18,3346.29,3317.24,3331.74,80784936.0
|
||||
1737489600,3331.74,3339.63,3303.34,3313.41,70082146.0
|
||||
1737493200,3313.43,3335.71,3301.87,3331.61,54339844.0
|
||||
1737496800,3331.58,3346.58,3326.0,3333.06,38895058.0
|
||||
1737500400,3333.91,3334.32,3307.27,3326.74,73269766.0
|
||||
1737504000,3326.74,3365.96,3322.53,3361.54,73640766.0
|
||||
1737507600,3361.54,3361.83,3321.61,3328.53,68953758.0
|
||||
1737511200,3328.34,3346.78,3322.0,3343.32,51151860.0
|
||||
1737514800,3343.32,3347.85,3327.2,3330.54,33271698.0
|
||||
1737518400,3330.54,3332.93,3315.84,3331.15,33810516.0
|
||||
1737522000,3331.15,3339.33,3317.01,3317.66,29934040.0
|
||||
1737525600,3317.66,3323.48,3301.61,3308.82,50870486.0
|
||||
1737529200,3308.82,3310.36,3286.18,3287.05,76036546.0
|
||||
1737532800,3286.97,3300.02,3273.0,3298.39,92349262.0
|
||||
1737536400,3298.39,3303.49,3289.1,3297.4,35948534.0
|
||||
1737540000,3297.4,3307.4,3287.82,3306.99,40550278.0
|
||||
1737543600,3306.99,3328.83,3303.3,3325.53,53272540.0
|
||||
1737547200,3325.53,3331.45,3310.14,3315.9,52994384.0
|
||||
1737550800,3315.85,3318.31,3279.25,3283.32,100473366.0
|
||||
1737554400,3283.39,3310.99,3266.7,3292.02,165657884.0
|
||||
1737558000,3292.72,3304.69,3268.95,3277.5,108866836.0
|
||||
1737561600,3277.5,3282.91,3261.0,3277.1,97706330.0
|
||||
1737565200,3277.15,3287.01,3271.02,3284.6,48476310.0
|
||||
1737568800,3284.6,3290.85,3239.08,3248.93,96991712.0
|
||||
1737572400,3249.38,3279.0,3247.52,3261.51,73598538.0
|
||||
1737576000,3261.51,3276.21,3253.01,3255.86,46359000.0
|
||||
1737579600,3255.82,3264.32,3240.0,3258.42,64803018.0
|
||||
1737583200,3258.42,3265.69,3231.89,3237.63,52779412.0
|
||||
1737586800,3237.63,3245.99,3221.53,3241.77,78166668.0
|
||||
1737590400,3241.77,3260.81,3237.04,3252.92,56714310.0
|
||||
1737594000,3252.92,3257.59,3220.0,3234.28,65753656.0
|
||||
1737597600,3234.34,3236.57,3203.23,3222.26,94598842.0
|
||||
1737601200,3222.48,3242.35,3214.38,3225.64,57395280.0
|
||||
1737604800,3225.71,3230.89,3182.66,3196.01,114327136.0
|
||||
1737608400,3196.01,3220.5,3195.34,3213.89,55114374.0
|
||||
1737612000,3213.89,3223.98,3203.26,3207.1,39191160.0
|
||||
1737615600,3207.1,3232.44,3197.39,3231.96,53561668.0
|
||||
1737619200,3231.96,3232.44,3207.44,3209.22,45550378.0
|
||||
1737622800,3209.26,3221.14,3191.13,3195.94,60758744.0
|
||||
1737626400,3195.94,3212.68,3187.34,3203.3,65237070.0
|
||||
1737630000,3203.3,3216.15,3194.82,3212.78,49857888.0
|
||||
1737633600,3212.78,3217.94,3188.52,3202.62,78595334.0
|
||||
1737637200,3202.62,3242.4,3201.12,3233.01,108985424.0
|
||||
1737640800,3232.89,3285.36,3221.81,3274.6,249651856.0
|
||||
1737644400,3274.6,3280.91,3225.01,3258.51,281277612.0
|
||||
1737648000,3258.51,3296.73,3252.0,3253.65,190993782.0
|
||||
1737651600,3253.64,3270.76,3230.02,3264.89,108032146.0
|
||||
1737655200,3264.92,3269.43,3234.0,3239.9,70643910.0
|
||||
1737658800,3239.9,3245.0,3193.09,3203.76,157291960.0
|
||||
1737662400,3203.71,3294.1,3195.86,3246.15,442292904.0
|
||||
1737666000,3246.11,3255.52,3212.77,3247.4,120420326.0
|
||||
1737669600,3247.85,3322.98,3245.19,3321.94,160218202.0
|
||||
1737673200,3321.66,3347.53,3311.27,3337.88,118336168.0
|
||||
1737676800,3337.88,3348.99,3284.11,3284.72,117342552.0
|
||||
1737680400,3284.69,3325.37,3283.51,3297.47,90561650.0
|
||||
1737684000,3297.49,3313.1,3278.58,3284.64,67232158.0
|
||||
1737687600,3285.21,3311.93,3275.0,3301.41,91279686.0
|
||||
1737691200,3301.35,3339.92,3291.13,3332.72,87852684.0
|
||||
1737694800,3332.45,3409.47,3328.55,3409.22,199536164.0
|
||||
1737698400,3409.28,3420.61,3378.01,3385.27,167874148.0
|
||||
1737702000,3385.32,3400.36,3371.39,3380.59,82619272.0
|
||||
1737705600,3380.59,3415.57,3374.6,3400.57,104233816.0
|
||||
1737709200,3400.58,3412.03,3391.01,3400.87,62308630.0
|
||||
1737712800,3400.73,3414.21,3390.53,3412.88,57566642.0
|
||||
1737716400,3412.88,3413.9,3391.8,3398.69,44487372.0
|
||||
1737720000,3398.69,3404.63,3385.41,3403.48,58305470.0
|
||||
1737723600,3403.48,3429.0,3390.25,3394.44,113839350.0
|
||||
1737727200,3394.44,3421.99,3378.04,3398.31,143869564.0
|
||||
1737730800,3398.34,3408.99,3355.0,3382.62,182652482.0
|
||||
1737734400,3382.62,3397.06,3375.88,3379.29,76641652.0
|
||||
1737738000,3379.35,3405.0,3371.76,3396.65,80638944.0
|
||||
1737741600,3396.6,3408.37,3373.79,3380.06,61571938.0
|
||||
1737745200,3380.0,3393.91,3363.16,3367.16,95047310.0
|
||||
1737748800,3367.19,3377.48,3325.47,3327.04,158225220.0
|
||||
1737752400,3327.75,3345.6,3322.22,3328.33,70830146.0
|
||||
1737756000,3328.59,3335.82,3321.01,3324.47,47898010.0
|
||||
1737759600,3324.47,3324.72,3302.5,3309.02,79667410.0
|
||||
1737763200,3309.02,3312.97,3268.32,3272.36,114215218.0
|
||||
1737766800,3272.27,3304.88,3267.35,3292.74,75981114.0
|
||||
1737770400,3292.74,3303.18,3287.86,3293.48,31908786.0
|
||||
1737774000,3293.48,3322.77,3290.52,3318.01,62089274.0
|
||||
1737777600,3318.01,3318.57,3296.69,3298.78,41619704.0
|
||||
1737781200,3298.78,3301.7,3286.6,3295.01,41837638.0
|
||||
1737784800,3295.01,3302.39,3282.57,3290.84,42042506.0
|
||||
1737788400,3290.78,3294.18,3281.83,3289.78,31177086.0
|
||||
1737792000,3289.78,3299.99,3281.43,3289.6,41350052.0
|
||||
1737795600,3289.6,3291.48,3280.0,3282.3,25686474.0
|
||||
1737799200,3282.25,3296.12,3270.4,3294.2,64393426.0
|
||||
1737802800,3294.24,3310.53,3289.62,3305.5,56540872.0
|
||||
1737806400,3305.5,3306.2,3293.02,3304.78,37528022.0
|
||||
1737810000,3304.78,3309.07,3299.01,3306.3,28691106.0
|
||||
1737813600,3306.3,3318.59,3297.0,3314.36,36664870.0
|
||||
1737817200,3314.41,3343.6,3308.42,3333.02,126783138.0
|
||||
1737820800,3332.94,3347.91,3328.7,3341.0,60067516.0
|
||||
1737824400,3341.08,3344.93,3329.09,3343.34,50834736.0
|
||||
1737828000,3343.34,3347.11,3334.0,3340.79,28862350.0
|
||||
1737831600,3340.79,3342.99,3333.01,3338.71,18958684.0
|
||||
1737835200,3338.71,3349.46,3334.15,3335.62,26919424.0
|
||||
1737838800,3335.66,3341.62,3330.89,3338.49,22254066.0
|
||||
1737842400,3338.49,3344.78,3330.72,3332.85,16937198.0
|
||||
1737846000,3332.85,3336.13,3313.36,3317.9,53735534.0
|
||||
1737849600,3317.9,3326.9,3312.38,3319.74,34181402.0
|
||||
1737853200,3319.74,3336.98,3317.18,3330.23,29997596.0
|
||||
1737856800,3330.16,3330.16,3316.39,3327.22,26861986.0
|
||||
1737860400,3327.22,3349.05,3324.99,3337.47,39360420.0
|
||||
1737864000,3337.47,3364.99,3335.0,3350.99,60812656.0
|
||||
1737867600,3350.99,3352.29,3336.82,3341.07,29644874.0
|
||||
1737871200,3341.07,3346.39,3337.0,3340.36,20394300.0
|
||||
1737874800,3340.36,3344.23,3328.67,3337.56,24837804.0
|
||||
1737878400,3337.56,3342.4,3322.11,3330.52,40964058.0
|
||||
1737882000,3330.52,3331.44,3291.46,3297.06,116578472.0
|
||||
1737885600,3297.06,3305.58,3294.6,3304.27,37585964.0
|
||||
1737889200,3304.27,3314.83,3301.35,3310.45,41215532.0
|
||||
1737892800,3310.45,3310.46,3298.41,3306.64,30648776.0
|
||||
1737896400,3306.47,3308.32,3299.05,3306.62,41653970.0
|
||||
1737900000,3306.62,3311.91,3295.61,3309.48,35483260.0
|
||||
1737903600,3309.48,3319.49,3307.06,3313.61,37547550.0
|
||||
1737907200,3313.61,3318.63,3306.37,3314.39,35922004.0
|
||||
1737910800,3314.39,3343.45,3313.81,3336.32,70866966.0
|
||||
1737914400,3336.32,3341.72,3332.34,3335.83,51959942.0
|
||||
1737918000,3335.83,3340.69,3328.77,3337.51,32405448.0
|
||||
1737921600,3337.51,3341.35,3325.55,3328.26,20768510.0
|
||||
1737925200,3328.3,3328.66,3290.68,3295.01,77584126.0
|
||||
1737928800,3294.88,3308.55,3280.01,3286.36,69877756.0
|
||||
1737932400,3286.31,3286.31,3227.47,3230.78,274026736.0
|
||||
1737936000,3230.65,3252.53,3208.01,3212.62,180964884.0
|
||||
1737939600,3212.63,3223.16,3186.86,3194.14,165658226.0
|
||||
1737943200,3193.87,3194.0,3162.32,3169.45,191214386.0
|
||||
1737946800,3169.45,3197.46,3160.24,3182.11,92065924.0
|
||||
1737950400,3182.11,3182.11,3152.65,3155.73,87218334.0
|
||||
1737954000,3155.73,3163.38,3113.02,3139.75,203842944.0
|
||||
1737957600,3139.8,3148.9,3077.81,3082.76,298234274.0
|
||||
1737961200,3082.76,3090.27,3020.93,3069.26,415672338.0
|
||||
1737964800,3069.11,3071.81,3037.23,3064.19,162267474.0
|
||||
1737968400,3064.24,3088.9,3056.92,3079.48,119649952.0
|
||||
1737972000,3079.48,3080.18,3032.7,3037.11,115805844.0
|
||||
1737975600,3037.05,3074.22,3036.34,3057.48,90659514.0
|
||||
1737979200,3057.48,3121.61,3052.14,3101.53,187794412.0
|
||||
1737982800,3101.47,3133.28,3087.85,3097.86,231867426.0
|
||||
1737986400,3097.86,3144.83,3081.86,3136.15,203057890.0
|
||||
1737990000,3135.72,3150.8,3110.7,3131.09,147629814.0
|
||||
1737993600,3131.97,3132.04,3074.43,3083.0,203328436.0
|
||||
1737997200,3082.95,3098.11,3065.24,3071.53,114566258.0
|
||||
1738000800,3071.57,3087.79,3045.3,3053.88,103725792.0
|
||||
1738004400,3053.91,3080.93,3053.91,3074.89,83541122.0
|
||||
1738008000,3074.89,3147.82,3071.83,3145.22,143522924.0
|
||||
1738011600,3143.97,3237.99,3141.23,3158.16,299739690.0
|
||||
1738015200,3158.16,3180.06,3145.0,3169.25,79707278.0
|
||||
1738018800,3169.25,3181.44,3158.71,3180.75,56318916.0
|
||||
1738022400,3180.79,3202.51,3163.01,3166.22,77481438.0
|
||||
1738026000,3166.27,3175.57,3150.31,3165.28,59624336.0
|
||||
1738029600,3164.96,3198.41,3164.14,3191.6,59293696.0
|
||||
1738033200,3191.56,3216.86,3174.1,3201.47,109284848.0
|
||||
1738036800,3201.47,3221.53,3196.11,3210.64,83876044.0
|
||||
1738040400,3210.67,3220.69,3207.19,3211.69,44259070.0
|
||||
1738044000,3211.69,3214.02,3190.44,3193.55,48991426.0
|
||||
1738047600,3193.43,3214.7,3187.51,3194.62,50038618.0
|
||||
1738051200,3194.62,3204.1,3182.01,3197.47,81225676.0
|
||||
1738054800,3197.42,3200.58,3183.0,3195.72,38138356.0
|
||||
1738058400,3195.72,3206.32,3187.84,3192.87,39941280.0
|
||||
1738062000,3192.87,3199.56,3175.47,3185.2,39874048.0
|
||||
1738065600,3185.2,3194.62,3165.52,3173.72,60379984.0
|
||||
1738069200,3173.64,3188.0,3169.01,3174.14,39825956.0
|
||||
1738072800,3174.14,3179.53,3156.0,3171.09,125274148.0
|
||||
1738076400,3171.34,3213.44,3169.55,3180.15,145496580.0
|
||||
1738080000,3180.15,3181.81,3130.72,3149.59,141098534.0
|
||||
1738083600,3149.46,3181.56,3149.05,3166.11,107192478.0
|
||||
1738087200,3166.07,3176.76,3139.18,3142.6,67871000.0
|
||||
1738090800,3142.6,3149.32,3118.98,3146.4,100694056.0
|
||||
1738094400,3146.48,3153.45,3092.03,3094.31,134835852.0
|
||||
1738098000,3093.42,3113.6,3051.32,3052.43,169849482.0
|
||||
1738101600,3052.32,3089.94,3038.2,3082.86,165296122.0
|
||||
1738105200,3082.93,3095.01,3052.51,3075.96,82801056.0
|
||||
1738108800,3075.96,3116.55,3075.12,3108.9,1274170.0
|
||||
1738112400,3108.85,3119.0,3097.82,3109.31,1144288.0
|
||||
1738116000,3109.31,3128.3,3108.69,3112.81,4755046.0
|
||||
1738119600,3112.74,3131.26,3106.65,3129.02,44437084.0
|
||||
1738123200,3129.04,3138.89,3121.51,3122.94,51333698.0
|
||||
1738126800,3122.87,3133.32,3115.46,3129.66,45341342.0
|
||||
1738130400,3129.66,3148.96,3127.16,3145.88,60770492.0
|
||||
1738134000,3145.88,3158.0,3137.4,3155.64,54759780.0
|
||||
1738137600,3155.64,3162.68,3143.0,3146.4,48938372.0
|
||||
1738141200,3146.4,3147.23,3121.93,3129.27,68754366.0
|
||||
1738144800,3129.27,3142.05,3119.65,3136.48,46871668.0
|
||||
1738148400,3136.48,3142.08,3130.31,3133.64,37915812.0
|
||||
1738152000,3133.64,3138.84,3112.15,3117.69,73520948.0
|
||||
1738155600,3117.69,3120.73,3087.77,3100.83,123395826.0
|
||||
1738159200,3100.81,3115.17,3079.41,3095.1,130122046.0
|
||||
1738162800,3095.81,3117.95,3092.7,3099.65,80143028.0
|
||||
|
172
adaptive_third_strategy/data_fetcher.py
Normal file
172
adaptive_third_strategy/data_fetcher.py
Normal file
@@ -0,0 +1,172 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Bitmart 多周期K线数据抓取 - 供回测与实盘使用
|
||||
与 bitmart/回测.py、bitmart/抓取多周期K线.py 相同的 API 调用方式
|
||||
"""
|
||||
|
||||
import time
|
||||
import csv
|
||||
import os
|
||||
from typing import List, Dict, Optional
|
||||
try:
|
||||
from loguru import logger
|
||||
except ImportError:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 项目根目录为 lm_code,bitmart 包在根目录
|
||||
import sys
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_DIR = os.path.dirname(SCRIPT_DIR)
|
||||
if ROOT_DIR not in sys.path:
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
# 默认 API(与 bitmart/回测.py 一致,回测仅拉数据可不填有效 key)
|
||||
# APIContract 在 fetch_klines / fetch_multi_timeframe 内按需导入,以便 --no-api 回测不依赖 bitmart
|
||||
DEFAULT_API_KEY = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8"
|
||||
DEFAULT_SECRET_KEY = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5"
|
||||
DEFAULT_MEMO = "合约交易"
|
||||
CONTRACT_SYMBOL = "ETHUSDT"
|
||||
|
||||
|
||||
def _format_bar(k: dict) -> dict:
|
||||
"""API 单根K线 -> 统一格式,id 为秒时间戳"""
|
||||
return {
|
||||
"id": int(k["timestamp"]),
|
||||
"open": float(k["open_price"]),
|
||||
"high": float(k["high_price"]),
|
||||
"low": float(k["low_price"]),
|
||||
"close": float(k["close_price"]),
|
||||
"volume": float(k.get("volume", 0)),
|
||||
}
|
||||
|
||||
|
||||
def fetch_klines(
|
||||
contract_api: "APIContract",
|
||||
step: int,
|
||||
start_time: int,
|
||||
end_time: int,
|
||||
symbol: str = CONTRACT_SYMBOL,
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
拉取指定周期的K线(与 bitmart 回测抓取方式一致)。
|
||||
:param contract_api: bitmart.api_contract.APIContract 实例
|
||||
:param step: K线周期(分钟),如 5/15/60
|
||||
:param start_time: 开始时间戳(秒)
|
||||
:param end_time: 结束时间戳(秒)
|
||||
:param symbol: 合约符号
|
||||
:return: [{"id", "open", "high", "low", "close", "volume"}, ...],按 id 升序
|
||||
"""
|
||||
all_data: List[Dict] = []
|
||||
existing_ids = set()
|
||||
request_interval = step * 60 * 500 # 每次最多约 500 根
|
||||
current_start = start_time
|
||||
|
||||
while current_start < end_time:
|
||||
current_end = min(current_start + request_interval, end_time)
|
||||
try:
|
||||
response = contract_api.get_kline(
|
||||
contract_symbol=symbol,
|
||||
step=step,
|
||||
start_time=current_start,
|
||||
end_time=current_end,
|
||||
)[0]
|
||||
if response.get("code") != 1000:
|
||||
logger.warning(f"get_kline code={response.get('code')}, msg={response.get('msg')}")
|
||||
time.sleep(1)
|
||||
current_start = current_end
|
||||
continue
|
||||
data = response.get("data", [])
|
||||
except Exception as e:
|
||||
logger.warning(f"get_kline 异常 step={step} {e},60秒后重试")
|
||||
time.sleep(60)
|
||||
continue
|
||||
|
||||
for k in data:
|
||||
k_id = int(k["timestamp"])
|
||||
if k_id in existing_ids:
|
||||
continue
|
||||
existing_ids.add(k_id)
|
||||
all_data.append(_format_bar(k))
|
||||
|
||||
if len(data) < 500:
|
||||
current_start = current_end
|
||||
else:
|
||||
all_data.sort(key=lambda x: x["id"])
|
||||
current_start = all_data[-1]["id"] + 1
|
||||
|
||||
time.sleep(0.25)
|
||||
|
||||
all_data.sort(key=lambda x: x["id"])
|
||||
return all_data
|
||||
|
||||
|
||||
def fetch_multi_timeframe(
|
||||
start_time: int,
|
||||
end_time: int,
|
||||
steps: List[int] = None,
|
||||
api_key: str = DEFAULT_API_KEY,
|
||||
secret_key: str = DEFAULT_SECRET_KEY,
|
||||
memo: str = DEFAULT_MEMO,
|
||||
symbol: str = CONTRACT_SYMBOL,
|
||||
) -> Dict[int, List[Dict]]:
|
||||
"""
|
||||
拉取多周期K线(5/15/60),供回测使用。
|
||||
:return: { step: [kline_list] }
|
||||
"""
|
||||
steps = steps or [5, 15, 60]
|
||||
from bitmart.api_contract import APIContract
|
||||
api = APIContract(api_key, secret_key, memo, timeout=(5, 15))
|
||||
result = {}
|
||||
for step in steps:
|
||||
logger.info(f"抓取 {step} 分钟 K 线: {start_time} ~ {end_time}")
|
||||
result[step] = fetch_klines(api, step, start_time, end_time, symbol)
|
||||
logger.info(f" -> {len(result[step])} 条")
|
||||
return result
|
||||
|
||||
|
||||
def save_klines_csv(klines: List[Dict], path: str) -> None:
|
||||
"""将K线保存为 CSV(id, open, high, low, close, volume)"""
|
||||
if not klines:
|
||||
return
|
||||
cols = ["id", "open", "high", "low", "close", "volume"]
|
||||
with open(path, "w", newline="", encoding="utf-8") as f:
|
||||
w = csv.DictWriter(f, fieldnames=cols)
|
||||
w.writeheader()
|
||||
for row in klines:
|
||||
w.writerow({k: row.get(k) for k in cols})
|
||||
logger.info(f"已保存 {len(klines)} 条到 {path}")
|
||||
|
||||
|
||||
def load_klines_csv(path: str) -> List[Dict]:
|
||||
"""从 CSV 加载K线"""
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
r = csv.DictReader(f)
|
||||
rows = list(r)
|
||||
out = []
|
||||
for row in rows:
|
||||
out.append({
|
||||
"id": int(row["id"]),
|
||||
"open": float(row["open"]),
|
||||
"high": float(row["high"]),
|
||||
"low": float(row["low"]),
|
||||
"close": float(row["close"]),
|
||||
"volume": float(row.get("volume", 0)),
|
||||
})
|
||||
out.sort(key=lambda x: x["id"])
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import datetime
|
||||
# 示例:拉取最近约 30 天 5/15/60 分钟数据并保存
|
||||
end_ts = int(time.time())
|
||||
start_ts = end_ts - 30 * 24 * 3600
|
||||
data_dir = os.path.join(SCRIPT_DIR, "data")
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
data = fetch_multi_timeframe(start_ts, end_ts, steps=[5, 15, 60])
|
||||
for step, klines in data.items():
|
||||
path = os.path.join(data_dir, f"kline_{step}m.csv")
|
||||
save_klines_csv(klines, path)
|
||||
86
adaptive_third_strategy/indicators.py
Normal file
86
adaptive_third_strategy/indicators.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
技术指标:EMA、ATR
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
|
||||
def ema(series: List[float], period: int) -> List[Optional[float]]:
|
||||
"""EMA(period),前 period-1 个为 None"""
|
||||
if not series or period < 1:
|
||||
return []
|
||||
k = 2.0 / (period + 1)
|
||||
out: List[Optional[float]] = [None] * (period - 1)
|
||||
s = sum(series[:period])
|
||||
out.append(s / period)
|
||||
for i in range(period, len(series)):
|
||||
val = series[i] * k + out[-1] * (1 - k) # type: ignore
|
||||
out.append(val)
|
||||
return out
|
||||
|
||||
|
||||
def atr(high: List[float], low: List[float], close: List[float], period: int) -> List[Optional[float]]:
|
||||
"""ATR(period),前 period 个为 None"""
|
||||
n = len(close)
|
||||
if n < period + 1 or len(high) != n or len(low) != n:
|
||||
return [None] * n
|
||||
tr_list: List[float] = []
|
||||
for i in range(n):
|
||||
if i == 0:
|
||||
tr_list.append(high[0] - low[0])
|
||||
else:
|
||||
tr = max(
|
||||
high[i] - low[i],
|
||||
abs(high[i] - close[i - 1]),
|
||||
abs(low[i] - close[i - 1])
|
||||
)
|
||||
tr_list.append(tr)
|
||||
return ema(tr_list, period)
|
||||
|
||||
|
||||
def get_ema_atr_from_klines(klines: List[Dict], ema_period: int, atr_period: int
|
||||
) -> tuple:
|
||||
"""
|
||||
从K线列表计算收盘价 EMA 和 ATR。
|
||||
返回 (ema_list, atr_list),长度与 klines 一致。
|
||||
"""
|
||||
close = [float(k["close"]) for k in klines]
|
||||
high = [float(k["high"]) for k in klines]
|
||||
low = [float(k["low"]) for k in klines]
|
||||
ema_list = ema(close, ema_period)
|
||||
atr_list = atr(high, low, close, atr_period)
|
||||
return ema_list, atr_list
|
||||
|
||||
|
||||
def align_higher_tf_ema(
|
||||
klines_5m: List[Dict],
|
||||
klines_higher: List[Dict],
|
||||
ema_fast: int,
|
||||
ema_slow: int
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
根据更高周期K线计算 EMA,并按 5 分钟时间对齐。
|
||||
返回列表长度与 klines_5m 一致,每项为 {"ema_fast": float, "ema_slow": float} 或 None。
|
||||
"""
|
||||
if not klines_higher or len(klines_higher) < ema_slow:
|
||||
return [{}] * len(klines_5m)
|
||||
close_hi = [float(k["close"]) for k in klines_higher]
|
||||
ema_f = ema(close_hi, ema_fast)
|
||||
ema_s = ema(close_hi, ema_slow)
|
||||
id_hi = [k["id"] for k in klines_higher]
|
||||
result = []
|
||||
for k5 in klines_5m:
|
||||
t = k5["id"]
|
||||
# 当前 5m 时刻所属的更高周期:取 <= t 的最后一根
|
||||
idx = -1
|
||||
for i, tid in enumerate(id_hi):
|
||||
if tid <= t:
|
||||
idx = i
|
||||
else:
|
||||
break
|
||||
if idx >= ema_slow - 1 and ema_f[idx] is not None and ema_s[idx] is not None:
|
||||
result.append({"ema_fast": ema_f[idx], "ema_slow": ema_s[idx]})
|
||||
else:
|
||||
result.append({})
|
||||
return result
|
||||
305
adaptive_third_strategy/strategy_core.py
Normal file
305
adaptive_third_strategy/strategy_core.py
Normal file
@@ -0,0 +1,305 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
自适应三分位趋势策略 - 核心逻辑
|
||||
趋势过滤、动态阈值、信号确认、市场状态
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
import sys
|
||||
import os
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if ROOT_DIR not in sys.path:
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
from adaptive_third_strategy.config import (
|
||||
MIN_BODY_ATR_RATIO,
|
||||
MIN_VOLATILITY_PERCENT,
|
||||
EMA_SHORT,
|
||||
EMA_LONG_FAST,
|
||||
EMA_LONG_SLOW,
|
||||
EMA_MID_FAST,
|
||||
EMA_MID_SLOW,
|
||||
ATR_PERIOD,
|
||||
VOLATILITY_COEF_CLAMP,
|
||||
BASE_COEF,
|
||||
TREND_FAVOR_COEF,
|
||||
TREND_AGAINST_COEF,
|
||||
TREND_MODE,
|
||||
CONFIRM_REQUIRED,
|
||||
VOLUME_MA_PERIOD,
|
||||
VOLUME_RATIO_THRESHOLD,
|
||||
REVERSE_BREAK_MULT,
|
||||
MIN_BARS_SINCE_ENTRY,
|
||||
FORBIDDEN_PERIODS,
|
||||
ATR_PAUSE_MULT,
|
||||
STRONG_TREND_COEF,
|
||||
RANGE_COEF,
|
||||
HIGH_VOL_EXTRA_CONFIRM,
|
||||
)
|
||||
from adaptive_third_strategy.indicators import (
|
||||
get_ema_atr_from_klines,
|
||||
align_higher_tf_ema,
|
||||
ema,
|
||||
)
|
||||
|
||||
|
||||
def get_body_size(candle: Dict) -> float:
|
||||
return abs(float(candle["open"]) - float(candle["close"]))
|
||||
|
||||
|
||||
def is_bullish(candle: Dict) -> bool:
|
||||
return float(candle["close"]) > float(candle["open"])
|
||||
|
||||
|
||||
def get_min_body_threshold(price: float, atr_value: Optional[float]) -> float:
|
||||
"""有效K线最小实体 = max(ATR*0.1, 价格*0.05%)"""
|
||||
min_vol = price * MIN_VOLATILITY_PERCENT
|
||||
if atr_value is not None and atr_value > 0:
|
||||
min_vol = max(min_vol, atr_value * MIN_BODY_ATR_RATIO)
|
||||
return min_vol
|
||||
|
||||
|
||||
def find_valid_prev_bar(
|
||||
all_data: List[Dict],
|
||||
current_idx: int,
|
||||
atr_series: List[Optional[float]],
|
||||
min_body_override: Optional[float] = None,
|
||||
) -> Tuple[Optional[int], Optional[Dict]]:
|
||||
"""从当前索引往前找实体>=阈值的K线。阈值 = max(ATR*0.1, 价格*0.05%)"""
|
||||
if current_idx <= 0:
|
||||
return None, None
|
||||
for i in range(current_idx - 1, -1, -1):
|
||||
prev = all_data[i]
|
||||
body = get_body_size(prev)
|
||||
price = float(prev["close"])
|
||||
atr_val = atr_series[i] if i < len(atr_series) else None
|
||||
th = min_body_override if min_body_override is not None else get_min_body_threshold(price, atr_val)
|
||||
if body >= th:
|
||||
return i, prev
|
||||
return None, None
|
||||
|
||||
|
||||
def get_trend(
|
||||
klines_5m: List[Dict],
|
||||
idx_5m: int,
|
||||
ema_5m: List[Optional[float]],
|
||||
ema_15m_align: List[Dict],
|
||||
ema_60m_align: List[Dict],
|
||||
) -> str:
|
||||
"""
|
||||
多时间框架趋势。返回 "long" / "short" / "neutral"。
|
||||
长期:1h EMA50 vs EMA200;中期:15m EMA20 vs EMA50;短期:5m close vs EMA9。
|
||||
ema_*_align: 与 5m 对齐的列表,每项 {"ema_fast", "ema_slow"}。
|
||||
"""
|
||||
if idx_5m >= len(klines_5m):
|
||||
return "neutral"
|
||||
curr = klines_5m[idx_5m]
|
||||
close_5 = float(curr["close"])
|
||||
# 短期
|
||||
ema9 = ema_5m[idx_5m] if idx_5m < len(ema_5m) else None
|
||||
short_bull = (ema9 is not None and close_5 > ema9)
|
||||
# 中期
|
||||
mid = ema_15m_align[idx_5m] if idx_5m < len(ema_15m_align) else {}
|
||||
mid_bull = (mid.get("ema_fast") is not None and mid.get("ema_slow") is not None
|
||||
and mid["ema_fast"] > mid["ema_slow"])
|
||||
# 长期
|
||||
long_d = ema_60m_align[idx_5m] if idx_5m < len(ema_60m_align) else {}
|
||||
long_bull = (long_d.get("ema_fast") is not None and long_d.get("ema_slow") is not None
|
||||
and long_d["ema_fast"] > long_d["ema_slow"])
|
||||
|
||||
if TREND_MODE == "aggressive":
|
||||
return "long" if short_bull else "short"
|
||||
if TREND_MODE == "conservative":
|
||||
if mid_bull and short_bull:
|
||||
return "long"
|
||||
if not mid_bull and not short_bull:
|
||||
return "short"
|
||||
return "neutral"
|
||||
# strict
|
||||
if long_bull and mid_bull and short_bull:
|
||||
return "long"
|
||||
if not long_bull and not mid_bull and not short_bull:
|
||||
return "short"
|
||||
return "neutral"
|
||||
|
||||
|
||||
def get_dynamic_trigger_levels(
|
||||
prev: Dict,
|
||||
atr_value: float,
|
||||
trend: str,
|
||||
market_state: str = "normal",
|
||||
) -> Tuple[Optional[float], Optional[float]]:
|
||||
"""
|
||||
动态三分位触发价。
|
||||
波动率系数 = clamp(实体/ATR, 0.3, 3.0),调整系数 = 0.33 * 波动率系数。
|
||||
顺势方向 ×0.8,逆势 ×1.2。
|
||||
"""
|
||||
body = get_body_size(prev)
|
||||
if body < 1e-6 or atr_value <= 0:
|
||||
return None, None
|
||||
vol_coef = body / atr_value
|
||||
vol_coef = max(VOLATILITY_COEF_CLAMP[0], min(VOLATILITY_COEF_CLAMP[1], vol_coef))
|
||||
adj = BASE_COEF * vol_coef
|
||||
if market_state == "strong_trend":
|
||||
adj = STRONG_TREND_COEF
|
||||
elif market_state == "range":
|
||||
adj = RANGE_COEF
|
||||
p_close = float(prev["close"])
|
||||
if trend == "long":
|
||||
long_adj = adj * TREND_FAVOR_COEF
|
||||
short_adj = adj * TREND_AGAINST_COEF
|
||||
elif trend == "short":
|
||||
long_adj = adj * TREND_AGAINST_COEF
|
||||
short_adj = adj * TREND_FAVOR_COEF
|
||||
else:
|
||||
long_adj = short_adj = adj
|
||||
long_trigger = p_close + body * long_adj
|
||||
short_trigger = p_close - body * short_adj
|
||||
return long_trigger, short_trigger
|
||||
|
||||
|
||||
def check_signal_confirm(
|
||||
curr: Dict,
|
||||
direction: str,
|
||||
trigger_price: float,
|
||||
all_data: List[Dict],
|
||||
current_idx: int,
|
||||
volume_ma: Optional[float],
|
||||
required: int = CONFIRM_REQUIRED,
|
||||
) -> int:
|
||||
"""
|
||||
确认条件计数:收盘价确认、成交量确认、动量确认。
|
||||
返回满足的个数。
|
||||
"""
|
||||
count = 0
|
||||
c_close = float(curr["close"])
|
||||
c_volume = float(curr.get("volume", 0))
|
||||
# 1. 收盘价确认
|
||||
if direction == "long" and c_close >= trigger_price:
|
||||
count += 1
|
||||
elif direction == "short" and c_close <= trigger_price:
|
||||
count += 1
|
||||
# 2. 成交量确认
|
||||
if volume_ma is not None and volume_ma > 0 and c_volume >= volume_ma * VOLUME_RATIO_THRESHOLD:
|
||||
count += 1
|
||||
# 3. 动量确认:当前K线实体方向与信号一致
|
||||
if direction == "long" and is_bullish(curr):
|
||||
count += 1
|
||||
elif direction == "short" and not is_bullish(curr):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def in_forbidden_period(ts_sec: int) -> bool:
|
||||
"""是否在禁止交易时段(按 UTC+8 小时:分)"""
|
||||
from datetime import datetime, timezone
|
||||
try:
|
||||
dt = datetime.fromtimestamp(ts_sec, tz=timezone.utc)
|
||||
except Exception:
|
||||
dt = datetime.utcfromtimestamp(ts_sec)
|
||||
# 转 UTC+8
|
||||
hour = (dt.hour + 8) % 24
|
||||
minute = dt.minute
|
||||
for h1, m1, h2, m2 in FORBIDDEN_PERIODS:
|
||||
t1 = h1 * 60 + m1
|
||||
t2 = h2 * 60 + m2
|
||||
t = hour * 60 + minute
|
||||
if t1 <= t < t2:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_market_state(
|
||||
atr_value: float,
|
||||
atr_avg: Optional[float],
|
||||
trend: str,
|
||||
) -> str:
|
||||
"""normal / strong_trend / range / high_vol"""
|
||||
if atr_avg is not None and atr_avg > 0 and atr_value >= atr_avg * ATR_PAUSE_MULT:
|
||||
return "high_vol"
|
||||
if trend in ("long", "short") and atr_avg is not None and atr_value > atr_avg * 1.2:
|
||||
return "strong_trend"
|
||||
if trend == "neutral":
|
||||
return "range"
|
||||
return "normal"
|
||||
|
||||
|
||||
def check_trigger(
|
||||
all_data: List[Dict],
|
||||
current_idx: int,
|
||||
atr_series: List[Optional[float]],
|
||||
ema_5m: List[Optional[float]],
|
||||
ema_15m_align: List[Dict],
|
||||
ema_60m_align: List[Dict],
|
||||
volume_ma_list: Optional[List[Optional[float]]] = None,
|
||||
use_confirm: bool = True,
|
||||
) -> Tuple[Optional[str], Optional[float], Optional[int], Optional[Dict]]:
|
||||
"""
|
||||
检查当前K线是否产生有效信号(含趋势过滤与确认)。
|
||||
返回 (方向, 触发价, 有效前一根索引, 有效前一根K线) 或 (None, None, None, None)。
|
||||
"""
|
||||
if current_idx <= 0 or current_idx >= len(all_data):
|
||||
return None, None, None, None
|
||||
curr = all_data[current_idx]
|
||||
valid_prev_idx, prev = find_valid_prev_bar(all_data, current_idx, atr_series)
|
||||
if prev is None:
|
||||
return None, None, None, None
|
||||
atr_val = atr_series[current_idx] if current_idx < len(atr_series) else None
|
||||
if atr_val is None or atr_val <= 0:
|
||||
return None, None, None, None
|
||||
trend = get_trend(
|
||||
all_data, current_idx, ema_5m, ema_15m_align, ema_60m_align,
|
||||
)
|
||||
atr_avg = None
|
||||
if atr_series:
|
||||
valid_atr = [x for x in atr_series[: current_idx + 1] if x is not None and x > 0]
|
||||
if len(valid_atr) >= ATR_PERIOD:
|
||||
atr_avg = sum(valid_atr) / len(valid_atr)
|
||||
market_state = get_market_state(atr_val, atr_avg, trend)
|
||||
if market_state == "high_vol":
|
||||
return None, None, None, None
|
||||
long_trigger, short_trigger = get_dynamic_trigger_levels(prev, atr_val, trend, market_state)
|
||||
if long_trigger is None:
|
||||
return None, None, None, None
|
||||
c_high = float(curr["high"])
|
||||
c_low = float(curr["low"])
|
||||
long_triggered = c_high >= long_trigger
|
||||
short_triggered = c_low <= short_trigger
|
||||
direction = None
|
||||
trigger_price = None
|
||||
if long_triggered and short_triggered:
|
||||
c_open = float(curr["open"])
|
||||
if abs(c_open - short_trigger) <= abs(c_open - long_trigger):
|
||||
direction, trigger_price = "short", short_trigger
|
||||
else:
|
||||
direction, trigger_price = "long", long_trigger
|
||||
elif short_triggered:
|
||||
direction, trigger_price = "short", short_trigger
|
||||
elif long_triggered:
|
||||
direction, trigger_price = "long", long_trigger
|
||||
if direction is None:
|
||||
return None, None, None, None
|
||||
# 趋势过滤:逆势不交易(可选,这里做过滤)
|
||||
if trend == "long" and direction == "short":
|
||||
return None, None, None, None
|
||||
if trend == "short" and direction == "long":
|
||||
return None, None, None, None
|
||||
# 禁止时段
|
||||
if in_forbidden_period(curr["id"]):
|
||||
return None, None, None, None
|
||||
# 信号确认
|
||||
if use_confirm and CONFIRM_REQUIRED > 0:
|
||||
vol_ma = volume_ma_list[current_idx] if volume_ma_list and current_idx < len(volume_ma_list) else None
|
||||
n = check_signal_confirm(curr, direction, trigger_price, all_data, current_idx, vol_ma, CONFIRM_REQUIRED)
|
||||
if n < CONFIRM_REQUIRED:
|
||||
return None, None, None, None
|
||||
return direction, trigger_price, valid_prev_idx, prev
|
||||
|
||||
|
||||
def build_volume_ma(klines: List[Dict], period: int = VOLUME_MA_PERIOD) -> List[Optional[float]]:
|
||||
"""前 period-1 为 None,之后为 volume 的 SMA"""
|
||||
vol = [float(k.get("volume", 0)) for k in klines]
|
||||
out: List[Optional[float]] = [None] * (period - 1)
|
||||
for i in range(period - 1, len(vol)):
|
||||
out.append(sum(vol[i - period + 1 : i + 1]) / period)
|
||||
return out
|
||||
187
adaptive_third_strategy/trade.py
Normal file
187
adaptive_third_strategy/trade.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
自适应三分位趋势策略 - 实盘交易
|
||||
拉取 Bitmart 5/15/60 分钟数据,计算信号,执行开平仓(沿用 交易/bitmart-三分之一策略交易 的浏览器/API 逻辑)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
try:
|
||||
from loguru import logger
|
||||
except ImportError:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if ROOT_DIR not in sys.path:
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
from bitmart.api_contract import APIContract
|
||||
from adaptive_third_strategy.config import (
|
||||
STEP_5M,
|
||||
STEP_15M,
|
||||
STEP_60M,
|
||||
ATR_PERIOD,
|
||||
EMA_SHORT,
|
||||
EMA_MID_FAST,
|
||||
EMA_MID_SLOW,
|
||||
EMA_LONG_FAST,
|
||||
EMA_LONG_SLOW,
|
||||
BASE_POSITION_PERCENT,
|
||||
MAX_POSITION_PERCENT,
|
||||
CONTRACT_SYMBOL,
|
||||
)
|
||||
from adaptive_third_strategy.indicators import get_ema_atr_from_klines, align_higher_tf_ema
|
||||
from adaptive_third_strategy.strategy_core import check_trigger, build_volume_ma
|
||||
from adaptive_third_strategy.data_fetcher import fetch_klines, _format_bar
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class AdaptiveThirdStrategyTrader:
|
||||
"""实盘:获取多周期K线 -> 策略信号 -> 开平仓(可接浏览器或纯 API)"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str,
|
||||
secret_key: str,
|
||||
memo: str = "合约交易",
|
||||
symbol: str = CONTRACT_SYMBOL,
|
||||
bit_id: Optional[str] = None,
|
||||
):
|
||||
self.api_key = api_key
|
||||
self.secret_key = secret_key
|
||||
self.memo = memo
|
||||
self.symbol = symbol
|
||||
self.bit_id = bit_id
|
||||
self.contractAPI = APIContract(api_key, secret_key, memo, timeout=(5, 15))
|
||||
self.check_interval = 3
|
||||
self.last_trigger_kline_id: Optional[int] = None
|
||||
self.last_trigger_direction: Optional[str] = None
|
||||
self.last_trade_kline_id: Optional[int] = None
|
||||
self.position_direction: int = 0 # 1 多 -1 空 0 无
|
||||
|
||||
def get_klines_multi(self) -> Tuple[List[Dict], List[Dict], List[Dict]]:
|
||||
"""拉取当前 5/15/60 分钟 K 线(最近约 3 小时足够算 EMA/ATR)"""
|
||||
end_ts = int(time.time())
|
||||
start_ts = end_ts - 3600 * 3
|
||||
k5 = fetch_klines(self.contractAPI, STEP_5M, start_ts, end_ts, self.symbol)
|
||||
k15 = fetch_klines(self.contractAPI, STEP_15M, start_ts, end_ts, self.symbol)
|
||||
k60 = fetch_klines(self.contractAPI, STEP_60M, start_ts, end_ts, self.symbol)
|
||||
return k5, k15, k60
|
||||
|
||||
def get_position_status(self) -> bool:
|
||||
"""查询当前持仓,更新 self.position_direction"""
|
||||
try:
|
||||
response = self.contractAPI.get_position(contract_symbol=self.symbol)[0]
|
||||
if response.get("code") != 1000:
|
||||
return False
|
||||
positions = response.get("data", [])
|
||||
if not positions:
|
||||
self.position_direction = 0
|
||||
return True
|
||||
self.position_direction = 1 if positions[0].get("position_type") == 1 else -1
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"持仓查询异常: {e}")
|
||||
return False
|
||||
|
||||
def check_realtime_signal(
|
||||
self,
|
||||
klines_5m: List[Dict],
|
||||
klines_15m: List[Dict],
|
||||
klines_60m: List[Dict],
|
||||
) -> Tuple[Optional[str], Optional[float], Optional[Dict]]:
|
||||
"""
|
||||
基于当前 K 线(最后一根未收盘)检测实时信号。
|
||||
返回 (方向, 触发价, 当前K线) 或 (None, None, None)。
|
||||
"""
|
||||
if len(klines_5m) < ATR_PERIOD + 2:
|
||||
return None, None, None
|
||||
from adaptive_third_strategy.indicators import get_ema_atr_from_klines
|
||||
from adaptive_third_strategy.strategy_core import check_trigger
|
||||
ema_5m, atr_5m = get_ema_atr_from_klines(klines_5m, EMA_SHORT, ATR_PERIOD)
|
||||
ema_15m_align = align_higher_tf_ema(klines_5m, klines_15m, EMA_MID_FAST, EMA_MID_SLOW)
|
||||
ema_60m_align = align_higher_tf_ema(klines_5m, klines_60m, EMA_LONG_FAST, EMA_LONG_SLOW)
|
||||
volume_ma = build_volume_ma(klines_5m)
|
||||
curr_idx = len(klines_5m) - 1
|
||||
direction, trigger_price, _, _ = check_trigger(
|
||||
klines_5m, curr_idx, atr_5m, ema_5m, ema_15m_align, ema_60m_align, volume_ma, use_confirm=True
|
||||
)
|
||||
curr = klines_5m[curr_idx]
|
||||
curr_id = curr["id"]
|
||||
if direction is None:
|
||||
return None, None, None
|
||||
if self.last_trigger_kline_id == curr_id and self.last_trigger_direction == direction:
|
||||
return None, None, None
|
||||
if self.last_trade_kline_id == curr_id:
|
||||
return None, None, None
|
||||
if (direction == "long" and self.position_direction == 1) or (direction == "short" and self.position_direction == -1):
|
||||
return None, None, None
|
||||
return direction, trigger_price, curr
|
||||
|
||||
def run_loop(self, ding_callback=None):
|
||||
"""
|
||||
主循环:拉数据 -> 检测信号 -> 若需交易则调用开平仓(需外部实现或注入)。
|
||||
ding_callback(msg, error=False) 可选,用于钉钉通知。
|
||||
"""
|
||||
def ding(msg: str, error: bool = False):
|
||||
if ding_callback:
|
||||
ding_callback(msg, error)
|
||||
else:
|
||||
if error:
|
||||
logger.error(msg)
|
||||
else:
|
||||
logger.info(msg)
|
||||
|
||||
logger.info("自适应三分位趋势策略实盘启动,按 Bitmart 方式拉取 5/15/60 分钟数据")
|
||||
while True:
|
||||
try:
|
||||
k5, k15, k60 = self.get_klines_multi()
|
||||
if len(k5) < ATR_PERIOD + 2:
|
||||
time.sleep(self.check_interval)
|
||||
continue
|
||||
if not self.get_position_status():
|
||||
time.sleep(self.check_interval)
|
||||
continue
|
||||
direction, trigger_price, curr = self.check_realtime_signal(k5, k15, k60)
|
||||
if direction and trigger_price is not None and curr is not None:
|
||||
curr_id = curr["id"]
|
||||
ding(f"信号: {direction} @ {trigger_price:.2f} 当前K线 id={curr_id}")
|
||||
# 这里接入你的开平仓实现:平仓 + 开单(可调用 交易/bitmart-三分之一策略交易 的 平仓/开单 或 API 下单)
|
||||
# 示例:仅记录,不实际下单
|
||||
self.last_trigger_kline_id = curr_id
|
||||
self.last_trigger_direction = direction
|
||||
self.last_trade_kline_id = curr_id
|
||||
time.sleep(self.check_interval)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
time.sleep(60)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="自适应三分位趋势策略实盘")
|
||||
parser.add_argument("--api-key", default="", help="Bitmart API Key")
|
||||
parser.add_argument("--secret-key", default="", help="Bitmart Secret Key")
|
||||
parser.add_argument("--bit-id", default="", help="比特浏览器 ID(若用浏览器下单)")
|
||||
args = parser.parse_args()
|
||||
api_key = args.api_key or os.environ.get("BITMART_API_KEY", "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8")
|
||||
secret_key = args.secret_key or os.environ.get("BITMART_SECRET_KEY", "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5")
|
||||
trader = AdaptiveThirdStrategyTrader(api_key, secret_key, bit_id=args.bit_id or None)
|
||||
try:
|
||||
from 交易.tools import send_dingtalk_message
|
||||
def ding(msg, error=False):
|
||||
prefix = "❌自适应三分位:" if error else "🔔自适应三分位:"
|
||||
send_dingtalk_message(f"{prefix}{msg}")
|
||||
except Exception:
|
||||
def ding(msg, error=False):
|
||||
logger.info(msg)
|
||||
trader.run_loop(ding_callback=ding)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user