代码结构优化
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
# 自适应三分位趋势策略
|
||||
|
||||
优化版实盘策略:趋势过滤 + 动态阈值 + 信号确认 + 完整风险管理。
|
||||
|
||||
## 目录结构
|
||||
|
||||
- `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。
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""自适应三分位趋势策略"""
|
||||
@@ -1,464 +0,0 @@
|
||||
# -*- 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()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,97 +0,0 @@
|
||||
# -*- 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
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,697 +0,0 @@
|
||||
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
|
||||
|
@@ -1,172 +0,0 @@
|
||||
# -*- 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)
|
||||
@@ -1,86 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,305 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,187 +0,0 @@
|
||||
# -*- 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()
|
||||
139
bieget/main.py
139
bieget/main.py
@@ -1,139 +0,0 @@
|
||||
import requests
|
||||
import datetime
|
||||
|
||||
cookies = {
|
||||
'bitget_lang': 'zh-CN',
|
||||
'_dx_kvani5r': '24e0dceb12f85d9a1279183ca29c09f772f8c61124e4eca228d8dd909c9b24b0019c2b9a',
|
||||
'OptanonAlertBoxClosed': 'Wed%20Sep%2024%202025%2017:12:32%20GMT+0800%20(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)',
|
||||
'OptanonConsent': 'isMarketing=1&isStatistic=1',
|
||||
'_cfuvid': 'IHXjOVVjx5iewfwL1nrgeHsZ6whu9s4iYYYvNvJvOQM-1758867462928-0.0.1.1-604800000',
|
||||
'dy_token': '68d630093VQFDNL1nUkv46lfDdhjHGYpoz9dddX1',
|
||||
'g_state': '{"i_l":0}',
|
||||
'theme': 'white',
|
||||
'BITGET_LOCAL_COOKIE': '{%22bitget_lang%22:%22zh-CN%22%2C%22bitget_unit%22:%22CNY%22%2C%22bitget_showasset%22:true%2C%22bitget_theme%22:%22white%22%2C%22bitget_layout%22:%22right%22%2C%22bitget_valuationunit%22:0%2C%22bitget_valuationunit_new%22:1%2C%22bitget_valuationunitandfiat%22:1%2C%22bitgt_login%22:false%2C%22theme%22:%22black%22%2C%22brand_simulation_trade%22:false%2C%22bitget_currency%22:%22%22}',
|
||||
'bt_rtoken': 'upex:session:id:1d00ce6487e37f4d7230a5aafe3ffc82e799156a86e9778bacb2310c7cf39b98',
|
||||
'bt_uid': '619BB9EC4D4A3F9FC1AC092D38818A00',
|
||||
'bt_sessonid': '0bf4ef42-798c-4f8d-8fe5-c07364d3b6b5',
|
||||
'bt_newsessionid': 'eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ZjJmNDBjYS0zNTkyLTRiNmItOTdiMy01OWQ5MTU5Y2E2YmQ0NzQ5ODIwMTAiLCJ1aWQiOiIxV00rVjRqcTJYSVRQemdJVWhUMnNRPT0iLCJzdWIiOiJsenoqKipvbSIsImlwIjoiM3dNK05yTkxjQXVQTG95dWZkWkI1QT09IiwiZGlkIjoiSjBZZjQ1NkV5RUxaSGhaa2tWYnNQZTVlSFVYYUNKZS9CM21wTHJ5SHJWMTEyUklzWlFLTTVIQmtTNHlUUmhuVyIsInN0cyI6MCwiaWF0IjoxNzU4ODY3NTgxLCJleHAiOjE3NTkyOTk1ODEsInB1c2hpZCI6ImRka1NMR1VDak9Sd1pFdU1rMFlaMWc9PSIsImlzcyI6InVwZXgifQ.Buwhs7UF7dOnOAso8l-RIyc_A47UdWSeVMiwIonOdiY',
|
||||
'USD': 'CNY',
|
||||
'__cf_bm': '3AMy5P3a.qp5uEqe9d_DonCDcL621CKULNKaUpfn2rE-1758868368-1.0.1.1-JzjFQidqwDxVV3gJH8ls0NeCGe.nsK.SFYP0hPXUcupuLTBpscmBYQRMfAjeOlnaZRBsQROFL2fETfXatDwQwDHU7aHOT4IMwECKB3sTIN4',
|
||||
}
|
||||
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'zh,zh-CN;q=0.9,zh-HK;q=0.8,en;q=0.7',
|
||||
'baggage': 'sentry-environment=online,sentry-release=905e3a416e2b0ad3c98a82ab9b94dd87f1a5c1fd,sentry-public_key=8aeef93d782e4ef5ae75634bfee9b590,sentry-trace_id=e7afec8af26a4480893c8b58257af49b',
|
||||
'cache-control': 'no-cache',
|
||||
'custom-token': '765eb29bf89843258756f8851ed5a48f',
|
||||
'deviceid': '7c88d47fdc39e5c422eff197b10d3485',
|
||||
'dy-token': '68d630093VQFDNL1nUkv46lfDdhjHGYpoz9dddX1',
|
||||
'language': 'zh_CN',
|
||||
'locale': 'zh_CN',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.bitget.com/zh-CN/futures/usdt/ETHUSDT',
|
||||
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'sentry-trace': 'e7afec8af26a4480893c8b58257af49b-8aacec1747b38bd7-0',
|
||||
'terminalcode': 'cc98a9dd01af7e2d50d9b9858723b25e',
|
||||
'terminaltype': '1',
|
||||
'tm': '1758868503025',
|
||||
'uhti': 'w17588685039139806ab12716',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
|
||||
'website': 'mix',
|
||||
# 'cookie': 'bitget_lang=zh-CN; _dx_kvani5r=24e0dceb12f85d9a1279183ca29c09f772f8c61124e4eca228d8dd909c9b24b0019c2b9a; OptanonAlertBoxClosed=Wed%20Sep%2024%202025%2017:12:32%20GMT+0800%20(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4); OptanonConsent=isMarketing=1&isStatistic=1; _cfuvid=IHXjOVVjx5iewfwL1nrgeHsZ6whu9s4iYYYvNvJvOQM-1758867462928-0.0.1.1-604800000; dy_token=68d630093VQFDNL1nUkv46lfDdhjHGYpoz9dddX1; g_state={"i_l":0}; theme=white; BITGET_LOCAL_COOKIE={%22bitget_lang%22:%22zh-CN%22%2C%22bitget_unit%22:%22CNY%22%2C%22bitget_showasset%22:true%2C%22bitget_theme%22:%22white%22%2C%22bitget_layout%22:%22right%22%2C%22bitget_valuationunit%22:0%2C%22bitget_valuationunit_new%22:1%2C%22bitget_valuationunitandfiat%22:1%2C%22bitgt_login%22:false%2C%22theme%22:%22black%22%2C%22brand_simulation_trade%22:false%2C%22bitget_currency%22:%22%22}; bt_rtoken=upex:session:id:1d00ce6487e37f4d7230a5aafe3ffc82e799156a86e9778bacb2310c7cf39b98; bt_uid=619BB9EC4D4A3F9FC1AC092D38818A00; bt_sessonid=0bf4ef42-798c-4f8d-8fe5-c07364d3b6b5; bt_newsessionid=eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ZjJmNDBjYS0zNTkyLTRiNmItOTdiMy01OWQ5MTU5Y2E2YmQ0NzQ5ODIwMTAiLCJ1aWQiOiIxV00rVjRqcTJYSVRQemdJVWhUMnNRPT0iLCJzdWIiOiJsenoqKipvbSIsImlwIjoiM3dNK05yTkxjQXVQTG95dWZkWkI1QT09IiwiZGlkIjoiSjBZZjQ1NkV5RUxaSGhaa2tWYnNQZTVlSFVYYUNKZS9CM21wTHJ5SHJWMTEyUklzWlFLTTVIQmtTNHlUUmhuVyIsInN0cyI6MCwiaWF0IjoxNzU4ODY3NTgxLCJleHAiOjE3NTkyOTk1ODEsInB1c2hpZCI6ImRka1NMR1VDak9Sd1pFdU1rMFlaMWc9PSIsImlzcyI6InVwZXgifQ.Buwhs7UF7dOnOAso8l-RIyc_A47UdWSeVMiwIonOdiY; USD=CNY; __cf_bm=3AMy5P3a.qp5uEqe9d_DonCDcL621CKULNKaUpfn2rE-1758868368-1.0.1.1-JzjFQidqwDxVV3gJH8ls0NeCGe.nsK.SFYP0hPXUcupuLTBpscmBYQRMfAjeOlnaZRBsQROFL2fETfXatDwQwDHU7aHOT4IMwECKB3sTIN4',
|
||||
}
|
||||
if __name__ == '__main__':
|
||||
|
||||
params = {
|
||||
'symbolId': 'ETHUSDT_UMCBL',
|
||||
'kLineStep': '1m',
|
||||
'kLineType': '1',
|
||||
'endTime': '1758864600000',
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
'https://www.bitget.com/v1/kline/getMoreKlineDataV2',
|
||||
params=params, cookies=cookies, headers=headers
|
||||
)
|
||||
|
||||
raw_data = response.json()["data"]
|
||||
|
||||
print(raw_data)
|
||||
|
||||
# 转换成字典格式,方便后续处理
|
||||
data = []
|
||||
for i in raw_data:
|
||||
data.append({
|
||||
'id': int(i[0]) // 1000, # 秒级时间戳
|
||||
'open': float(i[1]),
|
||||
'high': float(i[2]),
|
||||
'low': float(i[3]),
|
||||
'close': float(i[4]),
|
||||
})
|
||||
|
||||
print(i)
|
||||
|
||||
print(data)
|
||||
|
||||
# 按时间排序
|
||||
sorted_data = sorted(data, key=lambda x: x['id'])
|
||||
|
||||
print(sorted_data)
|
||||
|
||||
signals = 0
|
||||
wins = 0
|
||||
lig_price = 0
|
||||
low_price = 0
|
||||
|
||||
for idx in range(1, len(sorted_data) - 2): # 至少留两根 K 线验证
|
||||
prev = sorted_data[idx - 1]
|
||||
curr = sorted_data[idx]
|
||||
future = sorted_data[idx + 2]
|
||||
|
||||
prev_open, prev_close = prev['open'], prev['close']
|
||||
curr_open, curr_close = curr['open'], curr['close']
|
||||
future_close = future['close']
|
||||
|
||||
# 当前为涨
|
||||
if curr_close > curr_open:
|
||||
# 前一笔涨 + 包裹
|
||||
if prev_close > prev_open and curr_open < prev_open and curr_close > prev_close:
|
||||
signals += 1
|
||||
lig_price += (future_close - curr_close)
|
||||
if future_close > curr_close:
|
||||
wins += 1
|
||||
# 前一笔跌 + 反包
|
||||
elif prev_close < prev_open and curr_open < prev_close and curr_close > prev_open:
|
||||
signals += 1
|
||||
lig_price += (future_close - curr_close)
|
||||
if future_close > curr_close:
|
||||
wins += 1
|
||||
|
||||
# 当前为跌
|
||||
elif curr_close < curr_open:
|
||||
# 前一笔跌 + 包裹
|
||||
if prev_close < prev_open and curr_open > prev_open and curr_close < prev_close:
|
||||
signals += 1
|
||||
low_price += (curr_close - future_close)
|
||||
if future_close < curr_close:
|
||||
wins += 1
|
||||
# 前一笔涨 + 反包
|
||||
elif prev_close > prev_open and curr_open > prev_close and curr_close < prev_open:
|
||||
signals += 1
|
||||
low_price += (curr_close - future_close)
|
||||
if future_close < curr_close:
|
||||
wins += 1
|
||||
|
||||
if signals > 0:
|
||||
win_rate = wins / signals * 100
|
||||
print(f"信号数={signals}, 胜率={win_rate:.2f}%")
|
||||
else:
|
||||
print("没有找到符合条件的形态")
|
||||
|
||||
print("上涨盈亏合计:", lig_price)
|
||||
print("下跌盈亏合计:", low_price)
|
||||
@@ -1,81 +0,0 @@
|
||||
import requests
|
||||
|
||||
cookies = {
|
||||
'bitget_lang': 'zh-CN',
|
||||
'_dx_kvani5r': '24e0dceb12f85d9a1279183ca29c09f772f8c61124e4eca228d8dd909c9b24b0019c2b9a',
|
||||
'OptanonAlertBoxClosed': 'Wed%20Sep%2024%202025%2017:12:32%20GMT+0800%20(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)',
|
||||
'OptanonConsent': 'isMarketing=1&isStatistic=1',
|
||||
'g_state': '{"i_l":0}',
|
||||
'theme': 'white',
|
||||
'BITGET_LOCAL_COOKIE': '{%22bitget_lang%22:%22zh-CN%22%2C%22bitget_unit%22:%22CNY%22%2C%22bitget_showasset%22:true%2C%22bitget_theme%22:%22white%22%2C%22bitget_layout%22:%22right%22%2C%22bitget_valuationunit%22:0%2C%22bitget_valuationunit_new%22:1%2C%22bitget_valuationunitandfiat%22:1%2C%22bitgt_login%22:false%2C%22theme%22:%22black%22%2C%22brand_simulation_trade%22:false%2C%22bitget_currency%22:%22%22}',
|
||||
'USD': 'CNY',
|
||||
'_gcl_au': '1.1.737358653.1759055313',
|
||||
'_ga': 'GA1.1.942322417.1759055313',
|
||||
'afUserId': '58c62981-8c6f-4601-adee-77489e65d72b-p',
|
||||
'AF_SYNC': '1759974778836',
|
||||
'bt_rtoken': 'upex:session:id:d250cc2b001c88f9e9c60a3686fd7dcf5773a666402d41b35c7a6fa3f853392d',
|
||||
'bt_uid': '619BB9EC4D4A3F9FC1AC092D38818A00',
|
||||
'bt_sessonid': '6ad2dd3f-35f3-4ef8-accb-3292ceb8b5d7',
|
||||
'bt_newsessionid': 'eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI1NTk2ZjA1MC0zOGFhLTQxMjktOTcyZS0wMWUyZDI1MWI0N2M1NzQxNTgyOTIiLCJ1aWQiOiIxV00rVjRqcTJYSVRQemdJVWhUMnNRPT0iLCJzdWIiOiJsenoqKipvbSIsImlwIjoiWGRhSGJmdHZQVGVpeFlnMmxlVmdUZz09IiwiZGlkIjoiSjBZZjQ1NkV5RUxaSGhaa2tWYnNQZTVlSFVYYUNKZS9CM21wTHJ5SHJWMTEyUklzWlFLTTVIQmtTNHlUUmhuVyIsInN0cyI6MCwiaWF0IjoxNzU5OTc0NzkwLCJleHAiOjE3NjA0MDY3OTAsInB1c2hpZCI6ImRka1NMR1VDak9Sd1pFdU1rMFlaMWc9PSIsImlzcyI6InVwZXgifQ.0SygmCSR3f5tfA0xQpdzS241nFDbsVultPm0LmleYgY',
|
||||
'_ga_Z8Q93KHR0F': 'GS2.1.s1759974680$o4$g1$t1759974795$j19$l0$h0',
|
||||
'dy_token': '68e74d44zBITZniRPCwXG8rs7bnFrrlGIHyxtKx1',
|
||||
'_cfuvid': '7C9e7Tl9zudw90jhhHGEM4H7SJnpWoi39xepaHJpJuM-1759995357475-0.0.1.1-604800000',
|
||||
'__cf_bm': 'y7VR5vFWhYBo1IWnsrX2txdXsSeztAnTxMOQjyhyuf0-1760001832-1.0.1.1-kj32unDiaucuNb8pjHeq89WUlOf.zwJW6gyN53JjOsQ8FhB1R23kpDJtzc4878CdMKYfk7xFyYUnUBEiESvziCBFUbhm3F.R0qaPyZ.dw8g',
|
||||
}
|
||||
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,zh-HK;q=0.8,en;q=0.7',
|
||||
'baggage': 'sentry-environment=online,sentry-release=776c8c2125078b22570121601aabb051a52c0936,sentry-public_key=8aeef93d782e4ef5ae75634bfee9b590,sentry-trace_id=cb93656935cd47f289b30e55dd96f6c0',
|
||||
'cache-control': 'no-cache',
|
||||
'content-type': 'application/json;charset=UTF-8',
|
||||
'custom-token': '2facaf72193a42ab84dd879b94b57c7e',
|
||||
'deviceid': '7c88d47fdc39e5c422eff197b10d3485',
|
||||
'dy-token': '68e74d44zBITZniRPCwXG8rs7bnFrrlGIHyxtKx1',
|
||||
'language': 'zh_CN',
|
||||
'locale': 'zh_CN',
|
||||
'origin': 'https://www.bitget.com',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.bitget.com/zh-CN/futures/usdt/ETHUSDT',
|
||||
'sec-ch-ua': '"Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'sentry-trace': 'cb93656935cd47f289b30e55dd96f6c0-b7272e524943c558-0',
|
||||
'terminalcode': 'cc98a9dd01af7e2d50d9b9858723b25e',
|
||||
'terminaltype': '1',
|
||||
'tm': '1760002404225',
|
||||
'uhti': 'w1760002409197066bf83bff8',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
|
||||
'website': 'mix',
|
||||
# 'cookie': 'bitget_lang=zh-CN; _dx_kvani5r=24e0dceb12f85d9a1279183ca29c09f772f8c61124e4eca228d8dd909c9b24b0019c2b9a; OptanonAlertBoxClosed=Wed%20Sep%2024%202025%2017:12:32%20GMT+0800%20(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4); OptanonConsent=isMarketing=1&isStatistic=1; g_state={"i_l":0}; theme=white; BITGET_LOCAL_COOKIE={%22bitget_lang%22:%22zh-CN%22%2C%22bitget_unit%22:%22CNY%22%2C%22bitget_showasset%22:true%2C%22bitget_theme%22:%22white%22%2C%22bitget_layout%22:%22right%22%2C%22bitget_valuationunit%22:0%2C%22bitget_valuationunit_new%22:1%2C%22bitget_valuationunitandfiat%22:1%2C%22bitgt_login%22:false%2C%22theme%22:%22black%22%2C%22brand_simulation_trade%22:false%2C%22bitget_currency%22:%22%22}; USD=CNY; _gcl_au=1.1.737358653.1759055313; _ga=GA1.1.942322417.1759055313; afUserId=58c62981-8c6f-4601-adee-77489e65d72b-p; AF_SYNC=1759974778836; bt_rtoken=upex:session:id:d250cc2b001c88f9e9c60a3686fd7dcf5773a666402d41b35c7a6fa3f853392d; bt_uid=619BB9EC4D4A3F9FC1AC092D38818A00; bt_sessonid=6ad2dd3f-35f3-4ef8-accb-3292ceb8b5d7; bt_newsessionid=eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI1NTk2ZjA1MC0zOGFhLTQxMjktOTcyZS0wMWUyZDI1MWI0N2M1NzQxNTgyOTIiLCJ1aWQiOiIxV00rVjRqcTJYSVRQemdJVWhUMnNRPT0iLCJzdWIiOiJsenoqKipvbSIsImlwIjoiWGRhSGJmdHZQVGVpeFlnMmxlVmdUZz09IiwiZGlkIjoiSjBZZjQ1NkV5RUxaSGhaa2tWYnNQZTVlSFVYYUNKZS9CM21wTHJ5SHJWMTEyUklzWlFLTTVIQmtTNHlUUmhuVyIsInN0cyI6MCwiaWF0IjoxNzU5OTc0NzkwLCJleHAiOjE3NjA0MDY3OTAsInB1c2hpZCI6ImRka1NMR1VDak9Sd1pFdU1rMFlaMWc9PSIsImlzcyI6InVwZXgifQ.0SygmCSR3f5tfA0xQpdzS241nFDbsVultPm0LmleYgY; _ga_Z8Q93KHR0F=GS2.1.s1759974680$o4$g1$t1759974795$j19$l0$h0; dy_token=68e74d44zBITZniRPCwXG8rs7bnFrrlGIHyxtKx1; _cfuvid=7C9e7Tl9zudw90jhhHGEM4H7SJnpWoi39xepaHJpJuM-1759995357475-0.0.1.1-604800000; __cf_bm=y7VR5vFWhYBo1IWnsrX2txdXsSeztAnTxMOQjyhyuf0-1760001832-1.0.1.1-kj32unDiaucuNb8pjHeq89WUlOf.zwJW6gyN53JjOsQ8FhB1R23kpDJtzc4878CdMKYfk7xFyYUnUBEiESvziCBFUbhm3F.R0qaPyZ.dw8g',
|
||||
}
|
||||
|
||||
json_data = {
|
||||
'startTime': 1759912500000,
|
||||
'endTime': 1760002409196,
|
||||
'symbolCode': 'ETHUSDT',
|
||||
'languageType': 1,
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
'https://www.bitget.com/v1/trigger/strategy/tradingview/queryHistorySignals',
|
||||
cookies=cookies,
|
||||
headers=headers,
|
||||
json=json_data,
|
||||
)
|
||||
|
||||
print(response.json())
|
||||
|
||||
# Note: json_data will not be serialized by requests
|
||||
# exactly as it was in the original request.
|
||||
# data = '{"startTime":1759912500000,"endTime":1760002409196,"symbolCode":"ETHUSDT","languageType":1}'
|
||||
# response = requests.post(
|
||||
# 'https://www.bitget.com/v1/trigger/strategy/tradingview/queryHistorySignals',
|
||||
# cookies=cookies,
|
||||
# headers=headers,
|
||||
# data=data,
|
||||
# )
|
||||
@@ -1,21 +0,0 @@
|
||||
2025-12-20 02:51:09.247 | WARNING | __main__:__init__:28 - 请确认Memo是否正确!默认Memo可能导致签名失败
|
||||
2025-12-20 02:51:09.252 | INFO | __main__:init_api_client:57 - 尝试初始化API客户端 (尝试 1/3)
|
||||
2025-12-20 02:51:09.253 | ERROR | __main__:test_api_connection:112 - API连接测试失败: APIContract.get_depth() got an unexpected keyword argument 'limit'
|
||||
2025-12-20 02:51:09.254 | SUCCESS | __main__:init_api_client:87 - API客户端初始化成功
|
||||
2025-12-20 02:51:09.254 | INFO | __main__:action:220 - 启动BitMart合约策略...
|
||||
2025-12-20 02:51:09.254 | INFO | __main__:set_leverage_with_retry:121 - 尝试设置杠杆 (尝试 1/3)
|
||||
2025-12-20 02:51:09.814 | ERROR | __main__:set_leverage_with_retry:149 - API异常: APIException(http status=401): response={"code":30005,"msg":"Header X-BM-SIGN is wrong","trace":"d9c107e3-7564-474e-9eaf-59667f869ae5"}
|
||||
|
||||
2025-12-20 02:51:09.814 | INFO | __main__:set_leverage_with_retry:161 - 等待1秒后重试...
|
||||
2025-12-20 02:51:10.815 | INFO | __main__:set_leverage_with_retry:121 - 尝试设置杠杆 (尝试 2/3)
|
||||
2025-12-20 02:51:10.923 | ERROR | __main__:set_leverage_with_retry:149 - API异常: APIException(http status=401): response={"trace":"a7fa76a2-2e34-435b-acbe-90e37b47fe48","code":30005,"msg":"Header X-BM-SIGN is wrong"}
|
||||
|
||||
2025-12-20 02:51:10.923 | INFO | __main__:set_leverage_with_retry:161 - 等待2秒后重试...
|
||||
2025-12-20 02:51:12.923 | INFO | __main__:set_leverage_with_retry:121 - 尝试设置杠杆 (尝试 3/3)
|
||||
2025-12-20 02:51:13.029 | ERROR | __main__:set_leverage_with_retry:149 - API异常: APIException(http status=401): response={"code":30005,"msg":"Header X-BM-SIGN is wrong","trace":"97b6c791-5812-4285-a659-dbba3f6170e6"}
|
||||
|
||||
2025-12-20 02:51:13.029 | WARNING | __main__:set_leverage_with_retry:168 - 杠杆设置失败,但程序将继续运行
|
||||
2025-12-20 02:51:13.030 | WARNING | __main__:action:226 - 杠杆设置失败,使用系统默认杠杆继续运行
|
||||
2025-12-20 02:51:13.150 | SUCCESS | __main__:action:254 - 获取到新K线: 2025-12-20 02:30:00
|
||||
2025-12-20 02:51:13.150 | INFO | __main__:action:259 - 当前价格: 2959.49
|
||||
2025-12-20 02:51:13.311 | INFO | __main__:action:268 - 用户中断程序
|
||||
@@ -1,46 +0,0 @@
|
||||
2025-12-20 03:08:42.274 | INFO | __main__:initialize_api:66 - 尝试API配置 1/3
|
||||
2025-12-20 03:08:42.275 | INFO | __main__:initialize_api:67 - Memo: ''
|
||||
2025-12-20 03:08:42.275 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:08:42.276 | WARNING | __main__:initialize_api:84 - API配置 1 连接失败,尝试下一个配置
|
||||
2025-12-20 03:08:42.276 | INFO | __main__:initialize_api:66 - 尝试API配置 2/3
|
||||
2025-12-20 03:08:42.276 | INFO | __main__:initialize_api:67 - Memo: '合约交易'
|
||||
2025-12-20 03:08:42.276 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:08:42.276 | WARNING | __main__:initialize_api:84 - API配置 2 连接失败,尝试下一个配置
|
||||
2025-12-20 03:08:42.276 | INFO | __main__:initialize_api:66 - 尝试API配置 3/3
|
||||
2025-12-20 03:08:42.277 | INFO | __main__:initialize_api:67 - Memo: 'None'
|
||||
2025-12-20 03:08:42.277 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:08:42.277 | WARNING | __main__:initialize_api:84 - API配置 3 连接失败,尝试下一个配置
|
||||
2025-12-20 03:08:42.277 | ERROR | __main__:initialize_api:89 - 所有API配置都失败
|
||||
2025-12-20 03:08:42.281 | ERROR | __main__:action:408 - API初始化失败,程序无法运行
|
||||
2025-12-20 03:08:48.311 | INFO | __main__:initialize_api:66 - 尝试API配置 1/3
|
||||
2025-12-20 03:08:48.312 | INFO | __main__:initialize_api:67 - Memo: ''
|
||||
2025-12-20 03:08:48.313 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:08:48.313 | WARNING | __main__:initialize_api:84 - API配置 1 连接失败,尝试下一个配置
|
||||
2025-12-20 03:08:48.313 | INFO | __main__:initialize_api:66 - 尝试API配置 2/3
|
||||
2025-12-20 03:08:48.313 | INFO | __main__:initialize_api:67 - Memo: '合约交易'
|
||||
2025-12-20 03:08:48.313 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:08:48.313 | WARNING | __main__:initialize_api:84 - API配置 2 连接失败,尝试下一个配置
|
||||
2025-12-20 03:08:48.313 | INFO | __main__:initialize_api:66 - 尝试API配置 3/3
|
||||
2025-12-20 03:08:48.313 | INFO | __main__:initialize_api:67 - Memo: 'None'
|
||||
2025-12-20 03:08:48.313 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:08:48.313 | WARNING | __main__:initialize_api:84 - API配置 3 连接失败,尝试下一个配置
|
||||
2025-12-20 03:08:48.314 | ERROR | __main__:initialize_api:89 - 所有API配置都失败
|
||||
2025-12-20 03:08:48.317 | ERROR | __main__:action:408 - API初始化失败,程序无法运行
|
||||
2025-12-20 03:09:25.953 | INFO | __main__:initialize_api:66 - 尝试API配置 1/3
|
||||
2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:67 - Memo: ''
|
||||
2025-12-20 03:09:25.954 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:09:25.954 | WARNING | __main__:initialize_api:84 - API配置 1 连接失败,尝试下一个配置
|
||||
2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:66 - 尝试API配置 2/3
|
||||
2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:67 - Memo: '合约交易'
|
||||
2025-12-20 03:09:25.954 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:09:25.954 | WARNING | __main__:initialize_api:84 - API配置 2 连接失败,尝试下一个配置
|
||||
2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:66 - 尝试API配置 3/3
|
||||
2025-12-20 03:09:25.954 | INFO | __main__:initialize_api:67 - Memo: 'None'
|
||||
2025-12-20 03:09:25.955 | ERROR | __main__:test_api_connection:108 - API连接测试失败: 'APIContract' object has no attribute 'get_contracts'
|
||||
2025-12-20 03:09:25.955 | WARNING | __main__:initialize_api:84 - API配置 3 连接失败,尝试下一个配置
|
||||
2025-12-20 03:09:25.955 | ERROR | __main__:initialize_api:89 - 所有API配置都失败
|
||||
2025-12-20 03:09:25.958 | ERROR | __main__:action:408 - API初始化失败,程序无法运行
|
||||
2025-12-20 03:12:23.884 | INFO | __main__:action:396 - 启动BitMart合约交易策略...
|
||||
2025-12-20 03:12:23.885 | INFO | __main__:test_api_connection:48 - 测试API连接...
|
||||
2025-12-20 03:12:24.359 | ERROR | __main__:test_api_connection:90 - API连接测试异常: 'tuple' object has no attribute 'get'
|
||||
2025-12-20 03:12:24.359 | ERROR | __main__:action:400 - API连接测试失败
|
||||
415
evm_助记词查询余额.py
415
evm_助记词查询余额.py
@@ -1,415 +0,0 @@
|
||||
"""
|
||||
EVM助记词碰撞工具 - 高速版本
|
||||
批量生成助记词并检查余额,发现有钱包自动保存
|
||||
|
||||
依赖安装:
|
||||
pip install mnemonic eth-account web3 loguru
|
||||
|
||||
使用方法:
|
||||
python evm_助记词查询余额.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from mnemonic import Mnemonic
|
||||
from eth_account import Account
|
||||
from web3 import Web3
|
||||
from loguru import logger
|
||||
import time
|
||||
|
||||
# 尝试导入POA中间件
|
||||
try:
|
||||
from web3.middleware import geth_poa_middleware
|
||||
except ImportError:
|
||||
try:
|
||||
from web3.middleware.geth_poa import geth_poa_middleware
|
||||
except ImportError:
|
||||
geth_poa_middleware = None
|
||||
|
||||
# 配置日志
|
||||
logger.remove()
|
||||
logger.add(lambda msg: print(msg, end=''), format="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | {message}", level="INFO")
|
||||
|
||||
# 全局统计
|
||||
stats = {
|
||||
'total_checked': 0,
|
||||
'found_balance': 0,
|
||||
'start_time': time.time(),
|
||||
'lock': threading.Lock()
|
||||
}
|
||||
|
||||
# 结果保存文件
|
||||
RESULTS_FILE = "found_wallets.txt"
|
||||
SAVE_LOCK = threading.Lock()
|
||||
|
||||
|
||||
class FastEVMWallet:
|
||||
"""高速EVM钱包碰撞工具"""
|
||||
|
||||
# 优化的RPC端点(使用多个备用节点提高速度)
|
||||
RPC_ENDPOINTS = {
|
||||
"ethereum": [
|
||||
"https://eth.llamarpc.com",
|
||||
"https://rpc.ankr.com/eth",
|
||||
"https://ethereum.publicnode.com",
|
||||
],
|
||||
"bsc": [
|
||||
"https://bsc-dataseed1.binance.org",
|
||||
"https://bsc-dataseed2.binance.org",
|
||||
"https://rpc.ankr.com/bsc",
|
||||
],
|
||||
# "polygon": [
|
||||
# "https://polygon-rpc.com",
|
||||
# "https://rpc.ankr.com/polygon",
|
||||
# ],
|
||||
# "arbitrum": [
|
||||
# "https://arb1.arbitrum.io/rpc",
|
||||
# "https://rpc.ankr.com/arbitrum",
|
||||
# ],
|
||||
# "optimism": [
|
||||
# "https://mainnet.optimism.io",
|
||||
# "https://rpc.ankr.com/optimism",
|
||||
# ],
|
||||
"base": [
|
||||
"https://mainnet.base.org",
|
||||
"https://rpc.ankr.com/base",
|
||||
],
|
||||
}
|
||||
|
||||
def __init__(self, chains=None):
|
||||
"""
|
||||
初始化
|
||||
|
||||
Args:
|
||||
chains: 要查询的链列表,None表示查询所有链
|
||||
"""
|
||||
self.mnemo = Mnemonic("english")
|
||||
Account.enable_unaudited_hdwallet_features()
|
||||
|
||||
# 选择要查询的链(默认只查询主要链以提高速度)
|
||||
if chains is None:
|
||||
self.chains = ["ethereum", "bsc", "polygon", "arbitrum", "base"]
|
||||
else:
|
||||
self.chains = chains
|
||||
|
||||
# 为每个链创建Web3连接池
|
||||
self.w3_pools = {}
|
||||
self._init_connections()
|
||||
|
||||
def _init_connections(self):
|
||||
"""初始化RPC连接"""
|
||||
logger.info("正在初始化RPC连接...")
|
||||
for chain in self.chains:
|
||||
if chain in self.RPC_ENDPOINTS:
|
||||
# 尝试所有RPC节点,直到找到一个可用的
|
||||
w3 = None
|
||||
for rpc_url in self.RPC_ENDPOINTS[chain]:
|
||||
try:
|
||||
# 设置更短的超时时间,避免阻塞
|
||||
w3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={'timeout': 3}))
|
||||
|
||||
# 某些链需要POA中间件
|
||||
if chain in ["bsc", "polygon"] and geth_poa_middleware is not None:
|
||||
try:
|
||||
w3.middleware_onion.inject(geth_poa_middleware, layer=0)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 测试连接(快速测试)
|
||||
try:
|
||||
if w3.is_connected():
|
||||
self.w3_pools[chain] = w3
|
||||
logger.info(f"✓ {chain} 连接成功: {rpc_url}")
|
||||
break
|
||||
except:
|
||||
continue
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
if chain not in self.w3_pools:
|
||||
logger.warning(f"✗ {chain} 连接失败,将跳过该链")
|
||||
|
||||
def generate_mnemonic(self, strength=128):
|
||||
"""快速生成助记词"""
|
||||
return self.mnemo.generate(strength=strength)
|
||||
|
||||
def mnemonic_to_address(self, mnemonic, account_index=0):
|
||||
"""
|
||||
从助记词快速生成地址(不生成私钥,节省时间)
|
||||
|
||||
Returns:
|
||||
str: 钱包地址,失败返回None
|
||||
"""
|
||||
try:
|
||||
account = Account.from_mnemonic(mnemonic, account_path=f"m/44'/60'/0'/0/{account_index}")
|
||||
return account.address
|
||||
except:
|
||||
return None
|
||||
|
||||
def quick_check_balance(self, address):
|
||||
"""
|
||||
快速检查地址是否有余额(只检查主要链,一旦发现余额就返回)
|
||||
|
||||
Returns:
|
||||
dict: 如果有余额返回余额信息,否则返回None
|
||||
"""
|
||||
# 只检查最快的链(ethereum和bsc),提高速度
|
||||
priority_chains = ["ethereum", "bsc"]
|
||||
|
||||
for chain in priority_chains:
|
||||
if chain not in self.chains:
|
||||
continue
|
||||
try:
|
||||
w3 = self.w3_pools.get(chain)
|
||||
if not w3:
|
||||
continue
|
||||
|
||||
# 查询余额(设置超时,避免阻塞)
|
||||
try:
|
||||
balance_wei = w3.eth.get_balance(address)
|
||||
|
||||
if balance_wei > 0:
|
||||
balance_eth = Web3.from_wei(balance_wei, 'ether')
|
||||
return {
|
||||
'chain': chain,
|
||||
'address': address,
|
||||
'balance_wei': balance_wei,
|
||||
'balance_eth': float(balance_eth),
|
||||
'balance_formatted': f"{balance_eth:.6f} ETH"
|
||||
}
|
||||
except Exception as e:
|
||||
# 查询失败,尝试下一个链
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
def check_all_chains(self, address):
|
||||
"""
|
||||
检查所有链的余额(发现余额后调用)
|
||||
|
||||
Returns:
|
||||
list: 所有有余额的链信息
|
||||
"""
|
||||
results = []
|
||||
for chain in self.chains:
|
||||
try:
|
||||
w3 = self.w3_pools.get(chain)
|
||||
if not w3 or not w3.is_connected():
|
||||
continue
|
||||
|
||||
balance_wei = w3.eth.get_balance(address)
|
||||
if balance_wei > 0:
|
||||
balance_eth = Web3.from_wei(balance_wei, 'ether')
|
||||
results.append({
|
||||
'chain': chain,
|
||||
'balance_wei': balance_wei,
|
||||
'balance_eth': float(balance_eth),
|
||||
'balance_formatted': f"{balance_eth:.6f} ETH"
|
||||
})
|
||||
except:
|
||||
continue
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def save_found_wallet(mnemonic, address, private_key, balances):
|
||||
"""保存发现的钱包信息"""
|
||||
with SAVE_LOCK:
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
info = {
|
||||
'timestamp': timestamp,
|
||||
'mnemonic': mnemonic,
|
||||
'address': address,
|
||||
'private_key': private_key,
|
||||
'balances': balances
|
||||
}
|
||||
|
||||
# 保存到文件
|
||||
with open(RESULTS_FILE, 'a', encoding='utf-8') as f:
|
||||
f.write("="*80 + "\n")
|
||||
f.write(f"发现时间: {timestamp}\n")
|
||||
f.write(f"助记词: {mnemonic}\n")
|
||||
f.write(f"地址: {address}\n")
|
||||
f.write(f"私钥: {private_key}\n")
|
||||
f.write("余额信息:\n")
|
||||
for balance in balances:
|
||||
f.write(f" {balance['chain'].upper()}: {balance['balance_formatted']}\n")
|
||||
f.write("="*80 + "\n\n")
|
||||
|
||||
# 同时保存JSON格式
|
||||
json_file = RESULTS_FILE.replace('.txt', '.json')
|
||||
try:
|
||||
if os.path.exists(json_file):
|
||||
with open(json_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = []
|
||||
|
||||
data.append(info)
|
||||
with open(json_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def worker_thread(wallet_tool, thread_id):
|
||||
"""工作线程:生成助记词并检查余额"""
|
||||
global stats
|
||||
|
||||
# 每个线程独立计数,减少锁竞争
|
||||
local_count = 0
|
||||
thread_start_time = time.time()
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 生成助记词
|
||||
mnemonic = wallet_tool.generate_mnemonic()
|
||||
|
||||
# 生成地址
|
||||
address = wallet_tool.mnemonic_to_address(mnemonic)
|
||||
if not address:
|
||||
local_count += 1
|
||||
# 立即更新统计(不等待)
|
||||
if local_count >= 1: # 每次都更新,确保统计正常
|
||||
with stats['lock']:
|
||||
stats['total_checked'] += local_count
|
||||
local_count = 0
|
||||
continue
|
||||
|
||||
# 先更新统计(在查询余额之前)
|
||||
local_count += 1
|
||||
if local_count >= 1: # 每次都更新
|
||||
with stats['lock']:
|
||||
stats['total_checked'] += local_count
|
||||
total = stats['total_checked']
|
||||
found = stats['found_balance']
|
||||
if total % 20 == 0: # 每20次打印一次
|
||||
elapsed = time.time() - stats['start_time']
|
||||
speed = total / elapsed if elapsed > 0 else 0
|
||||
logger.info(f"已检查: {total} | 发现: {found} | 速度: {speed:.1f} 个/秒")
|
||||
local_count = 0
|
||||
|
||||
# 快速检查余额(只检查主要链,设置超时)
|
||||
try:
|
||||
balance_info = wallet_tool.quick_check_balance(address)
|
||||
except:
|
||||
balance_info = None
|
||||
|
||||
# 如果发现余额
|
||||
if balance_info:
|
||||
# 生成私钥
|
||||
account = Account.from_mnemonic(mnemonic, account_path="m/44'/60'/0'/0/0")
|
||||
private_key = account.key.hex()
|
||||
|
||||
# 检查所有链的余额
|
||||
all_balances = wallet_tool.check_all_chains(address)
|
||||
|
||||
# 保存结果
|
||||
save_found_wallet(mnemonic, address, private_key, all_balances)
|
||||
|
||||
# 更新统计并打印
|
||||
with stats['lock']:
|
||||
stats['found_balance'] += 1
|
||||
|
||||
logger.success(f"🎉 发现余额钱包!")
|
||||
logger.success(f"地址: {address}")
|
||||
logger.success(f"助记词: {mnemonic}")
|
||||
for bal in all_balances:
|
||||
logger.success(f" {bal['chain'].upper()}: {bal['balance_formatted']}")
|
||||
|
||||
except Exception as e:
|
||||
# 即使出错也更新统计
|
||||
local_count += 1
|
||||
if local_count >= 1:
|
||||
with stats['lock']:
|
||||
stats['total_checked'] += local_count
|
||||
local_count = 0
|
||||
continue
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("="*80)
|
||||
print("EVM助记词碰撞工具 - 高速版本")
|
||||
print("="*80)
|
||||
print(f"结果将保存到: {RESULTS_FILE}")
|
||||
print(f"查询的链: ethereum, bsc, polygon, arbitrum, base")
|
||||
print("="*80)
|
||||
print()
|
||||
|
||||
# 选择要查询的链(可以修改这里)
|
||||
chains = ["ethereum", "bsc", "polygon", "arbitrum", "base"]
|
||||
|
||||
# 创建钱包工具
|
||||
wallet_tool = FastEVMWallet(chains=chains)
|
||||
|
||||
# 设置线程数(根据CPU核心数调整)
|
||||
num_threads = os.cpu_count() or 4
|
||||
print(f"启动 {num_threads} 个工作线程...")
|
||||
print("按 Ctrl+C 停止程序\n")
|
||||
|
||||
# 创建线程
|
||||
threads = []
|
||||
|
||||
for i in range(num_threads):
|
||||
t = threading.Thread(target=worker_thread, args=(wallet_tool, i), daemon=True)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
# 等待一小段时间确保线程启动
|
||||
time.sleep(1)
|
||||
|
||||
# 检查是否有线程在运行
|
||||
active_threads = sum(1 for t in threads if t.is_alive())
|
||||
if active_threads == 0:
|
||||
logger.error("所有工作线程都停止了,请检查RPC连接")
|
||||
return
|
||||
else:
|
||||
logger.info(f"✓ {active_threads} 个工作线程正在运行")
|
||||
|
||||
# 测试:生成一个测试地址并查询,验证功能是否正常
|
||||
logger.info("正在测试功能...")
|
||||
test_mnemonic = wallet_tool.generate_mnemonic()
|
||||
test_address = wallet_tool.mnemonic_to_address(test_mnemonic)
|
||||
if test_address:
|
||||
logger.info(f"测试地址生成成功: {test_address[:10]}...")
|
||||
test_balance = wallet_tool.quick_check_balance(test_address)
|
||||
if test_balance is None:
|
||||
logger.info("测试余额查询成功(无余额,正常)")
|
||||
else:
|
||||
logger.warning(f"测试发现余额!这不太可能,请检查")
|
||||
else:
|
||||
logger.error("测试地址生成失败!")
|
||||
|
||||
try:
|
||||
# 主线程等待
|
||||
last_count = 0
|
||||
while True:
|
||||
time.sleep(2) # 每2秒更新一次显示
|
||||
# 定期打印统计信息
|
||||
elapsed = time.time() - stats['start_time']
|
||||
current_count = stats['total_checked']
|
||||
|
||||
# 如果计数没有变化,可能是线程有问题
|
||||
if elapsed > 10 and current_count == last_count and current_count == 0:
|
||||
logger.warning("警告:工作线程可能没有正常工作,请检查RPC连接")
|
||||
|
||||
if elapsed > 0:
|
||||
speed = current_count / elapsed if current_count > 0 else 0
|
||||
print(f"\r运行时间: {int(elapsed)}秒 | 已检查: {current_count} | 发现: {stats['found_balance']} | 速度: {speed:.1f} 个/秒", end='', flush=True)
|
||||
|
||||
last_count = current_count
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n正在停止...")
|
||||
print(f"\n最终统计:")
|
||||
print(f" 总检查数: {stats['total_checked']}")
|
||||
print(f" 发现余额: {stats['found_balance']}")
|
||||
print(f" 结果已保存到: {RESULTS_FILE}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
kline_data.xlsx
BIN
kline_data.xlsx
Binary file not shown.
483
mexc/30分钟.py
483
mexc/30分钟.py
@@ -1,483 +0,0 @@
|
||||
"""
|
||||
量化交易回测系统 - 30分钟K线策略回测(Weex数据源)
|
||||
|
||||
========== 策略规则 ==========
|
||||
重要:所有开仓和平仓操作都在下一根K线的开盘价执行
|
||||
|
||||
【策略流程】
|
||||
1. 开仓条件(信号出现时,下一根K线开盘价开仓):
|
||||
- 阳包阴(涨包跌)信号 -> 开多
|
||||
* 前一根是跌(阴线),后一根是涨(阳线)
|
||||
* 且:涨的收盘价 > 跌的开盘价
|
||||
|
||||
- 阴包阳(跌包涨)信号 -> 开空
|
||||
* 前一根是涨(阳线),后一根是跌(阴线)
|
||||
* 且:跌的收盘价 < 涨的开盘价
|
||||
|
||||
2. 平仓条件(所有平仓都在下一根K线开盘价执行):
|
||||
- 持有多单时:遇到两根连续的阴线 -> 下一根K线开盘价平仓
|
||||
- 持有空单时:遇到两根连续的阳线 -> 下一根K线开盘价平仓
|
||||
- 遇到反向信号:下一根K线开盘价平仓并反手开仓
|
||||
* 例如:持有多单时遇到阴包阳信号 -> 平多开空
|
||||
|
||||
3. 续持条件:
|
||||
- 遇到同向信号:续持
|
||||
- 未满足平仓条件:续持
|
||||
|
||||
【数据处理流程】
|
||||
1. 从数据库读取数据
|
||||
2. 数据排序(按时间戳升序)
|
||||
3. 开始回测
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import calendar
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Optional
|
||||
from loguru import logger
|
||||
|
||||
from models.mexc import Mexc30
|
||||
|
||||
|
||||
# ========================= 工具函数 =========================
|
||||
|
||||
# 交易对的最小价格单位(tick size)配置
|
||||
# 格式:交易对符号 -> 最小单位
|
||||
TICK_SIZE_MAP = {
|
||||
'SOLUSDT': 0.01,
|
||||
'BTCUSDT': 0.1,
|
||||
'ETHUSDT': 0.01,
|
||||
'BNBUSDT': 0.01,
|
||||
# 可以根据需要添加更多交易对
|
||||
}
|
||||
|
||||
# 默认最小单位(如果交易对不在配置中)
|
||||
DEFAULT_TICK_SIZE = 0.01
|
||||
|
||||
|
||||
def get_tick_size(symbol: str) -> float:
|
||||
"""
|
||||
获取交易对的最小价格单位
|
||||
:param symbol: 交易对符号,如 'SOLUSDT'
|
||||
:return: 最小单位,如 0.01
|
||||
"""
|
||||
return TICK_SIZE_MAP.get(symbol.upper(), DEFAULT_TICK_SIZE)
|
||||
|
||||
|
||||
def adjust_price_for_trade(price: float, direction: str, symbol: str = 'SOLUSDT') -> float:
|
||||
"""
|
||||
根据交易方向调整价格,考虑买卖价差
|
||||
|
||||
买入(开多/平空):价格 + tick_size
|
||||
卖出(开空/平多):价格 - tick_size
|
||||
|
||||
:param price: 原始价格(K线开盘价或收盘价)
|
||||
:param direction: 交易方向,'long' 表示买入,'short' 表示卖出
|
||||
:param symbol: 交易对符号,用于获取最小单位
|
||||
:return: 调整后的价格
|
||||
"""
|
||||
tick_size = get_tick_size(symbol)
|
||||
|
||||
if direction == 'long':
|
||||
# 买入:价格 + tick_size
|
||||
adjusted = price + tick_size
|
||||
elif direction == 'short':
|
||||
# 卖出:价格 - tick_size
|
||||
adjusted = price - tick_size
|
||||
else:
|
||||
# 未知方向,返回原价
|
||||
adjusted = price
|
||||
|
||||
# 确保价格不会为负
|
||||
return max(adjusted, tick_size)
|
||||
|
||||
|
||||
def is_bullish(c): # 阳线
|
||||
return float(c['close']) > float(c['open'])
|
||||
|
||||
|
||||
def is_bearish(c): # 阴线
|
||||
return float(c['close']) < float(c['open'])
|
||||
|
||||
|
||||
def check_signal(prev, curr):
|
||||
"""
|
||||
包住形态信号判定(优化版):
|
||||
只看两种信号,严格按照收盘价与开盘价的比较:
|
||||
|
||||
1. 阳包阴(涨包跌,前跌后涨)-> 做多:
|
||||
- 前一根是跌(阴线:close < open)
|
||||
- 后一根是涨(阳线:close > open)
|
||||
- 且:涨的收盘价 > 跌的开盘价(curr['close'] > prev['open'])
|
||||
|
||||
2. 阴包阳(跌包涨,前涨后跌)-> 做空:
|
||||
- 前一根是涨(阳线:close > open)
|
||||
- 后一根是跌(阴线:close < open)
|
||||
- 且:跌的收盘价 < 涨的开盘价(curr['close'] < prev['open'])
|
||||
"""
|
||||
p_open = float(prev['open'])
|
||||
c_close = float(curr['close'])
|
||||
|
||||
# 阳包阴(涨包跌,前跌后涨) -> 做多:涨的收盘价 > 跌的开盘价
|
||||
if is_bearish(prev) and is_bullish(curr) and c_close > p_open:
|
||||
return "long", "bear_bull_engulf"
|
||||
|
||||
# 阴包阳(跌包涨,前涨后跌) -> 做空:跌的收盘价 < 涨的开盘价
|
||||
if is_bullish(prev) and is_bearish(curr) and c_close < p_open:
|
||||
return "short", "bull_bear_engulf"
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def get_data_by_date(model, date_str: str):
|
||||
"""
|
||||
按天获取指定表的数据(30分钟K线)
|
||||
数据格式:时间戳(毫秒级) 开盘价 最高价 最低价 收盘价
|
||||
例如:1767461400000 3106.68 3109.1 3106.22 3107.22
|
||||
|
||||
注意:返回的数据已按时间戳(id)升序排序
|
||||
"""
|
||||
try:
|
||||
target_date = datetime.datetime.strptime(date_str, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
logger.error("日期格式不正确,请使用 YYYY-MM-DD 格式。")
|
||||
return []
|
||||
|
||||
# 将日期转换为毫秒级时间戳进行查询
|
||||
start_ts = int(target_date.timestamp() * 1000)
|
||||
end_ts = int((target_date + datetime.timedelta(days=1)).timestamp() * 1000) - 1
|
||||
|
||||
# 查询时按时间戳升序排序
|
||||
query = model.select().where(model.id.between(start_ts, end_ts)).order_by(model.id.asc())
|
||||
data = [{'id': i.id, 'open': i.open, 'high': i.high, 'low': i.low, 'close': i.close} for i in query]
|
||||
|
||||
# 确保数据已排序
|
||||
if data:
|
||||
data.sort(key=lambda x: x['id'])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# ========================= 回测逻辑 =========================
|
||||
|
||||
def backtest_15m_trend_optimized(dates: List[str], symbol: str = 'SOLUSDT'):
|
||||
"""
|
||||
回测策略逻辑:
|
||||
1. 开仓条件(信号出现时,下一根K线开盘价开仓):
|
||||
- 阳包阴(涨包跌)信号 -> 开多
|
||||
- 阴包阳(跌包涨)信号 -> 开空
|
||||
|
||||
2. 平仓条件(所有平仓都在下一根K线开盘价执行):
|
||||
- 持有多单时:遇到两根连续的阴线 -> 下一根K线开盘价平仓
|
||||
- 持有空单时:遇到两根连续的阳线 -> 下一根K线开盘价平仓
|
||||
- 遇到反向信号:下一根K线开盘价平仓并反手开仓
|
||||
(例如:持有多单时遇到阴包阳信号 -> 平多开空)
|
||||
|
||||
3. 续持条件:
|
||||
- 遇到同向信号:续持
|
||||
- 未满足平仓条件:续持
|
||||
"""
|
||||
# ==================== 步骤1:从数据库读取数据 ====================
|
||||
all_data: List[Dict] = []
|
||||
total_queried = 0
|
||||
for d in dates:
|
||||
day_data = get_data_by_date(Mexc30, d)
|
||||
all_data.extend(day_data)
|
||||
if day_data:
|
||||
total_queried += len(day_data)
|
||||
|
||||
logger.info(f"总共查询了 {len(dates)} 天,获取到 {total_queried} 条K线数据")
|
||||
|
||||
if not all_data:
|
||||
logger.warning("未获取到任何数据,请检查数据库连接和日期范围")
|
||||
return [], {
|
||||
'bear_bull_engulf': {'count': 0, 'wins': 0, 'total_profit': 0.0, 'name': '涨包跌'},
|
||||
'bull_bear_engulf': {'count': 0, 'wins': 0, 'total_profit': 0.0, 'name': '跌包涨'},
|
||||
}
|
||||
|
||||
# ==================== 步骤2:数据去重 + 排序(重要!必须先处理再回测) ====================
|
||||
# 以时间戳(id)为唯一键去重,保留最先出现的一条
|
||||
unique_by_id: Dict[int, Dict] = {}
|
||||
for item in all_data:
|
||||
if item['id'] not in unique_by_id:
|
||||
unique_by_id[item['id']] = item
|
||||
all_data = list(unique_by_id.values())
|
||||
all_data.sort(key=lambda x: x['id'])
|
||||
|
||||
# 验证排序结果
|
||||
if len(all_data) > 1:
|
||||
first_ts = all_data[0]['id']
|
||||
last_ts = all_data[-1]['id']
|
||||
first_time = datetime.datetime.fromtimestamp(first_ts / 1000)
|
||||
last_time = datetime.datetime.fromtimestamp(last_ts / 1000)
|
||||
logger.info(f"数据已按时间排序:{first_time.strftime('%Y-%m-%d %H:%M:%S')} 到 {last_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
# ==================== 步骤3:开始回测 ====================
|
||||
|
||||
stats = {
|
||||
'bear_bull_engulf': {'count': 0, 'wins': 0, 'total_profit': 0.0, 'name': '涨包跌'},
|
||||
'bull_bear_engulf': {'count': 0, 'wins': 0, 'total_profit': 0.0, 'name': '跌包涨'},
|
||||
}
|
||||
|
||||
trades: List[Dict] = []
|
||||
current_position: Optional[Dict] = None # 开仓信息
|
||||
consecutive_opposite_count = 0 # 连续反色K线计数
|
||||
idx = 1
|
||||
|
||||
while idx < len(all_data) - 1:
|
||||
prev, curr, next_bar = all_data[idx - 1], all_data[idx], all_data[idx + 1]
|
||||
direction, signal_key = check_signal(prev, curr)
|
||||
|
||||
# ==================== 空仓状态:遇到信号则开仓 ====================
|
||||
# 策略:阳包阴信号 -> 开多,阴包阳信号 -> 开空
|
||||
if current_position is None:
|
||||
if direction:
|
||||
# 信号出现(prev和curr形成信号),在下一根K线(next_bar)的开盘价开仓
|
||||
# 应用买卖价差:买入(开多)时价格+0.01,卖出(开空)时价格-0.01
|
||||
raw_price = float(next_bar['open'])
|
||||
entry_price = adjust_price_for_trade(raw_price, direction, symbol)
|
||||
current_position = {
|
||||
'direction': direction,
|
||||
'signal': stats[signal_key]['name'],
|
||||
'signal_key': signal_key,
|
||||
'entry_price': entry_price,
|
||||
'entry_time': next_bar['id']
|
||||
}
|
||||
consecutive_opposite_count = 0 # 重置连续反色计数
|
||||
stats[signal_key]['count'] += 1
|
||||
logger.debug(f"开仓: {stats[signal_key]['name']} {'做多' if direction == 'long' else '做空'} @ {entry_price:.2f} (原始价格: {raw_price:.2f})")
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
# ==================== 有仓位状态:检查平仓条件 ====================
|
||||
pos_dir = current_position['direction']
|
||||
pos_sig_key = current_position['signal_key']
|
||||
|
||||
# 1. 反向信号 -> 下一根K线开盘价平仓并反手开仓
|
||||
# 策略:遇到反向信号(如持有多单时遇到阴包阳),平仓并反手开仓
|
||||
if direction and direction != pos_dir:
|
||||
# 平仓:持有多单时卖出(价格-0.01),持有空单时买入(价格+0.01)
|
||||
raw_exit_price = float(next_bar['open'])
|
||||
exit_direction = 'short' if pos_dir == 'long' else 'long' # 平仓方向与持仓方向相反
|
||||
exit_price = adjust_price_for_trade(raw_exit_price, exit_direction, symbol)
|
||||
|
||||
diff = (exit_price - current_position['entry_price']) if pos_dir == 'long' else (
|
||||
current_position['entry_price'] - exit_price)
|
||||
trades.append({
|
||||
'entry_time': datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000),
|
||||
'exit_time': datetime.datetime.fromtimestamp(next_bar['id'] / 1000),
|
||||
'signal': current_position['signal'],
|
||||
'direction': '做多' if pos_dir == 'long' else '做空',
|
||||
'entry': current_position['entry_price'],
|
||||
'exit': exit_price,
|
||||
'diff': diff
|
||||
})
|
||||
stats[pos_sig_key]['total_profit'] += diff
|
||||
if diff > 0: stats[pos_sig_key]['wins'] += 1
|
||||
|
||||
# 反手开仓(下一根K线开盘价,应用买卖价差)
|
||||
raw_entry_price = float(next_bar['open'])
|
||||
entry_price = adjust_price_for_trade(raw_entry_price, direction, symbol)
|
||||
current_position = {
|
||||
'direction': direction,
|
||||
'signal': stats[signal_key]['name'],
|
||||
'signal_key': signal_key,
|
||||
'entry_price': entry_price,
|
||||
'entry_time': next_bar['id']
|
||||
}
|
||||
consecutive_opposite_count = 0 # 重置连续反色计数
|
||||
stats[signal_key]['count'] += 1
|
||||
logger.debug(f"反向信号反手: 平{'做多' if pos_dir == 'long' else '做空'} @ {exit_price:.2f} (原始: {raw_exit_price:.2f}), 开{'做多' if direction == 'long' else '做空'} @ {entry_price:.2f} (原始: {raw_entry_price:.2f})")
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
# 2. 检查连续反色K线平仓条件(下一根K线开盘价平仓)
|
||||
# 策略:持有多单时,遇到两根连续的阴线 -> 平仓
|
||||
# 持有空单时,遇到两根连续的阳线 -> 平仓
|
||||
if pos_dir == 'long' and is_bearish(curr):
|
||||
consecutive_opposite_count += 1
|
||||
# 如果已经连续两根阴线,下一根K线开盘价平仓
|
||||
if consecutive_opposite_count >= 2:
|
||||
logger.debug(f"平仓: 做多遇到连续两根阴线")
|
||||
# 平多单:卖出,价格 - 0.01
|
||||
raw_exit_price = float(next_bar['open'])
|
||||
exit_price = adjust_price_for_trade(raw_exit_price, 'short', symbol)
|
||||
diff = exit_price - current_position['entry_price']
|
||||
trades.append({
|
||||
'entry_time': datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000),
|
||||
'exit_time': datetime.datetime.fromtimestamp(next_bar['id'] / 1000),
|
||||
'signal': current_position['signal'],
|
||||
'direction': '做多',
|
||||
'entry': current_position['entry_price'],
|
||||
'exit': exit_price,
|
||||
'diff': diff
|
||||
})
|
||||
stats[pos_sig_key]['total_profit'] += diff
|
||||
if diff > 0: stats[pos_sig_key]['wins'] += 1
|
||||
current_position = None
|
||||
consecutive_opposite_count = 0
|
||||
idx += 1
|
||||
continue
|
||||
else:
|
||||
# 只有一根阴线,续持
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
# 持有空单:检查是否连续两根阳线
|
||||
elif pos_dir == 'short' and is_bullish(curr):
|
||||
consecutive_opposite_count += 1
|
||||
# 如果已经连续两根阳线,下一根K线开盘价平仓
|
||||
if consecutive_opposite_count >= 2:
|
||||
logger.debug(f"平仓: 做空遇到连续两根阳线")
|
||||
# 平空单:买入,价格 + 0.01
|
||||
raw_exit_price = float(next_bar['open'])
|
||||
exit_price = adjust_price_for_trade(raw_exit_price, 'long', symbol)
|
||||
diff = current_position['entry_price'] - exit_price
|
||||
trades.append({
|
||||
'entry_time': datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000),
|
||||
'exit_time': datetime.datetime.fromtimestamp(next_bar['id'] / 1000),
|
||||
'signal': current_position['signal'],
|
||||
'direction': '做空',
|
||||
'entry': current_position['entry_price'],
|
||||
'exit': exit_price,
|
||||
'diff': diff
|
||||
})
|
||||
stats[pos_sig_key]['total_profit'] += diff
|
||||
if diff > 0: stats[pos_sig_key]['wins'] += 1
|
||||
current_position = None
|
||||
consecutive_opposite_count = 0
|
||||
idx += 1
|
||||
continue
|
||||
else:
|
||||
# 只有一根阳线,续持
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
# 3. 同向K线或同向信号 -> 续持,重置连续反色计数
|
||||
if (pos_dir == 'long' and is_bullish(curr)) or (pos_dir == 'short' and is_bearish(curr)):
|
||||
consecutive_opposite_count = 0 # 重置连续反色计数
|
||||
|
||||
# 同向信号 -> 续持
|
||||
if direction and direction == pos_dir:
|
||||
consecutive_opposite_count = 0 # 重置连续反色计数
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
idx += 1
|
||||
|
||||
# 尾仓:最后一根收盘价平仓
|
||||
if current_position:
|
||||
last = all_data[-1]
|
||||
pos_dir = current_position['direction']
|
||||
# 平仓:持有多单时卖出(价格-0.01),持有空单时买入(价格+0.01)
|
||||
raw_exit_price = float(last['close'])
|
||||
exit_direction = 'short' if pos_dir == 'long' else 'long' # 平仓方向与持仓方向相反
|
||||
exit_price = adjust_price_for_trade(raw_exit_price, exit_direction, symbol)
|
||||
|
||||
diff = (exit_price - current_position['entry_price']) if pos_dir == 'long' else (
|
||||
current_position['entry_price'] - exit_price)
|
||||
trades.append({
|
||||
'entry_time': datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000),
|
||||
'exit_time': datetime.datetime.fromtimestamp(last['id'] / 1000),
|
||||
'signal': current_position['signal'],
|
||||
'direction': '做多' if pos_dir == 'long' else '做空',
|
||||
'entry': current_position['entry_price'],
|
||||
'exit': exit_price,
|
||||
'diff': diff
|
||||
})
|
||||
stats[current_position['signal_key']]['total_profit'] += diff
|
||||
if diff > 0: stats[current_position['signal_key']]['wins'] += 1
|
||||
|
||||
return trades, stats
|
||||
|
||||
|
||||
# ========================= 运行示例(优化版盈利计算) =========================
|
||||
if __name__ == '__main__':
|
||||
dates = []
|
||||
# 获取当前日期
|
||||
today = datetime.datetime.now()
|
||||
target_year = 2025
|
||||
|
||||
for month in range(1, 13):
|
||||
# 获取该月的实际天数
|
||||
days_in_month = calendar.monthrange(target_year, month)[1]
|
||||
for day in range(1, days_in_month + 1):
|
||||
# 只添加今天及之前的日期
|
||||
date_str = f"{target_year}-{month:02d}-{day:02d}"
|
||||
date_obj = datetime.datetime.strptime(date_str, '%Y-%m-%d')
|
||||
# 如果日期在今天之后,跳过
|
||||
if date_obj > today:
|
||||
break
|
||||
dates.append(date_str)
|
||||
|
||||
print(dates)
|
||||
|
||||
# dates = [f"2025-09-{i}" for i in range(1, 32)]
|
||||
# 指定交易对符号,用于获取正确的最小价格单位
|
||||
symbol = 'SOLUSDT'
|
||||
trades, stats = backtest_15m_trend_optimized(dates, symbol=symbol)
|
||||
|
||||
logger.info("===== 每笔交易详情 =====")
|
||||
|
||||
# === 参数设定 ===
|
||||
contract_size = 10000 # 合约规模(1手对应多少基础货币)
|
||||
open_fee_fixed = 5 # 固定开仓手续费
|
||||
close_fee_rate = 0.0005 # 按成交额比例的平仓手续费率
|
||||
|
||||
total_points_profit = 0 # 累计点差
|
||||
total_money_profit = 0 # 累计金额盈利
|
||||
total_fee = 0 # 累计手续费
|
||||
|
||||
for t in trades:
|
||||
entry = t['entry']
|
||||
exit = t['exit']
|
||||
direction = t['direction']
|
||||
|
||||
# === 1️⃣ 原始价差(点差) ===
|
||||
point_diff = (exit - entry) if direction == '做多' else (entry - exit)
|
||||
|
||||
# === 2️⃣ 金额盈利(考虑合约规模) ===
|
||||
money_profit = point_diff / entry * contract_size # 利润以基础货币计(例如USD)
|
||||
|
||||
# === 3️⃣ 手续费计算 ===
|
||||
# 开仓 + 平仓手续费(按比例计算 + 固定)
|
||||
fee = open_fee_fixed + (contract_size / entry * exit * close_fee_rate)
|
||||
|
||||
# === 4️⃣ 净利润 ===
|
||||
net_profit = money_profit - fee
|
||||
|
||||
# 保存计算结果
|
||||
t.update({
|
||||
'point_diff': point_diff,
|
||||
'raw_profit': money_profit,
|
||||
'fee': fee,
|
||||
'net_profit': net_profit
|
||||
})
|
||||
|
||||
total_points_profit += point_diff
|
||||
total_money_profit += money_profit
|
||||
total_fee += fee
|
||||
|
||||
# if net_profit < -400:
|
||||
logger.info(
|
||||
f"{t['entry_time']} {direction}({t['signal']}) "
|
||||
f"入={entry:.2f} 出={exit:.2f} 差价={point_diff:.2f} "
|
||||
f"原始盈利={money_profit:.2f} 手续费={fee:.2f} 净利润={net_profit:.2f} {t['exit_time']}"
|
||||
)
|
||||
|
||||
# === 汇总统计 ===
|
||||
total_net_profit = total_money_profit - total_fee
|
||||
print(f"\n一共交易笔数:{len(trades)}")
|
||||
print(f"总点差:{total_points_profit:.2f}")
|
||||
print(f"总原始盈利(未扣费):{total_money_profit:.2f}")
|
||||
print(f"总手续费:{total_fee:.2f}")
|
||||
print(f"总净利润:{total_net_profit:.2f}\n")
|
||||
|
||||
print(total_money_profit - total_fee * 0.1)
|
||||
|
||||
print("===== 信号统计 =====")
|
||||
for k, v in stats.items():
|
||||
name, count, wins, total_p = v['name'], v['count'], v['wins'], v['total_profit']
|
||||
win_rate = (wins / count * 100) if count > 0 else 0.0
|
||||
avg_p = (total_p / count) if count > 0 else 0.0
|
||||
print(f"{name}: 次数={count} 胜率={win_rate:.2f}% 总价差={total_p:.2f} 平均价差={avg_p:.2f}")
|
||||
659
mexc/获取数据.py
659
mexc/获取数据.py
@@ -1,659 +0,0 @@
|
||||
import requests
|
||||
import pandas as pd
|
||||
import datetime
|
||||
import time
|
||||
from models.mexc import Mexc1, Mexc15, Mexc30, Mexc1Hour
|
||||
|
||||
|
||||
def get_mexc_klines(symbol="SOLUSDT", interval="30m", limit=500):
|
||||
url = "https://api.mexc.com/api/v3/klines"
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
params = {
|
||||
"symbol": symbol.upper(),
|
||||
"interval": interval,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
try:
|
||||
resp = requests.get(url, params=params, headers=headers, timeout=10)
|
||||
resp.raise_for_status() # 提前抛出 HTTP 错误
|
||||
data = resp.json()
|
||||
|
||||
if not isinstance(data, list) or not data:
|
||||
print("返回数据异常:", data)
|
||||
return None
|
||||
|
||||
# 使用正确的 8 列
|
||||
columns = [
|
||||
"open_time", "open", "high", "low", "close",
|
||||
"volume", "close_time", "quote_volume"
|
||||
]
|
||||
|
||||
df = pd.DataFrame(data, columns=columns)
|
||||
|
||||
# 保存原始时间戳(用于数据库id)
|
||||
df["open_time_ms"] = df["open_time"].astype(int)
|
||||
|
||||
# 时间戳转可读时间
|
||||
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
|
||||
df["close_time"] = pd.to_datetime(df["close_time"], unit="ms")
|
||||
|
||||
# 常用字段排序 + 类型转换
|
||||
df = df[[
|
||||
"open_time", "open", "high", "low", "close",
|
||||
"volume", "quote_volume", "open_time_ms"
|
||||
]]
|
||||
|
||||
# 转成浮点数(方便后续计算)
|
||||
for col in ["open", "high", "low", "close", "volume", "quote_volume"]:
|
||||
df[col] = pd.to_numeric(df[col], errors="coerce")
|
||||
|
||||
print(f"成功获取 {len(df)} 条 {interval} K线({symbol})")
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
print("请求失败:", str(e))
|
||||
return None
|
||||
|
||||
|
||||
def normalize_futures_symbol(symbol):
|
||||
"""将现货格式转换为合约格式,如 SOLUSDT -> SOL_USDT"""
|
||||
if "_" in symbol:
|
||||
return symbol.upper()
|
||||
symbol = symbol.upper()
|
||||
for quote in ["USDT", "USDC", "USD", "BTC", "ETH"]:
|
||||
if symbol.endswith(quote):
|
||||
base = symbol[:-len(quote)]
|
||||
return f"{base}_{quote}"
|
||||
return symbol
|
||||
|
||||
|
||||
def map_futures_interval(interval):
|
||||
"""合约K线周期映射"""
|
||||
mapping = {
|
||||
"1m": "Min1",
|
||||
"5m": "Min5",
|
||||
"15m": "Min15",
|
||||
"30m": "Min30",
|
||||
"1h": "Min60",
|
||||
"4h": "Hour4",
|
||||
"8h": "Hour8",
|
||||
"1d": "Day1",
|
||||
"1w": "Week1",
|
||||
"1M": "Month1",
|
||||
}
|
||||
return mapping.get(interval, interval)
|
||||
|
||||
|
||||
def get_mexc_klines_with_time(symbol="SOLUSDT", interval="30m", start_time_ms=None, end_time_ms=None, limit=1000):
|
||||
"""
|
||||
获取指定时间范围内的K线数据
|
||||
:param symbol: 交易对符号
|
||||
:param interval: 时间间隔,如 "30m", "1m", "15m", "1h"
|
||||
:param start_time_ms: 开始时间(毫秒级时间戳)
|
||||
:param end_time_ms: 结束时间(毫秒级时间戳)
|
||||
:param limit: 单次获取数量(最大1000)
|
||||
:return: DataFrame或None
|
||||
"""
|
||||
def _fetch(params):
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
resp = requests.get(url, params=params, headers=headers, timeout=10)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return resp, data
|
||||
|
||||
url = "https://api.mexc.com/api/v3/klines"
|
||||
params = {
|
||||
"symbol": symbol.upper(),
|
||||
"interval": interval,
|
||||
"limit": limit
|
||||
}
|
||||
if start_time_ms:
|
||||
params["startTime"] = start_time_ms
|
||||
if end_time_ms:
|
||||
params["endTime"] = end_time_ms
|
||||
|
||||
try:
|
||||
resp, data = _fetch(params)
|
||||
|
||||
# 检查是否是错误响应
|
||||
if isinstance(data, dict) and 'code' in data:
|
||||
print(f"API返回错误: {data}, 请求参数: {params}")
|
||||
return None
|
||||
|
||||
# 尝试秒级时间戳回退(部分接口可能只接受秒)
|
||||
if (not isinstance(data, list) or not data) and (start_time_ms or end_time_ms):
|
||||
params_s = params.copy()
|
||||
if start_time_ms:
|
||||
params_s["startTime"] = int(start_time_ms // 1000)
|
||||
if end_time_ms:
|
||||
params_s["endTime"] = int(end_time_ms // 1000)
|
||||
resp_s, data_s = _fetch(params_s)
|
||||
if isinstance(data_s, list) and data_s:
|
||||
print("检测到秒级时间戳可用,已自动回退到秒级参数")
|
||||
resp, data = resp_s, data_s
|
||||
params = params_s
|
||||
|
||||
if not isinstance(data, list) or not data:
|
||||
print(f"API返回数据异常: {data}, 请求参数: {params}")
|
||||
print(f"完整URL: {resp.url}")
|
||||
return None
|
||||
|
||||
columns = [
|
||||
"open_time", "open", "high", "low", "close",
|
||||
"volume", "close_time", "quote_volume"
|
||||
]
|
||||
|
||||
df = pd.DataFrame(data, columns=columns)
|
||||
# 判断时间单位(秒/毫秒)
|
||||
max_open = pd.to_numeric(df["open_time"], errors="coerce").max()
|
||||
if max_open < 1_000_000_000_000: # 小于1e12视为秒
|
||||
df["open_time_ms"] = (df["open_time"].astype(int) * 1000)
|
||||
df["open_time"] = pd.to_datetime(df["open_time"], unit="s")
|
||||
df["close_time"] = pd.to_datetime(df["close_time"], unit="s")
|
||||
else:
|
||||
df["open_time_ms"] = df["open_time"].astype(int)
|
||||
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
|
||||
df["close_time"] = pd.to_datetime(df["close_time"], unit="ms")
|
||||
|
||||
df = df[[
|
||||
"open_time", "open", "high", "low", "close",
|
||||
"volume", "quote_volume", "open_time_ms"
|
||||
]]
|
||||
|
||||
for col in ["open", "high", "low", "close", "volume", "quote_volume"]:
|
||||
df[col] = pd.to_numeric(df[col], errors="coerce")
|
||||
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
print(f"请求失败: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
def get_mexc_futures_klines(symbol="SOLUSDT", interval="30m", limit=500):
|
||||
return get_mexc_futures_klines_with_time(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_time_ms=None,
|
||||
end_time_ms=None,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
|
||||
def get_mexc_futures_klines_with_time(symbol="SOLUSDT", interval="30m", start_time_ms=None, end_time_ms=None, limit=1000):
|
||||
"""
|
||||
获取合约K线数据(使用 contract.mexc.com)
|
||||
"""
|
||||
url = "https://contract.mexc.com/api/v1/contract/kline"
|
||||
symbol = normalize_futures_symbol(symbol)
|
||||
interval = map_futures_interval(interval)
|
||||
|
||||
def _fetch(params):
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
resp = requests.get(url, params=params, headers=headers, timeout=10)
|
||||
resp.raise_for_status()
|
||||
return resp, resp.json()
|
||||
|
||||
params = {
|
||||
"symbol": symbol,
|
||||
"interval": interval,
|
||||
"limit": limit
|
||||
}
|
||||
# 合约接口通常使用 start/end(秒级),这里默认秒级
|
||||
if start_time_ms:
|
||||
params["start"] = int(start_time_ms // 1000)
|
||||
if end_time_ms:
|
||||
params["end"] = int(end_time_ms // 1000)
|
||||
|
||||
try:
|
||||
resp, data = _fetch(params)
|
||||
|
||||
# 兼容错误结构
|
||||
if isinstance(data, dict) and ("success" in data and data.get("success") is False):
|
||||
print(f"合约API返回错误: {data}, 请求参数: {params}")
|
||||
return None
|
||||
|
||||
payload = data
|
||||
if isinstance(data, dict) and "data" in data:
|
||||
payload = data["data"]
|
||||
|
||||
# 兼容返回为 dict of arrays
|
||||
if isinstance(payload, dict) and "time" in payload:
|
||||
times = payload.get("time", [])
|
||||
opens = payload.get("open", [])
|
||||
highs = payload.get("high", [])
|
||||
lows = payload.get("low", [])
|
||||
closes = payload.get("close", [])
|
||||
vols = payload.get("vol", [])
|
||||
rows = list(zip(times, opens, highs, lows, closes, vols))
|
||||
payload = rows
|
||||
|
||||
if not isinstance(payload, list) or not payload:
|
||||
print(f"合约API返回数据异常: {payload}, 请求参数: {params}")
|
||||
print(f"完整URL: {resp.url}")
|
||||
return None
|
||||
|
||||
# 兼容 list of lists: [time, open, high, low, close, vol, ...]
|
||||
columns = ["open_time", "open", "high", "low", "close", "volume"]
|
||||
df = pd.DataFrame(payload, columns=columns + list(range(len(payload[0]) - len(columns))))
|
||||
df = df[columns]
|
||||
|
||||
# 判断时间单位(秒/毫秒)
|
||||
max_open = pd.to_numeric(df["open_time"], errors="coerce").max()
|
||||
if max_open < 1_000_000_000_000:
|
||||
df["open_time_ms"] = (df["open_time"].astype(int) * 1000)
|
||||
df["open_time"] = pd.to_datetime(df["open_time"], unit="s")
|
||||
else:
|
||||
df["open_time_ms"] = df["open_time"].astype(int)
|
||||
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
|
||||
|
||||
for col in ["open", "high", "low", "close", "volume"]:
|
||||
df[col] = pd.to_numeric(df[col], errors="coerce")
|
||||
|
||||
# 合约接口不一定返回 quote_volume,这里保持一致字段结构
|
||||
df["quote_volume"] = pd.NA
|
||||
df = df[[
|
||||
"open_time", "open", "high", "low", "close",
|
||||
"volume", "quote_volume", "open_time_ms"
|
||||
]]
|
||||
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
print(f"合约请求失败: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
def get_interval_ms(interval):
|
||||
"""
|
||||
将时间间隔字符串转换为毫秒数
|
||||
:param interval: 如 "1m", "15m", "30m", "1h"
|
||||
:return: 毫秒数
|
||||
"""
|
||||
if interval.endswith("m"):
|
||||
minutes = int(interval[:-1])
|
||||
return minutes * 60 * 1000
|
||||
elif interval.endswith("h"):
|
||||
hours = int(interval[:-1])
|
||||
return hours * 60 * 60 * 1000
|
||||
elif interval.endswith("d"):
|
||||
days = int(interval[:-1])
|
||||
return days * 24 * 60 * 60 * 1000
|
||||
else:
|
||||
return 60 * 1000 # 默认1分钟
|
||||
|
||||
|
||||
def fetch_historical_data(symbol="SOLUSDT", interval="30m", start_date="2024-01-01", end_date=None, auto_save=True, mode="auto", market="spot"):
|
||||
"""
|
||||
批量获取历史K线数据并存储到数据库
|
||||
:param symbol: 交易对符号
|
||||
:param interval: 时间间隔,如 "30m", "1m", "15m", "1h"
|
||||
:param start_date: 开始日期,格式 "YYYY-MM-DD",默认 "2024-01-01"
|
||||
:param end_date: 结束日期,格式 "YYYY-MM-DD",默认当前时间
|
||||
:param auto_save: 是否自动保存到数据库,默认True
|
||||
:param mode: 获取模式,"auto" 自动判断,"forward" 正向,"reverse" 反向
|
||||
:return: 总共获取的数据条数
|
||||
"""
|
||||
# 转换日期为时间戳(毫秒)
|
||||
start_dt = datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
||||
start_dt = start_dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_time_ms = int(start_dt.timestamp() * 1000)
|
||||
|
||||
if end_date:
|
||||
end_dt = datetime.datetime.strptime(end_date, "%Y-%m-%d")
|
||||
end_dt = end_dt.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||||
else:
|
||||
end_dt = datetime.datetime.now()
|
||||
end_time_ms = int(end_dt.timestamp() * 1000)
|
||||
|
||||
print(f"开始获取 {symbol} {interval} K线数据")
|
||||
print(f"时间范围: {start_date} 至 {end_dt.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
current_start = start_time_ms
|
||||
total_count = 0
|
||||
limit = 1000 # API最大限制
|
||||
interval_ms = get_interval_ms(interval)
|
||||
|
||||
# 计算每次请求的时间窗口(1000条数据的时间跨度)
|
||||
window_ms = limit * interval_ms
|
||||
|
||||
if mode == "reverse":
|
||||
return fetch_historical_data_reverse(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
auto_save=auto_save,
|
||||
market=market
|
||||
)
|
||||
|
||||
fetch_fn = get_mexc_klines_with_time
|
||||
latest_fn = get_mexc_klines
|
||||
if market == "futures":
|
||||
fetch_fn = get_mexc_futures_klines_with_time
|
||||
latest_fn = get_mexc_futures_klines
|
||||
|
||||
if mode == "auto":
|
||||
# 探测:如果 start+end 为空、start 单独返回最新数据,说明 startTime 被忽略,改用反向获取
|
||||
probe_end = min(start_time_ms + window_ms, end_time_ms)
|
||||
probe_df = fetch_fn(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_time_ms=start_time_ms,
|
||||
end_time_ms=probe_end,
|
||||
limit=10
|
||||
)
|
||||
if probe_df is None or probe_df.empty:
|
||||
probe_df2 = fetch_fn(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_time_ms=start_time_ms,
|
||||
end_time_ms=None,
|
||||
limit=10
|
||||
)
|
||||
if probe_df2 is not None and not probe_df2.empty:
|
||||
latest_ms = int(probe_df2.iloc[-1]["open_time_ms"])
|
||||
if latest_ms > probe_end:
|
||||
print("检测到 startTime 被忽略,自动改为反向获取模式")
|
||||
return fetch_historical_data_reverse(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
auto_save=auto_save,
|
||||
market=market
|
||||
)
|
||||
|
||||
while current_start < end_time_ms:
|
||||
current_end = min(current_start + window_ms, end_time_ms)
|
||||
|
||||
print(f"正在获取: {datetime.datetime.fromtimestamp(current_start/1000)} 至 {datetime.datetime.fromtimestamp(current_end/1000)}")
|
||||
print(f"时间戳: {current_start} 至 {current_end}")
|
||||
|
||||
df = fetch_fn(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_time_ms=current_start,
|
||||
end_time_ms=current_end,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
if df is None or df.empty:
|
||||
# 先测试一下不带时间参数的最新数据是否能获取到
|
||||
if current_start == start_time_ms and total_count == 0:
|
||||
print("\n=== 开始调试 ===")
|
||||
print("测试1:尝试获取最新数据(不带时间参数)...")
|
||||
test_df = latest_fn(symbol=symbol, interval=interval, limit=10)
|
||||
if test_df is not None and not test_df.empty:
|
||||
print(f"✓ 测试1成功!最新数据可以获取")
|
||||
print(f" 最新时间: {test_df.iloc[-1]['open_time']}")
|
||||
print(f" 最新时间戳: {test_df.iloc[-1]['open_time_ms']}")
|
||||
|
||||
# 测试2:只带startTime,不带endTime
|
||||
print(f"\n测试2:尝试只带startTime(从{start_date}开始)...")
|
||||
test_df2 = fetch_fn(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_time_ms=current_start,
|
||||
end_time_ms=None,
|
||||
limit=100
|
||||
)
|
||||
if test_df2 is not None and not test_df2.empty:
|
||||
print(f"✓ 测试2成功!从指定时间开始获取数据")
|
||||
print(f" 获取到 {len(test_df2)} 条数据")
|
||||
print(f" 第一条时间: {test_df2.iloc[0]['open_time']}")
|
||||
print(f" 最后一条时间: {test_df2.iloc[-1]['open_time']}")
|
||||
print("=== 调试结束 ===\n")
|
||||
# 使用测试2的方法继续,赋值给df
|
||||
df = test_df2
|
||||
# 不break,继续下面的处理流程
|
||||
else:
|
||||
print("✗ 测试2失败:带startTime无法获取数据")
|
||||
print("\n建议:MEXC API可能不支持从历史日期开始查询")
|
||||
print("可以尝试:1. 检查时间范围是否在API支持范围内")
|
||||
print(" 2. 使用反向获取:从最新数据往前推")
|
||||
print("=== 调试结束 ===\n")
|
||||
break
|
||||
else:
|
||||
print("✗ 测试1失败:即使不带时间参数也无法获取数据")
|
||||
print("可能原因:交易对符号或时间间隔不正确")
|
||||
print("=== 调试结束 ===\n")
|
||||
break
|
||||
|
||||
# 如果测试后df仍然是None或空,说明无法获取数据
|
||||
if df is None or df.empty:
|
||||
if total_count > 0:
|
||||
print("已到达数据末尾")
|
||||
else:
|
||||
print("本次获取数据为空,跳过")
|
||||
break
|
||||
|
||||
count = len(df)
|
||||
total_count += count
|
||||
print(f"本次获取 {count} 条数据,累计 {total_count} 条")
|
||||
|
||||
# 自动保存到数据库
|
||||
if auto_save:
|
||||
save_to_database(df, interval=interval, symbol=symbol)
|
||||
|
||||
# 更新下一次的起始时间(从最后一条数据的开盘时间 + 一个间隔)
|
||||
last_open_ms = int(df.iloc[-1]['open_time_ms'])
|
||||
current_start = last_open_ms + interval_ms
|
||||
|
||||
# 避免请求过快
|
||||
time.sleep(0.2)
|
||||
|
||||
print(f"完成!总共获取 {total_count} 条 {symbol} {interval} K线数据")
|
||||
return total_count
|
||||
|
||||
|
||||
def fetch_historical_data_reverse(symbol="SOLUSDT", interval="30m", start_date="2024-01-01", end_date=None, auto_save=True, market="spot"):
|
||||
"""
|
||||
反向获取历史K线数据(从最新往前推,使用startTime参数)
|
||||
"""
|
||||
start_dt = datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
||||
start_dt = start_dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_time_ms = int(start_dt.timestamp() * 1000)
|
||||
|
||||
if end_date:
|
||||
end_dt = datetime.datetime.strptime(end_date, "%Y-%m-%d")
|
||||
end_dt = end_dt.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||||
else:
|
||||
end_dt = datetime.datetime.now()
|
||||
end_time_ms = int(end_dt.timestamp() * 1000)
|
||||
|
||||
print(f"开始反向获取 {symbol} {interval} K线数据(从最新往前推)")
|
||||
print(f"目标时间范围: {start_date} 至 {end_dt.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
total_count = 0
|
||||
limit = 1000 # 每次尝试获取1000条
|
||||
interval_ms = get_interval_ms(interval)
|
||||
|
||||
# 使用带时间参数的函数
|
||||
fetch_fn = get_mexc_klines_with_time
|
||||
if market == "futures":
|
||||
fetch_fn = get_mexc_futures_klines_with_time
|
||||
|
||||
# 先获取最新数据,确定当前最新时间
|
||||
print("\n第 1 批:获取最新数据...")
|
||||
df_latest = get_mexc_klines(symbol=symbol, interval=interval, limit=100)
|
||||
if df_latest is None or df_latest.empty:
|
||||
print("无法获取最新数据,停止")
|
||||
return 0
|
||||
|
||||
latest_time_ms = int(df_latest.iloc[-1]["open_time_ms"])
|
||||
earliest_fetched_ms = int(df_latest.iloc[0]["open_time_ms"])
|
||||
|
||||
# 保存第一批数据
|
||||
df_latest_filtered = df_latest[df_latest["open_time_ms"] >= start_time_ms]
|
||||
if not df_latest_filtered.empty:
|
||||
count = len(df_latest_filtered)
|
||||
total_count += count
|
||||
print(f"获取 {count} 条最新数据,时间范围: {datetime.datetime.fromtimestamp(earliest_fetched_ms/1000)} 至 {datetime.datetime.fromtimestamp(latest_time_ms/1000)}")
|
||||
if auto_save:
|
||||
save_to_database(df_latest_filtered, interval=interval, symbol=symbol)
|
||||
|
||||
# 从已获取的最早时间往前推
|
||||
current_start_ms = earliest_fetched_ms - interval_ms
|
||||
batch_num = 1
|
||||
|
||||
while current_start_ms >= start_time_ms:
|
||||
batch_num += 1
|
||||
# 计算本次请求的时间窗口(往前推limit条数据的时间跨度)
|
||||
window_ms = limit * interval_ms
|
||||
batch_start_ms = max(current_start_ms - window_ms, start_time_ms)
|
||||
|
||||
print(f"\n第 {batch_num} 批:从 {datetime.datetime.fromtimestamp(batch_start_ms/1000)} 开始获取...")
|
||||
|
||||
# 使用startTime参数,尝试获取更早的数据
|
||||
df = fetch_fn(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_time_ms=batch_start_ms,
|
||||
end_time_ms=current_start_ms,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
if df is None or df.empty:
|
||||
# 如果返回空,尝试只使用startTime,不带endTime
|
||||
print(" 带endTime返回空,尝试只使用startTime...")
|
||||
df = fetch_fn(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
start_time_ms=batch_start_ms,
|
||||
end_time_ms=None,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
if df is None or df.empty:
|
||||
print(f" 无法获取数据,可能已到达API历史数据限制")
|
||||
break
|
||||
|
||||
# 检查返回的数据是否真的从startTime开始(判断startTime是否被忽略)
|
||||
returned_earliest = int(df.iloc[0]["open_time_ms"])
|
||||
returned_latest = int(df.iloc[-1]["open_time_ms"])
|
||||
|
||||
# 如果返回的数据仍然是最新的,说明startTime被忽略
|
||||
if returned_latest >= latest_time_ms - 1000: # 允许1秒误差
|
||||
print(f" 检测到startTime被忽略(返回最新数据),无法继续往前获取")
|
||||
break
|
||||
|
||||
# 过滤掉已获取的数据和早于start_date的数据
|
||||
df = df[df["open_time_ms"] < current_start_ms]
|
||||
df = df[df["open_time_ms"] >= start_time_ms]
|
||||
|
||||
if df.empty:
|
||||
print(f" 过滤后无新数据")
|
||||
# 如果已经到达目标起始时间,完成
|
||||
if batch_start_ms <= start_time_ms:
|
||||
print(f"已到达目标起始时间({start_date}),完成")
|
||||
break
|
||||
# 否则继续往前推
|
||||
current_start_ms = batch_start_ms - interval_ms
|
||||
time.sleep(0.3)
|
||||
continue
|
||||
|
||||
# 更新已获取的最早时间戳
|
||||
current_earliest = int(df.iloc[0]["open_time_ms"])
|
||||
if current_earliest < earliest_fetched_ms:
|
||||
earliest_fetched_ms = current_earliest
|
||||
|
||||
count = len(df)
|
||||
total_count += count
|
||||
print(f" 获取 {count} 条新数据,最早时间: {datetime.datetime.fromtimestamp(current_earliest/1000)}")
|
||||
print(f" 累计获取 {total_count} 条数据")
|
||||
|
||||
if auto_save:
|
||||
save_to_database(df, interval=interval, symbol=symbol)
|
||||
|
||||
# 更新下一次的起始时间
|
||||
current_start_ms = current_earliest - interval_ms
|
||||
|
||||
# 避免请求过快
|
||||
time.sleep(0.3)
|
||||
|
||||
# 安全限制
|
||||
if batch_num > 500:
|
||||
print("批次过多,停止")
|
||||
break
|
||||
|
||||
print(f"\n完成!总共获取 {total_count} 条 {symbol} {interval} K线数据")
|
||||
if earliest_fetched_ms:
|
||||
print(f"最早数据时间: {datetime.datetime.fromtimestamp(earliest_fetched_ms/1000)}")
|
||||
if earliest_fetched_ms > start_time_ms:
|
||||
print(f"⚠️ 注意:最早数据时间 ({datetime.datetime.fromtimestamp(earliest_fetched_ms/1000)}) 晚于目标起始时间 ({start_date})")
|
||||
print(f" 这可能是因为MEXC API的历史数据限制,无法获取更早的数据")
|
||||
return total_count
|
||||
|
||||
|
||||
def save_to_database(df, interval="30m", symbol="SOLUSDT"):
|
||||
"""
|
||||
将K线数据存储到数据库
|
||||
:param df: pandas DataFrame,包含K线数据
|
||||
:param interval: 时间间隔,如 "30m", "1m", "15m", "1h"
|
||||
:param symbol: 交易对符号
|
||||
"""
|
||||
if df is None or df.empty:
|
||||
print("数据为空,无法存储")
|
||||
return
|
||||
|
||||
# 根据时间间隔选择对应的模型
|
||||
interval_map = {
|
||||
"1m": Mexc1,
|
||||
"15m": Mexc15,
|
||||
"30m": Mexc30,
|
||||
"1h": Mexc1Hour,
|
||||
}
|
||||
|
||||
model_class = interval_map.get(interval)
|
||||
if model_class is None:
|
||||
print(f"不支持的时间间隔: {interval}")
|
||||
return
|
||||
|
||||
# 存储数据到数据库
|
||||
saved_count = 0
|
||||
for _, row in df.iterrows():
|
||||
# 使用原始时间戳(毫秒级)作为 id
|
||||
timestamp_ms = int(row['open_time_ms'])
|
||||
|
||||
model_class.get_or_create(
|
||||
id=timestamp_ms,
|
||||
defaults={
|
||||
'open': float(row['open']),
|
||||
'high': float(row['high']),
|
||||
'low': float(row['low']),
|
||||
'close': float(row['close']),
|
||||
}
|
||||
)
|
||||
saved_count += 1
|
||||
|
||||
print(f"成功存储 {saved_count} 条 {interval} K线数据({symbol})到数据库")
|
||||
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 方式1: 获取最新200条数据
|
||||
# df = get_mexc_klines("SOLUSDT", "30m", 200)
|
||||
# if df is not None:
|
||||
# print(df.tail(8))
|
||||
# save_to_database(df, interval="30m", symbol="SOLUSDT")
|
||||
|
||||
# 方式2: 批量获取2024年至今的历史数据(使用现货接口,反向获取)
|
||||
fetch_historical_data(
|
||||
symbol="SOLUSDT",
|
||||
interval="30m",
|
||||
start_date="2024-01-01",
|
||||
end_date=None, # None表示到当前时间
|
||||
auto_save=True,
|
||||
market="spot", # 使用现货接口
|
||||
mode="reverse" # 强制使用反向获取模式(从最新往前推)
|
||||
)
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
211
okx/test.py
211
okx/test.py
@@ -1,211 +0,0 @@
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
# ================== 通用请求头 ==================
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'zh,zh-CN;q=0.9,zh-HK;q=0.8,en;q=0.7',
|
||||
'cache-control': 'no-cache',
|
||||
'origin': 'https://www.okx.com',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.okx.com/',
|
||||
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
|
||||
}
|
||||
|
||||
|
||||
# ================== 获取 OKX K线数据 ==================
|
||||
def fetch_kline_okx(day: int, year: int = 2025, month: int = 9, period='15m', symbol="ETH-USDT"):
|
||||
"""
|
||||
从 OKX 获取指定日期的K线数据
|
||||
:param day: 日期
|
||||
:param year: 年份
|
||||
:param month: 月份
|
||||
:param period: K线周期 (如 '1m', '5m', '15m', '1H')
|
||||
:param symbol: 交易对
|
||||
"""
|
||||
time_ser = datetime.datetime(year, month, day)
|
||||
start_of_day = int(time_ser.replace(hour=0, minute=0, second=0, microsecond=0).timestamp() * 1000)
|
||||
end_of_day = int(time_ser.replace(hour=23, minute=59, second=59, microsecond=0).timestamp() * 1000)
|
||||
|
||||
url = "https://www.okx.com/api/v5/market/candles"
|
||||
params = {
|
||||
"instId": symbol,
|
||||
"bar": period,
|
||||
"limit": "1000"
|
||||
}
|
||||
|
||||
all_data = []
|
||||
next_start = end_of_day
|
||||
|
||||
while True:
|
||||
params["after"] = next_start
|
||||
resp = requests.get(url, params=params, headers=headers)
|
||||
data = resp.json().get("data", [])
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
for d in data:
|
||||
candle = {
|
||||
"id": int(int(d[0]) / 1000), # 秒级时间戳
|
||||
"open": d[1],
|
||||
"high": d[2],
|
||||
"low": d[3],
|
||||
"close": d[4],
|
||||
"volume": d[5]
|
||||
}
|
||||
all_data.append(candle)
|
||||
|
||||
next_start = int(data[-1][0])
|
||||
if len(data) < 1000:
|
||||
break
|
||||
|
||||
# OKX返回数据按时间倒序排列
|
||||
return sorted(all_data, key=lambda x: x['id'])
|
||||
|
||||
|
||||
# ================== 辅助函数 ==================
|
||||
def is_bullish(candle):
|
||||
"""判断是否是阳线(涨)"""
|
||||
return float(candle['open']) < float(candle['close'])
|
||||
|
||||
|
||||
def is_bearish(candle):
|
||||
"""判断是否是阴线(跌)"""
|
||||
return float(candle['open']) > float(candle['close'])
|
||||
|
||||
|
||||
def check_signal(prev, curr):
|
||||
"""
|
||||
判断包住形态信号:
|
||||
1. 前跌后涨包住 -> 做多信号 (bear_bull_engulf)
|
||||
2. 前涨后跌包住 -> 做空信号 (bull_bear_engulf)
|
||||
"""
|
||||
p_open, p_close = float(prev['open']), float(prev['close'])
|
||||
c_open, c_close = float(curr['open']), float(curr['close'])
|
||||
|
||||
# 前跌后涨包住 -> 做多
|
||||
if is_bullish(curr) and is_bearish(prev):
|
||||
if c_open < p_close and c_close > p_open:
|
||||
return "long", "bear_bull_engulf"
|
||||
|
||||
# 前涨后跌包住 -> 做空
|
||||
if is_bearish(curr) and is_bullish(prev):
|
||||
if c_open > p_close and c_close < p_open:
|
||||
return "short", "bull_bear_engulf"
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def simulate_trade(direction, entry_price, future_candles, take_profit_diff=30, stop_loss_diff=-10):
|
||||
"""
|
||||
模拟逐根K线的止盈止损
|
||||
"""
|
||||
for candle in future_candles:
|
||||
high, low, close = float(candle['high']), float(candle['low']), float(candle['close'])
|
||||
|
||||
if direction == "long":
|
||||
if high >= entry_price + take_profit_diff:
|
||||
return entry_price + take_profit_diff, take_profit_diff, candle['id'] # 止盈
|
||||
if low <= entry_price + stop_loss_diff:
|
||||
return entry_price + stop_loss_diff, stop_loss_diff, candle['id'] # 止损
|
||||
|
||||
elif direction == "short":
|
||||
if low <= entry_price - take_profit_diff:
|
||||
return entry_price - take_profit_diff, take_profit_diff, candle['id']
|
||||
if high >= entry_price - stop_loss_diff:
|
||||
return entry_price - stop_loss_diff, stop_loss_diff, candle['id']
|
||||
|
||||
final_price = float(future_candles[-1]['close'])
|
||||
diff_money = (final_price - entry_price) if direction == "long" else (entry_price - final_price)
|
||||
return final_price, diff_money, future_candles[-1]['id']
|
||||
|
||||
|
||||
# ================== 主程序 ==================
|
||||
if __name__ == '__main__':
|
||||
all_trades = []
|
||||
signal_stats = {
|
||||
"bear_bull_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "涨包跌"},
|
||||
"bull_bear_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "跌包涨"}
|
||||
}
|
||||
|
||||
datas = []
|
||||
for i in range(1, 31):
|
||||
logger.info(f"Fetching 2025-09-{i}")
|
||||
sorted_data = fetch_kline_okx(year=2025, month=1, day=i, period='15m', symbol="ETH-USDT")
|
||||
datas.extend(sorted_data)
|
||||
time.sleep(0.3)
|
||||
|
||||
# # 可选:保存数据方便离线分析
|
||||
# with open("okx_eth_15m_sep2025.json", "w", encoding="utf-8") as f:
|
||||
# json.dump(datas, f, ensure_ascii=False, indent=2)
|
||||
|
||||
datas = sorted(datas, key=lambda x: x["id"])
|
||||
|
||||
daily_signals = daily_wins = daily_profit = 0
|
||||
|
||||
for idx in range(1, len(datas) - 2):
|
||||
prev, curr = datas[idx - 1], datas[idx]
|
||||
entry_candle = datas[idx + 1]
|
||||
future_candles = datas[idx + 2:]
|
||||
|
||||
entry_open = float(entry_candle['open'])
|
||||
direction, signal_type = check_signal(prev, curr)
|
||||
|
||||
if direction and signal_type:
|
||||
daily_signals += 1
|
||||
exit_price, diff, exit_time = simulate_trade(direction, entry_open, future_candles,
|
||||
take_profit_diff=30, stop_loss_diff=-2)
|
||||
|
||||
signal_stats[signal_type]["count"] += 1
|
||||
signal_stats[signal_type]["total_profit"] += diff
|
||||
if diff > 0:
|
||||
signal_stats[signal_type]["wins"] += 1
|
||||
daily_wins += 1
|
||||
|
||||
daily_profit += diff
|
||||
|
||||
local_time = datetime.datetime.fromtimestamp(entry_candle['id'])
|
||||
formatted_time = local_time.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
all_trades.append((
|
||||
formatted_time,
|
||||
"做多" if direction == "long" else "做空",
|
||||
signal_stats[signal_type]["name"],
|
||||
entry_open,
|
||||
exit_price,
|
||||
diff,
|
||||
exit_time
|
||||
))
|
||||
|
||||
# ================= 输出交易详情 =================
|
||||
logger.info("===== 每笔交易详情 =====")
|
||||
n = n1 = 0
|
||||
for date, direction, signal_name, entry, exit, diff, end_time in all_trades:
|
||||
profit_amount = diff / entry * 10000
|
||||
close_fee = 10000 / entry * exit * 0.0005
|
||||
|
||||
logger.info(
|
||||
f"{date} {direction}({signal_name}) 入场={entry:.2f} 出场={exit:.2f} 出场时间={end_time} "
|
||||
f"差价={diff:.2f} 盈利={profit_amount:.2f} "
|
||||
f"开仓手续费=5u 平仓手续费={close_fee:.2f}"
|
||||
)
|
||||
n1 += 5 + close_fee
|
||||
n += profit_amount
|
||||
|
||||
print("=" * 60)
|
||||
print(f"交易总数:{len(all_trades)}")
|
||||
print(f"总盈利:{n:.2f} u")
|
||||
print(f"总手续费:{n1:.2f} u")
|
||||
print(f"净利润:{n - n1:.2f} u")
|
||||
print("=" * 60)
|
||||
@@ -1,25 +0,0 @@
|
||||
# 基于开盘价的五分之一策略
|
||||
|
||||
策略规则(1111):
|
||||
|
||||
- **做多触发价** = 当前K线开盘价 + 前一根实体/5
|
||||
- **做空触发价** = 当前K线开盘价 - 前一根实体/5
|
||||
- **前一根有效K线**:实体 ≥ 0.1
|
||||
|
||||
## 执行逻辑
|
||||
|
||||
- 当前K线最高价 ≥ 做多触发价 → 做多信号
|
||||
- 当前K线最低价 ≤ 做空触发价 → 做空信号
|
||||
- 同根K线多空都触及时,用1分钟K线判断先后
|
||||
- 触及信号则开仓或反手,同根3分钟K线只交易一次
|
||||
|
||||
## 运行
|
||||
|
||||
```bash
|
||||
cd /path/to/lm_code
|
||||
python open_fifth_strategy/main.py
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
在 `config.py` 中修改 API、合约、杠杆等参数。
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""基于开盘价的五分之一策略"""
|
||||
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
基于开盘价的五分之一策略 - 配置文件
|
||||
|
||||
策略规则(1111):
|
||||
- 做多触发价 = 当前K线开盘价 + 前一根实体/5
|
||||
- 做空触发价 = 当前K线开盘价 - 前一根实体/5
|
||||
- 前一根有效K线:实体 >= 0.1
|
||||
"""
|
||||
|
||||
# BitMart API(请勿提交敏感信息到版本库)
|
||||
API_KEY = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8"
|
||||
SECRET_KEY = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5"
|
||||
MEMO = "合约交易"
|
||||
|
||||
# 交易参数
|
||||
CONTRACT_SYMBOL = "ETHUSDT"
|
||||
KLINE_STEP = 3 # 3分钟K线
|
||||
MIN_BODY_SIZE = 0.1 # 有效K线最小实体
|
||||
CHECK_INTERVAL = 3 # 检测间隔(秒)
|
||||
LEVERAGE = "100"
|
||||
OPEN_TYPE = "cross" # 全仓
|
||||
RISK_PERCENT = 0.01 # 每次开仓占用可用余额的比例
|
||||
|
||||
# 比特浏览器ID(用于网页下单)
|
||||
BIT_ID = "f2320f57e24c45529a009e1541e25961"
|
||||
@@ -1,536 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
BitMart 基于开盘价的五分之一策略交易
|
||||
|
||||
策略规则(1111):
|
||||
- 做多触发价 = 当前K线开盘价 + 实体/5
|
||||
- 做空触发价 = 当前K线开盘价 - 实体/5
|
||||
- 基于前一根有效K线(实体 >= 0.1)
|
||||
|
||||
执行:触及做多/做空触发价则开仓或反手,同根K线只交易一次
|
||||
|
||||
运行:python open_fifth_strategy/main.py(在项目根目录 lm_code 下)
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_root = Path(__file__).resolve().parent.parent
|
||||
if str(_root) not in sys.path:
|
||||
sys.path.insert(0, str(_root))
|
||||
|
||||
import random
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from loguru import logger
|
||||
from bit_tools import openBrowser
|
||||
from DrissionPage import ChromiumPage
|
||||
from DrissionPage import ChromiumOptions
|
||||
|
||||
from bitmart.api_contract import APIContract
|
||||
from 交易.tools import send_dingtalk_message
|
||||
|
||||
from open_fifth_strategy.config import (
|
||||
API_KEY,
|
||||
SECRET_KEY,
|
||||
MEMO,
|
||||
CONTRACT_SYMBOL,
|
||||
KLINE_STEP,
|
||||
MIN_BODY_SIZE,
|
||||
CHECK_INTERVAL,
|
||||
LEVERAGE,
|
||||
OPEN_TYPE,
|
||||
RISK_PERCENT,
|
||||
BIT_ID,
|
||||
)
|
||||
|
||||
ding_executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="dingtalk")
|
||||
|
||||
|
||||
class OpenBasedFifthStrategy:
|
||||
"""基于开盘价的五分之一策略"""
|
||||
|
||||
def __init__(self, bit_id=None):
|
||||
self.page = None
|
||||
self.api_key = API_KEY
|
||||
self.secret_key = SECRET_KEY
|
||||
self.memo = MEMO
|
||||
self.contract_symbol = CONTRACT_SYMBOL
|
||||
self.contractAPI = APIContract(
|
||||
self.api_key, self.secret_key, self.memo, timeout=(5, 15)
|
||||
)
|
||||
|
||||
self.start = 0 # 持仓: -1空, 0无, 1多
|
||||
self.open_avg_price = None
|
||||
self.current_amount = None
|
||||
self.position_cross = None
|
||||
self.bit_id = bit_id or BIT_ID
|
||||
|
||||
self.min_body_size = MIN_BODY_SIZE
|
||||
self.kline_step = KLINE_STEP
|
||||
self.check_interval = CHECK_INTERVAL
|
||||
self.leverage = LEVERAGE
|
||||
self.open_type = OPEN_TYPE
|
||||
self.risk_percent = RISK_PERCENT
|
||||
|
||||
self.last_trigger_kline_id = None
|
||||
self.last_trigger_direction = None
|
||||
self.last_trade_kline_id = None
|
||||
|
||||
# ==================== 策略核心 ====================
|
||||
|
||||
def get_body_size(self, candle):
|
||||
return abs(float(candle["open"]) - float(candle["close"]))
|
||||
|
||||
def find_valid_prev_bar(self, all_data, current_idx):
|
||||
"""找前一根有效K线(实体>=min_body_size)"""
|
||||
if current_idx <= 0:
|
||||
return None, None
|
||||
for i in range(current_idx - 1, -1, -1):
|
||||
prev = all_data[i]
|
||||
if self.get_body_size(prev) >= self.min_body_size:
|
||||
return i, prev
|
||||
return None, None
|
||||
|
||||
def get_trigger_levels(self, prev, curr_open):
|
||||
"""
|
||||
基于当前K线开盘价计算触发价(1111)
|
||||
做多触发 = 当前K线开盘价 + 实体/5
|
||||
做空触发 = 当前K线开盘价 - 实体/5
|
||||
"""
|
||||
body = self.get_body_size(prev)
|
||||
if body < 0.001:
|
||||
return None, None
|
||||
curr_o = float(curr_open)
|
||||
return curr_o + body / 5, curr_o - body / 5
|
||||
|
||||
def get_1m_bars_for_3m_bar(self, bar_3m):
|
||||
"""获取当前3分钟K线对应的3根1分钟K线"""
|
||||
try:
|
||||
start_ts = int(bar_3m["id"])
|
||||
end_ts = start_ts + 3 * 60
|
||||
response = self.contractAPI.get_kline(
|
||||
contract_symbol=self.contract_symbol,
|
||||
step=1,
|
||||
start_time=start_ts,
|
||||
end_time=end_ts,
|
||||
)[0]
|
||||
if response.get("code") != 1000:
|
||||
return []
|
||||
data = response.get("data", [])
|
||||
out = []
|
||||
for k in data:
|
||||
out.append(
|
||||
{
|
||||
"id": int(k["timestamp"]),
|
||||
"open": float(k["open_price"]),
|
||||
"high": float(k["high_price"]),
|
||||
"low": float(k["low_price"]),
|
||||
"close": float(k["close_price"]),
|
||||
}
|
||||
)
|
||||
out.sort(key=lambda x: x["id"])
|
||||
return out
|
||||
except Exception as e:
|
||||
logger.warning(f"获取1分钟K线失败: {e}")
|
||||
return []
|
||||
|
||||
def determine_trigger_order_by_1m(self, bars_1m, long_trigger, short_trigger):
|
||||
"""同根K线多空都触及时,用1分钟K线判断先后"""
|
||||
if not bars_1m:
|
||||
return None
|
||||
for bar in bars_1m:
|
||||
high = float(bar["high"])
|
||||
low = float(bar["low"])
|
||||
open_price = float(bar["open"])
|
||||
long_ok = high >= long_trigger
|
||||
short_ok = low <= short_trigger
|
||||
if long_ok and not short_ok:
|
||||
return "long"
|
||||
if short_ok and not long_ok:
|
||||
return "short"
|
||||
if long_ok and short_ok:
|
||||
d_long = abs(long_trigger - open_price)
|
||||
d_short = abs(short_trigger - open_price)
|
||||
return "short" if d_short < d_long else "long"
|
||||
return None
|
||||
|
||||
def check_trigger(self, kline_data):
|
||||
"""
|
||||
检测当前K线是否触发信号(1111:当前开盘价±实体/5)
|
||||
返回:(方向, 触发价, 有效前一根, 当前K线) 或 (None,...)
|
||||
"""
|
||||
if len(kline_data) < 2:
|
||||
return None, None, None, None
|
||||
|
||||
curr = kline_data[-1]
|
||||
curr_kline_id = curr["id"]
|
||||
curr_open = curr["open"]
|
||||
_, prev = self.find_valid_prev_bar(kline_data, len(kline_data) - 1)
|
||||
if prev is None:
|
||||
return None, None, None, None
|
||||
|
||||
long_trigger, short_trigger = self.get_trigger_levels(prev, curr_open)
|
||||
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:
|
||||
bars_1m = self.get_1m_bars_for_3m_bar(curr)
|
||||
if bars_1m:
|
||||
direction = self.determine_trigger_order_by_1m(
|
||||
bars_1m, long_trigger, short_trigger
|
||||
)
|
||||
trigger_price = long_trigger if direction == "long" else short_trigger
|
||||
if direction is None:
|
||||
c_open_f = float(curr["open"])
|
||||
d_long = abs(long_trigger - c_open_f)
|
||||
d_short = abs(short_trigger - c_open_f)
|
||||
direction = "short" if d_short <= d_long else "long"
|
||||
trigger_price = long_trigger if direction == "long" else short_trigger
|
||||
elif short_triggered:
|
||||
direction = "short"
|
||||
trigger_price = short_trigger
|
||||
elif long_triggered:
|
||||
direction = "long"
|
||||
trigger_price = long_trigger
|
||||
|
||||
if direction is None:
|
||||
return None, None, None, None
|
||||
if (
|
||||
self.last_trigger_kline_id == curr_kline_id
|
||||
and self.last_trigger_direction == direction
|
||||
):
|
||||
return None, None, None, None
|
||||
|
||||
return direction, trigger_price, prev, curr
|
||||
|
||||
# ==================== BitMart API ====================
|
||||
|
||||
def get_klines(self):
|
||||
try:
|
||||
end_time = int(time.time())
|
||||
response = self.contractAPI.get_kline(
|
||||
contract_symbol=self.contract_symbol,
|
||||
step=self.kline_step,
|
||||
start_time=end_time - 3600 * 3,
|
||||
end_time=end_time,
|
||||
)[0]["data"]
|
||||
formatted = []
|
||||
for k in response:
|
||||
formatted.append(
|
||||
{
|
||||
"id": int(k["timestamp"]),
|
||||
"open": float(k["open_price"]),
|
||||
"high": float(k["high_price"]),
|
||||
"low": float(k["low_price"]),
|
||||
"close": float(k["close_price"]),
|
||||
}
|
||||
)
|
||||
formatted.sort(key=lambda x: x["id"])
|
||||
return formatted
|
||||
except Exception as e:
|
||||
if "429" in str(e) or "too many requests" in str(e).lower():
|
||||
logger.warning(f"API限流,等待60秒: {e}")
|
||||
time.sleep(60)
|
||||
else:
|
||||
logger.error(f"获取K线异常: {e}")
|
||||
self.ding("获取K线异常", error=True)
|
||||
return None
|
||||
|
||||
def get_available_balance(self):
|
||||
try:
|
||||
response = self.contractAPI.get_assets_detail()[0]
|
||||
if response["code"] == 1000:
|
||||
data = response["data"]
|
||||
if isinstance(data, dict):
|
||||
return float(data.get("available_balance", 0))
|
||||
for asset in data:
|
||||
if asset.get("currency") == "USDT":
|
||||
return float(asset.get("available_balance", 0))
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"余额查询异常: {e}")
|
||||
return None
|
||||
|
||||
def get_position_status(self):
|
||||
try:
|
||||
response = self.contractAPI.get_position(
|
||||
contract_symbol=self.contract_symbol
|
||||
)[0]
|
||||
if response["code"] == 1000:
|
||||
positions = response["data"]
|
||||
if not positions:
|
||||
self.start = 0
|
||||
self.open_avg_price = None
|
||||
self.current_amount = None
|
||||
self.position_cross = None
|
||||
return True
|
||||
self.start = 1 if positions[0]["position_type"] == 1 else -1
|
||||
self.open_avg_price = positions[0]["open_avg_price"]
|
||||
self.current_amount = positions[0]["current_amount"]
|
||||
self.position_cross = positions[0]["position_cross"]
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"持仓查询异常: {e}")
|
||||
return False
|
||||
|
||||
def set_leverage(self):
|
||||
try:
|
||||
response = self.contractAPI.post_submit_leverage(
|
||||
contract_symbol=self.contract_symbol,
|
||||
leverage=self.leverage,
|
||||
open_type=self.open_type,
|
||||
)[0]
|
||||
if response["code"] == 1000:
|
||||
logger.success(f"全仓 {self.leverage}x 杠杆设置成功")
|
||||
return True
|
||||
logger.error(f"杠杆设置失败: {response}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"设置杠杆异常: {e}")
|
||||
return False
|
||||
|
||||
# ==================== 浏览器 ====================
|
||||
|
||||
def _open_browser(self):
|
||||
try:
|
||||
bit_port = openBrowser(id=self.bit_id)
|
||||
co = ChromiumOptions()
|
||||
co.set_local_port(port=bit_port)
|
||||
self.page = ChromiumPage(addr_or_opts=co)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def close_extra_tabs(self):
|
||||
try:
|
||||
for idx, tab in enumerate(self.page.get_tabs()):
|
||||
if idx > 0:
|
||||
tab.close()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def click_safe(self, xpath, sleep=0.5):
|
||||
try:
|
||||
ele = self.page.ele(xpath)
|
||||
if not ele:
|
||||
return False
|
||||
ele.scroll.to_see(center=True)
|
||||
time.sleep(sleep)
|
||||
ele.click(by_js=True)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def 平仓(self):
|
||||
logger.info("执行平仓...")
|
||||
self.click_safe('x://span[normalize-space(text()) ="市价"]')
|
||||
time.sleep(0.5)
|
||||
self.ding("执行平仓操作")
|
||||
|
||||
def 开单(self, marketPriceLongOrder=0, size=None):
|
||||
if size is None or size <= 0:
|
||||
logger.warning("开单金额无效")
|
||||
return False
|
||||
direction_str = "做多" if marketPriceLongOrder == 1 else "做空"
|
||||
logger.info(f"执行{direction_str},金额: {size}")
|
||||
size = max(1, min(25, int(size)))
|
||||
try:
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
self.page.ele('x://*[@id="size_0"]').input(str(size))
|
||||
if marketPriceLongOrder == -1:
|
||||
self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]')
|
||||
else:
|
||||
self.click_safe('x://span[normalize-space(text()) ="买入/做多"]')
|
||||
self.ding(f"执行{direction_str},金额: {size}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"开单异常: {e}")
|
||||
return False
|
||||
|
||||
def ding(self, msg, error=False):
|
||||
prefix = "❌五分之一:" if error else "🔔五分之一:"
|
||||
full_msg = f"{prefix}{msg}"
|
||||
if error:
|
||||
logger.error(msg)
|
||||
for _ in range(3):
|
||||
ding_executor.submit(self._send_ding_safe, full_msg)
|
||||
else:
|
||||
logger.info(msg)
|
||||
ding_executor.submit(self._send_ding_safe, full_msg)
|
||||
|
||||
def _send_ding_safe(self, msg):
|
||||
try:
|
||||
send_dingtalk_message(msg)
|
||||
except Exception as e:
|
||||
logger.warning(f"消息发送失败: {e}")
|
||||
|
||||
def _send_position_message(self, latest_kline):
|
||||
current_price = float(latest_kline["close"])
|
||||
balance = self.get_available_balance()
|
||||
self.balance = balance if balance is not None else 0.0
|
||||
if self.start != 0:
|
||||
open_avg_price = float(self.open_avg_price)
|
||||
current_amount = float(self.current_amount)
|
||||
position_cross = float(getattr(self, "position_cross", 0) or 0)
|
||||
if self.start == 1:
|
||||
unrealized_pnl = current_amount * 0.001 * (
|
||||
current_price - open_avg_price
|
||||
)
|
||||
else:
|
||||
unrealized_pnl = current_amount * 0.001 * (
|
||||
open_avg_price - current_price
|
||||
)
|
||||
pnl_rate = (
|
||||
(current_price - open_avg_price) / open_avg_price * 100
|
||||
if self.start == 1
|
||||
else (open_avg_price - current_price) / open_avg_price * 100
|
||||
)
|
||||
direction_str = "空" if self.start == -1 else "多"
|
||||
msg = (
|
||||
f"【五分之一 {self.contract_symbol}】\n"
|
||||
f"方向:{direction_str}\n"
|
||||
f"现价:{current_price:.2f}\n"
|
||||
f"开仓均价:{open_avg_price:.2f}\n"
|
||||
f"浮动盈亏:{unrealized_pnl:+.2f} USDT ({pnl_rate:+.2f}%)\n"
|
||||
f"余额:{self.balance:.2f}"
|
||||
)
|
||||
else:
|
||||
msg = (
|
||||
f"【五分之一 {self.contract_symbol}】\n"
|
||||
f"方向:无\n"
|
||||
f"现价:{current_price:.2f}\n"
|
||||
f"余额:{self.balance:.2f}"
|
||||
)
|
||||
self.ding(msg)
|
||||
|
||||
# ==================== 主循环 ====================
|
||||
|
||||
def action(self):
|
||||
if not self.set_leverage():
|
||||
logger.error("杠杆设置失败")
|
||||
return
|
||||
if not self._open_browser():
|
||||
self.ding("打开浏览器失败!", error=True)
|
||||
return
|
||||
logger.info("浏览器打开成功")
|
||||
self.close_extra_tabs()
|
||||
self.page.get("https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT")
|
||||
time.sleep(2)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
logger.info(
|
||||
f"五分之一策略(3分钟K线)开始监测,间隔: {self.check_interval}秒"
|
||||
)
|
||||
|
||||
last_report_time = 0
|
||||
report_interval = 300
|
||||
|
||||
while True:
|
||||
for _ in range(5):
|
||||
if self._open_browser():
|
||||
break
|
||||
time.sleep(5)
|
||||
else:
|
||||
self.ding("打开浏览器失败!", error=True)
|
||||
return
|
||||
self.close_extra_tabs()
|
||||
self.page.get("https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT")
|
||||
time.sleep(2)
|
||||
self.click_safe('x://button[normalize-space(text()) ="市价"]')
|
||||
|
||||
try:
|
||||
kline_data = self.get_klines()
|
||||
if not kline_data or len(kline_data) < 3:
|
||||
logger.warning("K线数据不足...")
|
||||
time.sleep(self.check_interval)
|
||||
continue
|
||||
|
||||
curr = kline_data[-1]
|
||||
if not self.get_position_status():
|
||||
logger.warning("获取仓位失败,使用缓存")
|
||||
|
||||
direction, trigger_price, valid_prev, curr_kline = (
|
||||
self.check_trigger(kline_data)
|
||||
)
|
||||
|
||||
if direction:
|
||||
curr_kline_id = curr_kline["id"]
|
||||
if self.last_trade_kline_id == curr_kline_id:
|
||||
self.last_trigger_kline_id = curr_kline_id
|
||||
self.last_trigger_direction = direction
|
||||
time.sleep(self.check_interval)
|
||||
continue
|
||||
|
||||
if (direction == "long" and self.start == 1) or (
|
||||
direction == "short" and self.start == -1
|
||||
):
|
||||
self.last_trigger_kline_id = curr_kline_id
|
||||
self.last_trigger_direction = direction
|
||||
time.sleep(self.check_interval)
|
||||
continue
|
||||
|
||||
balance = self.get_available_balance()
|
||||
trade_size = (balance or 0) * self.risk_percent
|
||||
executed = False
|
||||
|
||||
if direction == "long":
|
||||
if self.start == -1:
|
||||
self.平仓()
|
||||
time.sleep(1)
|
||||
self.开单(marketPriceLongOrder=1, size=trade_size)
|
||||
executed = True
|
||||
elif self.start == 0:
|
||||
self.开单(marketPriceLongOrder=1, size=trade_size)
|
||||
executed = True
|
||||
elif direction == "short":
|
||||
if self.start == 1:
|
||||
self.平仓()
|
||||
time.sleep(1)
|
||||
self.开单(marketPriceLongOrder=-1, size=trade_size)
|
||||
executed = True
|
||||
elif self.start == 0:
|
||||
self.开单(marketPriceLongOrder=-1, size=trade_size)
|
||||
executed = True
|
||||
|
||||
self.last_trigger_kline_id = curr_kline_id
|
||||
self.last_trigger_direction = direction
|
||||
if executed:
|
||||
self.last_trade_kline_id = curr_kline_id
|
||||
self.get_position_status()
|
||||
self._send_position_message(curr_kline)
|
||||
last_report_time = time.time()
|
||||
|
||||
if time.time() - last_report_time >= report_interval:
|
||||
if self.get_position_status():
|
||||
self._send_position_message(kline_data[-1])
|
||||
last_report_time = time.time()
|
||||
|
||||
time.sleep(self.check_interval)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"主循环异常: {e}")
|
||||
time.sleep(self.check_interval)
|
||||
time.sleep(3)
|
||||
if random.randint(1, 10) > 7:
|
||||
self.page.close()
|
||||
time.sleep(15)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
OpenBasedFifthStrategy(bit_id=BIT_ID).action()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("程序被用户中断")
|
||||
finally:
|
||||
ding_executor.shutdown(wait=True)
|
||||
logger.info("已退出")
|
||||
949
test2.py
949
test2.py
@@ -1,949 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
import datetime
|
||||
from dataclasses import dataclass
|
||||
|
||||
from tqdm import tqdm
|
||||
from loguru import logger
|
||||
|
||||
from bitmart.api_contract import APIContract
|
||||
from bitmart.lib.cloud_exceptions import APIException
|
||||
|
||||
from 交易.tools import send_dingtalk_message
|
||||
|
||||
|
||||
@dataclass
|
||||
class StrategyConfig:
|
||||
# =============================
|
||||
# 1m | ETH 永续 | 控止损≤5/日
|
||||
# =============================
|
||||
|
||||
# ===== 合约 =====
|
||||
contract_symbol: str = "ETHUSDT"
|
||||
open_type: str = "cross"
|
||||
leverage: str = "30"
|
||||
|
||||
# ===== K线与指标 =====
|
||||
step_min: int = 1
|
||||
lookback_min: int = 240
|
||||
ema_len: int = 36
|
||||
atr_len: int = 14
|
||||
|
||||
# ===== ADX 趋势过滤(新增)=====
|
||||
# 目的:单边趋势(ADX高)时,抑制/禁止逆势均值回归单,避免反复反向开仓止损
|
||||
enable_adx_filter: bool = True
|
||||
adx_len: int = 14
|
||||
adx_threshold: float = 25.0 # 常用:20~30区间,你可按回测调整
|
||||
# 过滤模式:
|
||||
# - "block_countertrend": 只禁止逆着 DI 的方向开仓(推荐,既防反手又不完全停机)
|
||||
# - "block_all": ADX 高时直接不允许任何新开仓(更保守)
|
||||
adx_mode: str = "block_countertrend"
|
||||
# 趋势保护冷却:当 ADX 高且刚止损,延长冷却,减少“止损->立刻反手”的连环
|
||||
cooldown_sec_after_sl_extra: int = 40
|
||||
|
||||
# =========================================================
|
||||
# ✅ 自动阈值:ATR/Price 分位数基准(更稳,不被短时噪声带跑)
|
||||
# =========================================================
|
||||
vol_baseline_window: int = 60
|
||||
vol_baseline_quantile: float = 0.65
|
||||
vol_scale_min: float = 0.80
|
||||
vol_scale_max: float = 1.60
|
||||
|
||||
# ✅ baseline 每 60 秒刷新一次(体感更明显、也省CPU)
|
||||
base_ratio_refresh_sec: int = 180
|
||||
|
||||
# =========================================================
|
||||
# ✅ 动态 floor(方案一)
|
||||
# floor = clamp(min, base_k * base_ratio, max)
|
||||
# 目的:跟着典型波动变,过滤小噪声;tp/sl 也随环境自适应
|
||||
# =========================================================
|
||||
# entry_dev_floor 动态
|
||||
entry_dev_floor_min: float = 0.0012 # 0.12%
|
||||
entry_dev_floor_max: float = 0.0030 # 0.30%
|
||||
entry_dev_floor_base_k: float = 1.10 # entry_floor = 1.10 * base_ratio
|
||||
|
||||
# tp_floor 动态
|
||||
tp_floor_min: float = 0.0006 # 0.06%
|
||||
tp_floor_max: float = 0.0020 # 0.20%
|
||||
tp_floor_base_k: float = 0.55 # tp_floor = 0.55 * base_ratio
|
||||
|
||||
# sl_floor 动态
|
||||
sl_floor_min: float = 0.0018 # 0.18%
|
||||
sl_floor_max: float = 0.0060 # 0.60%
|
||||
sl_floor_base_k: float = 1.35 # sl_floor = 1.35 * base_ratio
|
||||
|
||||
# =========================================================
|
||||
# ✅ 动态阈值倍率
|
||||
# =========================================================
|
||||
entry_k: float = 1.45
|
||||
tp_k: float = 0.65
|
||||
sl_k: float = 1.05
|
||||
|
||||
# ===== 时间/冷却 =====
|
||||
max_hold_sec: int = 75
|
||||
cooldown_sec_after_exit: int = 20
|
||||
|
||||
# ===== 下单/仓位 =====
|
||||
risk_percent: float = 0.004
|
||||
min_size: int = 1
|
||||
max_size: int = 5000
|
||||
|
||||
# ===== 日内风控 =====
|
||||
daily_loss_limit: float = 0.02
|
||||
daily_profit_cap: float = 0.01
|
||||
|
||||
# ===== 危险模式过滤 =====
|
||||
atr_ratio_kill: float = 0.0038
|
||||
big_body_kill: float = 0.010
|
||||
|
||||
# ===== 轮询节奏 =====
|
||||
klines_refresh_sec: int = 10
|
||||
tick_refresh_sec: int = 1
|
||||
status_notify_sec: int = 60
|
||||
|
||||
# =========================================================
|
||||
# ✅ 止损后同向入场加门槛(你原来的逻辑保留)
|
||||
# =========================================================
|
||||
reentry_penalty_mult: float = 1.55
|
||||
reentry_penalty_max_sec: int = 180
|
||||
reset_band_k: float = 0.45
|
||||
reset_band_floor: float = 0.0006
|
||||
|
||||
# =========================================================
|
||||
# ✅ 止损后同方向 SL 放宽幅度与"止损时 vol_scale"联动
|
||||
# =========================================================
|
||||
post_sl_sl_max_sec: int = 90
|
||||
post_sl_mult_min: float = 1.02
|
||||
post_sl_mult_max: float = 1.16
|
||||
post_sl_vol_alpha: float = 0.20
|
||||
|
||||
|
||||
class BitmartFuturesMeanReversionBot:
|
||||
def __init__(self, cfg: StrategyConfig):
|
||||
self.cfg = cfg
|
||||
|
||||
self.api_key = os.getenv("BITMART_API_KEY", "").strip()
|
||||
self.secret_key = os.getenv("BITMART_SECRET_KEY", "").strip()
|
||||
self.memo = os.getenv("BITMART_MEMO", "合约交易").strip()
|
||||
|
||||
if not self.api_key or not self.secret_key:
|
||||
raise RuntimeError("请先设置环境变量 BITMART_API_KEY / BITMART_SECRET_KEY / BITMART_MEMO(可选)")
|
||||
|
||||
self.contractAPI = APIContract(self.api_key, self.secret_key, self.memo, timeout=(5, 15))
|
||||
|
||||
# 持仓状态: -1 空, 0 无, 1 多
|
||||
self.pos = 0
|
||||
self.entry_price = None
|
||||
self.entry_ts = None
|
||||
self.last_exit_ts = 0
|
||||
|
||||
# 日内权益基准
|
||||
self.day_start_equity = None
|
||||
self.trading_enabled = True
|
||||
self.day_tag = datetime.date.today()
|
||||
|
||||
# 缓存
|
||||
self._klines_cache = None
|
||||
self._klines_cache_ts = 0
|
||||
self._last_status_notify_ts = 0
|
||||
|
||||
# ✅ base_ratio 缓存
|
||||
self._base_ratio_cached = 0.0015 # 初始化默认值 0.15%
|
||||
self._base_ratio_ts = 0.0
|
||||
|
||||
# ✅ 止损后"同向入场加门槛"状态
|
||||
self.last_sl_dir = 0 # 1=多止损,-1=空止损,0=无
|
||||
self.last_sl_ts = 0.0
|
||||
|
||||
# ✅ 止损后"同方向 SL 联动放宽"状态
|
||||
self.post_sl_dir = 0
|
||||
self.post_sl_ts = 0.0
|
||||
self.post_sl_vol_scale = 1.0 # 记录止损时的 vol_scale
|
||||
|
||||
self.pbar = tqdm(total=60, desc="运行中(秒)", ncols=90)
|
||||
logger.info(f"初始化完成,基准波动率默认值: {self._base_ratio_cached * 100:.4f}%")
|
||||
|
||||
# ----------------- 通用工具 -----------------
|
||||
def ding(self, msg, error=False):
|
||||
prefix = "❌bitmart:" if error else "🔔bitmart:"
|
||||
if error:
|
||||
for _ in range(3):
|
||||
send_dingtalk_message(f"{prefix}{msg}")
|
||||
else:
|
||||
send_dingtalk_message(f"{prefix}{msg}")
|
||||
|
||||
def set_leverage(self) -> bool:
|
||||
try:
|
||||
resp = self.contractAPI.post_submit_leverage(
|
||||
contract_symbol=self.cfg.contract_symbol,
|
||||
leverage=self.cfg.leverage,
|
||||
open_type=self.cfg.open_type
|
||||
)[0]
|
||||
if resp.get("code") == 1000:
|
||||
logger.success(f"设置杠杆成功:{self.cfg.open_type} + {self.cfg.leverage}x")
|
||||
return True
|
||||
logger.error(f"设置杠杆失败: {resp}")
|
||||
self.ding(f"设置杠杆失败: {resp}", error=True)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"设置杠杆异常: {e}")
|
||||
self.ding(f"设置杠杆异常: {e}", error=True)
|
||||
return False
|
||||
|
||||
# ----------------- 行情/指标 -----------------
|
||||
def get_klines_cached(self):
|
||||
now = time.time()
|
||||
if self._klines_cache is not None and (now - self._klines_cache_ts) < self.cfg.klines_refresh_sec:
|
||||
return self._klines_cache
|
||||
|
||||
kl = self.get_klines()
|
||||
if kl:
|
||||
self._klines_cache = kl
|
||||
self._klines_cache_ts = now
|
||||
return self._klines_cache
|
||||
|
||||
def get_klines(self):
|
||||
try:
|
||||
end_time = int(time.time())
|
||||
start_time = end_time - 60 * self.cfg.lookback_min
|
||||
|
||||
resp = self.contractAPI.get_kline(
|
||||
contract_symbol=self.cfg.contract_symbol,
|
||||
step=self.cfg.step_min,
|
||||
start_time=start_time,
|
||||
end_time=end_time
|
||||
)[0]
|
||||
|
||||
if resp.get("code") != 1000:
|
||||
logger.error(f"获取K线失败: {resp}")
|
||||
return None
|
||||
|
||||
data = resp.get("data", [])
|
||||
formatted = []
|
||||
for k in data:
|
||||
formatted.append({
|
||||
"id": int(k["timestamp"]),
|
||||
"open": float(k["open_price"]),
|
||||
"high": float(k["high_price"]),
|
||||
"low": float(k["low_price"]),
|
||||
"close": float(k["close_price"]),
|
||||
})
|
||||
formatted.sort(key=lambda x: x["id"])
|
||||
return formatted
|
||||
except Exception as e:
|
||||
logger.error(f"获取K线异常: {e}")
|
||||
self.ding(f"获取K线异常: {e}", error=True)
|
||||
return None
|
||||
|
||||
def get_last_price(self, fallback_close: float) -> float:
|
||||
try:
|
||||
if hasattr(self.contractAPI, "get_contract_details"):
|
||||
r = self.contractAPI.get_contract_details(contract_symbol=self.cfg.contract_symbol)[0]
|
||||
d = r.get("data") if isinstance(r, dict) else None
|
||||
if isinstance(d, dict):
|
||||
for key in ("last_price", "mark_price", "index_price"):
|
||||
if key in d and d[key] is not None:
|
||||
return float(d[key])
|
||||
|
||||
if hasattr(self.contractAPI, "get_ticker"):
|
||||
r = self.contractAPI.get_ticker(contract_symbol=self.cfg.contract_symbol)[0]
|
||||
d = r.get("data") if isinstance(r, dict) else None
|
||||
if isinstance(d, dict):
|
||||
for key in ("last_price", "price", "last", "close"):
|
||||
if key in d and d[key] is not None:
|
||||
return float(d[key])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return float(fallback_close)
|
||||
|
||||
@staticmethod
|
||||
def ema(values, n: int) -> float:
|
||||
k = 2 / (n + 1)
|
||||
e = values[0]
|
||||
for v in values[1:]:
|
||||
e = v * k + e * (1 - k)
|
||||
return e
|
||||
|
||||
@staticmethod
|
||||
def atr(klines, n: int) -> float:
|
||||
if len(klines) < n + 1:
|
||||
return 0.0
|
||||
trs = []
|
||||
for i in range(-n, 0):
|
||||
cur = klines[i]
|
||||
prev = klines[i - 1]
|
||||
tr = max(
|
||||
cur["high"] - cur["low"],
|
||||
abs(cur["high"] - prev["close"]),
|
||||
abs(cur["low"] - prev["close"]),
|
||||
)
|
||||
trs.append(tr)
|
||||
return sum(trs) / len(trs)
|
||||
|
||||
@staticmethod
|
||||
def _wilder_smooth(prev: float, val: float, n: int) -> float:
|
||||
# Wilder smoothing: prev - prev/n + val
|
||||
return prev - (prev / n) + val
|
||||
|
||||
def adx(self, klines, n: int):
|
||||
"""
|
||||
返回 (adx, plus_di, minus_di)
|
||||
采用经典 Wilder ADX/DI 计算(足够稳,避免趋势期逆势反复开仓)
|
||||
"""
|
||||
if len(klines) < (2 * n + 2):
|
||||
return 0.0, 0.0, 0.0
|
||||
|
||||
highs = [k["high"] for k in klines]
|
||||
lows = [k["low"] for k in klines]
|
||||
closes = [k["close"] for k in klines]
|
||||
|
||||
# 计算 TR, +DM, -DM 序列(从1开始)
|
||||
tr_list = []
|
||||
pdm_list = []
|
||||
mdm_list = []
|
||||
|
||||
for i in range(1, len(klines)):
|
||||
high = highs[i]
|
||||
low = lows[i]
|
||||
prev_close = closes[i - 1]
|
||||
prev_high = highs[i - 1]
|
||||
prev_low = lows[i - 1]
|
||||
|
||||
tr = max(high - low, abs(high - prev_close), abs(low - prev_close))
|
||||
up_move = high - prev_high
|
||||
down_move = prev_low - low
|
||||
|
||||
pdm = up_move if (up_move > down_move and up_move > 0) else 0.0
|
||||
mdm = down_move if (down_move > up_move and down_move > 0) else 0.0
|
||||
|
||||
tr_list.append(tr)
|
||||
pdm_list.append(pdm)
|
||||
mdm_list.append(mdm)
|
||||
|
||||
# 初始平滑值(第一个n段的和)
|
||||
tr14 = sum(tr_list[:n])
|
||||
pdm14 = sum(pdm_list[:n])
|
||||
mdm14 = sum(mdm_list[:n])
|
||||
|
||||
def safe_div(a, b):
|
||||
return (a / b) if b != 0 else 0.0
|
||||
|
||||
plus_di = 100.0 * safe_div(pdm14, tr14)
|
||||
minus_di = 100.0 * safe_div(mdm14, tr14)
|
||||
dx = 100.0 * safe_div(abs(plus_di - minus_di), (plus_di + minus_di))
|
||||
dx_list = [dx]
|
||||
|
||||
# 继续平滑并计算后续 DX
|
||||
for i in range(n, len(tr_list)):
|
||||
tr14 = self._wilder_smooth(tr14, tr_list[i], n)
|
||||
pdm14 = self._wilder_smooth(pdm14, pdm_list[i], n)
|
||||
mdm14 = self._wilder_smooth(mdm14, mdm_list[i], n)
|
||||
|
||||
plus_di = 100.0 * safe_div(pdm14, tr14)
|
||||
minus_di = 100.0 * safe_div(mdm14, tr14)
|
||||
dx = 100.0 * safe_div(abs(plus_di - minus_di), (plus_di + minus_di))
|
||||
dx_list.append(dx)
|
||||
|
||||
# ADX 是 DX 的 Wilder 平滑,常见做法:先取前 n 个 DX 的均值作为初值
|
||||
if len(dx_list) < (n + 1):
|
||||
return 0.0, plus_di, minus_di
|
||||
|
||||
adx0 = sum(dx_list[:n]) / n
|
||||
adx_val = adx0
|
||||
for j in range(n, len(dx_list)):
|
||||
adx_val = (adx_val * (n - 1) + dx_list[j]) / n
|
||||
|
||||
return float(adx_val), float(plus_di), float(minus_di)
|
||||
|
||||
def is_danger_market(self, klines, price: float) -> bool:
|
||||
last = klines[-1]
|
||||
body = abs(last["close"] - last["open"]) / last["open"] if last["open"] else 0.0
|
||||
if body >= self.cfg.big_body_kill:
|
||||
return True
|
||||
|
||||
a = self.atr(klines, self.cfg.atr_len)
|
||||
atr_ratio = (a / price) if price > 0 else 0.0
|
||||
if atr_ratio >= self.cfg.atr_ratio_kill:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def atr_ratio_baseline(self, klines) -> float:
|
||||
"""简化版ATR基准计算"""
|
||||
window = min(self.cfg.vol_baseline_window, len(klines) - self.cfg.atr_len - 1)
|
||||
if window <= 10:
|
||||
logger.warning(f"数据不足计算基准: {len(klines)}根K线")
|
||||
return 0.0
|
||||
|
||||
ratios = []
|
||||
step = 3
|
||||
for i in range(-window, 0, step):
|
||||
if len(klines) + i < self.cfg.atr_len + 1:
|
||||
continue
|
||||
|
||||
start_idx = len(klines) + i - self.cfg.atr_len
|
||||
end_idx = len(klines) + i
|
||||
if start_idx < 0 or end_idx <= start_idx:
|
||||
continue
|
||||
|
||||
sub_klines = klines[start_idx:end_idx]
|
||||
if len(sub_klines) >= self.cfg.atr_len + 1:
|
||||
a = self.atr(sub_klines, self.cfg.atr_len)
|
||||
price = klines[end_idx - 1]["close"]
|
||||
if a > 0 and price > 0:
|
||||
ratio = a / price
|
||||
if 0.0001 < ratio < 0.01:
|
||||
ratios.append(ratio)
|
||||
|
||||
if len(ratios) < 5:
|
||||
a = self.atr(klines[-60:], self.cfg.atr_len)
|
||||
price = klines[-1]["close"]
|
||||
if a > 0 and price > 0:
|
||||
baseline = a / price
|
||||
logger.debug(f"使用全量数据计算基准: {baseline * 100:.4f}%")
|
||||
return baseline
|
||||
return 0.0
|
||||
|
||||
ratios.sort()
|
||||
idx = min(len(ratios) - 1, max(0, int(self.cfg.vol_baseline_quantile * (len(ratios) - 1))))
|
||||
baseline = ratios[idx]
|
||||
logger.debug(
|
||||
f"基准计算: 样本数={len(ratios)}, 基准={baseline * 100:.4f}%, "
|
||||
f"范围=[{ratios[0] * 100:.4f}%, {ratios[-1] * 100:.4f}%]"
|
||||
)
|
||||
return baseline
|
||||
|
||||
def get_base_ratio_cached(self, klines) -> float:
|
||||
"""获取缓存的基准波动率,定期刷新"""
|
||||
now = time.time()
|
||||
refresh_sec = self.cfg.base_ratio_refresh_sec
|
||||
|
||||
if (self._base_ratio_cached is None or (now - self._base_ratio_ts) >= refresh_sec):
|
||||
baseline = self.atr_ratio_baseline(klines)
|
||||
if baseline > 0.0001:
|
||||
self._base_ratio_cached = baseline
|
||||
self._base_ratio_ts = now
|
||||
logger.info(f"基准波动率更新: {baseline * 100:.4f}%")
|
||||
else:
|
||||
current_price = klines[-1]["close"] if klines else 3000
|
||||
if current_price > 4000:
|
||||
default_baseline = 0.0010
|
||||
elif current_price > 3500:
|
||||
default_baseline = 0.0012
|
||||
elif current_price > 3000:
|
||||
default_baseline = 0.0015
|
||||
elif current_price > 2500:
|
||||
default_baseline = 0.0018
|
||||
else:
|
||||
default_baseline = 0.0020
|
||||
|
||||
self._base_ratio_cached = default_baseline
|
||||
self._base_ratio_ts = now
|
||||
logger.warning(
|
||||
f"使用价格动态默认基准: {default_baseline * 100:.4f}% (价格=${current_price:.0f})"
|
||||
)
|
||||
|
||||
return self._base_ratio_cached
|
||||
|
||||
@staticmethod
|
||||
def _clamp(x: float, lo: float, hi: float) -> float:
|
||||
return max(lo, min(hi, x))
|
||||
|
||||
def dynamic_thresholds(self, atr_ratio: float, base_ratio: float):
|
||||
"""
|
||||
✅ entry/tp/sl 全部动态(修复版):
|
||||
- vol_scale:atr_ratio/base_ratio 限幅
|
||||
- floor:方案一 (floor = clamp(min, k*base_ratio, max))
|
||||
- 最终阈值:max(floor, k * vol_scale * atr_ratio)
|
||||
"""
|
||||
if atr_ratio <= 0:
|
||||
logger.warning(f"ATR比率异常: {atr_ratio}")
|
||||
atr_ratio = 0.001
|
||||
|
||||
if base_ratio < 0.0005:
|
||||
base_ratio = max(0.001, atr_ratio * 1.2)
|
||||
logger.debug(f"基准太小,使用调整后的atr_ratio: {base_ratio * 100:.4f}%")
|
||||
|
||||
if base_ratio > 0:
|
||||
raw_scale = atr_ratio / base_ratio
|
||||
vol_scale = self._clamp(raw_scale, self.cfg.vol_scale_min, self.cfg.vol_scale_max)
|
||||
logger.debug(
|
||||
f"vol_scale: {raw_scale:.2f} → {vol_scale:.2f} (atr={atr_ratio * 100:.3f}%, base={base_ratio * 100:.3f}%)"
|
||||
)
|
||||
else:
|
||||
vol_scale = 1.0
|
||||
logger.warning("基准无效,使用默认vol_scale=1.0")
|
||||
|
||||
entry_floor_raw = self.cfg.entry_dev_floor_base_k * base_ratio
|
||||
entry_floor = self._clamp(entry_floor_raw, self.cfg.entry_dev_floor_min, self.cfg.entry_dev_floor_max)
|
||||
|
||||
tp_floor_raw = self.cfg.tp_floor_base_k * base_ratio
|
||||
tp_floor = self._clamp(tp_floor_raw, self.cfg.tp_floor_min, self.cfg.tp_floor_max)
|
||||
|
||||
sl_floor_raw = self.cfg.sl_floor_base_k * base_ratio
|
||||
sl_floor = self._clamp(sl_floor_raw, self.cfg.sl_floor_min, self.cfg.sl_floor_max)
|
||||
|
||||
entry_dev_atr_part = self.cfg.entry_k * vol_scale * atr_ratio
|
||||
entry_dev = max(entry_floor, entry_dev_atr_part)
|
||||
|
||||
tp_atr_part = self.cfg.tp_k * vol_scale * atr_ratio
|
||||
tp = max(tp_floor, tp_atr_part)
|
||||
|
||||
sl_atr_part = self.cfg.sl_k * vol_scale * atr_ratio
|
||||
sl = max(sl_floor, sl_atr_part)
|
||||
|
||||
entry_dev = max(entry_dev, self.cfg.entry_dev_floor_min)
|
||||
|
||||
logger.info(
|
||||
f"动态阈值: atr={atr_ratio * 100:.4f}%, base={base_ratio * 100:.4f}%, "
|
||||
f"vol_scale={vol_scale:.2f}, floor={entry_floor * 100:.4f}%, "
|
||||
f"atr_part={entry_dev_atr_part * 100:.4f}%, 最终entry_dev={entry_dev * 100:.4f}%"
|
||||
)
|
||||
|
||||
return entry_dev, tp, sl, vol_scale, entry_floor, tp_floor, sl_floor
|
||||
|
||||
# ----------------- 账户/仓位 -----------------
|
||||
def get_assets_available(self) -> float:
|
||||
try:
|
||||
resp = self.contractAPI.get_assets_detail()[0]
|
||||
if resp.get("code") != 1000:
|
||||
return 0.0
|
||||
data = resp.get("data")
|
||||
if isinstance(data, dict):
|
||||
return float(data.get("available_balance", 0))
|
||||
if isinstance(data, list):
|
||||
for asset in data:
|
||||
if asset.get("currency") == "USDT":
|
||||
return float(asset.get("available_balance", 0))
|
||||
return 0.0
|
||||
except Exception as e:
|
||||
logger.error(f"余额查询异常: {e}")
|
||||
return 0.0
|
||||
|
||||
def get_position_status(self) -> bool:
|
||||
try:
|
||||
resp = self.contractAPI.get_position(contract_symbol=self.cfg.contract_symbol)[0]
|
||||
if resp.get("code") != 1000:
|
||||
return False
|
||||
|
||||
positions = resp.get("data", [])
|
||||
if not positions:
|
||||
self.pos = 0
|
||||
return True
|
||||
|
||||
p = positions[0]
|
||||
self.pos = 1 if p["position_type"] == 1 else -1
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"持仓查询异常: {e}")
|
||||
self.ding(f"持仓查询异常: {e}", error=True)
|
||||
return False
|
||||
|
||||
def get_equity_proxy(self) -> float:
|
||||
return self.get_assets_available()
|
||||
|
||||
def refresh_daily_baseline(self):
|
||||
today = datetime.date.today()
|
||||
if today != self.day_tag:
|
||||
self.day_tag = today
|
||||
self.day_start_equity = None
|
||||
self.trading_enabled = True
|
||||
self.ding(f"新的一天({today}):重置日内风控基准")
|
||||
|
||||
def risk_kill_switch(self):
|
||||
self.refresh_daily_baseline()
|
||||
equity = self.get_equity_proxy()
|
||||
if equity <= 0:
|
||||
return
|
||||
|
||||
if self.day_start_equity is None:
|
||||
self.day_start_equity = equity
|
||||
logger.info(f"日内权益基准设定:{equity:.2f} USDT")
|
||||
return
|
||||
|
||||
pnl = (equity - self.day_start_equity) / self.day_start_equity
|
||||
if pnl <= -self.cfg.daily_loss_limit:
|
||||
self.trading_enabled = False
|
||||
self.ding(f"触发日止损:{pnl * 100:.2f}% -> 停机", error=True)
|
||||
|
||||
if pnl >= self.cfg.daily_profit_cap:
|
||||
self.trading_enabled = False
|
||||
self.ding(f"达到日盈利封顶:{pnl * 100:.2f}% -> 停机")
|
||||
|
||||
# ----------------- 下单 -----------------
|
||||
def calculate_size(self, price: float) -> int:
|
||||
bal = self.get_assets_available()
|
||||
if bal < 10:
|
||||
return 0
|
||||
|
||||
margin = bal * self.cfg.risk_percent
|
||||
lev = int(self.cfg.leverage)
|
||||
|
||||
# ⚠️ 沿用你的原假设:1张≈0.001ETH
|
||||
size = int((margin * lev) / (price * 0.001))
|
||||
size = max(self.cfg.min_size, size)
|
||||
size = min(self.cfg.max_size, size)
|
||||
return size
|
||||
|
||||
def place_market_order(self, side: int, size: int) -> bool:
|
||||
if size <= 0:
|
||||
return False
|
||||
|
||||
client_order_id = f"mr_{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
try:
|
||||
resp = self.contractAPI.post_submit_order(
|
||||
contract_symbol=self.cfg.contract_symbol,
|
||||
client_order_id=client_order_id,
|
||||
side=side,
|
||||
mode=1,
|
||||
type="market",
|
||||
leverage=self.cfg.leverage,
|
||||
open_type=self.cfg.open_type,
|
||||
size=size
|
||||
)[0]
|
||||
|
||||
logger.info(f"order_resp: {resp}")
|
||||
|
||||
if resp.get("code") == 1000:
|
||||
return True
|
||||
|
||||
self.ding(f"下单失败: {resp}", error=True)
|
||||
return False
|
||||
|
||||
except APIException as e:
|
||||
logger.error(f"API下单异常: {e}")
|
||||
self.ding(f"API下单异常: {e}", error=True)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"下单未知异常: {e}")
|
||||
self.ding(f"下单未知异常: {e}", error=True)
|
||||
return False
|
||||
|
||||
def close_position_all(self):
|
||||
if self.pos == 1:
|
||||
ok = self.place_market_order(3, 999999)
|
||||
if ok:
|
||||
self.pos = 0
|
||||
elif self.pos == -1:
|
||||
ok = self.place_market_order(2, 999999)
|
||||
if ok:
|
||||
self.pos = 0
|
||||
|
||||
# ----------------- 止损后机制 -----------------
|
||||
def _reentry_penalty_active(self, dev: float, entry_dev: float) -> bool:
|
||||
"""检查是否需要应用重新入场惩罚(你原逻辑保留)"""
|
||||
if self.last_sl_dir == 0:
|
||||
return False
|
||||
|
||||
if (time.time() - self.last_sl_ts) > self.cfg.reentry_penalty_max_sec:
|
||||
self.last_sl_dir = 0
|
||||
return False
|
||||
|
||||
reset_band = max(self.cfg.reset_band_floor, self.cfg.reset_band_k * entry_dev)
|
||||
if abs(dev) <= reset_band:
|
||||
self.last_sl_dir = 0
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _post_sl_dynamic_mult(self) -> float:
|
||||
"""计算止损后SL放宽倍数"""
|
||||
if self.post_sl_dir == 0:
|
||||
return 1.0
|
||||
|
||||
if (time.time() - self.post_sl_ts) > self.cfg.post_sl_sl_max_sec:
|
||||
self.post_sl_dir = 0
|
||||
self.post_sl_vol_scale = 1.0
|
||||
return 1.0
|
||||
|
||||
raw = 1.0 + self.cfg.post_sl_vol_alpha * (self.post_sl_vol_scale - 1.0)
|
||||
raw = max(1.0, raw) # 不缩小止损,只放宽
|
||||
return max(self.cfg.post_sl_mult_min, min(self.cfg.post_sl_mult_max, raw))
|
||||
|
||||
# ----------------- 交易逻辑 -----------------
|
||||
def in_cooldown(self) -> bool:
|
||||
"""检查是否在冷却期内(新增:止损后可额外延长冷却,用于抑制反手连环)"""
|
||||
cd = self.cfg.cooldown_sec_after_exit
|
||||
if self.last_sl_ts and (time.time() - self.last_sl_ts) < self.cfg.reentry_penalty_max_sec:
|
||||
cd += max(0, self.cfg.cooldown_sec_after_sl_extra)
|
||||
return (time.time() - self.last_exit_ts) < cd
|
||||
|
||||
def _adx_blocks_entry(self, adx_val: float, plus_di: float, minus_di: float, want_dir: int) -> bool:
|
||||
"""
|
||||
ADX 趋势过滤:
|
||||
- want_dir: 1=想开多, -1=想开空
|
||||
"""
|
||||
if not self.cfg.enable_adx_filter:
|
||||
return False
|
||||
if adx_val < self.cfg.adx_threshold:
|
||||
return False
|
||||
|
||||
if self.cfg.adx_mode == "block_all":
|
||||
return True
|
||||
|
||||
# block_countertrend:只禁止逆 DI 方向
|
||||
# 上升趋势:+DI > -DI => 禁止开空
|
||||
# 下降趋势:-DI > +DI => 禁止开多
|
||||
if plus_di > minus_di and want_dir == -1:
|
||||
return True
|
||||
if minus_di > plus_di and want_dir == 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
def maybe_enter(self, price: float, ema_value: float, entry_dev: float,
|
||||
adx_val: float, plus_di: float, minus_di: float):
|
||||
"""检查并执行入场(新增:ADX趋势过滤,防止趋势期逆势反复开仓)"""
|
||||
if self.pos != 0:
|
||||
return
|
||||
if self.in_cooldown():
|
||||
return
|
||||
|
||||
dev = (price - ema_value) / ema_value if ema_value else 0.0
|
||||
size = self.calculate_size(price)
|
||||
if size <= 0:
|
||||
return
|
||||
|
||||
penalty_active = self._reentry_penalty_active(dev, entry_dev)
|
||||
|
||||
long_th = -entry_dev
|
||||
short_th = entry_dev
|
||||
|
||||
if penalty_active:
|
||||
if self.last_sl_dir == 1:
|
||||
long_th = -entry_dev * self.cfg.reentry_penalty_mult
|
||||
logger.info(f"多头止损后惩罚生效: long_th={long_th * 100:.3f}%")
|
||||
elif self.last_sl_dir == -1:
|
||||
short_th = entry_dev * self.cfg.reentry_penalty_mult
|
||||
logger.info(f"空头止损后惩罚生效: short_th={short_th * 100:.3f}%")
|
||||
|
||||
logger.info(
|
||||
f"入场检查: price={price:.2f}, ema={ema_value:.2f}, dev={dev * 100:.3f}% "
|
||||
f"(entry_dev={entry_dev * 100:.3f}%, long_th={long_th * 100:.3f}%, short_th={short_th * 100:.3f}%) "
|
||||
f"ADX={adx_val:.2f} +DI={plus_di:.2f} -DI={minus_di:.2f} "
|
||||
f"size={size}, penalty={penalty_active}, last_sl_dir={self.last_sl_dir}"
|
||||
)
|
||||
|
||||
# 先判断信号,再用 ADX 过滤(这样日志更直观)
|
||||
if dev <= long_th:
|
||||
if self._adx_blocks_entry(adx_val, plus_di, minus_di, want_dir=1):
|
||||
logger.warning(
|
||||
f"ADX过滤:趋势期禁止逆势开多(ADX={adx_val:.2f}, +DI={plus_di:.2f}, -DI={minus_di:.2f})"
|
||||
)
|
||||
return
|
||||
|
||||
if self.place_market_order(1, size):
|
||||
self.pos = 1
|
||||
self.entry_price = price
|
||||
self.entry_ts = time.time()
|
||||
self.ding(f"✅开多:dev={dev * 100:.3f}% size={size} entry={price:.2f}")
|
||||
|
||||
elif dev >= short_th:
|
||||
if self._adx_blocks_entry(adx_val, plus_di, minus_di, want_dir=-1):
|
||||
logger.warning(
|
||||
f"ADX过滤:趋势期禁止逆势开空(ADX={adx_val:.2f}, +DI={plus_di:.2f}, -DI={minus_di:.2f})"
|
||||
)
|
||||
return
|
||||
|
||||
if self.place_market_order(4, size):
|
||||
self.pos = -1
|
||||
self.entry_price = price
|
||||
self.entry_ts = time.time()
|
||||
self.ding(f"✅开空:dev={dev * 100:.3f}% size={size} entry={price:.2f}")
|
||||
|
||||
def maybe_exit(self, price: float, tp: float, sl: float, vol_scale: float):
|
||||
"""检查并执行出场"""
|
||||
if self.pos == 0 or self.entry_price is None or self.entry_ts is None:
|
||||
return
|
||||
|
||||
hold = time.time() - self.entry_ts
|
||||
|
||||
if self.pos == 1:
|
||||
pnl = (price - self.entry_price) / self.entry_price
|
||||
else:
|
||||
pnl = (self.entry_price - price) / self.entry_price
|
||||
|
||||
sl_mult = 1.0
|
||||
if self.post_sl_dir == self.pos and self.post_sl_dir != 0:
|
||||
sl_mult = self._post_sl_dynamic_mult()
|
||||
effective_sl = sl * sl_mult
|
||||
|
||||
if pnl >= tp:
|
||||
self.close_position_all()
|
||||
self.ding(f"🎯止盈:pnl={pnl * 100:.3f}% price={price:.2f} tp={tp * 100:.3f}%")
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
|
||||
elif pnl <= -effective_sl:
|
||||
sl_dir = self.pos
|
||||
self.close_position_all()
|
||||
self.ding(
|
||||
f"🛑止损:pnl={pnl * 100:.3f}% price={price:.2f} "
|
||||
f"sl={sl * 100:.3f}% effective_sl={effective_sl * 100:.3f}%(×{sl_mult:.2f})",
|
||||
error=True
|
||||
)
|
||||
|
||||
# 记录止损方向与时间
|
||||
self.last_sl_dir = sl_dir
|
||||
self.last_sl_ts = time.time()
|
||||
|
||||
self.post_sl_dir = sl_dir
|
||||
self.post_sl_ts = time.time()
|
||||
self.post_sl_vol_scale = float(vol_scale)
|
||||
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
|
||||
elif hold >= self.cfg.max_hold_sec:
|
||||
self.close_position_all()
|
||||
self.ding(f"⏱超时:hold={int(hold)}s pnl={pnl * 100:.3f}% price={price:.2f}")
|
||||
self.entry_price, self.entry_ts = None, None
|
||||
self.last_exit_ts = time.time()
|
||||
|
||||
def notify_status_throttled(self, price: float, ema_value: float, dev: float, bal: float,
|
||||
atr_ratio: float, base_ratio: float, vol_scale: float,
|
||||
entry_dev: float, tp: float, sl: float,
|
||||
entry_floor: float, tp_floor: float, sl_floor: float,
|
||||
adx_val: float, plus_di: float, minus_di: float):
|
||||
"""限频状态通知"""
|
||||
now = time.time()
|
||||
if (now - self._last_status_notify_ts) < self.cfg.status_notify_sec:
|
||||
return
|
||||
self._last_status_notify_ts = now
|
||||
|
||||
direction_str = "多" if self.pos == 1 else ("空" if self.pos == -1 else "无")
|
||||
penalty_active = self._reentry_penalty_active(dev, entry_dev)
|
||||
|
||||
sl_mult = 1.0
|
||||
if self.pos != 0 and self.post_sl_dir == self.pos:
|
||||
sl_mult = self._post_sl_dynamic_mult()
|
||||
|
||||
base_age = int(now - self._base_ratio_ts) if self._base_ratio_ts else -1
|
||||
|
||||
msg = (
|
||||
f"【BitMart {self.cfg.contract_symbol}|1m均值回归(动态阈值+ADX过滤)】\n"
|
||||
f"📊 状态:{direction_str}\n"
|
||||
f"💰 现价:{price:.2f} | EMA{self.cfg.ema_len}:{ema_value:.2f}\n"
|
||||
f"📈 偏离:{dev * 100:.3f}% (入场阈值:±{entry_dev * 100:.3f}%)\n"
|
||||
f"🌊 波动率:ATR比={atr_ratio * 100:.3f}% | 基准={base_ratio * 100:.3f}% | 缩放={vol_scale:.2f}\n"
|
||||
f"🧭 趋势:ADX={adx_val:.2f} | +DI={plus_di:.2f} | -DI={minus_di:.2f} "
|
||||
f"(阈值={self.cfg.adx_threshold:.1f}, 模式={self.cfg.adx_mode})\n"
|
||||
f"🎯 动态Floor:入场={entry_floor * 100:.3f}% | 止盈={tp_floor * 100:.3f}% | 止损={sl_floor * 100:.3f}%\n"
|
||||
f"💰 止盈/止损:{tp * 100:.3f}% / {sl * 100:.3f}% (盈亏比:{tp / sl:.2f})\n"
|
||||
f"🔄 基准刷新:{self.cfg.base_ratio_refresh_sec}s (已过={base_age}s)\n"
|
||||
f"⚠️ 止损同向加门槛:{'开启' if penalty_active else '关闭'} (方向={self.last_sl_dir})\n"
|
||||
f"💳 可用余额:{bal:.2f} USDT | 杠杆:{self.cfg.leverage}x\n"
|
||||
f"⏱️ 持仓限制:{self.cfg.max_hold_sec}s | 冷却:{self.cfg.cooldown_sec_after_exit}s"
|
||||
)
|
||||
self.ding(msg)
|
||||
|
||||
def action(self):
|
||||
"""主循环"""
|
||||
if not self.set_leverage():
|
||||
self.ding("杠杆设置失败,停止运行", error=True)
|
||||
return
|
||||
|
||||
while True:
|
||||
now_dt = datetime.datetime.now()
|
||||
self.pbar.n = now_dt.second
|
||||
self.pbar.refresh()
|
||||
|
||||
# 1. 获取K线数据
|
||||
klines = self.get_klines_cached()
|
||||
if not klines or len(klines) < (max(self.cfg.ema_len + 5, 2 * self.cfg.adx_len + 5)):
|
||||
logger.warning("K线数据不足,等待...")
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# 2. 计算技术指标
|
||||
last_k = klines[-1]
|
||||
closes = [k["close"] for k in klines[-(self.cfg.ema_len + 1):]]
|
||||
ema_value = self.ema(closes, self.cfg.ema_len)
|
||||
|
||||
price = self.get_last_price(fallback_close=float(last_k["close"]))
|
||||
dev = (price - ema_value) / ema_value if ema_value else 0.0
|
||||
|
||||
# 3. 计算波动率相关指标
|
||||
a = self.atr(klines, self.cfg.atr_len)
|
||||
atr_ratio = (a / price) if price > 0 else 0.0
|
||||
base_ratio = self.get_base_ratio_cached(klines)
|
||||
|
||||
# 4. 计算动态阈值
|
||||
entry_dev, tp, sl, vol_scale, entry_floor, tp_floor, sl_floor = self.dynamic_thresholds(
|
||||
atr_ratio, base_ratio
|
||||
)
|
||||
|
||||
# 5. 计算 ADX(新增)
|
||||
adx_val, plus_di, minus_di = self.adx(klines, self.cfg.adx_len)
|
||||
logger.info(f"ADX: {adx_val:.2f} (+DI={plus_di:.2f}, -DI={minus_di:.2f})")
|
||||
|
||||
# 6. 风控检查
|
||||
self.risk_kill_switch()
|
||||
|
||||
# 7. 获取持仓状态
|
||||
if not self.get_position_status():
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# 8. 检查交易是否启用
|
||||
if not self.trading_enabled:
|
||||
if self.pos != 0:
|
||||
self.close_position_all()
|
||||
logger.warning("交易被禁用(风控触发),等待...")
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
# 9. 检查危险市场
|
||||
if self.is_danger_market(klines, price):
|
||||
logger.warning("危险模式:高波动/大实体K,暂停开仓")
|
||||
self.maybe_exit(price, tp, sl, vol_scale)
|
||||
time.sleep(self.cfg.tick_refresh_sec)
|
||||
continue
|
||||
|
||||
# 10. 执行交易逻辑
|
||||
self.maybe_exit(price, tp, sl, vol_scale)
|
||||
self.maybe_enter(price, ema_value, entry_dev, adx_val, plus_di, minus_di)
|
||||
|
||||
# 11. 状态通知
|
||||
bal = self.get_assets_available()
|
||||
self.notify_status_throttled(
|
||||
price, ema_value, dev, bal,
|
||||
atr_ratio, base_ratio, vol_scale,
|
||||
entry_dev, tp, sl,
|
||||
entry_floor, tp_floor, sl_floor,
|
||||
adx_val, plus_di, minus_di
|
||||
)
|
||||
|
||||
time.sleep(self.cfg.tick_refresh_sec)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""
|
||||
Windows PowerShell:
|
||||
setx BITMART_API_KEY "你的key"
|
||||
setx BITMART_SECRET_KEY "你的secret"
|
||||
setx BITMART_MEMO "合约交易"
|
||||
重新打开终端再运行。
|
||||
|
||||
Linux/macOS:
|
||||
export BITMART_API_KEY="你的key"
|
||||
export BITMART_SECRET_KEY="你的secret"
|
||||
export BITMART_MEMO "合约交易"
|
||||
"""
|
||||
cfg = StrategyConfig()
|
||||
bot = BitmartFuturesMeanReversionBot(cfg)
|
||||
|
||||
logger.remove()
|
||||
logger.add(lambda msg: tqdm.write(msg, end=""), level="INFO")
|
||||
|
||||
try:
|
||||
bot.action()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("程序被用户中断")
|
||||
bot.ding("🤖 策略已手动停止")
|
||||
except Exception as e:
|
||||
logger.error(f"程序异常退出: {e}")
|
||||
bot.ding(f"❌ 策略异常退出: {e}", error=True)
|
||||
raise
|
||||
@@ -1,410 +0,0 @@
|
||||
import re
|
||||
import json
|
||||
import hmac
|
||||
import time
|
||||
import base64
|
||||
import hashlib
|
||||
import datetime
|
||||
import requests
|
||||
|
||||
from tqdm import *
|
||||
from loguru import *
|
||||
from DrissionPage import *
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
def is_bullish(c): # 阳线
|
||||
return float(c['close']) > float(c['open'])
|
||||
|
||||
|
||||
def is_bearish(c): # 阴线
|
||||
return float(c['close']) < float(c['open'])
|
||||
|
||||
|
||||
class WeexTransaction:
|
||||
def __init__(self, tge_id):
|
||||
self.tge_port = None # tge浏览器使用端口
|
||||
self.tge_id = tge_id # tge id
|
||||
self.tge_url = "http://127.0.0.1:50326" # tge本地服务url
|
||||
self.tge_headers = {
|
||||
"Authorization": f"Bearer asp_174003986c9b0799677c5b2c1adb76e402735d753bc91a91",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# 替换为你自己的钉钉机器人 Webhook 地址
|
||||
self.webhook_url = "https://oapi.dingtalk.com/robot/send?access_token=e2fafb3f46866d50fe52cbb29650ba9ef1cbc97915dde238192f04c906fe4125"
|
||||
# 替换为你自己的钉钉机器人秘钥
|
||||
self.secret = "SEC5f320e72d7a4eaca540c66c3d09edff2f74936517390dee99ece6dd1b3611998"
|
||||
|
||||
self.page = None # 浏览器对象
|
||||
|
||||
self.start = 0 # 持仓状态 -1:做空,0:维持仓,1:做多
|
||||
self.kline_1 = None # 0:跌,1:涨
|
||||
self.kline_2 = None # 0:跌,1:涨
|
||||
|
||||
self.direction = None # 信号类型
|
||||
|
||||
self.pbar = None # 进度条对象
|
||||
|
||||
def get_signature(self, timestamp):
|
||||
# 将时间戳和密钥拼接
|
||||
string_to_sign = f'{timestamp}\n{self.secret}'
|
||||
string_to_sign = string_to_sign.encode('utf-8')
|
||||
# 使用 HMAC-SHA256 算法进行签名
|
||||
hmac_code = hmac.new(self.secret.encode('utf-8'), string_to_sign, digestmod=hashlib.sha256).digest()
|
||||
# 对签名结果进行 Base64 编码
|
||||
sign = base64.b64encode(hmac_code).decode('utf-8')
|
||||
return sign
|
||||
|
||||
# def send_dingtalk_message(self, message_content):
|
||||
# # 获取当前时间戳(毫秒)
|
||||
# timestamp = str(round(time.time() * 1000))
|
||||
# # 生成签名
|
||||
# sign = self.get_signature(timestamp, )
|
||||
# # 拼接带有签名信息的完整 Webhook URL
|
||||
# full_url = f"{self.webhook_url}×tamp={timestamp}&sign={sign}"
|
||||
#
|
||||
# # 定义消息内容
|
||||
# message = {
|
||||
# "msgtype": "text",
|
||||
# "text": {
|
||||
# "content": message_content
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # 设置请求头
|
||||
# headers = {
|
||||
# "Content-Type": "application/json"
|
||||
# }
|
||||
#
|
||||
# try:
|
||||
# # 发送 POST 请求
|
||||
# response = requests.post(full_url, headers=headers, data=json.dumps(message))
|
||||
#
|
||||
# except requests.RequestException as e:
|
||||
# print(f"请求发生错误: {e}")
|
||||
|
||||
def send_dingtalk_message(self, message_content):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# url = "http://8.137.99.82:9005/api/send_click?token=fegergauiernguie&phone=8613661496481"
|
||||
#
|
||||
# res = requests.post(
|
||||
# url=url,
|
||||
# json={
|
||||
# "phone": "8613661496481",
|
||||
# "bot_name": "ergggreef",
|
||||
# "datas": [
|
||||
# {"send_message": [message_content], "click_button": [""], },
|
||||
# ]
|
||||
#
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# print(res.json())
|
||||
|
||||
def openBrowser(self, ): # 直接指定ID打开窗口,也可以使用 createBrowser 方法返回的ID
|
||||
|
||||
try:
|
||||
|
||||
response = requests.post(
|
||||
f"{self.tge_url}/api/browser/start",
|
||||
json={"envId": self.tge_id},
|
||||
headers=self.tge_headers
|
||||
)
|
||||
|
||||
self.tge_port = response.json()["data"]["port"]
|
||||
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def take_over_browser(self):
|
||||
try:
|
||||
co = ChromiumOptions()
|
||||
co.set_local_port(self.tge_port)
|
||||
|
||||
self.page = ChromiumPage(addr_or_opts=co)
|
||||
|
||||
self.page.set.window.max()
|
||||
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_bullish(self, c): # 阳线
|
||||
return float(c['close']) > float(c['open'])
|
||||
|
||||
def is_bearish(self, c): # 阴线
|
||||
return float(c['close']) < float(c['open'])
|
||||
|
||||
def check_signal(self, prev, curr):
|
||||
"""
|
||||
包住形态信号判定(仅15分钟K线):
|
||||
- 前跌后涨包住 -> 做多
|
||||
- 前涨后跌包住 -> 做空
|
||||
"""
|
||||
p_open, p_close = float(prev['open']), float(prev['close'])
|
||||
c_open, c_close = float(curr['open']), float(curr['close'])
|
||||
|
||||
# 前跌后涨包住 -> 做多
|
||||
if is_bullish(curr) and is_bearish(prev) and c_open <= p_close and c_close >= p_open:
|
||||
return "long", "bear_bull_engulf"
|
||||
|
||||
# 前涨后跌包住 -> 做空
|
||||
if is_bearish(curr) and is_bullish(prev) and c_open >= p_close and c_close <= p_open:
|
||||
return "short", "bull_bear_engulf"
|
||||
|
||||
return None, None
|
||||
|
||||
def get_price(self):
|
||||
|
||||
for i in range(3):
|
||||
try:
|
||||
logger.info(f"获取最新数据:{i + 1}次。。。")
|
||||
self.mn_tab.get(url="https://www.weeaxs.site/zh-CN/futures/demo-trading/ETH-SUSDT")
|
||||
res = self.mn_tab.listen.wait(timeout=15) # 等待并获取一个数据包
|
||||
|
||||
datas = []
|
||||
if res:
|
||||
for data in res.response.body['data']["dataList"]:
|
||||
insert_data = {
|
||||
'id': int(data[4]),
|
||||
'open': float(data[3]),
|
||||
'high': float(data[1]),
|
||||
'low': float(data[2]),
|
||||
'close': float(data[0])
|
||||
}
|
||||
|
||||
datas.append(insert_data)
|
||||
|
||||
return datas
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def remove_tags_and_spaces(self,html):
|
||||
# 创建 BeautifulSoup 对象
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
# 获取去除标签后的文本
|
||||
text = soup.get_text()
|
||||
# 去除所有空格
|
||||
text = text.replace(" ", "")
|
||||
return text
|
||||
|
||||
def to_do_page(self):
|
||||
# self.page.get("https://www.weeaxs.site/zh-CN/futures/demo-trading/ETH-SUSDT")
|
||||
|
||||
self.mn_tab.ele('x://*[contains(text(), "市价")]', timeout=15).click()
|
||||
time.sleep(1)
|
||||
html_text = self.remove_tags_and_spaces(html=self.mn_tab.html)
|
||||
# 定义正则表达式模式,用于匹配包含逗号和小数点的数值
|
||||
pattern = r'委托可用([\d,]+\.\d+)SUSDT'
|
||||
|
||||
# 使用 re.search 方法查找匹配项
|
||||
match = re.search(pattern, html_text)
|
||||
number = ""
|
||||
if match:
|
||||
# 提取匹配到的数值字符串
|
||||
number_str = match.group(1).replace(',', '')
|
||||
# 将提取的字符串转换为浮点数
|
||||
number = float(number_str)
|
||||
|
||||
if not number:
|
||||
return
|
||||
|
||||
self.mn_tab.ele('x://input[@placeholder="请输入数量"]').input(number/10)
|
||||
time.sleep(1)
|
||||
|
||||
if self.direction == "long" and not self.start:
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},开多")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},开多")
|
||||
self.mn_tab.ele('x://*[contains(text(), "买入开多")]').click()
|
||||
self.start = 1
|
||||
elif self.direction == "short" and not self.start:
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},开空")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},开空")
|
||||
self.mn_tab.ele('x://*[contains(text(), "卖出开空")]').click()
|
||||
self.start = -1
|
||||
elif self.direction == "long" and self.start == -1:
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},反手平空做多")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},反手平空做多")
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').scroll.to_see(center=True)
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').click()
|
||||
time.sleep(3)
|
||||
self.mn_tab.ele('x://*[contains(text(), "买入开多")]').click()
|
||||
self.start = 1
|
||||
elif self.direction == "short" and self.start == 1:
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},反手平多做空")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},反手平多做空")
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').scroll.to_see(center=True)
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').click()
|
||||
time.sleep(3)
|
||||
self.mn_tab.ele('x://*[contains(text(), "卖出开空")]').click()
|
||||
self.start = -1
|
||||
|
||||
def get_text(self, target_text):
|
||||
# 去除目标文本中的空白字符
|
||||
cleaned_target_text = re.sub(r'\s', '', target_text)
|
||||
|
||||
# 创建 BeautifulSoup 对象
|
||||
soup = BeautifulSoup(self.mn_tab.html, 'html.parser')
|
||||
|
||||
# 遍历所有标签的文本内容
|
||||
for tag in soup.find_all():
|
||||
tag_text = tag.get_text()
|
||||
# 去除标签文本中的空白字符
|
||||
cleaned_tag_text = re.sub(r'\s', '', tag_text)
|
||||
if cleaned_target_text in cleaned_tag_text:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_now_time(self):
|
||||
# 获取当前时间戳
|
||||
current_timestamp = time.time()
|
||||
# 将当前时间戳转换为 datetime 对象
|
||||
current_datetime = datetime.datetime.fromtimestamp(current_timestamp)
|
||||
|
||||
# 计算距离当前时间最近的整点或 30 分时刻
|
||||
if current_datetime.minute < 30:
|
||||
target_datetime = current_datetime.replace(minute=0, second=0, microsecond=0)
|
||||
else:
|
||||
target_datetime = current_datetime.replace(minute=30, second=0, microsecond=0)
|
||||
|
||||
# 将目标 datetime 对象转换为时间戳
|
||||
target_timestamp = target_datetime.timestamp()
|
||||
|
||||
return int(target_timestamp) * 1000
|
||||
|
||||
def close_extra_tabs_in_browser(self):
|
||||
|
||||
try:
|
||||
for _, i in enumerate(self.page.get_tabs()):
|
||||
if _ == 0:
|
||||
continue
|
||||
|
||||
i.close()
|
||||
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def action(self):
|
||||
# 获取比特端口
|
||||
if self.openBrowser():
|
||||
logger.info("获取打开比特成功,成功获取端口!!!")
|
||||
else:
|
||||
logger.error("打开比特失败!!!")
|
||||
return
|
||||
|
||||
# 接管浏览器
|
||||
if self.take_over_browser():
|
||||
logger.info("接管比特浏览器成功!!!")
|
||||
else:
|
||||
logger.error("接管浏览器失败!!!")
|
||||
return
|
||||
|
||||
if self.close_extra_tabs_in_browser():
|
||||
logger.info('关闭多余标签页成功!!!')
|
||||
else:
|
||||
logger.info('关闭多余标签页失败!!!')
|
||||
|
||||
self.mn_tab = self.page.new_tab()
|
||||
self.mn_tab.listen.start("public/quote/v1/getKlineV2")
|
||||
logger.success("浏览器开启抓包模式。。。")
|
||||
|
||||
self.mn_tab.get(url="https://www.weeaxs.site/zh-CN/futures/demo-trading/ETH-SUSDT") # 打开网页
|
||||
|
||||
self.pbar = tqdm(total=30, desc="等待时间中", ncols=80) # desc:进度条说明,ncols:长度
|
||||
|
||||
while True:
|
||||
# 获取当前时间
|
||||
current_time = time.localtime()
|
||||
current_minute = current_time.tm_min
|
||||
|
||||
if current_minute < 30:
|
||||
self.pbar.n = current_minute
|
||||
self.pbar.refresh()
|
||||
else:
|
||||
self.pbar.n = current_minute - 30
|
||||
self.pbar.refresh()
|
||||
|
||||
if current_minute not in [0, 1, 2, 3, 4, 5, 30, 31, 32, 33, 34, 58]: # 判断是否是 新的30分钟了
|
||||
time.sleep(10)
|
||||
continue
|
||||
|
||||
new_price_datas = self.get_price()
|
||||
if new_price_datas:
|
||||
logger.success("获取最新交易价格成功!!!")
|
||||
else:
|
||||
logger.info("获取最新价格有问题!!!")
|
||||
continue
|
||||
|
||||
new_price_datas1 = sorted(new_price_datas, key=lambda x: x["id"])
|
||||
self.kline_1, self.kline_2, self.kline_3 = new_price_datas1[-3:]
|
||||
|
||||
# 判断抓取的数据是否正确
|
||||
if self.get_now_time() != self.kline_3["id"]:
|
||||
continue
|
||||
|
||||
time.sleep(15)
|
||||
if self.get_text(target_text="仓位(1)"):
|
||||
if self.get_text(target_text="ETH/SUSDT多"):
|
||||
self.start = 1
|
||||
elif self.get_text(target_text="ETH/SUSDT空"):
|
||||
self.start = -1
|
||||
else:
|
||||
self.start = 0
|
||||
|
||||
if self.start == 1:
|
||||
if is_bearish(self.kline_1) and is_bearish(self.kline_2):
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},平多")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},平多")
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').scroll.to_see(center=True)
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').click()
|
||||
self.start = 0
|
||||
elif self.start == -1:
|
||||
if is_bullish(self.kline_1) and is_bullish(self.kline_2):
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},平空")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},平空")
|
||||
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').scroll.to_see(center=True)
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').click()
|
||||
self.start = 0
|
||||
|
||||
self.direction, signal_key = self.check_signal(prev=self.kline_1, curr=self.kline_2) # 判断信号
|
||||
|
||||
if self.direction:
|
||||
try:
|
||||
self.to_do_page()
|
||||
except Exception as e:
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},{e}")
|
||||
|
||||
self.pbar.reset() # 重置进度条
|
||||
self.send_dingtalk_message(
|
||||
message_content=
|
||||
f"{datetime.datetime.now()},"
|
||||
f"目前有持仓:{"无" if self.start == 0 else ("多" if self.start == 1 else "空")},"
|
||||
f"当前信号:{"无" if not self.direction else ("多" if self.direction == "long" else "空")}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
WeexTransaction(
|
||||
tge_id=146473,
|
||||
).action()
|
||||
|
||||
# //*[contains(text(), '特定文本')]
|
||||
@@ -1,408 +0,0 @@
|
||||
import re
|
||||
import json
|
||||
import hmac
|
||||
import time
|
||||
import base64
|
||||
import hashlib
|
||||
import datetime
|
||||
import requests
|
||||
|
||||
from tqdm import *
|
||||
from loguru import *
|
||||
from DrissionPage import *
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
def is_bullish(c): # 阳线
|
||||
return float(c['close']) > float(c['open'])
|
||||
|
||||
|
||||
def is_bearish(c): # 阴线
|
||||
return float(c['close']) < float(c['open'])
|
||||
|
||||
|
||||
class WeexTransaction:
|
||||
def __init__(self, tge_id):
|
||||
self.tge_port = None # tge浏览器使用端口
|
||||
self.tge_id = tge_id # tge id
|
||||
self.tge_url = "http://127.0.0.1:50326" # tge本地服务url
|
||||
self.tge_headers = {
|
||||
"Authorization": f"Bearer asp_174003986c9b0799677c5b2c1adb76e402735d753bc91a91",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# 替换为你自己的钉钉机器人 Webhook 地址
|
||||
self.webhook_url = "https://oapi.dingtalk.com/robot/send?access_token=e2fafb3f46866d50fe52cbb29650ba9ef1cbc97915dde238192f04c906fe4125"
|
||||
# 替换为你自己的钉钉机器人秘钥
|
||||
self.secret = "SEC5f320e72d7a4eaca540c66c3d09edff2f74936517390dee99ece6dd1b3611998"
|
||||
|
||||
self.page = None # 浏览器对象
|
||||
|
||||
self.start = 0 # 持仓状态 -1:做空,0:维持仓,1:做多
|
||||
self.kline_1 = None # 0:跌,1:涨
|
||||
self.kline_2 = None # 0:跌,1:涨
|
||||
|
||||
self.direction = None # 信号类型
|
||||
|
||||
self.pbar = None # 进度条对象
|
||||
|
||||
def get_signature(self, timestamp):
|
||||
# 将时间戳和密钥拼接
|
||||
string_to_sign = f'{timestamp}\n{self.secret}'
|
||||
string_to_sign = string_to_sign.encode('utf-8')
|
||||
# 使用 HMAC-SHA256 算法进行签名
|
||||
hmac_code = hmac.new(self.secret.encode('utf-8'), string_to_sign, digestmod=hashlib.sha256).digest()
|
||||
# 对签名结果进行 Base64 编码
|
||||
sign = base64.b64encode(hmac_code).decode('utf-8')
|
||||
return sign
|
||||
|
||||
# def send_dingtalk_message(self, message_content):
|
||||
# # 获取当前时间戳(毫秒)
|
||||
# timestamp = str(round(time.time() * 1000))
|
||||
# # 生成签名
|
||||
# sign = self.get_signature(timestamp, )
|
||||
# # 拼接带有签名信息的完整 Webhook URL
|
||||
# full_url = f"{self.webhook_url}×tamp={timestamp}&sign={sign}"
|
||||
#
|
||||
# # 定义消息内容
|
||||
# message = {
|
||||
# "msgtype": "text",
|
||||
# "text": {
|
||||
# "content": message_content
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # 设置请求头
|
||||
# headers = {
|
||||
# "Content-Type": "application/json"
|
||||
# }
|
||||
#
|
||||
# try:
|
||||
# # 发送 POST 请求
|
||||
# response = requests.post(full_url, headers=headers, data=json.dumps(message))
|
||||
#
|
||||
# except requests.RequestException as e:
|
||||
# print(f"请求发生错误: {e}")
|
||||
|
||||
def send_dingtalk_message(self, message_content):
|
||||
|
||||
pass
|
||||
|
||||
# url = "http://8.137.99.82:9005/api/send_click?token=fegergauiernguie&phone=8613661496481"
|
||||
#
|
||||
# res = requests.post(
|
||||
# url=url,
|
||||
# json={
|
||||
# "phone": "8613661496481",
|
||||
# "bot_name": "ergggreef",
|
||||
# "datas": [
|
||||
# {"send_message": [message_content], "click_button": [""], },
|
||||
# ]
|
||||
#
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# print(res.json())
|
||||
|
||||
def openBrowser(self, ): # 直接指定ID打开窗口,也可以使用 createBrowser 方法返回的ID
|
||||
|
||||
try:
|
||||
|
||||
response = requests.post(
|
||||
f"{self.tge_url}/api/browser/start",
|
||||
json={"envId": self.tge_id},
|
||||
headers=self.tge_headers
|
||||
)
|
||||
|
||||
self.tge_port = response.json()["data"]["port"]
|
||||
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def take_over_browser(self):
|
||||
try:
|
||||
co = ChromiumOptions()
|
||||
co.set_local_port(self.tge_port)
|
||||
|
||||
self.page = ChromiumPage(addr_or_opts=co)
|
||||
|
||||
self.page.set.window.max()
|
||||
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_bullish(self, c): # 阳线
|
||||
return float(c['close']) > float(c['open'])
|
||||
|
||||
def is_bearish(self, c): # 阴线
|
||||
return float(c['close']) < float(c['open'])
|
||||
|
||||
def check_signal(self, prev, curr):
|
||||
"""
|
||||
包住形态信号判定(仅15分钟K线):
|
||||
- 前跌后涨包住 -> 做多
|
||||
- 前涨后跌包住 -> 做空
|
||||
"""
|
||||
p_open, p_close = float(prev['open']), float(prev['close'])
|
||||
c_open, c_close = float(curr['open']), float(curr['close'])
|
||||
|
||||
# 前跌后涨包住 -> 做多
|
||||
if is_bullish(curr) and is_bearish(prev) and c_open <= p_close and c_close >= p_open:
|
||||
return "long", "bear_bull_engulf"
|
||||
|
||||
# 前涨后跌包住 -> 做空
|
||||
if is_bearish(curr) and is_bullish(prev) and c_open >= p_close and c_close <= p_open:
|
||||
return "short", "bull_bear_engulf"
|
||||
|
||||
return None, None
|
||||
|
||||
def get_price(self):
|
||||
|
||||
for i in range(3):
|
||||
try:
|
||||
logger.info(f"获取最新数据:{i + 1}次。。。")
|
||||
self.mn_tab.get(url="https://www.weeaxs.site/zh-CN/futures/ETH-USDT")
|
||||
res = self.mn_tab.listen.wait(timeout=15) # 等待并获取一个数据包
|
||||
|
||||
datas = []
|
||||
if res:
|
||||
for data in res.response.body['data']["dataList"]:
|
||||
insert_data = {
|
||||
'id': int(data[4]),
|
||||
'open': float(data[3]),
|
||||
'high': float(data[1]),
|
||||
'low': float(data[2]),
|
||||
'close': float(data[0])
|
||||
}
|
||||
|
||||
datas.append(insert_data)
|
||||
|
||||
return datas
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def remove_tags_and_spaces(self, html):
|
||||
# 创建 BeautifulSoup 对象
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
# 获取去除标签后的文本
|
||||
text = soup.get_text()
|
||||
# 去除所有空格
|
||||
text = text.replace(" ", "")
|
||||
return text
|
||||
|
||||
def to_do_page(self):
|
||||
# self.page.get("https://www.weeaxs.site/zh-CN/futures/ETH-USDT")
|
||||
|
||||
self.mn_tab.ele('x://*[contains(text(), "市价")]', timeout=15).click()
|
||||
time.sleep(1)
|
||||
html_text = self.remove_tags_and_spaces(html=self.mn_tab.html)
|
||||
# 使用 re.search 方法查找匹配项
|
||||
# match = re.search(r'委托可用\s*([0-9]+(?:\.[0-9]+)?)SUSDT', html_text)
|
||||
match = re.search(r"可用\s*([0-9,]+(?:\.[0-9]+)?)", html_text)
|
||||
number = ""
|
||||
if match:
|
||||
# 提取匹配到的数值字符串
|
||||
number_str = match.group(1).replace(',', '')
|
||||
# 将提取的字符串转换为浮点数
|
||||
number = float(number_str)
|
||||
|
||||
if not number:
|
||||
return
|
||||
|
||||
self.mn_tab.ele('x://input[@placeholder="请输入数量"]').input(number / 100)
|
||||
time.sleep(1)
|
||||
|
||||
if self.direction == "long" and not self.start:
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},开多")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},开多")
|
||||
self.mn_tab.ele('x://*[contains(text(), "买入开多")]').click()
|
||||
self.start = 1
|
||||
elif self.direction == "short" and not self.start:
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},开空")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},开空")
|
||||
self.mn_tab.ele('x://*[contains(text(), "卖出开空")]').click()
|
||||
self.start = -1
|
||||
elif self.direction == "long" and self.start == -1:
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},反手平空做多")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},反手平空做多")
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').scroll.to_see(center=True)
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').click()
|
||||
time.sleep(3)
|
||||
self.mn_tab.ele('x://*[contains(text(), "买入开多")]').click()
|
||||
self.start = 1
|
||||
elif self.direction == "short" and self.start == 1:
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},反手平多做空")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},反手平多做空")
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').scroll.to_see(center=True)
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').click()
|
||||
time.sleep(3)
|
||||
self.mn_tab.ele('x://*[contains(text(), "卖出开空")]').click()
|
||||
self.start = -1
|
||||
|
||||
def get_text(self, target_text):
|
||||
# 去除目标文本中的空白字符
|
||||
cleaned_target_text = re.sub(r'\s', '', target_text)
|
||||
|
||||
# 创建 BeautifulSoup 对象
|
||||
soup = BeautifulSoup(self.mn_tab.html, 'html.parser')
|
||||
|
||||
# 遍历所有标签的文本内容
|
||||
for tag in soup.find_all():
|
||||
tag_text = tag.get_text()
|
||||
# 去除标签文本中的空白字符
|
||||
cleaned_tag_text = re.sub(r'\s', '', tag_text)
|
||||
if cleaned_target_text in cleaned_tag_text:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_now_time(self):
|
||||
# 获取当前时间戳
|
||||
current_timestamp = time.time()
|
||||
# 将当前时间戳转换为 datetime 对象
|
||||
current_datetime = datetime.datetime.fromtimestamp(current_timestamp)
|
||||
|
||||
# 计算距离当前时间最近的整点或 30 分时刻
|
||||
if current_datetime.minute < 30:
|
||||
target_datetime = current_datetime.replace(minute=0, second=0, microsecond=0)
|
||||
else:
|
||||
target_datetime = current_datetime.replace(minute=30, second=0, microsecond=0)
|
||||
|
||||
# 将目标 datetime 对象转换为时间戳
|
||||
target_timestamp = target_datetime.timestamp()
|
||||
|
||||
return int(target_timestamp) * 1000
|
||||
|
||||
def close_extra_tabs_in_browser(self):
|
||||
|
||||
try:
|
||||
for _, i in enumerate(self.page.get_tabs()):
|
||||
if _ == 0:
|
||||
continue
|
||||
|
||||
i.close()
|
||||
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def action(self):
|
||||
# 获取比特端口
|
||||
if self.openBrowser():
|
||||
logger.info("获取打开比特成功,成功获取端口!!!")
|
||||
else:
|
||||
logger.error("打开比特失败!!!")
|
||||
return
|
||||
|
||||
# 接管浏览器
|
||||
if self.take_over_browser():
|
||||
logger.info("接管比特浏览器成功!!!")
|
||||
else:
|
||||
logger.error("接管浏览器失败!!!")
|
||||
return
|
||||
|
||||
if self.close_extra_tabs_in_browser():
|
||||
logger.info('关闭多余标签页成功!!!')
|
||||
else:
|
||||
logger.info('关闭多余标签页失败!!!')
|
||||
|
||||
self.mn_tab = self.page.new_tab()
|
||||
self.mn_tab.listen.start("public/quote/v1/getKlineV2")
|
||||
logger.success("浏览器开启抓包模式。。。")
|
||||
|
||||
self.mn_tab.get(url="https://www.weeaxs.site/zh-CN/futures/ETH-USDT") # 打开网页
|
||||
|
||||
self.pbar = tqdm(total=30, desc="等待时间中", ncols=80) # desc:进度条说明,ncols:长度
|
||||
|
||||
while True:
|
||||
# 获取当前时间
|
||||
current_time = time.localtime()
|
||||
current_minute = current_time.tm_min
|
||||
|
||||
if current_minute < 30:
|
||||
self.pbar.n = current_minute
|
||||
self.pbar.refresh()
|
||||
else:
|
||||
self.pbar.n = current_minute - 30
|
||||
self.pbar.refresh()
|
||||
|
||||
# if current_minute not in range(60): # 判断是否是 新的30分钟了
|
||||
if current_minute not in [0, 1, 2, 3, 4, 5, 30, 31, 32, 33, 34, 58]: # 判断是否是 新的30分钟了
|
||||
time.sleep(10)
|
||||
continue
|
||||
|
||||
new_price_datas = self.get_price()
|
||||
if new_price_datas:
|
||||
logger.success("获取最新交易价格成功!!!")
|
||||
else:
|
||||
logger.info("获取最新价格有问题!!!")
|
||||
continue
|
||||
|
||||
new_price_datas1 = sorted(new_price_datas, key=lambda x: x["id"])
|
||||
self.kline_1, self.kline_2, self.kline_3 = new_price_datas1[-3:]
|
||||
|
||||
# 判断抓取的数据是否正确
|
||||
if self.get_now_time() != self.kline_3["id"]:
|
||||
continue
|
||||
|
||||
time.sleep(15)
|
||||
|
||||
if self.get_text(target_text="ETH/USDT多"):
|
||||
self.start = 1
|
||||
elif self.get_text(target_text="ETH/USDT空"):
|
||||
self.start = -1
|
||||
else:
|
||||
self.start = 0
|
||||
|
||||
if self.start == 1:
|
||||
if is_bearish(self.kline_1) and is_bearish(self.kline_2):
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},平多")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},平多")
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').scroll.to_see(center=True)
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').click()
|
||||
self.start = 0
|
||||
elif self.start == -1:
|
||||
if is_bullish(self.kline_1) and is_bullish(self.kline_2):
|
||||
logger.success(f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},平空")
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},第一根信号:{self.kline_1},{self.kline_2},平空")
|
||||
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').scroll.to_see(center=True)
|
||||
self.mn_tab.ele('x://*[contains(text(), "闪电平仓")]').click()
|
||||
self.start = 0
|
||||
|
||||
self.direction, signal_key = self.check_signal(prev=self.kline_1, curr=self.kline_2) # 判断信号
|
||||
|
||||
if self.direction:
|
||||
try:
|
||||
self.to_do_page()
|
||||
except Exception as e:
|
||||
self.send_dingtalk_message(
|
||||
message_content=f"{datetime.datetime.now()},{e}")
|
||||
|
||||
self.pbar.reset() # 重置进度条
|
||||
self.send_dingtalk_message(
|
||||
message_content=
|
||||
f"{datetime.datetime.now()},"
|
||||
f"目前有持仓:{"无" if self.start == 0 else ("多" if self.start == 1 else "空")},"
|
||||
f"当前信号:{"无" if not self.direction else ("多" if self.direction == "long" else "空")}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
WeexTransaction(
|
||||
tge_id=146473,
|
||||
).action()
|
||||
|
||||
# //*[contains(text(), '特定文本')]
|
||||
@@ -1,15 +0,0 @@
|
||||
import requests
|
||||
|
||||
url = "http://8.137.99.82:9005/api/send_click?token=fegergauiernguie&phone=8613661496481"
|
||||
|
||||
res = requests.post(
|
||||
url=url,
|
||||
json={
|
||||
"phone": "8613661496481",
|
||||
"bot_name": "ergggreef",
|
||||
"datas": [
|
||||
{"send_message": ["grgegg"], "click_button": [""], },
|
||||
]
|
||||
|
||||
}
|
||||
)
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,277 +0,0 @@
|
||||
开仓时间,平仓时间,数量,开仓价,平仓价,盈亏,类型,开仓订单ID,平仓订单ID,时间_x
|
||||
2025-08-28 09:35:18+08:00,2025-08-28 09:44:18+08:00,664.0,4514.69,4507.43,4820.639999999541,平空,e1d3a2da-219e-436f-ac9e-a7ea93de15bd,abfa0508-9399-4f1b-90c7-7716c3e7f511,2025-08-28 01:44:00
|
||||
2025-08-28 10:06:42+08:00,2025-08-28 10:10:07+08:00,1106.0,4519.88,4515.64,-4689.439999999759,平多,594bd10c-2120-4692-a8eb-9f5325e26ef0,bd643cab-14d4-414d-912e-e9257ddb96de,2025-08-28 02:10:00
|
||||
2025-08-28 10:45:07+08:00,2025-08-28 10:51:41+08:00,1108.0,4512.13,4516.84,-5218.68000000004,平空,fc0c73ab-9fff-41b5-98c1-ef6f7d300256,206688da-848a-4822-9571-c05302ab7a29,2025-08-28 02:51:00
|
||||
2025-08-28 11:07:30+08:00,2025-08-28 11:13:19+08:00,200.0,4525.0,4529.25,850.0,平多,6197fb5c-6202-422f-9012-5395c5393fb3,59d8859f-2b64-4e8b-aefe-57998b198841,2025-08-28 03:13:00
|
||||
2025-08-28 11:07:30+08:00,2025-08-28 11:13:19+08:00,100.0,4525.0,4529.25,425.0,平多,794c0925-6e48-4b9a-a162-d31d7a3c42cc,59d8859f-2b64-4e8b-aefe-57998b198841,2025-08-28 03:13:00
|
||||
2025-08-28 11:07:31+08:00,2025-08-28 11:13:19+08:00,483.0,4525.0,4529.25,2052.75,平多,65f7d764-ad05-40c7-bce1-6b227ad5f5c0,59d8859f-2b64-4e8b-aefe-57998b198841,2025-08-28 03:13:00
|
||||
2025-08-28 11:07:31+08:00,2025-08-28 11:13:19+08:00,100.0,4525.0,4529.25,425.0,平多,35e1c160-4129-425a-ae46-b8fb45d53ec8,59d8859f-2b64-4e8b-aefe-57998b198841,2025-08-28 03:13:00
|
||||
2025-08-28 11:28:58+08:00,2025-08-28 11:34:21+08:00,400.0,4533.74,4537.34,1440.0000000001455,平多,a983b8f5-70d1-441b-87df-397c6c1ce843,3e161e4e-f367-421a-bc0d-8095f7b79675,2025-08-28 03:34:00
|
||||
2025-08-28 11:28:58+08:00,2025-08-28 11:34:21+08:00,482.0,4533.74,4537.34,1735.2000000001754,平多,a983b8f5-70d1-441b-87df-397c6c1ce843,45ddb145-d637-4985-9c4b-3dee2fd189b5,2025-08-28 03:34:00
|
||||
2025-08-28 12:22:53+08:00,2025-08-28 12:23:33+08:00,478.0,4550.8,4545.04,-2753.2800000001043,平多,37d97b24-c1fd-4a2d-ba0e-75b818062686,fcfe39c7-0bcf-47f6-9b48-a1dd70bd5444,2025-08-28 04:23:00
|
||||
2025-08-28 12:22:53+08:00,2025-08-28 12:23:33+08:00,400.0,4550.8,4545.04,-2304.0000000000873,平多,37d97b24-c1fd-4a2d-ba0e-75b818062686,867b1b2c-0fe5-466b-a89f-ec8dce25bcda,2025-08-28 04:23:00
|
||||
2025-08-28 12:26:39+08:00,2025-08-28 12:34:19+08:00,879.0,4549.6,4555.44,5133.3599999993285,平多,0a3a08a1-61c7-4997-b13c-4da05b2c5dbc,ad149e7f-7088-4dd9-9698-9b7bd615478e,2025-08-28 04:34:00
|
||||
2025-08-28 12:46:19+08:00,2025-08-28 12:48:10+08:00,600.0,4561.8,4567.53,-3437.999999999738,平空,18929fa1-2cc7-4e7f-8e76-c3937f16f877,8283a8b1-17f1-4818-843d-33877996af35,2025-08-28 04:48:00
|
||||
2025-08-28 12:46:19+08:00,2025-08-28 12:48:10+08:00,276.0,4561.8,4567.53,-1581.4799999998795,平空,18929fa1-2cc7-4e7f-8e76-c3937f16f877,c0a1d021-0708-476e-a766-722bc73ab740,2025-08-28 04:48:00
|
||||
2025-08-28 12:53:40+08:00,2025-08-28 12:57:03+08:00,873.0,4577.05,4569.51,6582.419999999968,平空,16f8970d-ef44-4026-ba19-9c9ff6dc70e1,e1f9de7d-115e-4289-8acd-bbba16e04ff0,2025-08-28 04:57:00
|
||||
2025-08-28 12:58:03+08:00,2025-08-28 13:01:30+08:00,475.0,4567.45,4572.58,-2436.750000000052,平空,8dbfcfc8-466c-4111-acdf-316a2a8c7428,3e0a22b4-7707-4332-b80d-87d3b07cf3cc,2025-08-28 05:01:00
|
||||
2025-08-28 12:58:03+08:00,2025-08-28 13:01:30+08:00,400.0,4567.45,4572.58,-2052.0000000000437,平空,8dbfcfc8-466c-4111-acdf-316a2a8c7428,29775823-c3fc-4dbc-8afa-f9587f803f2a,2025-08-28 05:01:00
|
||||
2025-08-28 13:32:49+08:00,2025-08-28 13:41:18+08:00,721.0,4564.18,4565.88,1225.6999999998689,平多,6c0a64d8-b890-42bc-ae09-152919931d57,5bba77bd-b49f-4c6d-9a12-fe062c687cae,2025-08-28 05:41:00
|
||||
2025-08-28 13:32:49+08:00,2025-08-28 13:41:18+08:00,155.0,4564.18,4565.88,263.4999999999718,平多,c0e17c5e-6a39-465c-a3b3-347f1af3d772,5bba77bd-b49f-4c6d-9a12-fe062c687cae,2025-08-28 05:41:00
|
||||
2025-08-28 13:52:44+08:00,2025-08-28 13:57:25+08:00,400.0,4555.2,4559.95,-1900.0,平空,8f641745-890a-4b9c-8d83-10163234429d,dd5f8c6a-74da-4984-aa24-eebfbceb6d22,2025-08-28 05:57:00
|
||||
2025-08-28 13:52:44+08:00,2025-08-28 13:57:25+08:00,478.0,4555.2,4559.95,-2270.5,平空,66c5bcfb-02ef-4a8c-a314-bd9d672decb5,dd5f8c6a-74da-4984-aa24-eebfbceb6d22,2025-08-28 05:57:00
|
||||
2025-08-28 14:40:01+08:00,2025-08-28 14:41:47+08:00,874.0,4572.59,4578.98,-5584.859999999491,平空,1b1efa90-7dd5-4b5f-a170-eb87df7fb7db,26b0c5e5-f4a4-4f47-9fdc-12c6ecd05704,2025-08-28 06:41:00
|
||||
2025-08-28 15:05:18+08:00,2025-08-28 15:07:11+08:00,60.0,4582.63,4577.98,-279.00000000003274,平多,61f7b24d-18b1-44ee-bc9e-60666e4b2346,e53afe94-f3fa-42c1-9e49-619cd77261ab,2025-08-28 07:07:00
|
||||
2025-08-28 15:05:18+08:00,2025-08-28 15:07:11+08:00,600.0,4582.63,4578.03,-2760.0000000002183,平多,61f7b24d-18b1-44ee-bc9e-60666e4b2346,b9cf7c9c-d909-4dad-ae66-64aa0706566f,2025-08-28 07:07:00
|
||||
2025-08-28 15:05:18+08:00,2025-08-28 15:07:11+08:00,212.0,4582.63,4577.52,-1083.3199999999306,平多,61f7b24d-18b1-44ee-bc9e-60666e4b2346,05d46d57-d923-4d18-8c18-5a49927ac910,2025-08-28 07:07:00
|
||||
2025-08-28 15:22:41+08:00,2025-08-28 15:23:11+08:00,600.0,4572.88,4567.91,-2982.000000000153,平多,184c5b21-ed98-43c2-8003-8cc5f8c1bb0a,10d151ec-2891-44fe-8527-89643b3f3b46,2025-08-28 07:23:00
|
||||
2025-08-28 15:22:41+08:00,2025-08-28 15:23:11+08:00,274.0,4572.88,4567.53,-1465.9000000000997,平多,184c5b21-ed98-43c2-8003-8cc5f8c1bb0a,2a5ef255-f800-4e98-a10c-ca3ed33bb372,2025-08-28 07:23:00
|
||||
2025-08-28 15:57:55+08:00,2025-08-28 16:02:09+08:00,547.0,4589.48,4607.47,9840.530000000377,平多,6fdd3eb9-13a9-4e2a-90b6-78e7d71f3fbc,81bc2d5e-8099-4d48-b968-d695a7f1fee4,2025-08-28 08:02:00
|
||||
2025-08-28 15:57:55+08:00,2025-08-28 16:02:09+08:00,324.0,4589.48,4607.47,5828.760000000224,平多,aec18848-d3b5-4786-8c51-a4fdfc454ee2,81bc2d5e-8099-4d48-b968-d695a7f1fee4,2025-08-28 08:02:00
|
||||
2025-08-28 16:05:54+08:00,2025-08-28 16:09:19+08:00,866.0,4614.86,4624.89,8685.980000000567,平多,8e6d3e81-c813-4759-b608-9ed4e547b24d,25509f73-848d-4620-b662-f7f93d76becc,2025-08-28 08:09:00
|
||||
2025-08-28 16:09:50+08:00,2025-08-28 16:15:00+08:00,865.0,4621.56,4617.66,-3373.500000000472,平多,29739f4f-b289-4a1e-a5e7-f6925dd72b4c,7e4c74ac-902f-47c5-9e0a-b2ef07f31e8e,2025-08-28 08:15:00
|
||||
2025-08-28 16:23:59+08:00,2025-08-28 16:28:31+08:00,867.0,4609.17,4602.01,6207.719999999874,平空,6e8dc270-512a-4826-84c9-cfdf82c6cd59,0df8cd52-9db6-4532-99e1-923fcd9c134d,2025-08-28 08:28:00
|
||||
2025-08-28 16:54:47+08:00,2025-08-28 16:58:41+08:00,870.0,4595.28,4599.69,-3836.6999999998734,平空,35598fc3-567f-4ee2-a9fe-db14aab8228b,9777e233-baa8-4238-8f48-ef3e303ff883,2025-08-28 08:58:00
|
||||
2025-08-28 17:06:53+08:00,2025-08-28 17:15:11+08:00,870.0,4595.19,4592.66,2201.0999999997784,平空,3432672f-ced3-401f-981d-7686ddd396e6,bf9ffdef-56bd-4d35-9eef-f92a668d5829,2025-08-28 09:15:00
|
||||
2025-08-28 17:43:59+08:00,2025-08-28 17:49:12+08:00,258.0,4584.79,4581.95,732.7200000000375,平空,c0b26494-b8c4-434e-bc49-e3b317c1fa88,ffbc0ba2-aa7f-4377-bade-cf31e3697b19,2025-08-28 09:49:00
|
||||
2025-08-28 17:43:59+08:00,2025-08-28 17:49:12+08:00,114.0,4584.79,4581.95,323.7600000000166,平空,6fdb9fc7-e0f4-4033-896b-a13160b4fd18,ffbc0ba2-aa7f-4377-bade-cf31e3697b19,2025-08-28 09:49:00
|
||||
2025-08-28 17:43:59+08:00,2025-08-28 17:49:12+08:00,500.0,4584.79,4581.95,1420.0000000000728,平空,b064c485-653b-4f88-a0d1-86961284dc70,ffbc0ba2-aa7f-4377-bade-cf31e3697b19,2025-08-28 09:49:00
|
||||
2025-08-28 17:49:44+08:00,2025-08-28 17:50:54+08:00,500.0,4579.81,4584.85,-2519.999999999982,平空,1f97429c-1de1-467a-8985-8f9bb17000b5,cf389028-3cfb-4521-bf8c-59c3d9782e85,2025-08-28 09:50:00
|
||||
2025-08-28 17:49:44+08:00,2025-08-28 17:50:54+08:00,139.0,4579.81,4584.85,-700.559999999995,平空,989c44a3-a81f-4939-ab13-d9884d845a65,cf389028-3cfb-4521-bf8c-59c3d9782e85,2025-08-28 09:50:00
|
||||
2025-08-28 17:49:44+08:00,2025-08-28 17:50:54+08:00,234.0,4579.58,4584.85,-1233.1800000001022,平空,0b83f7ef-0985-4168-a211-d009f6c6ff1a,cf389028-3cfb-4521-bf8c-59c3d9782e85,2025-08-28 09:50:00
|
||||
2025-08-28 18:36:51+08:00,2025-08-28 18:43:09+08:00,533.0,4609.97,4604.0,-3182.0100000001357,平多,b8391ca4-74b4-41bc-85cd-b4c82cf6e8f3,2926d3da-8a19-4fae-938f-e2556e994376,2025-08-28 10:43:00
|
||||
2025-08-28 18:36:51+08:00,2025-08-28 18:43:09+08:00,84.0,4610.0,4604.0,-504.0,平多,e441f55d-693a-4301-b6c9-10d54ab10357,2926d3da-8a19-4fae-938f-e2556e994376,2025-08-28 10:43:00
|
||||
2025-08-28 18:36:51+08:00,2025-08-28 18:43:09+08:00,33.0,4609.97,4604.0,-197.0100000000084,平多,9dbce008-1d12-422c-80f8-a2c928610074,2926d3da-8a19-4fae-938f-e2556e994376,2025-08-28 10:43:00
|
||||
2025-08-28 19:46:14+08:00,2025-08-28 19:54:40+08:00,654.0,4586.91,4591.36,-2910.299999999881,平空,005b10f4-329d-4d34-8263-b8f63f526ac8,ea07e34c-cd41-4c80-b13f-6b8308257b92,2025-08-28 11:54:00
|
||||
2025-08-28 20:30:04+08:00,2025-08-28 20:30:34+08:00,517.0,4587.25,4588.6,697.9500000001881,平多,26fe1b1b-f4f0-4c49-b6d0-f8445d4e996e,9011ad7e-ef83-4c62-ac90-3e538965ad3f,2025-08-28 12:30:00
|
||||
2025-08-28 20:30:05+08:00,2025-08-28 20:30:34+08:00,354.0,4587.44,4588.6,410.64000000027045,平多,32f1db9d-6a9b-413f-abba-3f698a83ec65,9011ad7e-ef83-4c62-ac90-3e538965ad3f,2025-08-28 12:30:00
|
||||
2025-08-28 20:31:22+08:00,2025-08-28 20:31:28+08:00,400.0,4583.96,4585.9,-775.9999999998399,平空,dd7d8a55-c4bf-4ff8-82b9-ec8591888b0d,ca1e611c-1ba6-4418-9f86-8e492a7726c5,2025-08-28 12:31:00
|
||||
2025-08-28 20:31:22+08:00,2025-08-28 20:31:29+08:00,472.0,4583.96,4585.9,-915.6799999998111,平空,dd7d8a55-c4bf-4ff8-82b9-ec8591888b0d,04792496-7da0-4a9b-95cf-293b41d8292a,2025-08-28 12:31:00
|
||||
2025-08-28 20:43:41+08:00,2025-08-28 20:46:26+08:00,92.0,4609.77,4603.6,-567.6400000000067,平多,da034c24-f333-4197-ae9d-7dfa8639be45,9d59670d-a478-4432-8ec0-e3a8c4ec4d8a,2025-08-28 12:46:00
|
||||
2025-08-28 20:43:41+08:00,2025-08-28 20:46:26+08:00,600.0,4609.77,4603.6,-3702.0000000000437,平多,da034c24-f333-4197-ae9d-7dfa8639be45,e83ff9d3-3078-4f5c-8e6a-fa5750c97b55,2025-08-28 12:46:00
|
||||
2025-08-28 20:43:41+08:00,2025-08-28 20:46:27+08:00,175.0,4609.77,4604.08,-995.7500000000891,平多,da034c24-f333-4197-ae9d-7dfa8639be45,433dc03b-8b85-4951-9f21-dfaa5d6a0de5,2025-08-28 12:46:00
|
||||
2025-08-29 09:45:36+08:00,2025-08-29 09:49:22+08:00,447.0,4466.85,4458.15,3888.9000000003252,平空,87d823b2-675d-4501-bb36-b40eef344b7c,28bb7706-f2ce-480e-b606-fc5073ce4153,2025-08-29 01:49:00
|
||||
2025-08-29 10:13:26+08:00,2025-08-29 10:18:05+08:00,2.0,4470.7,4473.08,4.760000000000218,平多,6d7fe294-0553-46ac-9179-e22446e727dc,b50fd6a4-865b-4cd4-8b83-015dde307912,2025-08-29 02:18:00
|
||||
2025-08-29 10:13:27+08:00,2025-08-29 10:18:05+08:00,445.0,4470.71,4473.08,1054.6499999999514,平多,3d441a61-f852-4d4d-88d9-a2c4494e3a5f,b50fd6a4-865b-4cd4-8b83-015dde307912,2025-08-29 02:18:00
|
||||
2025-08-29 10:21:04+08:00,2025-08-29 10:25:10+08:00,446.0,4479.68,4483.07,1511.9399999997404,平多,9ac71ec9-58a8-43af-83ca-cac772d44dce,7db1d0e0-cdda-45a4-af09-9ec2f6a830a5,2025-08-29 02:25:00
|
||||
2025-08-29 11:08:08+08:00,2025-08-29 11:10:24+08:00,446.0,4477.82,4481.35,1574.380000000292,平多,4c447405-283c-40f7-bc05-42ae2aeaaaeb,de3c12d5-ba88-4b3d-8e8a-2158228465cb,2025-08-29 03:10:00
|
||||
2025-08-29 11:25:26+08:00,2025-08-29 11:31:14+08:00,446.0,4478.16,4482.39,1886.580000000211,平多,877eb021-8a47-4d55-8efd-789fde87699c,33addd17-0151-418d-b11e-d68a26c179e6,2025-08-29 03:31:00
|
||||
2025-08-29 11:35:59+08:00,2025-08-29 11:43:49+08:00,446.0,4484.05,4494.74,4767.7399999998215,平多,913408fa-2541-48c8-a8a0-d352b8b74a93,a7fd376e-dd1c-4350-8c58-1913d130902c,2025-08-29 03:43:00
|
||||
2025-08-29 11:55:41+08:00,2025-08-29 12:00:52+08:00,444.0,4498.11,4489.03,4031.5199999999677,平空,6450e48b-f901-4907-b065-18e073f86534,7cfe2e33-9b17-4785-9e45-dd5654c483c2,2025-08-29 04:00:00
|
||||
2025-08-29 12:54:59+08:00,2025-08-29 12:58:10+08:00,670.0,4477.15,4471.43,3832.3999999995613,平空,13bb04ff-529a-48f4-a062-bdbda01abe9a,01a6ebd4-6c63-4e58-9f52-4317a83a6c27,2025-08-29 04:58:00
|
||||
2025-08-29 13:05:31+08:00,2025-08-29 13:06:05+08:00,671.0,4466.57,4472.34,-3871.670000000293,平空,5c439227-9432-48c1-8c70-184e65586599,398e954e-0534-4f4c-bdda-5d14d858afae,2025-08-29 05:06:00
|
||||
2025-08-29 13:17:39+08:00,2025-08-29 13:19:35+08:00,448.0,4461.42,4468.3,-3082.240000000049,平空,d770f2c0-6aed-4bc2-bee2-f98209775454,14ce65ce-8af2-4c07-8441-bdeea724197e,2025-08-29 05:19:00
|
||||
2025-08-29 13:59:25+08:00,2025-08-29 14:01:39+08:00,46.0,4476.01,4484.33,382.7199999999866,平多,1f80cf00-8910-42af-a7ac-dd2199579d04,96e160f6-0a35-4ac4-9f3e-295065a56092,2025-08-29 06:01:00
|
||||
2025-08-29 13:59:25+08:00,2025-08-29 14:01:39+08:00,400.0,4476.01,4484.33,3327.9999999998836,平多,1f80cf00-8910-42af-a7ac-dd2199579d04,db2ea541-8ece-4876-baa0-f803629d4675,2025-08-29 06:01:00
|
||||
2025-08-29 14:15:38+08:00,2025-08-29 14:18:12+08:00,448.0,4457.41,4451.26,2755.199999999837,平空,2616d754-2ae8-43c0-a382-46901e0dba21,913e0886-23e4-467d-932d-79e960df2947,2025-08-29 06:18:00
|
||||
2025-08-29 14:37:31+08:00,2025-08-29 14:39:31+08:00,449.0,4451.44,4451.34,-44.89999999975498,平多,9f225f1e-cdc5-42cc-8c22-6d39f68da6d6,db5c616d-faf7-4183-8e46-b95f09dbad99,2025-08-29 06:39:00
|
||||
2025-08-29 14:41:21+08:00,2025-08-29 14:44:17+08:00,449.0,4451.53,4455.7,1872.3300000000327,平多,5ba24885-5747-405a-8fa9-6a9749dbe6b4,f7e02df2-857b-4e3a-a6aa-c0d7362cb5bb,2025-08-29 06:44:00
|
||||
2025-08-29 15:01:14+08:00,2025-08-29 15:02:16+08:00,449.0,4448.83,4428.34,9200.009999999902,平空,a605bd22-e12e-44a4-9e1c-87c145bc5aa4,365ff72f-891a-4314-a005-d320d2074d5f,2025-08-29 07:02:00
|
||||
2025-08-29 15:12:00+08:00,2025-08-29 15:15:04+08:00,453.0,4412.37,4393.89,8371.439999999802,平空,03169227-80cc-4c41-89e5-7cbc59174c90,7939f969-7566-453d-a718-dfa6b96ee987,2025-08-29 07:15:00
|
||||
2025-08-29 15:44:10+08:00,2025-08-29 15:51:01+08:00,683.0,4387.22,4391.19,-2711.5099999995527,平空,e2151c19-3f56-4239-8368-77af88a0c2c1,f90b5024-cd5b-44d5-a140-4d6253d49d6a,2025-08-29 07:51:00
|
||||
2025-08-29 15:57:12+08:00,2025-08-29 16:00:46+08:00,400.0,4383.78,4387.09,-1324.00000000016,平空,f2affaff-2beb-481f-b346-c9453422f536,70cc7f5d-b472-40a1-b00b-7e4b032722fc,2025-08-29 08:00:00
|
||||
2025-08-29 15:57:12+08:00,2025-08-29 16:00:46+08:00,284.0,4383.78,4387.09,-940.0400000001137,平空,f2affaff-2beb-481f-b346-c9453422f536,676c76cd-7afa-40db-a0a7-89004bb9387b,2025-08-29 08:00:00
|
||||
2025-08-29 16:08:15+08:00,2025-08-29 16:11:24+08:00,383.0,4386.41,4376.76,3695.9499999998607,平空,da4365c6-5bed-4bff-824c-580b3be527bc,0fdf84b4-d9bd-419e-8fd0-037e8631eefd,2025-08-29 08:11:00
|
||||
2025-08-29 16:08:15+08:00,2025-08-29 16:11:24+08:00,300.0,4386.5,4376.76,2921.9999999999345,平空,940d2d22-8103-4bdd-8eaa-ade1277111da,0fdf84b4-d9bd-419e-8fd0-037e8631eefd,2025-08-29 08:11:00
|
||||
2025-08-29 16:17:21+08:00,2025-08-29 16:17:29+08:00,187.0,4364.59,4365.41,-153.33999999994558,平空,89b93201-dfd2-408c-a5de-b6ecd72424cd,db922d70-7a90-4cf4-9a17-a598fe213f8b,2025-08-29 08:17:00
|
||||
2025-08-29 16:17:21+08:00,2025-08-29 16:17:29+08:00,400.0,4364.59,4365.41,-327.9999999998836,平空,89b93201-dfd2-408c-a5de-b6ecd72424cd,574a0e60-b26e-46bf-a696-cae5868b7af2,2025-08-29 08:17:00
|
||||
2025-08-29 16:17:21+08:00,2025-08-29 16:17:29+08:00,100.0,4365.0,4365.41,-40.99999999998545,平空,e6be51d9-e746-45dd-8522-877cf7eafd33,574a0e60-b26e-46bf-a696-cae5868b7af2,2025-08-29 08:17:00
|
||||
2025-08-29 16:20:18+08:00,2025-08-29 16:20:36+08:00,688.0,4359.92,4362.29,-1630.559999999925,平空,d302895c-7318-4851-838d-396e7100fc09,719a6eff-a759-4947-8f0a-45f2ac94dfc4,2025-08-29 08:20:00
|
||||
2025-08-29 16:29:12+08:00,2025-08-29 16:33:21+08:00,527.0,4355.55,4362.63,3731.1599999999617,平多,f587dcce-e557-4b34-a55f-bf449234faca,e833feb6-8ba7-4fa5-adea-678f690e16b3,2025-08-29 08:33:00
|
||||
2025-08-29 16:29:12+08:00,2025-08-29 16:33:21+08:00,161.0,4355.55,4362.63,1139.8799999999883,平多,22c48502-01b1-4efd-9809-3bc8487b0a9b,e833feb6-8ba7-4fa5-adea-678f690e16b3,2025-08-29 08:33:00
|
||||
2025-08-29 16:37:39+08:00,2025-08-29 16:40:08+08:00,688.0,4356.21,4352.88,2291.03999999995,平空,b209c4ae-8c33-4ba5-ad48-7559d1eafc6a,f7b8931f-1568-4387-a132-8ac6f7f34891,2025-08-29 08:40:00
|
||||
2025-08-29 16:40:40+08:00,2025-08-29 16:41:12+08:00,140.0,4348.53,4353.19,-652.3999999999796,平空,9f88809c-8f0c-4dd4-b83c-552c6b78da3a,b2c3aa90-dcc1-469d-bf31-6c74b8e4099b,2025-08-29 08:41:00
|
||||
2025-08-29 16:40:40+08:00,2025-08-29 16:41:12+08:00,549.0,4348.53,4353.19,-2558.33999999992,平空,3efb68fb-7389-44fd-b48a-d749af57e70f,b2c3aa90-dcc1-469d-bf31-6c74b8e4099b,2025-08-29 08:41:00
|
||||
2025-08-29 18:24:56+08:00,2025-08-29 18:28:40+08:00,500.0,4354.11,4357.4,1644.9999999999818,平多,5b3555c7-8c22-45ad-a4b5-ef6b586335d5,88db631a-7f3b-4b85-9887-94f906259583,2025-08-29 10:28:00
|
||||
2025-08-29 18:24:56+08:00,2025-08-29 18:28:40+08:00,189.0,4354.11,4357.4,621.8099999999931,平多,f6c1a4f1-6aa0-4645-b991-503a320913be,6132d1a3-6d55-4517-b5ba-115198948b85,2025-08-29 10:28:00
|
||||
2025-08-29 18:30:45+08:00,2025-08-29 18:49:35+08:00,189.0,4351.51,4343.72,1472.3099999999931,平空,b67b7a3a-cf62-4731-bc9c-1d333f42e639,2d4f2adb-d806-4f37-8ff1-a46eddccc6b1,2025-08-29 10:49:00
|
||||
2025-08-29 18:30:45+08:00,2025-08-29 18:49:35+08:00,500.0,4351.51,4343.72,3894.999999999982,平空,0acabab2-2ab4-4921-82a0-df806d4eb40d,2d4f2adb-d806-4f37-8ff1-a46eddccc6b1,2025-08-29 10:49:00
|
||||
2025-08-29 18:58:56+08:00,2025-08-29 19:02:25+08:00,690.0,4345.89,4340.99,-3381.0000000003765,平多,b525febe-c7eb-4db9-a4f8-4a09406c9f7d,51ddc961-238a-4c9d-b985-e6ad6ef2aac7,2025-08-29 11:02:00
|
||||
2025-08-29 19:14:13+08:00,2025-08-29 19:18:33+08:00,689.0,4351.49,4346.64,-3341.649999999624,平多,da8dccbd-f79c-476b-9471-d28b719da66f,cfbfe97f-e085-41db-8679-58574bd165fd,2025-08-29 11:18:00
|
||||
2025-08-29 19:49:41+08:00,2025-08-29 19:51:55+08:00,690.0,4345.68,4340.66,-3463.800000000301,平多,98c03fb1-dca8-43ef-9ca7-0fe6d842946b,e0c7b8be-c842-412e-bb4f-e43b51fa53bb,2025-08-29 11:51:00
|
||||
2025-08-29 20:22:08+08:00,2025-08-29 20:30:35+08:00,687.0,4365.85,4411.23,31176.059999999452,平多,7ab49fe4-9bd7-4368-bb45-ffe9970e4999,c3d6e447-288e-48d1-8977-ccceda6a2e41,2025-08-29 12:30:00
|
||||
2025-08-29 20:31:05+08:00,2025-08-29 20:31:21+08:00,16.0,4439.0,4448.35,149.60000000000582,平多,76a58764-fd12-4fb4-8ee9-407aa2213a12,e3f58dbc-6a93-4c16-9dc3-7e351493f00f,2025-08-29 12:31:00
|
||||
2025-08-29 20:31:05+08:00,2025-08-29 20:31:21+08:00,659.0,4439.0,4448.35,6161.65000000024,平多,cdd4a741-4195-4fac-9896-35ee5c09a7b9,e3f58dbc-6a93-4c16-9dc3-7e351493f00f,2025-08-29 12:31:00
|
||||
2025-08-29 20:31:46+08:00,2025-08-29 20:33:05+08:00,180.0,4409.9,4406.3,-647.9999999999018,平多,a47f68a7-9565-4ea7-b3ab-770d376ffcea,46e071a8-871b-4373-92a5-8cd6cd46cc56,2025-08-29 12:33:00
|
||||
2025-08-29 20:31:46+08:00,2025-08-29 20:33:05+08:00,500.0,4409.9,4406.3,-1799.9999999997272,平多,a47f68a7-9565-4ea7-b3ab-770d376ffcea,c67209e2-0598-443d-85fa-752bdae6059b,2025-08-29 12:33:00
|
||||
2025-08-29 20:43:25+08:00,2025-08-29 20:44:07+08:00,684.0,4382.62,4386.13,-2400.8400000001493,平空,af3305f0-ec39-48fe-bf71-ac9acb281b25,7556c209-5046-4e8b-8682-99dc36803a62,2025-08-29 12:44:00
|
||||
2025-08-29 20:45:44+08:00,2025-08-29 20:49:11+08:00,102.0,4389.13,4407.06,1828.8600000000297,平多,a19435e4-fbf9-4217-bc5d-d7fc07d9818f,cc0577e9-e4b6-45fd-bf5a-47ccf6aec320,2025-08-29 12:49:00
|
||||
2025-08-29 20:45:44+08:00,2025-08-29 20:49:11+08:00,581.0,4389.13,4407.06,10417.33000000017,平多,a19435e4-fbf9-4217-bc5d-d7fc07d9818f,4fa96e6c-6692-426b-beca-c138c8505a82,2025-08-29 12:49:00
|
||||
2025-08-29 20:49:46+08:00,2025-08-29 20:53:14+08:00,500.0,4406.49,4406.02,234.99999999967258,平空,d11f3a55-a1d8-41ff-a979-778c232459c9,b2de5ff9-9281-4c2e-86bd-a071a5e1e864,2025-08-29 12:53:00
|
||||
2025-08-29 20:49:46+08:00,2025-08-29 20:53:14+08:00,180.0,4406.49,4406.02,84.59999999988213,平空,d11f3a55-a1d8-41ff-a979-778c232459c9,a4e3c4e5-4280-417c-8018-8386a63c318a,2025-08-29 12:53:00
|
||||
2025-08-29 21:00:24+08:00,2025-08-29 21:00:33+08:00,680.0,4408.35,4405.11,-2203.20000000047,平多,26e94954-9b31-48a9-9b2b-cb08da0f8e3e,14772435-8e33-4ee5-a87c-81342a7f8449,2025-08-29 13:00:00
|
||||
2025-08-29 21:01:47+08:00,2025-08-29 21:06:54+08:00,39.0,4406.15,4391.41,574.8599999999915,平空,8665f24a-3e13-4d3f-ad28-a702c79c73ed,7addab21-fcf3-4921-97ab-d77462067550,2025-08-29 13:06:00
|
||||
2025-08-29 21:01:47+08:00,2025-08-29 21:06:54+08:00,141.0,4406.15,4391.41,2078.339999999969,平空,0170663d-0cf1-43a4-8595-4511c59b3107,7addab21-fcf3-4921-97ab-d77462067550,2025-08-29 13:06:00
|
||||
2025-08-29 21:01:47+08:00,2025-08-29 21:06:54+08:00,500.0,4406.15,4391.41,7369.999999999891,平空,0170663d-0cf1-43a4-8595-4511c59b3107,3b754022-7c7c-4b14-8251-c2da4478df27,2025-08-29 13:06:00
|
||||
2025-08-29 21:10:41+08:00,2025-08-29 21:11:27+08:00,14.0,4400.55,4392.88,-107.38000000000102,平多,37e79409-f3ed-4e62-811c-6cc437396a41,f3a43d61-4d64-4d82-aada-31b38d441baa,2025-08-29 13:11:00
|
||||
2025-08-29 21:10:41+08:00,2025-08-29 21:11:27+08:00,67.0,4400.55,4392.79,-519.9200000000146,平多,37e79409-f3ed-4e62-811c-6cc437396a41,86bfa728-af41-4e41-9e87-d6ef88b1a063,2025-08-29 13:11:00
|
||||
2025-08-29 21:10:41+08:00,2025-08-29 21:11:27+08:00,600.0,4400.55,4392.88,-4602.000000000044,平多,37e79409-f3ed-4e62-811c-6cc437396a41,af01f0d0-8844-4267-8476-4000d0b2dd8b,2025-08-29 13:11:00
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,72.0,4386.05,4373.6,896.3999999999869,平空,fecb81e1-3b36-4ae6-91ad-b6a2616ee608,aee7c322-ba5d-4b89-8b93-1c50ce5be75b,2025-08-29 13:34:00
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,211.0,4386.05,4372.44,2871.710000000123,平空,fecb81e1-3b36-4ae6-91ad-b6a2616ee608,e20951ed-ff4e-4a06-8bb5-8112a72e38bf,2025-08-29 13:34:00
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,99.0,4386.05,4372.44,1347.3900000000576,平空,fecb81e1-3b36-4ae6-91ad-b6a2616ee608,16329965-69b4-42a4-8145-79d1a0d2e92b,2025-08-29 13:34:00
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,101.0,4386.29,4372.44,1398.8500000000367,平空,f1e4b54c-d392-4dc4-8f73-40a8733b85fe,16329965-69b4-42a4-8145-79d1a0d2e92b,2025-08-29 13:34:00
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,200.0,4386.29,4372.44,2770.0000000000728,平空,f1e4b54c-d392-4dc4-8f73-40a8733b85fe,1b66a876-041a-480e-a9d6-428a3e301ec4,2025-08-29 13:34:00
|
||||
2025-08-30 08:15:30+08:00,2025-08-30 08:25:24+08:00,400.0,4343.62,4355.47,4740.0000000001455,平多,fb636839-9fb2-4e2f-bdb0-14bd57ade58a,6b8d4b73-132c-4c86-92f9-2a5df18943fd,2025-08-30 00:25:00
|
||||
2025-08-30 08:15:30+08:00,2025-08-30 08:25:24+08:00,290.0,4343.62,4355.47,3436.5000000001055,平多,fb636839-9fb2-4e2f-bdb0-14bd57ade58a,a744bd39-3c81-4cb1-a153-60ed71d6748f,2025-08-30 00:25:00
|
||||
2025-08-30 08:32:12+08:00,2025-08-30 08:35:26+08:00,64.0,4342.48,4337.98,-288.0,平多,e6185c1c-debd-4a9c-928c-edd28b360578,401ccb35-f82c-4df2-9a29-f9cd57481767,2025-08-30 00:35:00
|
||||
2025-08-30 08:32:12+08:00,2025-08-30 08:35:26+08:00,563.0,4342.48,4335.64,-3850.91999999957,平多,e6185c1c-debd-4a9c-928c-edd28b360578,69d6c400-d534-47b6-b2a8-8da09b2986aa,2025-08-30 00:35:00
|
||||
2025-08-30 08:32:12+08:00,2025-08-30 08:35:26+08:00,63.0,4342.48,4335.73,-425.25,平多,e6185c1c-debd-4a9c-928c-edd28b360578,4aa4a001-f8b0-4aea-93c3-546c71b81ee0,2025-08-30 00:35:00
|
||||
2025-08-30 08:37:25+08:00,2025-08-30 08:41:59+08:00,691.0,4336.85,4332.05,3316.8000000001257,平空,5cc6dd95-ec96-4864-a085-95b875673f0f,07f9b455-a026-41df-95c6-f9cf37f23a90,2025-08-30 00:41:00
|
||||
2025-08-30 09:26:28+08:00,2025-08-30 09:29:48+08:00,293.0,4326.54,4324.64,556.6999999998934,平空,ce7db548-7de1-448e-92c9-25139a5f23a4,70062cb1-9074-49a9-8e7f-b5b7bae639d4,2025-08-30 01:29:00
|
||||
2025-08-30 09:26:28+08:00,2025-08-30 09:29:48+08:00,400.0,4326.54,4323.46,1231.999999999971,平空,ce7db548-7de1-448e-92c9-25139a5f23a4,6145a0a3-248c-4822-8e8c-5efb6daf7d24,2025-08-30 01:29:00
|
||||
2025-08-30 09:51:41+08:00,2025-08-30 09:55:09+08:00,177.0,4312.57,4289.19,4138.260000000019,平空,4b786a41-410d-4c93-b1c6-30b118f9e7f2,f78be76a-092e-4e0e-b8c3-26867d78c1de,2025-08-30 01:55:00
|
||||
2025-08-30 09:51:41+08:00,2025-08-30 09:55:09+08:00,518.0,4312.57,4289.19,12110.840000000057,平空,96fbc46a-1dd3-4fc6-9e14-4a032e9464bf,f78be76a-092e-4e0e-b8c3-26867d78c1de,2025-08-30 01:55:00
|
||||
2025-08-30 09:55:49+08:00,2025-08-30 09:55:50+08:00,700.0,4282.58,4282.19,-273.0000000002292,平多,201dbee0-8f0d-427d-95d5-ef84e08a8aab,37ff5d0c-e10e-480c-91ca-0506e61330b6,2025-08-30 01:55:00
|
||||
2025-08-30 09:58:19+08:00,2025-08-30 09:58:59+08:00,700.0,4281.59,4280.23,-952.0000000004075,平多,0e651112-db3b-402d-a268-1ea200cf8729,65b07548-3473-4570-be22-0172c49bdb94,2025-08-30 01:58:00
|
||||
2025-08-30 10:00:09+08:00,2025-08-30 10:00:52+08:00,703.0,4264.04,4268.35,-3029.9300000002813,平空,d9703fdd-ea02-410d-8303-d1d13b94748f,17fca37e-fc89-48a9-92ed-671ab0b9088e,2025-08-30 02:00:00
|
||||
2025-08-30 10:01:23+08:00,2025-08-30 10:02:02+08:00,110.0,4268.54,4271.97,-377.300000000032,平空,022ce461-b78f-4127-9157-8c34a5e9272e,89468587-89ce-49cc-a9e3-343e24cb8036,2025-08-30 02:02:00
|
||||
2025-08-30 10:01:23+08:00,2025-08-30 10:02:02+08:00,592.0,4268.54,4271.97,-2030.5600000001723,平空,9bdcfab5-9b8e-447c-b4ac-a82724e2774d,89468587-89ce-49cc-a9e3-343e24cb8036,2025-08-30 02:02:00
|
||||
2025-08-30 10:02:55+08:00,2025-08-30 10:04:26+08:00,400.0,4275.14,4280.56,-2168.000000000029,平空,1b938bf4-dc99-4d2f-9e0b-c2fa5092d19d,4e6c40d8-e7c7-45ac-800e-5f71611479f1,2025-08-30 02:04:00
|
||||
2025-08-30 10:02:55+08:00,2025-08-30 10:04:26+08:00,141.0,4275.14,4280.56,-764.2200000000103,平空,1b938bf4-dc99-4d2f-9e0b-c2fa5092d19d,864390a3-33dd-4a47-a3c2-471b8a0104f1,2025-08-30 02:04:00
|
||||
2025-08-30 10:02:55+08:00,2025-08-30 10:04:26+08:00,160.0,4275.14,4280.56,-867.2000000000116,平空,7eb3eaf4-30fa-495d-a74c-61d725625c42,864390a3-33dd-4a47-a3c2-471b8a0104f1,2025-08-30 02:04:00
|
||||
2025-08-30 10:11:35+08:00,2025-08-30 10:18:43+08:00,232.0,4296.88,4306.91,2326.959999999941,平多,b1a002d6-951f-4912-b8cf-19bfc98f61ba,743a1e0f-48f3-43fe-9b6a-47536fa12ce1,2025-08-30 02:18:00
|
||||
2025-08-30 10:11:35+08:00,2025-08-30 10:18:43+08:00,466.0,4296.91,4306.91,4660.0,平多,94664c85-6be6-42d8-984a-f3293ed9750f,743a1e0f-48f3-43fe-9b6a-47536fa12ce1,2025-08-30 02:18:00
|
||||
2025-08-30 10:21:54+08:00,2025-08-30 10:27:07+08:00,696.0,4307.47,4306.32,800.4000000003798,平空,641e7574-db24-4c78-8782-566b13de1e39,817cbe73-8f5f-4c27-893c-ddfe59e1890d,2025-08-30 02:27:00
|
||||
2025-08-30 10:28:32+08:00,2025-08-30 10:35:18+08:00,500.0,4305.75,4311.46,2855.000000000018,平多,dee31b34-3e7f-4b74-8774-ccd70f3cb124,d37eec35-4b90-42c7-82e7-7a56e909d268,2025-08-30 02:35:00
|
||||
2025-08-30 10:28:32+08:00,2025-08-30 10:35:18+08:00,196.0,4305.75,4311.25,1078.0,平多,dee31b34-3e7f-4b74-8774-ccd70f3cb124,9cbf242a-7e93-4a3c-b798-6e3f7a88025d,2025-08-30 02:35:00
|
||||
2025-08-30 10:48:32+08:00,2025-08-30 10:57:53+08:00,695.0,4313.05,4303.81,6421.799999999848,平空,0efcab97-a0b6-4cd6-ad7e-22911ea32fa2,57fa177a-12c5-4127-91c4-289668a191ae,2025-08-30 02:57:00
|
||||
2025-08-30 11:03:45+08:00,2025-08-30 11:10:25+08:00,296.0,4308.11,4322.99,4404.480000000032,平多,36e3b573-d4be-4d54-9af3-8190731448c9,1e9213a7-3443-4209-a26a-d3e0feeaf325,2025-08-30 03:10:00
|
||||
2025-08-30 11:03:45+08:00,2025-08-30 11:10:25+08:00,400.0,4308.11,4322.99,5952.000000000044,平多,36e3b573-d4be-4d54-9af3-8190731448c9,a344d092-7b22-4230-8ba9-0702dd353041,2025-08-30 03:10:00
|
||||
2025-08-30 11:11:04+08:00,2025-08-30 11:17:45+08:00,693.0,4323.24,4328.87,3901.5900000000756,平多,c2e861bb-6de2-4dc0-aed3-7a385c0f34b8,d786c18b-8199-4335-ad6b-6115f39e089f,2025-08-30 03:17:00
|
||||
2025-08-30 11:18:36+08:00,2025-08-30 11:23:02+08:00,400.0,4333.76,4358.0,9695.999999999913,平多,3c5242f4-5844-46a7-8b84-a6c73fed27c7,d71ce99c-8d8e-4f15-be2e-85d21e853298,2025-08-30 03:23:00
|
||||
2025-08-30 11:18:36+08:00,2025-08-30 11:23:02+08:00,134.0,4333.76,4358.0,3248.1599999999708,平多,3c5242f4-5844-46a7-8b84-a6c73fed27c7,fe3607bd-9257-42ce-a0b3-06d882cb74af,2025-08-30 03:23:00
|
||||
2025-08-30 11:18:36+08:00,2025-08-30 11:23:02+08:00,158.0,4333.76,4358.0,3829.9199999999655,平多,ce9f82f0-70a9-44b1-9175-4ee31dbc8fd6,fe3607bd-9257-42ce-a0b3-06d882cb74af,2025-08-30 03:23:00
|
||||
2025-08-30 11:52:34+08:00,2025-08-30 11:57:42+08:00,69.0,4352.62,4352.32,-20.70000000001255,平多,a7a38452-b2e4-46c1-a14c-d85c0a79b250,078499df-67ff-4bdd-acaf-4e4fcf4457c0,2025-08-30 03:57:00
|
||||
2025-08-30 11:52:34+08:00,2025-08-30 11:57:42+08:00,620.0,4352.62,4352.32,-186.00000000011278,平多,76c6328b-ae56-4313-9b73-3bc43f644ef4,078499df-67ff-4bdd-acaf-4e4fcf4457c0,2025-08-30 03:57:00
|
||||
2025-08-30 12:01:25+08:00,2025-08-30 12:07:56+08:00,500.0,4351.41,4357.44,-3014.9999999998727,平空,2228c2d3-6d7b-4835-8065-42ce6ad3a5af,3544a077-b511-45ef-b088-c380928048ee,2025-08-30 04:07:00
|
||||
2025-08-30 12:01:25+08:00,2025-08-30 12:07:56+08:00,121.0,4351.7,4357.44,-694.5399999999736,平空,e33d8985-328c-4a44-829e-b8ebf6127582,3544a077-b511-45ef-b088-c380928048ee,2025-08-30 04:07:00
|
||||
2025-08-30 12:01:25+08:00,2025-08-30 12:07:56+08:00,527.0,4351.41,4357.44,-3177.809999999866,平空,6f169ccd-3d5a-4e35-9653-7efd0a1580dc,3544a077-b511-45ef-b088-c380928048ee,2025-08-30 04:07:00
|
||||
2025-08-30 13:01:32+08:00,2025-08-30 13:07:24+08:00,577.0,4353.26,4358.47,-3006.170000000021,平空,27412351-b7db-41d2-941e-014723b46c1a,f65cc5f5-8a78-4a59-bb58-cf0af4df8448,2025-08-30 05:07:00
|
||||
2025-08-30 13:01:33+08:00,2025-08-30 13:07:24+08:00,298.0,4353.26,4358.47,-1552.5800000000108,平空,cab58243-ce8d-4149-b279-3417eac36fcd,f65cc5f5-8a78-4a59-bb58-cf0af4df8448,2025-08-30 05:07:00
|
||||
2025-08-30 13:01:33+08:00,2025-08-30 13:07:24+08:00,273.0,4353.26,4359.1,-1594.3200000000397,平空,cab58243-ce8d-4149-b279-3417eac36fcd,c28367f5-fcb9-4345-82f2-c56451e2e462,2025-08-30 05:07:00
|
||||
2025-08-30 13:20:13+08:00,2025-08-30 13:32:38+08:00,1143.0,4373.03,4377.0,-4537.710000000291,平空,0f73205b-a08a-4fad-9e77-f5d2fbbf1aee,6d5e0a1c-53e6-4fac-a76a-77cb3341a976,2025-08-30 05:32:00
|
||||
2025-08-30 13:33:46+08:00,2025-08-30 13:42:14+08:00,1140.0,4385.31,4391.97,7592.399999999834,平多,11f0898c-7cba-49da-91d9-1fc6e9b57ab2,e7744350-f030-4566-bdeb-8a76060f8066,2025-08-30 05:42:00
|
||||
2025-08-30 13:48:01+08:00,2025-08-30 13:56:36+08:00,1137.0,4395.74,4382.93,14564.96999999942,平空,d00ac585-7cde-477d-af47-a5889aa5ef70,e99ef7df-85e7-46b4-bc8a-4a6f2fc811fe,2025-08-30 05:56:00
|
||||
2025-08-30 13:58:04+08:00,2025-08-30 14:03:08+08:00,1141.0,4381.43,4385.01,-4084.779999999917,平空,76216a51-657b-4659-86cb-2295aa642dac,8a9ab27e-38f2-4ec3-bbaf-642afd20a6e5,2025-08-30 06:03:00
|
||||
2025-08-30 14:10:44+08:00,2025-08-30 14:15:13+08:00,601.0,4383.15,4377.63,3317.5199999997158,平空,587020d7-a3eb-4e44-b6c7-5c95b8c5451a,8ecfa693-c528-405b-8db4-0b02ccc7b9b3,2025-08-30 06:15:00
|
||||
2025-08-30 14:10:44+08:00,2025-08-30 14:15:13+08:00,500.0,4383.15,4377.63,2759.9999999997635,平空,587020d7-a3eb-4e44-b6c7-5c95b8c5451a,babe1585-607c-4004-9aa9-19e1f5374ac1,2025-08-30 06:15:00
|
||||
2025-08-30 14:10:44+08:00,2025-08-30 14:15:13+08:00,39.0,4383.15,4377.63,215.27999999998156,平空,587020d7-a3eb-4e44-b6c7-5c95b8c5451a,ab4e9d81-4133-42b0-9460-9a2efd36ec0b,2025-08-30 06:15:00
|
||||
2025-08-30 14:26:36+08:00,2025-08-30 14:33:14+08:00,1140.0,4384.3,4391.39,8082.600000000166,平多,f703a5cc-3428-4796-a288-bfa6442800cb,a4e60efd-9afc-48b1-8850-6054e1330780,2025-08-30 06:33:00
|
||||
2025-08-30 14:45:13+08:00,2025-08-30 14:52:41+08:00,1137.0,4395.22,4390.35,5537.189999999876,平空,b1f16517-7b15-43a4-a3e7-8f0b60f9f732,0445e9e3-c80c-4beb-a121-18b6094f52eb,2025-08-30 06:52:00
|
||||
2025-08-30 15:20:15+08:00,2025-08-30 15:40:40+08:00,52.0,4392.17,4398.92,-351.0,平空,504ad303-3016-470e-9deb-a717d25a761c,9a648f36-ef00-45a4-8e56-868ab74acce6,2025-08-30 07:40:00
|
||||
2025-08-30 15:20:15+08:00,2025-08-30 15:40:40+08:00,500.0,4392.17,4398.92,-3375.0,平空,59f674e3-aff1-4b79-b8b0-c650b299d27d,9a648f36-ef00-45a4-8e56-868ab74acce6,2025-08-30 07:40:00
|
||||
2025-08-30 15:20:15+08:00,2025-08-30 15:40:40+08:00,586.0,4392.17,4398.92,-3955.5,平空,77548f6a-59ae-4ac2-880c-ea1f5679f507,9a648f36-ef00-45a4-8e56-868ab74acce6,2025-08-30 07:40:00
|
||||
2025-08-30 15:46:04+08:00,2025-08-30 15:50:09+08:00,1134.0,4408.51,4402.78,-6497.820000000536,平多,76eb4bce-ff36-4d4c-b97a-cda6d9f7b544,63f7184c-7211-417f-b594-e39436d14940,2025-08-30 07:50:00
|
||||
2025-08-30 16:11:35+08:00,2025-08-30 16:32:13+08:00,400.0,4393.35,4389.12,1692.0000000001892,平空,be9ff2ce-0d9e-4184-8d43-c49b33c71c95,754db465-b458-43a3-b2b5-3fb4f9fc2aaf,2025-08-30 08:32:00
|
||||
2025-08-30 16:11:35+08:00,2025-08-30 16:32:13+08:00,500.0,4393.35,4389.12,2115.0000000002365,平空,cdf388db-68a2-47ea-ba94-431f02dadeac,754db465-b458-43a3-b2b5-3fb4f9fc2aaf,2025-08-30 08:32:00
|
||||
2025-08-30 16:11:35+08:00,2025-08-30 16:32:13+08:00,237.0,4393.35,4389.12,1002.5100000001121,平空,e6f8d379-c20f-4a9d-90a4-7a5904bff1f9,754db465-b458-43a3-b2b5-3fb4f9fc2aaf,2025-08-30 08:32:00
|
||||
2025-08-30 17:05:10+08:00,2025-08-30 17:11:26+08:00,133.0,4390.54,4387.01,469.48999999996613,平空,d4db2208-ba63-42c7-af51-9cec92bfe6f1,46858333-121c-4020-9567-bc44d63dec01,2025-08-30 09:11:00
|
||||
2025-08-30 17:05:10+08:00,2025-08-30 17:11:26+08:00,190.0,4390.54,4387.01,670.6999999999516,平空,d4db2208-ba63-42c7-af51-9cec92bfe6f1,4d19376a-cfb7-4da9-8859-982e073624d1,2025-08-30 09:11:00
|
||||
2025-08-30 17:05:10+08:00,2025-08-30 17:11:26+08:00,310.0,4390.54,4387.01,1094.299999999921,平空,00dda8f5-7156-4d25-9ea0-29e5fb2a4aca,4d19376a-cfb7-4da9-8859-982e073624d1,2025-08-30 09:11:00
|
||||
2025-08-30 17:05:10+08:00,2025-08-30 17:11:26+08:00,505.0,4390.54,4387.01,1782.6499999998714,平空,00dda8f5-7156-4d25-9ea0-29e5fb2a4aca,e28b9838-7c48-4c78-a369-8259844cc3f4,2025-08-30 09:11:00
|
||||
2025-08-30 17:18:39+08:00,2025-08-30 17:28:37+08:00,1139.0,4389.73,4385.08,-5296.349999999586,平多,2789c169-8a5d-47da-a6f9-6800e72ec26e,cd9481fb-33de-4e75-beb8-119ace92b5f6,2025-08-30 09:28:00
|
||||
2025-08-30 17:31:17+08:00,2025-08-30 17:37:08+08:00,1141.0,4379.77,4384.13,-4974.759999999626,平空,434fc13a-925d-4abd-8677-0cd2b1a412f0,1e63ca1f-7f77-4ed9-a885-159f5dac641e,2025-08-30 09:37:00
|
||||
2025-08-30 17:44:10+08:00,2025-08-30 17:48:40+08:00,1140.0,4384.13,4388.17,-4605.5999999999585,平空,288b4dc7-affd-4b36-897a-e02a024dbae0,70c0e310-219d-45d2-a91d-6b685af4a065,2025-08-30 09:48:00
|
||||
2025-08-30 17:50:34+08:00,2025-08-30 17:54:02+08:00,1138.0,4389.94,4393.0,3482.2800000004554,平多,b78acf6a-d3b3-44c2-b484-ad95dfedd167,9473c228-7657-44cd-9038-38aeb833c3ab,2025-08-30 09:54:00
|
||||
2025-08-30 18:05:09+08:00,2025-08-30 18:07:30+08:00,1138.0,4392.24,4394.52,2594.640000000745,平多,44cd4f02-5250-47d0-8e71-a66f856405ba,81abc1fa-1789-4f69-aaaa-2da1f3f34186,2025-08-30 10:07:00
|
||||
2025-08-31 10:28:55+08:00,2025-08-31 10:34:47+08:00,326.0,4450.3,4446.25,-1320.3000000000593,平多,0fe00795-d7d9-4586-b864-e445017c7f9d,716e265c-d39a-46b3-92e2-99f5d8a6b45f,2025-08-31 02:34:00
|
||||
2025-08-31 10:28:55+08:00,2025-08-31 10:34:47+08:00,572.0,4450.3,4446.25,-2316.600000000104,平多,0fe00795-d7d9-4586-b864-e445017c7f9d,e7b360d6-da94-4cb7-b8c7-0b10db53a03b,2025-08-31 02:34:00
|
||||
2025-08-31 11:08:10+08:00,2025-08-31 11:12:01+08:00,897.0,4454.6,4458.68,-3659.7599999999347,平空,c31d66e2-a558-4b86-a520-3a9eb9d55346,f737efdc-5818-4bc5-b3db-1da76e7caf71,2025-08-31 03:12:00
|
||||
2025-08-31 11:24:10+08:00,2025-08-31 11:27:33+08:00,508.0,4457.8,4453.59,-2138.6800000000185,平多,38413547-9d48-43fc-a2be-12d2a8e0cd9f,ce5c15c0-d938-4b48-ab08-807531847f2e,2025-08-31 03:27:00
|
||||
2025-08-31 11:24:10+08:00,2025-08-31 11:27:33+08:00,389.0,4457.8,4453.59,-1637.6900000000142,平多,816792ec-d409-4688-b7fd-2ee3e3f4491e,ce5c15c0-d938-4b48-ab08-807531847f2e,2025-08-31 03:27:00
|
||||
2025-08-31 11:49:43+08:00,2025-08-31 11:53:50+08:00,500.0,4462.28,4469.74,3730.000000000018,平多,b1e2c3bf-db41-4762-b152-7a6a469c8cdb,52c185f1-3f74-42b6-afe5-6032df9dcb11,2025-08-31 03:53:00
|
||||
2025-08-31 11:49:43+08:00,2025-08-31 11:53:50+08:00,396.0,4462.28,4469.74,2954.1600000000144,平多,b1e2c3bf-db41-4762-b152-7a6a469c8cdb,5fbbe739-8d3c-4aab-8333-7860bdca1d69,2025-08-31 03:53:00
|
||||
2025-08-31 11:54:26+08:00,2025-08-31 12:00:20+08:00,293.0,4475.79,4483.53,2267.819999999936,平多,614c6489-c5a5-451d-a2a0-699e38d5a695,5da7f47f-33e3-45b8-9dfe-8232807978fc,2025-08-31 04:00:00
|
||||
2025-08-31 11:54:26+08:00,2025-08-31 12:00:20+08:00,200.0,4475.79,4483.53,1547.9999999999563,平多,c443995b-f986-4a55-b6d9-59c43fa71fd2,5da7f47f-33e3-45b8-9dfe-8232807978fc,2025-08-31 04:00:00
|
||||
2025-08-31 11:54:26+08:00,2025-08-31 12:00:20+08:00,400.0,4475.79,4483.53,3095.9999999999127,平多,c443995b-f986-4a55-b6d9-59c43fa71fd2,7d96c3c9-b227-48f3-b9fe-9a5c08871cfe,2025-08-31 04:00:00
|
||||
2025-08-31 12:15:25+08:00,2025-08-31 12:25:03+08:00,894.0,4473.35,4467.05,5632.200000000163,平空,d5064049-73a7-4a01-8d14-3d0ba056d572,b4537133-4056-4afc-80c2-7656e28cff88,2025-08-31 04:25:00
|
||||
2025-08-31 12:30:21+08:00,2025-08-31 12:33:51+08:00,500.0,4461.85,4465.99,-2069.999999999709,平空,516d9754-0fe4-4f2e-9289-5827765f7c67,13f52cac-de10-4bba-93d5-8376ba2c5139,2025-08-31 04:33:00
|
||||
2025-08-31 12:30:21+08:00,2025-08-31 12:33:51+08:00,100.0,4461.85,4465.88,-402.99999999997453,平空,516d9754-0fe4-4f2e-9289-5827765f7c67,a77196b2-cc47-482b-8fb0-a140d25da936,2025-08-31 04:33:00
|
||||
2025-08-31 12:30:21+08:00,2025-08-31 12:33:51+08:00,296.0,4461.85,4465.99,-1225.4399999998277,平空,516d9754-0fe4-4f2e-9289-5827765f7c67,c529dbf2-2dd4-46aa-b5b6-dd0ed4f473e2,2025-08-31 04:33:00
|
||||
2025-08-31 12:42:50+08:00,2025-08-31 12:48:13+08:00,661.0,4464.2,4468.3,-2710.1000000002405,平空,0f3ef65f-e754-4229-87e6-e0f102020ce9,bce7cdc5-2c37-4bda-9b2a-05c865dd69ba,2025-08-31 04:48:00
|
||||
2025-08-31 12:42:50+08:00,2025-08-31 12:48:13+08:00,235.0,4464.2,4468.3,-963.5000000000855,平空,10e40e0b-a920-4e80-8d17-ea6f73d645e3,bce7cdc5-2c37-4bda-9b2a-05c865dd69ba,2025-08-31 04:48:00
|
||||
2025-08-31 13:31:35+08:00,2025-08-31 13:38:28+08:00,100.0,4453.99,4450.23,-376.0000000000218,平多,e65553ad-4738-4b5f-af08-c7373301aef8,ce3b836f-f3c8-4e27-bdd5-33b9e1ba2bc9,2025-08-31 05:38:00
|
||||
2025-08-31 13:31:35+08:00,2025-08-31 13:38:28+08:00,600.0,4454.98,4450.23,-2850.0,平多,57f64aba-942f-4563-a3c7-9dfdfeaf98e4,ce3b836f-f3c8-4e27-bdd5-33b9e1ba2bc9,2025-08-31 05:38:00
|
||||
2025-08-31 13:31:35+08:00,2025-08-31 13:38:28+08:00,198.0,4454.98,4450.23,-940.5,平多,597f272b-ce3c-45bf-99ee-4abe2f630972,ce3b836f-f3c8-4e27-bdd5-33b9e1ba2bc9,2025-08-31 05:38:00
|
||||
2025-08-31 14:22:05+08:00,2025-08-31 14:23:54+08:00,275.0,4441.46,4446.28,-1325.49999999992,平空,eafee2f8-8601-47c7-bfe0-2f0a35bd1411,3744b6fa-5971-44d1-885f-7c7f93da8e70,2025-08-31 06:23:00
|
||||
2025-08-31 14:22:05+08:00,2025-08-31 14:23:54+08:00,100.0,4442.21,4446.28,-406.9999999999709,平空,702cd777-dc0b-4602-8c19-618520cf46f4,3744b6fa-5971-44d1-885f-7c7f93da8e70,2025-08-31 06:23:00
|
||||
2025-08-31 14:22:05+08:00,2025-08-31 14:23:54+08:00,525.0,4441.46,4446.28,-2530.499999999847,平空,be46bffb-290a-477a-81d9-fd4e7c1d2f3c,3744b6fa-5971-44d1-885f-7c7f93da8e70,2025-08-31 06:23:00
|
||||
2025-08-31 14:33:49+08:00,2025-08-31 14:39:00+08:00,900.0,4439.81,4444.22,-3968.999999999869,平空,f25e9b2a-1bb7-4bc5-852e-5c953f222f61,2d0c6c6c-070a-498b-ade7-0235a6d3e644,2025-08-31 06:39:00
|
||||
2025-08-31 14:40:14+08:00,2025-08-31 14:47:51+08:00,411.0,4444.0,4439.28,-1939.9200000001047,平多,46e5c712-8dff-423d-805d-d09da5e43571,9a2c96f9-3c09-4cca-8539-7251e96e99df,2025-08-31 06:47:00
|
||||
2025-08-31 14:40:14+08:00,2025-08-31 14:47:51+08:00,400.0,4444.0,4439.13,-1947.9999999999563,平多,46e5c712-8dff-423d-805d-d09da5e43571,f92ff1ec-cfa2-4576-bcbb-978910e5200c,2025-08-31 06:47:00
|
||||
2025-08-31 14:40:14+08:00,2025-08-31 14:47:51+08:00,89.0,4444.0,4439.13,-433.4299999999903,平多,46e5c712-8dff-423d-805d-d09da5e43571,5f572e4d-e1a7-45cf-bc1b-b55ce56f12b4,2025-08-31 06:47:00
|
||||
2025-08-31 14:51:09+08:00,2025-08-31 14:57:17+08:00,450.0,4436.57,4435.16,634.4999999999345,平空,3d5b8a22-edc9-4076-8d61-23f8ed561202,9c959838-3125-426e-abaf-d09fc309fda2,2025-08-31 06:57:00
|
||||
2025-08-31 15:30:46+08:00,2025-08-31 15:33:17+08:00,449.0,4450.96,4446.74,-1894.7800000001143,平多,45ada65c-b8e3-4316-9414-5faf1dcad582,57564f53-121e-47ac-8804-e448bb39c82c,2025-08-31 07:33:00
|
||||
2025-08-31 16:05:31+08:00,2025-08-31 16:08:48+08:00,448.0,4461.21,4457.34,-1733.759999999951,平多,050937e7-f1e4-459e-982a-8d5263e718cb,ad5c9d31-f050-4981-9f51-7852dbf0df37,2025-08-31 08:08:00
|
||||
2025-08-31 16:20:45+08:00,2025-08-31 16:24:22+08:00,48.0,4463.0,4462.93,-3.35999999998603,平多,e41030f3-095f-4e36-b465-e7f7d20b2972,83b7259d-91f7-485a-92ee-37c94790c7f4,2025-08-31 08:24:00
|
||||
2025-08-31 16:20:45+08:00,2025-08-31 16:24:22+08:00,400.0,4463.0,4462.93,-27.999999999883585,平多,e41030f3-095f-4e36-b465-e7f7d20b2972,de316218-6bb2-48c0-b540-fc4babf5ce9b,2025-08-31 08:24:00
|
||||
2025-08-31 17:24:37+08:00,2025-08-31 17:27:28+08:00,447.0,4473.73,4467.56,2757.989999999626,平空,b8d42b12-b7c8-465d-a67b-56085ceae48f,74ed9f75-4d45-47a8-a636-5a1cbe5511ce,2025-08-31 09:27:00
|
||||
2025-08-31 17:48:57+08:00,2025-08-31 17:52:50+08:00,448.0,4463.45,4470.31,3073.2800000002608,平多,9b9c8688-3c74-48c3-b1bf-6bbc162b3edf,70a47e37-f8fb-440f-9ea6-50ffbf8055f6,2025-08-31 09:52:00
|
||||
2025-08-31 17:59:19+08:00,2025-08-31 18:00:09+08:00,152.0,4461.24,4465.19,-600.3999999999724,平空,28a00768-c1e1-42d0-b75e-f7b6b8608b60,9263141f-f5ac-4173-b3c2-b8e2f7ba0985,2025-08-31 10:00:00
|
||||
2025-08-31 17:59:19+08:00,2025-08-31 18:00:09+08:00,520.0,4461.24,4465.19,-2053.9999999999054,平空,28a00768-c1e1-42d0-b75e-f7b6b8608b60,32d89f55-ae9c-49a6-8e2a-e47b5f4167ab,2025-08-31 10:00:00
|
||||
2025-08-31 18:07:59+08:00,2025-08-31 18:12:30+08:00,672.0,4459.1,4452.94,4139.520000000513,平空,765d6b9a-0683-4dca-b76c-6e9a2ea65cd5,7f047d14-dac5-4fca-94ed-c0fdfec63578,2025-08-31 10:12:00
|
||||
2025-08-31 18:17:18+08:00,2025-08-31 18:29:14+08:00,673.0,4451.84,4455.42,2409.339999999951,平多,755ad988-d66e-4bc8-9907-b69112e3a11a,775a3323-bbd4-4e5c-ae56-d68e178eb05e,2025-08-31 10:29:00
|
||||
2025-08-31 22:07:31+08:00,2025-08-31 22:13:15+08:00,1122.0,4453.82,4450.0,4286.0399999996735,平空,c3c54d31-6a99-45ad-8139-546ac61bed34,ef4df9cc-3394-4d53-8fed-7e261a8b0c60,2025-08-31 14:13:00
|
||||
2025-08-31 22:14:11+08:00,2025-08-31 22:20:53+08:00,500.0,4453.63,4459.25,2809.9999999999454,平多,18042f61-552b-41bb-b228-8d8031ee3c14,ad7b6056-101b-49e4-9b9f-dea68880cd52,2025-08-31 14:20:00
|
||||
2025-08-31 22:14:11+08:00,2025-08-31 22:20:53+08:00,122.0,4453.63,4459.25,685.6399999999867,平多,208d18b8-941b-4ce3-aa29-2f5fb074cc01,ad7b6056-101b-49e4-9b9f-dea68880cd52,2025-08-31 14:20:00
|
||||
2025-08-31 22:14:11+08:00,2025-08-31 22:20:53+08:00,500.0,4453.63,4459.25,2809.9999999999454,平多,4e557687-e4c4-4f2d-85b6-364f6e5fd752,ad7b6056-101b-49e4-9b9f-dea68880cd52,2025-08-31 14:20:00
|
||||
2025-08-31 23:05:00+08:00,2025-08-31 23:09:16+08:00,1117.0,4473.11,4471.27,-2055.2799999991466,平多,41d06c42-ac03-418f-9451-6f5a5fdda71a,5cd65f94-490b-4bb2-bd42-d1a64942d201,2025-08-31 15:09:00
|
||||
2025-09-01 09:07:41+08:00,2025-09-01 09:11:05+08:00,400.0,4394.0,4383.39,4243.999999999869,平空,092adf53-a461-4de7-8e04-9bfe1de8c387,b3e26049-21f9-495e-9a96-7498f06dcd82,2025-09-01 01:11:00
|
||||
2025-09-01 09:07:41+08:00,2025-09-01 09:11:05+08:00,55.0,4394.0,4383.39,583.549999999982,平空,092adf53-a461-4de7-8e04-9bfe1de8c387,0614bd80-655c-49d4-b895-798f56f54803,2025-09-01 01:11:00
|
||||
2025-09-01 09:20:32+08:00,2025-09-01 09:25:17+08:00,454.0,4399.41,4396.45,-1343.8400000000165,平多,ef1554ec-bb75-4b5d-a3fd-03325d8ce248,11fb1482-9f6a-4b3f-aec4-e0e9f46ec4eb,2025-09-01 01:25:00
|
||||
2025-09-01 09:31:04+08:00,2025-09-01 09:32:03+08:00,453.0,4409.02,4405.26,-1703.2800000000989,平多,8050d64e-1c53-40e1-89e7-68481d09e689,079730bc-d8ce-413a-a77f-a889bed73972,2025-09-01 01:32:00
|
||||
2025-09-01 09:34:47+08:00,2025-09-01 09:37:04+08:00,453.0,4405.92,4417.68,5327.280000000099,平多,4f3b316a-37b6-43b9-a742-28760ce5267b,18f19970-4048-485b-b95a-00b3e804d0d9,2025-09-01 01:37:00
|
||||
2025-09-01 10:11:29+08:00,2025-09-01 10:16:05+08:00,400.0,4400.12,4391.86,3304.0000000000873,平空,9b7a45aa-4ed0-4fa2-9c77-ee71e8319ecf,6adc05fa-6727-4961-8ae9-ef7f248ce06f,2025-09-01 02:16:00
|
||||
2025-09-01 10:11:30+08:00,2025-09-01 10:16:05+08:00,54.0,4400.12,4391.86,446.0400000000118,平空,a2b8e0df-1a70-4cbe-b513-734c169a45e5,6adc05fa-6727-4961-8ae9-ef7f248ce06f,2025-09-01 02:16:00
|
||||
2025-09-01 10:29:03+08:00,2025-09-01 10:30:26+08:00,683.0,4389.33,4386.21,-2130.9599999999255,平多,9295d00e-a050-472f-b008-426e40eb0f71,73e0f83a-583a-4ae6-8dc3-41c2f37bb58e,2025-09-01 02:30:00
|
||||
2025-09-01 10:35:00+08:00,2025-09-01 10:36:12+08:00,685.0,4375.45,4379.38,-2692.0500000001994,平空,8e5b463d-f4fe-47f3-b39b-ec3721c79e7a,2f96c834-2272-491f-8bc1-8cbe6c4ea452,2025-09-01 02:36:00
|
||||
2025-09-01 11:03:26+08:00,2025-09-01 11:05:42+08:00,684.0,4383.62,4391.35,5287.3200000003235,平多,ad2ab2b0-b070-4027-ba77-039ae801c30b,ab66e717-8f29-4ebe-80c7-165f36806e5f,2025-09-01 03:05:00
|
||||
2025-09-01 11:12:18+08:00,2025-09-01 11:15:56+08:00,184.0,4382.88,4374.85,1477.5199999999531,平空,60e70f9e-3ed0-402a-a33e-0d9104ac17b6,2c200cb7-b183-474a-b78f-bba9e592c8c0,2025-09-01 03:15:00
|
||||
2025-09-01 11:12:18+08:00,2025-09-01 11:15:56+08:00,500.0,4382.88,4374.85,4014.9999999998727,平空,4f57713c-6a4c-42d4-aa6b-c03bae934835,2c200cb7-b183-474a-b78f-bba9e592c8c0,2025-09-01 03:15:00
|
||||
2025-09-01 11:34:38+08:00,2025-09-01 11:41:09+08:00,286.0,4370.18,4382.21,3440.579999999927,平多,7716be61-be89-4b30-8939-ff0c2c42b4e2,15dadfe0-75d8-4cc7-838d-931a1d21ccf8,2025-09-01 03:41:00
|
||||
2025-09-01 11:34:38+08:00,2025-09-01 11:41:09+08:00,400.0,4370.18,4382.21,4811.999999999898,平多,7716be61-be89-4b30-8939-ff0c2c42b4e2,9ae0cd54-0bfb-4a24-8c7a-1a247e078eb8,2025-09-01 03:41:00
|
||||
2025-09-01 11:54:59+08:00,2025-09-01 11:58:53+08:00,10.0,4396.0,4403.97,79.70000000000255,平多,b0876036-7e56-46cb-bddd-ccabb6519f7c,600628e2-049f-44c8-a905-46ea1135e601,2025-09-01 03:58:00
|
||||
2025-09-01 11:54:59+08:00,2025-09-01 11:58:53+08:00,145.0,4396.47,4403.97,1087.5,平多,4bc131e8-70e0-4140-a2c8-b054e0507acd,600628e2-049f-44c8-a905-46ea1135e601,2025-09-01 03:58:00
|
||||
2025-09-01 11:54:59+08:00,2025-09-01 11:58:53+08:00,355.0,4396.47,4403.97,2662.5,平多,4bc131e8-70e0-4140-a2c8-b054e0507acd,9d3eca96-2443-4c6e-b40e-1020bde26895,2025-09-01 03:58:00
|
||||
2025-09-01 11:54:59+08:00,2025-09-01 11:58:53+08:00,172.0,4396.47,4403.97,1290.0,平多,86ab0723-57a8-4e31-9000-9b73f49ad308,9d3eca96-2443-4c6e-b40e-1020bde26895,2025-09-01 03:58:00
|
||||
2025-09-01 12:19:34+08:00,2025-09-01 12:23:09+08:00,500.0,4389.68,4388.07,805.000000000291,平空,2aec9027-7a84-45c2-83d3-8c5871d90106,95aa0191-4e59-46b0-b538-e0a3fd31a029,2025-09-01 04:23:00
|
||||
2025-09-01 12:19:34+08:00,2025-09-01 12:23:09+08:00,87.0,4389.68,4388.0,146.16000000002532,平空,2aec9027-7a84-45c2-83d3-8c5871d90106,861ff638-8ec8-4a6a-bafa-74206b093040,2025-09-01 04:23:00
|
||||
2025-09-01 12:19:34+08:00,2025-09-01 12:23:09+08:00,52.0,4389.68,4388.07,83.72000000003027,平空,2aec9027-7a84-45c2-83d3-8c5871d90106,6cff4ab5-d999-4dda-87e2-067c9adbd240,2025-09-01 04:23:00
|
||||
2025-09-01 12:19:34+08:00,2025-09-01 12:23:09+08:00,500.0,4389.68,4388.07,805.000000000291,平空,2aec9027-7a84-45c2-83d3-8c5871d90106,4dc3eeb9-b54d-4820-bc73-2172e8e5fd66,2025-09-01 04:23:00
|
||||
2025-09-01 12:52:26+08:00,2025-09-01 12:56:26+08:00,1141.0,4381.97,4385.85,4427.0800000001245,平多,a3ef0945-101d-4f17-9cda-711fe44e2cf1,ab314993-0d4e-4632-8bcd-66aaaef57066,2025-09-01 04:56:00
|
||||
2025-09-01 13:11:24+08:00,2025-09-01 13:12:28+08:00,475.0,4363.3,4368.36,-2403.499999999758,平空,4d1d7cf6-26fe-44f0-be21-d10a9871ae1a,53f10353-21b1-41c1-8554-18c5bac4991a,2025-09-01 05:12:00
|
||||
2025-09-01 13:11:24+08:00,2025-09-01 13:12:28+08:00,400.0,4363.3,4368.36,-2023.9999999997963,平空,63978423-878f-4bc0-8ac6-7526d1aa3b24,53f10353-21b1-41c1-8554-18c5bac4991a,2025-09-01 05:12:00
|
||||
2025-09-01 13:11:24+08:00,2025-09-01 13:12:28+08:00,500.0,4363.3,4368.36,-2529.9999999997453,平空,9736d4ed-bfd4-4fb4-a2b9-7a335070a480,53f10353-21b1-41c1-8554-18c5bac4991a,2025-09-01 05:12:00
|
||||
2025-09-01 13:19:17+08:00,2025-09-01 13:20:04+08:00,1376.0,4357.3,4364.42,-9797.11999999985,平空,9d7b259b-c1ec-4d54-83c5-e1588347d99f,a63b68c2-d467-4b4b-992a-175e562a59cd,2025-09-01 05:20:00
|
||||
2025-09-01 13:23:06+08:00,2025-09-01 13:33:39+08:00,291.0,4390.89,4384.71,-1798.3800000000847,平多,348b7ba0-df18-4d21-b652-1c54dc5b4635,2a709f71-d330-49c0-9759-3b65a140431c,2025-09-01 05:33:00
|
||||
2025-09-01 13:23:06+08:00,2025-09-01 13:33:39+08:00,300.0,4390.89,4384.71,-1854.0000000000873,平多,c8c60f9e-a45b-4f5a-b000-a1a436757403,2a709f71-d330-49c0-9759-3b65a140431c,2025-09-01 05:33:00
|
||||
2025-09-01 13:23:06+08:00,2025-09-01 13:33:39+08:00,300.0,4390.89,4384.71,-1854.0000000000873,平多,970891fc-d2ec-4c54-a196-7a450a88c968,2a709f71-d330-49c0-9759-3b65a140431c,2025-09-01 05:33:00
|
||||
2025-09-01 13:23:06+08:00,2025-09-01 13:33:39+08:00,20.0,4390.01,4384.71,-106.00000000000364,平多,71cd000e-8c95-4f6f-a32f-1440db6e8c0d,2a709f71-d330-49c0-9759-3b65a140431c,2025-09-01 05:33:00
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,287.0,4396.35,4396.21,-40.18000000009397,平多,83a1a14a-a4e3-40f1-86bd-915987501b35,4c298f23-9a76-4a0f-8bc1-f8ed23f12ede,2025-09-01 05:54:00
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,200.0,4396.35,4396.21,-28.000000000065484,平多,83a1a14a-a4e3-40f1-86bd-915987501b35,46728c29-6645-408a-bba2-07f01cd5d06d,2025-09-01 05:54:00
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,13.0,4396.35,4396.21,-1.8200000000042564,平多,83a1a14a-a4e3-40f1-86bd-915987501b35,c4cb8611-b4dd-41fd-95c2-c9455c1237e6,2025-09-01 05:54:00
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,187.0,4396.35,4396.21,-26.180000000061227,平多,47230a4e-8ee9-4d68-aa51-01753afc623b,c4cb8611-b4dd-41fd-95c2-c9455c1237e6,2025-09-01 05:54:00
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,213.0,4396.35,4396.13,-46.86000000005424,平多,47230a4e-8ee9-4d68-aa51-01753afc623b,e06fcad9-5742-4c94-82eb-2f34884e3f4a,2025-09-01 05:54:00
|
||||
2025-09-01 13:49:14+08:00,2025-09-01 13:54:15+08:00,9.0,4396.35,4396.13,-1.980000000002292,平多,1743d43c-c642-434b-940f-b6d33e6718b9,e06fcad9-5742-4c94-82eb-2f34884e3f4a,2025-09-01 05:54:00
|
||||
2025-09-01 15:34:19+08:00,2025-09-01 15:35:08+08:00,403.0,4429.5,4426.01,-1406.469999999912,平多,e9dad14a-702d-4396-a259-38e6c3b62876,76c3e1c9-aeee-41bd-b8d4-37e8948af1c4,2025-09-01 07:35:00
|
||||
2025-09-01 15:34:19+08:00,2025-09-01 15:35:08+08:00,500.0,4429.5,4426.01,-1744.9999999998909,平多,431b7e4b-b81e-4626-a6de-f5acd5a7cf20,76c3e1c9-aeee-41bd-b8d4-37e8948af1c4,2025-09-01 07:35:00
|
||||
2025-09-01 15:36:02+08:00,2025-09-01 15:36:51+08:00,780.0,4424.4,4419.44,-3868.8000000000284,平多,d7ba8670-d0d7-4817-b30f-ed7d2ba4fd3c,74c07207-3acc-4f9c-b98b-ae54e4085d96,2025-09-01 07:36:00
|
||||
2025-09-01 15:36:02+08:00,2025-09-01 15:36:51+08:00,124.0,4424.4,4420.0,-545.5999999999549,平多,d7ba8670-d0d7-4817-b30f-ed7d2ba4fd3c,a4bea0b9-d2b0-42bf-9e3b-371747ea4fa3,2025-09-01 07:36:00
|
||||
2025-09-01 15:44:38+08:00,2025-09-01 15:47:21+08:00,676.0,4420.39,4426.94,4427.799999999508,平多,17564d45-1b57-4822-91dd-7bf3a162e7b1,131e056d-1779-4de8-bb12-1663936c3ffe,2025-09-01 07:47:00
|
||||
2025-09-01 15:44:38+08:00,2025-09-01 15:47:21+08:00,22.0,4420.0,4426.94,152.6799999999912,平多,7e280213-31c8-4848-b164-57ce0f3a30c3,131e056d-1779-4de8-bb12-1663936c3ffe,2025-09-01 07:47:00
|
||||
2025-09-01 15:44:38+08:00,2025-09-01 15:47:21+08:00,206.0,4420.39,4426.94,1349.2999999998501,平多,37898b4e-9a13-4bc0-a9e6-3fa908bd1e80,131e056d-1779-4de8-bb12-1663936c3ffe,2025-09-01 07:47:00
|
||||
2025-09-01 15:51:28+08:00,2025-09-01 15:54:46+08:00,387.0,4434.86,4450.54,6068.160000000113,平多,a62d6355-e666-4b24-a9fe-1a8efd802cd4,c5b380bf-78c7-4dfd-a1b8-f212e0562e5d,2025-09-01 07:54:00
|
||||
2025-09-01 15:51:28+08:00,2025-09-01 15:54:46+08:00,514.0,4434.86,4450.54,8059.52000000015,平多,9383482c-67d4-4a52-9c9a-bf106f8a66ed,c5b380bf-78c7-4dfd-a1b8-f212e0562e5d,2025-09-01 07:54:00
|
||||
2025-09-01 15:59:35+08:00,2025-09-01 16:02:01+08:00,898.0,4454.21,4472.89,16774.64000000026,平多,b4194b4d-5b4a-4387-879f-b208a173d23e,0b3a10ad-d3bc-49e7-bc76-95d3f3f7b012,2025-09-01 08:02:00
|
||||
2025-09-01 16:05:00+08:00,2025-09-01 16:05:43+08:00,891.0,4486.3,4479.29,-6245.9100000001945,平多,62035134-6e2c-4e78-a57e-90883d6fd125,e148f8b3-d2b0-49a8-9702-bf0ef6d70a11,2025-09-01 08:05:00
|
||||
2025-09-01 16:06:22+08:00,2025-09-01 16:13:08+08:00,893.0,4478.53,4480.64,-1884.2300000005198,平空,d901988b-b5b6-4a5e-a03c-83cedbdbbbda,dcc81b54-5253-4a49-a426-f28316341b68,2025-09-01 08:13:00
|
||||
2025-09-01 16:18:38+08:00,2025-09-01 16:19:03+08:00,895.0,4467.53,4470.68,-2819.2500000004884,平空,2a066536-51d7-425d-ae9e-83613597c612,41ad454f-5790-480c-9f16-80b478841763,2025-09-01 08:19:00
|
||||
2025-09-01 16:20:05+08:00,2025-09-01 16:22:22+08:00,894.0,4473.83,4478.12,-3835.2599999999675,平空,93df10c4-453d-405e-aa78-3870c5c5f78f,5aeedf17-a10d-4388-b16e-e18251d6e1e5,2025-09-01 08:22:00
|
||||
2025-09-01 16:23:14+08:00,2025-09-01 16:27:37+08:00,893.0,4475.22,4469.41,5188.330000000357,平空,d9277a5f-844a-4afc-b784-0343d373a87e,7def0a70-6cc3-4880-9da3-16671ec6a296,2025-09-01 08:27:00
|
||||
2025-09-01 16:33:42+08:00,2025-09-01 16:41:25+08:00,894.0,4470.45,4469.46,885.0599999998049,平空,9e7bf3df-d22c-413b-8b29-024b9c830f6b,3e80d0e3-3466-4e63-b75d-c86b67b6eb0b,2025-09-01 08:41:00
|
||||
2025-09-01 16:55:38+08:00,2025-09-01 17:04:46+08:00,893.0,4474.93,4476.7,-1580.6099999995777,平空,aca7bfae-9112-4a7a-8a95-7b7661be7d13,20115f47-253a-435a-9b43-1cfcbdc9c12e,2025-09-01 09:04:00
|
||||
2025-09-01 17:10:25+08:00,2025-09-01 17:15:02+08:00,894.0,4472.69,4480.01,6544.080000000553,平多,44c66b7e-5da3-4773-b5a0-73b1cd1c13b9,d8403012-f909-414d-bb21-a012dc5722c2,2025-09-01 09:15:00
|
||||
2025-09-01 17:35:29+08:00,2025-09-01 17:38:36+08:00,300.0,4473.01,4460.17,3852.0000000000437,平空,67981b9f-af13-4595-9a36-80db526ac0e4,de9b300a-19de-4cc4-b86e-e5d8ade4eb2f,2025-09-01 09:38:00
|
||||
2025-09-01 17:35:29+08:00,2025-09-01 17:38:36+08:00,70.0,4473.01,4460.17,898.8000000000102,平空,67981b9f-af13-4595-9a36-80db526ac0e4,f7f5c314-6e7b-498a-99cf-a2c868021b32,2025-09-01 09:38:00
|
||||
2025-09-01 17:35:29+08:00,2025-09-01 17:38:36+08:00,236.0,4473.01,4460.17,3030.2400000000343,平空,67981b9f-af13-4595-9a36-80db526ac0e4,f34ae213-3bb5-4bc5-96b5-1efb92648427,2025-09-01 09:38:00
|
||||
2025-09-01 17:35:29+08:00,2025-09-01 17:38:36+08:00,287.0,4473.01,4460.17,3685.0800000000418,平空,da23b541-1bde-4417-8875-4a52000d3183,f34ae213-3bb5-4bc5-96b5-1efb92648427,2025-09-01 09:38:00
|
||||
|
72
价格展示/test.py
72
价格展示/test.py
@@ -1,72 +0,0 @@
|
||||
import requests
|
||||
import pandas as pd
|
||||
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'zh,zh-CN;q=0.9,zh-HK;q=0.8,en;q=0.7',
|
||||
'cache-control': 'no-cache',
|
||||
'origin': 'https://www.websea.com',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.websea.com/',
|
||||
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
datas = []
|
||||
import datetime
|
||||
|
||||
# 定义开始日期和结束日期
|
||||
start_date = datetime.datetime(2025, 8, 28)
|
||||
end_date = datetime.datetime(2025, 9, 2)
|
||||
|
||||
# 初始化当前日期为开始日期
|
||||
current_date = start_date
|
||||
|
||||
# 循环遍历日期范围
|
||||
while current_date <= end_date:
|
||||
# 获取当天开始时刻(00:00:00)
|
||||
start_of_day = current_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
# 获取当天结束时刻(23:59:59)
|
||||
end_of_day = current_date.replace(hour=23, minute=59, second=59, microsecond=0)
|
||||
|
||||
# 将开始时刻和结束时刻转换为时间戳
|
||||
start_timestamp = start_of_day.timestamp()
|
||||
end_timestamp = end_of_day.timestamp()
|
||||
|
||||
print(f"日期: {current_date.strftime('%Y.%m.%d')}")
|
||||
print(f" 开始时刻时间戳: {start_timestamp}")
|
||||
print(f" 结束时刻时间戳: {end_timestamp}")
|
||||
|
||||
params = {
|
||||
'symbol': 'ETH-USDT',
|
||||
'period': '1min',
|
||||
'start': int(start_timestamp),
|
||||
'end': int(end_timestamp),
|
||||
}
|
||||
|
||||
response = requests.get('https://capi.websea.com/webApi/market/getKline', params=params, headers=headers)
|
||||
|
||||
# 提取数据
|
||||
data = response.json()['result']['data']
|
||||
print(data)
|
||||
|
||||
for i in data:
|
||||
datas.append(i)
|
||||
|
||||
# 日期加一天
|
||||
current_date += datetime.timedelta(days=1)
|
||||
|
||||
# 将数据转换为 DataFrame
|
||||
df = pd.DataFrame(datas)
|
||||
|
||||
# 保存为 Excel 文件
|
||||
df.to_excel('kline_data.xlsx', index=False)
|
||||
|
||||
print("数据已成功保存到 kline_data.xlsx 文件中。")
|
||||
712
价格展示/test1.py
712
价格展示/test1.py
@@ -1,712 +0,0 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import warnings
|
||||
import os
|
||||
import uuid
|
||||
|
||||
# ========== 配置 ==========
|
||||
KLINE_XLSX = "kline_data.xlsx" # K线数据文件名
|
||||
ORDERS_XLSX = "做市策略.xls" # 订单数据文件名
|
||||
OUTPUT_HTML = "kline_with_trades.html"
|
||||
SYMBOL = "ETH-USDT" # 交易对筛选
|
||||
|
||||
# 时间与对齐配置
|
||||
ORDERS_TIME_IS_LOCAL_ASIA_SH = True # 订单时间是否为东八区时间
|
||||
SNAP_TRADES_TO_NEAREST_CANDLE = True # 对齐交易点到最近的K线时间
|
||||
SNAP_TOLERANCE_MULTIPLIER = 1.5 # 对齐容忍度倍数
|
||||
|
||||
# 图表尺寸配置 - 更宽更扁
|
||||
CHART_WIDTH = 2200 # 更宽的图表
|
||||
CHART_HEIGHT = 600 # 更矮的图表
|
||||
FONT_SIZE = 12 # 字体大小
|
||||
ANNOTATION_FONT_SIZE = 10 # 标注字体大小
|
||||
MARKER_SIZE = 10 # 标记大小
|
||||
LINE_WIDTH = 1.5 # 连接线宽度
|
||||
|
||||
# 颜色配置 - 所有文本使用黑色
|
||||
TEXT_COLOR = "black" # 所有文本使用黑色
|
||||
TEXT_OFFSET = 10 # 文本偏移量(像素)
|
||||
|
||||
|
||||
# ========== 工具函数 ==========
|
||||
def parse_numeric(x):
|
||||
"""高效解析数值类型,支持多种格式"""
|
||||
if pd.isna(x):
|
||||
return np.nan
|
||||
try:
|
||||
# 尝试直接转换(大多数情况)
|
||||
return float(x)
|
||||
except:
|
||||
# 处理特殊格式
|
||||
s = str(x).replace(",", "").replace("USDT", "").replace("张", "").strip()
|
||||
if s.endswith("%"):
|
||||
s = s[:-1]
|
||||
return float(s) if s else np.nan
|
||||
|
||||
|
||||
def epoch_to_dt(x):
|
||||
"""将时间戳转换为上海时区时间"""
|
||||
try:
|
||||
return pd.to_datetime(int(x), unit="s", utc=True).tz_convert("Asia/Shanghai")
|
||||
except:
|
||||
return pd.NaT
|
||||
|
||||
|
||||
def zh_side(row):
|
||||
"""解析交易方向"""
|
||||
direction = str(row.get("方向", "")).strip()
|
||||
if "开多" in direction: return "long_open"
|
||||
if "平多" in direction: return "long_close"
|
||||
if "开空" in direction: return "short_open"
|
||||
if "平空" in direction: return "short_close"
|
||||
return "unknown"
|
||||
|
||||
|
||||
# ========== 数据加载与预处理 ==========
|
||||
def load_kline_data():
|
||||
"""加载并预处理K线数据"""
|
||||
if not os.path.exists(KLINE_XLSX):
|
||||
raise FileNotFoundError(f"K线数据文件不存在: {KLINE_XLSX}")
|
||||
|
||||
kdf = pd.read_excel(KLINE_XLSX, dtype=str)
|
||||
kdf.columns = [str(c).strip().lower() for c in kdf.columns]
|
||||
|
||||
# 验证必要列
|
||||
required_cols = {"id", "open", "close", "low", "high"}
|
||||
missing = required_cols - set(kdf.columns)
|
||||
if missing:
|
||||
raise ValueError(f"K线表缺少列: {missing}")
|
||||
|
||||
# 时间转换 - 确保id是秒级时间戳
|
||||
kdf["time"] = kdf["id"].apply(epoch_to_dt)
|
||||
|
||||
# 数值转换(向量化操作提升性能)
|
||||
for col in ["open", "close", "low", "high"]:
|
||||
kdf[col] = pd.to_numeric(kdf[col].apply(parse_numeric), errors="coerce")
|
||||
|
||||
# 清理无效数据
|
||||
kdf = kdf.dropna(subset=["time", "open", "close", "low", "high"])
|
||||
kdf = kdf.sort_values("time").reset_index(drop=True)
|
||||
|
||||
# 计算K线周期(用于交易点对齐)
|
||||
if len(kdf) >= 3:
|
||||
median_step = kdf["time"].diff().median()
|
||||
else:
|
||||
median_step = pd.Timedelta(minutes=1)
|
||||
|
||||
return kdf, median_step
|
||||
|
||||
|
||||
def load_order_data():
|
||||
"""加载并预处理订单数据"""
|
||||
if not os.path.exists(ORDERS_XLSX):
|
||||
raise FileNotFoundError(f"订单数据文件不存在: {ORDERS_XLSX}")
|
||||
|
||||
odf = pd.read_excel(ORDERS_XLSX, dtype=str)
|
||||
|
||||
# 验证必要列
|
||||
need_order_cols = ["时间", "交易对", "方向", "模式", "数量(张)", "成交价", "交易额", "消耗手续费", "用户盈亏"]
|
||||
missing = set(need_order_cols) - set(odf.columns)
|
||||
if missing:
|
||||
raise ValueError(f"订单表缺少列: {missing}")
|
||||
|
||||
# 筛选交易对
|
||||
if SYMBOL and "交易对" in odf.columns:
|
||||
odf = odf[odf["交易对"].astype(str).str.strip() == SYMBOL]
|
||||
|
||||
# 时间处理 - 确保时间格式正确
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
if ORDERS_TIME_IS_LOCAL_ASIA_SH:
|
||||
# 尝试多种格式解析时间
|
||||
odf["时间"] = pd.to_datetime(odf["时间"], errors="coerce", format="mixed")
|
||||
# 本地化为上海时区
|
||||
odf["时间"] = odf["时间"].dt.tz_localize("Asia/Shanghai", ambiguous="NaT", nonexistent="shift_forward")
|
||||
else:
|
||||
# 如果Excel时间已经是UTC
|
||||
odf["时间"] = pd.to_datetime(odf["时间"], utc=True, errors="coerce").dt.tz_convert("Asia/Shanghai")
|
||||
|
||||
# 数值转换
|
||||
numeric_cols = {
|
||||
"数量(张)": "数量",
|
||||
"成交价": "价格",
|
||||
"交易额": "交易额_num",
|
||||
"消耗手续费": "手续费",
|
||||
"用户盈亏": "盈亏"
|
||||
}
|
||||
|
||||
for src, dest in numeric_cols.items():
|
||||
odf[dest] = pd.to_numeric(odf[src].apply(parse_numeric), errors="coerce")
|
||||
|
||||
# 解析交易方向
|
||||
odf["side"] = odf.apply(zh_side, axis=1)
|
||||
|
||||
# 为每个订单生成唯一ID
|
||||
odf["order_id"] = [str(uuid.uuid4()) for _ in range(len(odf))]
|
||||
|
||||
# 计算本金(数量 * 价格)
|
||||
odf["本金"] = odf["数量"] * odf["价格"]
|
||||
|
||||
# 清理无效数据
|
||||
odf = odf.dropna(subset=["时间", "价格"])
|
||||
odf = odf.sort_values("时间").reset_index(drop=True)
|
||||
|
||||
return odf
|
||||
|
||||
|
||||
def align_trades_to_candles(kdf, odf, median_step):
|
||||
"""将交易点对齐到最近的K线时间"""
|
||||
if not SNAP_TRADES_TO_NEAREST_CANDLE or kdf.empty or odf.empty:
|
||||
return odf.assign(时间_x=odf["时间"])
|
||||
|
||||
snap_tolerance = pd.Timedelta(seconds=max(1, int(median_step.total_seconds() * SNAP_TOLERANCE_MULTIPLIER)))
|
||||
|
||||
# 使用merge_asof高效对齐
|
||||
anchor = kdf[["time"]].copy().rename(columns={"time": "k_time"})
|
||||
odf_sorted = odf.sort_values("时间")
|
||||
|
||||
aligned = pd.merge_asof(
|
||||
odf_sorted,
|
||||
anchor,
|
||||
left_on="时间",
|
||||
right_on="k_time",
|
||||
direction="nearest",
|
||||
tolerance=snap_tolerance
|
||||
)
|
||||
|
||||
# 保留原始时间作为参考
|
||||
aligned["原始时间"] = aligned["时间"]
|
||||
aligned["时间_x"] = aligned["k_time"].fillna(aligned["时间"])
|
||||
|
||||
return aligned
|
||||
|
||||
|
||||
# ========== 持仓跟踪与盈亏计算 ==========
|
||||
class PositionTracker:
|
||||
"""FIFO持仓跟踪器,支持订单走向可视化"""
|
||||
|
||||
def __init__(self):
|
||||
self.long_lots = [] # (数量, 价格, 时间, 手续费, 订单ID)
|
||||
self.short_lots = [] # (数量, 价格, 时间, 手续费, 订单ID)
|
||||
self.realized_pnl = 0.0
|
||||
self.history = [] # 记录所有交易历史
|
||||
self.trade_connections = [] # 记录开平仓连接关系
|
||||
|
||||
def open_long(self, qty, price, time, fee, order_id):
|
||||
"""开多仓"""
|
||||
if qty > 1e-9:
|
||||
self.long_lots.append((qty, price, time, fee, order_id))
|
||||
|
||||
def close_long(self, qty, price, time, fee, order_id):
|
||||
"""平多仓"""
|
||||
remaining = qty
|
||||
local_pnl = 0.0
|
||||
connections = [] # 本次平仓的连接关系
|
||||
|
||||
while remaining > 1e-9 and self.long_lots:
|
||||
lot_qty, lot_price, lot_time, lot_fee, open_order_id = self.long_lots[0]
|
||||
take = min(lot_qty, remaining)
|
||||
pnl = (price - lot_price) * take
|
||||
local_pnl += pnl
|
||||
lot_qty -= take
|
||||
remaining -= take
|
||||
|
||||
# 记录开平仓连接
|
||||
connection = {
|
||||
"open_time": lot_time,
|
||||
"close_time": time,
|
||||
"open_price": lot_price,
|
||||
"close_price": price,
|
||||
"qty": take,
|
||||
"pnl": pnl,
|
||||
"type": "long",
|
||||
"open_order_id": open_order_id,
|
||||
"close_order_id": order_id
|
||||
}
|
||||
self.trade_connections.append(connection)
|
||||
connections.append(connection)
|
||||
|
||||
# 记录平仓详情
|
||||
self.history.append({
|
||||
"开仓时间": lot_time,
|
||||
"平仓时间": time,
|
||||
"数量": take,
|
||||
"开仓价": lot_price,
|
||||
"平仓价": price,
|
||||
"盈亏": pnl,
|
||||
"类型": "平多",
|
||||
"开仓订单ID": open_order_id,
|
||||
"平仓订单ID": order_id
|
||||
})
|
||||
|
||||
if lot_qty <= 1e-9:
|
||||
self.long_lots.pop(0)
|
||||
else:
|
||||
self.long_lots[0] = (lot_qty, lot_price, lot_time, lot_fee, open_order_id)
|
||||
|
||||
local_pnl -= fee
|
||||
self.realized_pnl += local_pnl
|
||||
return local_pnl, connections
|
||||
|
||||
def open_short(self, qty, price, time, fee, order_id):
|
||||
"""开空仓"""
|
||||
if qty > 1e-9:
|
||||
self.short_lots.append((qty, price, time, fee, order_id))
|
||||
|
||||
def close_short(self, qty, price, time, fee, order_id):
|
||||
"""平空仓"""
|
||||
remaining = qty
|
||||
local_pnl = 0.0
|
||||
connections = [] # 本次平仓的连接关系
|
||||
|
||||
while remaining > 1e-9 and self.short_lots:
|
||||
lot_qty, lot_price, lot_time, lot_fee, open_order_id = self.short_lots[0]
|
||||
take = min(lot_qty, remaining)
|
||||
pnl = (lot_price - price) * take
|
||||
local_pnl += pnl
|
||||
lot_qty -= take
|
||||
remaining -= take
|
||||
|
||||
# 记录开平仓连接
|
||||
connection = {
|
||||
"open_time": lot_time,
|
||||
"close_time": time,
|
||||
"open_price": lot_price,
|
||||
"close_price": price,
|
||||
"qty": take,
|
||||
"pnl": pnl,
|
||||
"type": "short",
|
||||
"open_order_id": open_order_id,
|
||||
"close_order_id": order_id
|
||||
}
|
||||
self.trade_connections.append(connection)
|
||||
connections.append(connection)
|
||||
|
||||
# 记录平仓详情
|
||||
self.history.append({
|
||||
"开仓时间": lot_time,
|
||||
"平仓时间": time,
|
||||
"数量": take,
|
||||
"开仓价": lot_price,
|
||||
"平仓价": price,
|
||||
"盈亏": pnl,
|
||||
"类型": "平空",
|
||||
"开仓订单ID": open_order_id,
|
||||
"平仓订单ID": order_id
|
||||
})
|
||||
|
||||
if lot_qty <= 1e-9:
|
||||
self.short_lots.pop(0)
|
||||
else:
|
||||
self.short_lots[0] = (lot_qty, lot_price, lot_time, lot_fee, open_order_id)
|
||||
|
||||
local_pnl -= fee
|
||||
self.realized_pnl += local_pnl
|
||||
return local_pnl, connections
|
||||
|
||||
|
||||
def calculate_pnl(odf):
|
||||
"""计算持仓盈亏和订单连接关系"""
|
||||
tracker = PositionTracker()
|
||||
all_connections = []
|
||||
|
||||
for idx, r in odf.iterrows():
|
||||
qty = r["数量"]
|
||||
price = r["价格"]
|
||||
ts = r["时间"]
|
||||
fee = r["手续费"]
|
||||
side = r["side"]
|
||||
order_id = r["order_id"]
|
||||
|
||||
if side == "long_open":
|
||||
tracker.open_long(qty, price, ts, fee, order_id)
|
||||
elif side == "long_close":
|
||||
_, connections = tracker.close_long(qty, price, ts, fee, order_id)
|
||||
all_connections.extend(connections)
|
||||
elif side == "short_open":
|
||||
tracker.open_short(qty, price, ts, fee, order_id)
|
||||
elif side == "short_close":
|
||||
_, connections = tracker.close_short(qty, price, ts, fee, order_id)
|
||||
all_connections.extend(connections)
|
||||
|
||||
# 创建盈亏DataFrame
|
||||
if tracker.history:
|
||||
pnl_df = pd.DataFrame(tracker.history)
|
||||
# 添加对齐后的时间
|
||||
pnl_df["时间_x"] = pnl_df["平仓时间"].apply(
|
||||
lambda x: odf.loc[odf["时间"] == x, "时间_x"].values[0] if not odf.empty else x
|
||||
)
|
||||
else:
|
||||
pnl_df = pd.DataFrame()
|
||||
|
||||
# 创建连接关系DataFrame
|
||||
connections_df = pd.DataFrame(all_connections) if all_connections else pd.DataFrame()
|
||||
|
||||
return pnl_df, tracker.realized_pnl, connections_df
|
||||
|
||||
|
||||
# ========== 可视化 ==========
|
||||
def create_trade_scatter(df, name, color, symbol):
|
||||
"""创建交易点散点图"""
|
||||
if df.empty:
|
||||
return None
|
||||
|
||||
# 为不同类型的交易点创建不同的文本标签
|
||||
if name == "开多":
|
||||
text = "开多\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["本金"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "平多":
|
||||
text = "平多\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["盈亏"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "开空":
|
||||
text = "开空\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["本金"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "平空":
|
||||
text = "平空\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["盈亏"].apply(lambda x: f"{x:.0f}")
|
||||
else:
|
||||
text = name
|
||||
|
||||
return go.Scatter(
|
||||
x=df["时间_x"],
|
||||
y=df["价格"],
|
||||
mode="markers+text",
|
||||
name=name,
|
||||
text=text,
|
||||
textposition="middle right", # 文本放在右侧中间位置
|
||||
textfont=dict(size=ANNOTATION_FONT_SIZE, color=TEXT_COLOR), # 使用黑色文本
|
||||
marker=dict(
|
||||
size=MARKER_SIZE,
|
||||
color=color,
|
||||
symbol=symbol,
|
||||
line=dict(width=1.5, color="black")
|
||||
),
|
||||
customdata=np.stack([
|
||||
df["数量"].to_numpy(),
|
||||
df["价格"].to_numpy(),
|
||||
df["手续费"].to_numpy(),
|
||||
df.get("盈亏", np.nan).to_numpy(),
|
||||
df.get("原始时间", df["时间"]).dt.strftime("%Y-%m-%d %H:%M:%S").to_numpy(),
|
||||
df["order_id"].to_numpy(),
|
||||
df["本金"].to_numpy()
|
||||
], axis=-1),
|
||||
hovertemplate=(
|
||||
f"<b>{name}</b><br>"
|
||||
"数量: %{customdata[0]:.0f}张<br>"
|
||||
"价格: %{customdata[1]:.2f}<br>"
|
||||
"手续费: %{customdata[2]:.6f}<br>"
|
||||
"盈亏: %{customdata[3]:.4f}<br>"
|
||||
"本金: %{customdata[6]:.0f}<br>"
|
||||
"时间: %{customdata[4]}<br>"
|
||||
"订单ID: %{customdata[5]}<extra></extra>"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def add_trade_connections(fig, connections_df, odf):
|
||||
"""添加开平仓连接线"""
|
||||
if connections_df.empty:
|
||||
return
|
||||
|
||||
# 为盈利和亏损的连接线分别创建轨迹
|
||||
profit_lines = []
|
||||
loss_lines = []
|
||||
|
||||
for _, conn in connections_df.iterrows():
|
||||
# 获取开仓点和平仓点的坐标
|
||||
open_point = odf[odf["order_id"] == conn["open_order_id"]].iloc[0]
|
||||
close_point = odf[odf["order_id"] == conn["close_order_id"]].iloc[0]
|
||||
|
||||
line_data = {
|
||||
"x": [open_point["时间_x"], close_point["时间_x"]],
|
||||
"y": [open_point["价格"], close_point["价格"]],
|
||||
"pnl": conn["pnl"],
|
||||
"type": conn["type"],
|
||||
"open_order_id": conn["open_order_id"],
|
||||
"close_order_id": conn["close_order_id"]
|
||||
}
|
||||
|
||||
if conn["pnl"] >= 0:
|
||||
profit_lines.append(line_data)
|
||||
else:
|
||||
loss_lines.append(line_data)
|
||||
|
||||
# 添加盈利连接线(绿色)
|
||||
if profit_lines:
|
||||
x_profit = []
|
||||
y_profit = []
|
||||
customdata_profit = []
|
||||
|
||||
for line in profit_lines:
|
||||
x_profit.extend(line["x"])
|
||||
y_profit.extend(line["y"])
|
||||
x_profit.append(None)
|
||||
y_profit.append(None)
|
||||
|
||||
# 为每个点添加自定义数据
|
||||
customdata_profit.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_profit.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_profit.append(None)
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=x_profit,
|
||||
y=y_profit,
|
||||
mode="lines",
|
||||
name="盈利订单",
|
||||
line=dict(color="rgba(46, 204, 113, 0.7)", width=LINE_WIDTH),
|
||||
hoverinfo="text",
|
||||
text=[f"盈利: {d[2]:.2f}" if d else None for d in customdata_profit],
|
||||
customdata=customdata_profit,
|
||||
hovertemplate=(
|
||||
"<b>%{text}</b><br>"
|
||||
"类型: %{customdata[3]}<br>"
|
||||
"开仓订单ID: %{customdata[0]}<br>"
|
||||
"平仓订单ID: %{customdata[1]}<extra></extra>"
|
||||
)
|
||||
))
|
||||
|
||||
# 添加亏损连接线(红色)
|
||||
if loss_lines:
|
||||
x_loss = []
|
||||
y_loss = []
|
||||
customdata_loss = []
|
||||
|
||||
for line in loss_lines:
|
||||
x_loss.extend(line["x"])
|
||||
y_loss.extend(line["y"])
|
||||
x_loss.append(None)
|
||||
y_loss.append(None)
|
||||
|
||||
# 为每个点添加自定义数据
|
||||
customdata_loss.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_loss.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_loss.append(None)
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=x_loss,
|
||||
y=y_loss,
|
||||
mode="lines",
|
||||
name="亏损订单",
|
||||
line=dict(color="rgba(231, 76, 60, 0.7)", width=LINE_WIDTH),
|
||||
hoverinfo="text",
|
||||
text=[f"亏损: {abs(d[2]):.2f}" if d else None for d in customdata_loss],
|
||||
customdata=customdata_loss,
|
||||
hovertemplate=(
|
||||
"<b>%{text}</b><br>"
|
||||
"类型: %{customdata[3]}<br>"
|
||||
"开仓订单ID: %{customdata[0]}<br>"
|
||||
"平仓订单ID: %{customdata[1]}<extra></extra>"
|
||||
)
|
||||
))
|
||||
|
||||
|
||||
def generate_chart(kdf, odf, pnl_df, cum_realized, connections_df):
|
||||
"""生成K线图与交易标注"""
|
||||
fig = go.Figure()
|
||||
|
||||
# K线主图
|
||||
fig.add_trace(go.Candlestick(
|
||||
x=kdf["time"],
|
||||
open=kdf["open"],
|
||||
high=kdf["high"],
|
||||
low=kdf["low"],
|
||||
close=kdf["close"],
|
||||
name="K线",
|
||||
increasing_line_color="#2ecc71",
|
||||
decreasing_line_color="#e74c3c"
|
||||
))
|
||||
|
||||
# 添加交易点
|
||||
trade_types = [
|
||||
(odf[odf["side"] == "long_open"], "开多", "#2ecc71", "triangle-up"),
|
||||
(odf[odf["side"] == "long_close"], "平多", "#27ae60", "circle"),
|
||||
(odf[odf["side"] == "short_open"], "开空", "#e74c3c", "triangle-down"),
|
||||
(odf[odf["side"] == "short_close"], "平空", "#c0392b", "x")
|
||||
]
|
||||
|
||||
for data, name, color, symbol in trade_types:
|
||||
trace = create_trade_scatter(data, name, color, symbol)
|
||||
if trace:
|
||||
fig.add_trace(trace)
|
||||
|
||||
# 添加开平仓连接线
|
||||
add_trade_connections(fig, connections_df, odf)
|
||||
|
||||
# 计算时间范围,确保所有点都显示在图表中
|
||||
all_times = pd.concat([kdf["time"], odf["时间_x"]])
|
||||
min_time = all_times.min() - pd.Timedelta(minutes=10)
|
||||
max_time = all_times.max() + pd.Timedelta(minutes=10)
|
||||
|
||||
# 计算价格范围,确保所有点都显示在图表中
|
||||
min_price = min(kdf["low"].min(), odf["价格"].min()) * 0.99
|
||||
max_price = max(kdf["high"].max(), odf["价格"].max()) * 1.01
|
||||
|
||||
# 布局配置 - 更宽更扁的图表
|
||||
fig.update_layout(
|
||||
xaxis_title="时间",
|
||||
yaxis_title="价格 (USDT)",
|
||||
legend=dict(
|
||||
orientation="h",
|
||||
yanchor="bottom",
|
||||
y=1.02,
|
||||
xanchor="left",
|
||||
x=0,
|
||||
font=dict(size=FONT_SIZE, color=TEXT_COLOR) # 图例文字使用黑色
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
gridcolor="rgba(128, 128, 128, 0.2)",
|
||||
range=[min_time, max_time], # 设置时间范围
|
||||
title_font=dict(color=TEXT_COLOR), # 坐标轴标题使用黑色
|
||||
tickfont=dict(color=TEXT_COLOR) # 刻度标签使用黑色
|
||||
),
|
||||
yaxis=dict(
|
||||
gridcolor="rgba(128, 128, 128, 0.2)",
|
||||
range=[min_price, max_price], # 设置价格范围
|
||||
title_font=dict(color=TEXT_COLOR), # 坐标轴标题使用黑色
|
||||
tickfont=dict(color=TEXT_COLOR) # 刻度标签使用黑色
|
||||
),
|
||||
hovermode="x unified",
|
||||
hoverlabel=dict(
|
||||
namelength=-1,
|
||||
bgcolor="rgba(255, 255, 255, 0.9)",
|
||||
font_size=FONT_SIZE,
|
||||
font_color=TEXT_COLOR # 悬停标签文字使用黑色
|
||||
),
|
||||
margin=dict(l=50, r=50, t=80, b=50),
|
||||
plot_bgcolor="rgba(240, 240, 240, 1)",
|
||||
width=CHART_WIDTH, # 使用配置的宽度
|
||||
height=CHART_HEIGHT, # 使用配置的高度
|
||||
font=dict(size=FONT_SIZE, color=TEXT_COLOR), # 全局字体大小和颜色
|
||||
|
||||
# 增强交互性配置
|
||||
dragmode="pan", # 默认拖拽模式为平移
|
||||
clickmode="event+select", # 点击模式
|
||||
selectdirection="h", # 水平选择方向
|
||||
modebar=dict(
|
||||
orientation="h", # 水平方向工具栏
|
||||
bgcolor="rgba(255, 255, 255, 0.7)", # 半透明背景
|
||||
color="rgba(0, 0, 0, 0.7)", # 图标颜色
|
||||
activecolor="rgba(0, 0, 0, 0.9)" # 激活图标颜色
|
||||
)
|
||||
)
|
||||
|
||||
# 添加模式栏按钮
|
||||
fig.update_layout(
|
||||
modebar_add=[
|
||||
"zoom2d",
|
||||
"pan2d",
|
||||
"select2d",
|
||||
"lasso2d",
|
||||
"zoomIn2d",
|
||||
"zoomOut2d",
|
||||
"autoScale2d",
|
||||
"resetScale2d",
|
||||
"toImage"
|
||||
]
|
||||
)
|
||||
|
||||
# 配置缩放行为 - 确保滚轮缩放正常工作
|
||||
fig.update_xaxes(
|
||||
autorange=False,
|
||||
fixedrange=False, # 允许缩放
|
||||
constrain="domain", # 约束在域内
|
||||
rangeslider=dict(visible=False) # 禁用范围滑块
|
||||
)
|
||||
|
||||
fig.update_yaxes(
|
||||
autorange=False,
|
||||
fixedrange=False, # 允许缩放
|
||||
scaleanchor="x", # 保持纵横比
|
||||
scaleratio=1, # 缩放比例
|
||||
constrain="domain" # 约束在域内
|
||||
)
|
||||
|
||||
# 保存并打开结果 - 启用滚轮缩放
|
||||
fig.write_html(
|
||||
OUTPUT_HTML,
|
||||
include_plotlyjs="cdn",
|
||||
auto_open=True,
|
||||
config={
|
||||
'scrollZoom': True, # 启用滚轮缩放
|
||||
'displayModeBar': True, # 显示工具栏
|
||||
'displaylogo': False, # 隐藏Plotly标志
|
||||
'responsive': True # 响应式布局
|
||||
}
|
||||
)
|
||||
print(f"图表已生成: {OUTPUT_HTML}")
|
||||
|
||||
# 返回盈亏详情
|
||||
if not pnl_df.empty:
|
||||
pnl_df.to_csv("pnl_details.csv", index=False)
|
||||
print(f"盈亏详情已保存: pnl_details.csv")
|
||||
|
||||
if not connections_df.empty:
|
||||
connections_df.to_csv("trade_connections.csv", index=False)
|
||||
print(f"订单连接关系已保存: trade_connections.csv")
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
# ========== 主执行流程 ==========
|
||||
def main():
|
||||
print("开始处理数据...")
|
||||
|
||||
# 加载数据
|
||||
kdf, median_step = load_kline_data()
|
||||
odf = load_order_data()
|
||||
|
||||
print(f"加载K线数据: {len(kdf)}条")
|
||||
print(f"加载订单数据: {len(odf)}条")
|
||||
|
||||
# 对齐交易时间
|
||||
odf = align_trades_to_candles(kdf, odf, median_step)
|
||||
|
||||
# 检查时间范围
|
||||
kline_min_time = kdf["time"].min()
|
||||
kline_max_time = kdf["time"].max()
|
||||
order_min_time = odf["时间"].min()
|
||||
order_max_time = odf["时间"].max()
|
||||
|
||||
print(f"K线时间范围: {kline_min_time} 至 {kline_max_time}")
|
||||
print(f"订单时间范围: {order_min_time} 至 {order_max_time}")
|
||||
|
||||
# 检查是否有订单在K线时间范围外
|
||||
outside_orders = odf[(odf["时间"] < kline_min_time) | (odf["时间"] > kline_max_time)]
|
||||
if not outside_orders.empty:
|
||||
print(f"警告: 有 {len(outside_orders)} 个订单在K线时间范围外")
|
||||
print(outside_orders[["时间", "方向", "价格"]])
|
||||
|
||||
# 计算盈亏和订单连接关系
|
||||
pnl_df, cum_realized, connections_df = calculate_pnl(odf)
|
||||
print(f"累计已实现盈亏: {cum_realized:.2f} USDT")
|
||||
print(f"订单连接关系: {len(connections_df)}条")
|
||||
|
||||
# 生成图表
|
||||
generate_chart(kdf, odf, pnl_df, cum_realized, connections_df)
|
||||
print("处理完成")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
main()
|
||||
713
价格展示/test2.py
713
价格展示/test2.py
@@ -1,713 +0,0 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import warnings
|
||||
import os
|
||||
import uuid
|
||||
|
||||
# ========== 配置 ==========
|
||||
KLINE_XLSX = "kline_data.xlsx" # K线数据文件名
|
||||
ORDERS_XLSX = "做市策略.xls" # 订单数据文件名
|
||||
OUTPUT_HTML = "kline_with_trades.html"
|
||||
SYMBOL = "ETH-USDT" # 交易对筛选
|
||||
|
||||
# 时间与对齐配置
|
||||
ORDERS_TIME_IS_LOCAL_ASIA_SH = True # 订单时间是否为东八区时间
|
||||
SNAP_TRADES_TO_NEAREST_CANDLE = True # 对齐交易点到最近的K线时间
|
||||
SNAP_TOLERANCE_MULTIPLIER = 1.5 # 对齐容忍度倍数
|
||||
|
||||
# 图表尺寸配置 - 更宽更扁
|
||||
CHART_WIDTH = 2200 # 更宽的图表
|
||||
CHART_HEIGHT = 600 # 更矮的图表
|
||||
FONT_SIZE = 12 # 字体大小
|
||||
ANNOTATION_FONT_SIZE = 10 # 标注字体大小
|
||||
MARKER_SIZE = 10 # 标记大小
|
||||
LINE_WIDTH = 1.5 # 连接线宽度
|
||||
|
||||
# 颜色配置 - 所有文本使用黑色
|
||||
TEXT_COLOR = "black" # 所有文本使用黑色
|
||||
TEXT_OFFSET = 10 # 文本偏移量(像素)
|
||||
|
||||
|
||||
# ========== 工具函数 ==========
|
||||
def parse_numeric(x):
|
||||
"""高效解析数值类型,支持多种格式"""
|
||||
if pd.isna(x):
|
||||
return np.nan
|
||||
try:
|
||||
# 尝试直接转换(大多数情况)
|
||||
return float(x)
|
||||
except:
|
||||
# 处理特殊格式
|
||||
s = str(x).replace(",", "").replace("USDT", "").replace("张", "").strip()
|
||||
if s.endswith("%"):
|
||||
s = s[:-1]
|
||||
return float(s) if s else np.nan
|
||||
|
||||
|
||||
def epoch_to_dt(x):
|
||||
"""将时间戳转换为上海时区时间"""
|
||||
try:
|
||||
return pd.to_datetime(int(x), unit="s", utc=True).tz_convert("Asia/Shanghai")
|
||||
except:
|
||||
return pd.NaT
|
||||
|
||||
|
||||
def zh_side(row):
|
||||
"""解析交易方向"""
|
||||
direction = str(row.get("方向", "")).strip()
|
||||
if "开多" in direction: return "long_open"
|
||||
if "平多" in direction: return "long_close"
|
||||
if "开空" in direction: return "short_open"
|
||||
if "平空" in direction: return "short_close"
|
||||
return "unknown"
|
||||
|
||||
|
||||
# ========== 数据加载与预处理 ==========
|
||||
def load_kline_data():
|
||||
"""加载并预处理K线数据"""
|
||||
if not os.path.exists(KLINE_XLSX):
|
||||
raise FileNotFoundError(f"K线数据文件不存在: {KLINE_XLSX}")
|
||||
|
||||
kdf = pd.read_excel(KLINE_XLSX, dtype=str)
|
||||
kdf.columns = [str(c).strip().lower() for c in kdf.columns]
|
||||
|
||||
# 验证必要列
|
||||
required_cols = {"id", "open", "close", "low", "high"}
|
||||
missing = required_cols - set(kdf.columns)
|
||||
if missing:
|
||||
raise ValueError(f"K线表缺少列: {missing}")
|
||||
|
||||
# 时间转换 - 确保id是秒级时间戳
|
||||
kdf["time"] = kdf["id"].apply(epoch_to_dt)
|
||||
|
||||
# 数值转换(向量化操作提升性能)
|
||||
for col in ["open", "close", "low", "high"]:
|
||||
kdf[col] = pd.to_numeric(kdf[col].apply(parse_numeric), errors="coerce")
|
||||
|
||||
# 清理无效数据
|
||||
kdf = kdf.dropna(subset=["time", "open", "close", "low", "high"])
|
||||
kdf = kdf.sort_values("time").reset_index(drop=True)
|
||||
|
||||
# 计算K线周期(用于交易点对齐)
|
||||
if len(kdf) >= 3:
|
||||
median_step = kdf["time"].diff().median()
|
||||
else:
|
||||
median_step = pd.Timedelta(minutes=1)
|
||||
|
||||
return kdf, median_step
|
||||
|
||||
|
||||
def load_order_data():
|
||||
"""加载并预处理订单数据"""
|
||||
if not os.path.exists(ORDERS_XLSX):
|
||||
raise FileNotFoundError(f"订单数据文件不存在: {ORDERS_XLSX}")
|
||||
|
||||
odf = pd.read_excel(ORDERS_XLSX, dtype=str)
|
||||
|
||||
# 验证必要列
|
||||
need_order_cols = ["时间", "交易对", "方向", "模式", "数量(张)", "成交价", "交易额", "消耗手续费", "用户盈亏"]
|
||||
missing = set(need_order_cols) - set(odf.columns)
|
||||
if missing:
|
||||
raise ValueError(f"订单表缺少列: {missing}")
|
||||
|
||||
# 筛选交易对
|
||||
if SYMBOL and "交易对" in odf.columns:
|
||||
odf = odf[odf["交易对"].astype(str).str.strip() == SYMBOL]
|
||||
|
||||
# 时间处理 - 确保时间格式正确
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
if ORDERS_TIME_IS_LOCAL_ASIA_SH:
|
||||
# 尝试多种格式解析时间
|
||||
odf["时间"] = pd.to_datetime(odf["时间"], errors="coerce", format="mixed")
|
||||
# 本地化为上海时区
|
||||
odf["时间"] = odf["时间"].dt.tz_localize("Asia/Shanghai", ambiguous="NaT", nonexistent="shift_forward")
|
||||
else:
|
||||
# 如果Excel时间已经是UTC
|
||||
odf["时间"] = pd.to_datetime(odf["时间"], utc=True, errors="coerce").dt.tz_convert("Asia/Shanghai")
|
||||
|
||||
# 数值转换
|
||||
numeric_cols = {
|
||||
"数量(张)": "数量",
|
||||
"成交价": "价格",
|
||||
"交易额": "交易额_num",
|
||||
"消耗手续费": "手续费",
|
||||
"用户盈亏": "盈亏"
|
||||
}
|
||||
|
||||
for src, dest in numeric_cols.items():
|
||||
odf[dest] = pd.to_numeric(odf[src].apply(parse_numeric), errors="coerce")
|
||||
|
||||
# 解析交易方向
|
||||
odf["side"] = odf.apply(zh_side, axis=1)
|
||||
|
||||
# 为每个订单生成唯一ID
|
||||
odf["order_id"] = [str(uuid.uuid4()) for _ in range(len(odf))]
|
||||
|
||||
# 计算本金(数量 * 价格)
|
||||
odf["本金"] = odf["数量"] * odf["价格"]
|
||||
|
||||
# 清理无效数据
|
||||
odf = odf.dropna(subset=["时间", "价格"])
|
||||
odf = odf.sort_values("时间").reset_index(drop=True)
|
||||
|
||||
return odf
|
||||
|
||||
|
||||
def align_trades_to_candles(kdf, odf, median_step):
|
||||
"""将交易点对齐到最近的K线时间"""
|
||||
if not SNAP_TRADES_TO_NEAREST_CANDLE or kdf.empty or odf.empty:
|
||||
return odf.assign(时间_x=odf["时间"])
|
||||
|
||||
snap_tolerance = pd.Timedelta(seconds=max(1, int(median_step.total_seconds() * SNAP_TOLERANCE_MULTIPLIER)))
|
||||
|
||||
# 使用merge_asof高效对齐 - 使用方向为'backward'确保交易点对齐到前一个K线
|
||||
anchor = kdf[["time"]].copy().rename(columns={"time": "k_time"})
|
||||
odf_sorted = odf.sort_values("时间")
|
||||
|
||||
# 关键优化:使用'backward'方向确保交易点对齐到前一个K线
|
||||
aligned = pd.merge_asof(
|
||||
odf_sorted,
|
||||
anchor,
|
||||
left_on="时间",
|
||||
right_on="k_time",
|
||||
direction="backward", # 使用'backward'确保交易点对齐到前一个K线
|
||||
tolerance=snap_tolerance
|
||||
)
|
||||
|
||||
# 保留原始时间作为参考
|
||||
aligned["原始时间"] = aligned["时间"]
|
||||
aligned["时间_x"] = aligned["k_time"].fillna(aligned["时间"])
|
||||
|
||||
return aligned
|
||||
|
||||
|
||||
# ========== 持仓跟踪与盈亏计算 ==========
|
||||
class PositionTracker:
|
||||
"""FIFO持仓跟踪器,支持订单走向可视化"""
|
||||
|
||||
def __init__(self):
|
||||
self.long_lots = [] # (数量, 价格, 时间, 手续费, 订单ID)
|
||||
self.short_lots = [] # (数量, 价格, 时间, 手续费, 订单ID)
|
||||
self.realized_pnl = 0.0
|
||||
self.history = [] # 记录所有交易历史
|
||||
self.trade_connections = [] # 记录开平仓连接关系
|
||||
|
||||
def open_long(self, qty, price, time, fee, order_id):
|
||||
"""开多仓"""
|
||||
if qty > 1e-9:
|
||||
self.long_lots.append((qty, price, time, fee, order_id))
|
||||
|
||||
def close_long(self, qty, price, time, fee, order_id):
|
||||
"""平多仓"""
|
||||
remaining = qty
|
||||
local_pnl = 0.0
|
||||
connections = [] # 本次平仓的连接关系
|
||||
|
||||
while remaining > 1e-9 and self.long_lots:
|
||||
lot_qty, lot_price, lot_time, lot_fee, open_order_id = self.long_lots[0]
|
||||
take = min(lot_qty, remaining)
|
||||
pnl = (price - lot_price) * take
|
||||
local_pnl += pnl
|
||||
lot_qty -= take
|
||||
remaining -= take
|
||||
|
||||
# 记录开平仓连接
|
||||
connection = {
|
||||
"open_time": lot_time,
|
||||
"close_time": time,
|
||||
"open_price": lot_price,
|
||||
"close_price": price,
|
||||
"qty": take,
|
||||
"pnl": pnl,
|
||||
"type": "long",
|
||||
"open_order_id": open_order_id,
|
||||
"close_order_id": order_id
|
||||
}
|
||||
self.trade_connections.append(connection)
|
||||
connections.append(connection)
|
||||
|
||||
# 记录平仓详情
|
||||
self.history.append({
|
||||
"开仓时间": lot_time,
|
||||
"平仓时间": time,
|
||||
"数量": take,
|
||||
"开仓价": lot_price,
|
||||
"平仓价": price,
|
||||
"盈亏": pnl,
|
||||
"类型": "平多",
|
||||
"开仓订单ID": open_order_id,
|
||||
"平仓订单ID": order_id
|
||||
})
|
||||
|
||||
if lot_qty <= 1e-9:
|
||||
self.long_lots.pop(0)
|
||||
else:
|
||||
self.long_lots[0] = (lot_qty, lot_price, lot_time, lot_fee, open_order_id)
|
||||
|
||||
local_pnl -= fee
|
||||
self.realized_pnl += local_pnl
|
||||
return local_pnl, connections
|
||||
|
||||
def open_short(self, qty, price, time, fee, order_id):
|
||||
"""开空仓"""
|
||||
if qty > 1e-9:
|
||||
self.short_lots.append((qty, price, time, fee, order_id))
|
||||
|
||||
def close_short(self, qty, price, time, fee, order_id):
|
||||
"""平空仓"""
|
||||
remaining = qty
|
||||
local_pnl = 0.0
|
||||
connections = [] # 本次平仓的连接关系
|
||||
|
||||
while remaining > 1e-9 and self.short_lots:
|
||||
lot_qty, lot_price, lot_time, lot_fee, open_order_id = self.short_lots[0]
|
||||
take = min(lot_qty, remaining)
|
||||
pnl = (lot_price - price) * take
|
||||
local_pnl += pnl
|
||||
lot_qty -= take
|
||||
remaining -= take
|
||||
|
||||
# 记录开平仓连接
|
||||
connection = {
|
||||
"open_time": lot_time,
|
||||
"close_time": time,
|
||||
"open_price": lot_price,
|
||||
"close_price": price,
|
||||
"q极": take,
|
||||
"pnl": pnl,
|
||||
"type": "short",
|
||||
"open_order_id": open_order_id,
|
||||
"close_order_id": order_id
|
||||
}
|
||||
self.trade_connections.append(connection)
|
||||
connections.append(connection)
|
||||
|
||||
# 记录平仓详情
|
||||
self.history.append({
|
||||
"开仓时间": lot_time,
|
||||
"平仓时间": time,
|
||||
"数量": take,
|
||||
"开仓价": lot_price,
|
||||
"平仓价": price,
|
||||
"盈亏": pnl,
|
||||
"类型": "平空",
|
||||
"开仓订单ID": open_order_id,
|
||||
"平仓订单ID": order_id
|
||||
})
|
||||
|
||||
if lot_qty <= 1e-9:
|
||||
self.short_lots.pop(0)
|
||||
else:
|
||||
self.short_lots[0] = (lot_qty, lot_price, lot_time, lot_fee, open_order_id)
|
||||
|
||||
local_pnl -= fee
|
||||
self.realized_pnl += local_pnl
|
||||
return local_pnl, connections
|
||||
|
||||
|
||||
def calculate_pnl(odf):
|
||||
"""计算持仓盈亏和订单连接关系"""
|
||||
tracker = PositionTracker()
|
||||
all_connections = []
|
||||
|
||||
for idx, r in odf.iterrows():
|
||||
qty = r["数量"]
|
||||
price = r["价格"]
|
||||
ts = r["时间"]
|
||||
fee = r["手续费"]
|
||||
side = r["side"]
|
||||
order_id = r["order_id"]
|
||||
|
||||
if side == "long_open":
|
||||
tracker.open_long(qty, price, ts, fee, order_id)
|
||||
elif side == "long_close":
|
||||
_, connections = tracker.close_long(qty, price, ts, fee, order_id)
|
||||
all_connections.extend(connections)
|
||||
elif side == "short_open":
|
||||
tracker.open_short(qty, price, ts, fee, order_id)
|
||||
elif side == "short_close":
|
||||
_, connections = tracker.close_short(qty, price, ts, fee, order_id)
|
||||
all_connections.extend(connections)
|
||||
|
||||
# 创建盈亏DataFrame
|
||||
if tracker.history:
|
||||
pnl_df = pd.DataFrame(tracker.history)
|
||||
# 添加对齐后的时间
|
||||
pnl_df["时间_x"] = pnl_df["平仓时间"].apply(
|
||||
lambda x: odf.loc[odf["时间"] == x, "时间_x"].values[0] if not odf.empty else x
|
||||
)
|
||||
else:
|
||||
pnl_df = pd.DataFrame()
|
||||
|
||||
# 创建连接关系DataFrame
|
||||
connections_df = pd.DataFrame(all_connections) if all_connections else pd.DataFrame()
|
||||
|
||||
return pnl_df, tracker.realized_pnl, connections_df
|
||||
|
||||
|
||||
# ========== 可视化 ==========
|
||||
def create_trade_scatter(df, name, color, symbol):
|
||||
"""创建交易点散点图"""
|
||||
if df.empty:
|
||||
return None
|
||||
|
||||
# 为不同类型的交易点创建不同的文本标签
|
||||
if name == "开多":
|
||||
text = "开多\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["本金"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "平多":
|
||||
text = "平多\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["盈亏"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "开空":
|
||||
text = "开空\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["本金"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "平空":
|
||||
text = "平空\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["盈亏"].apply(lambda x: f"{x:.0f}")
|
||||
else:
|
||||
text = name
|
||||
|
||||
return go.Scatter(
|
||||
x=df["时间_x"],
|
||||
y=df["价格"],
|
||||
mode="markers+text",
|
||||
name=name,
|
||||
text=text,
|
||||
textposition="middle right", # 文本放在右侧中间位置
|
||||
textfont=dict(size=ANNOTATION_FONT_SIZE, color=TEXT_COLOR), # 使用黑色文本
|
||||
marker=dict(
|
||||
size=MARKER_SIZE,
|
||||
color=color,
|
||||
symbol=symbol,
|
||||
line=dict(width=1.5, color="black")
|
||||
),
|
||||
customdata=np.stack([
|
||||
df["数量"].to_numpy(),
|
||||
df["价格"].to_numpy(),
|
||||
df["手续费"].to_numpy(),
|
||||
df.get("盈亏", np.nan).to_numpy(),
|
||||
df.get("原始时间", df["时间"]).dt.strftime("%Y-%m-%d %H:%M:%S").to_numpy(),
|
||||
df["order_id"].to_numpy(),
|
||||
df["本金"].to_numpy()
|
||||
], axis=-1),
|
||||
hovertemplate=(
|
||||
f"<b>{name}</b><br>"
|
||||
"数量: %{customdata[0]:.0f}张<br>"
|
||||
"价格: %{customdata[1]:.2f}<br>"
|
||||
"手续费: %{customdata[2]:.6f}<br>"
|
||||
"盈亏: %{customdata[3]:.4f}<br>"
|
||||
"本金: %{customdata[6]:.0f}<br>"
|
||||
"时间: %{customdata[4]}<br>"
|
||||
"订单ID: %{customdata[5]}<extra></extra>"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def add_trade_connections(fig, connections_df, odf):
|
||||
"""添加开平仓连接线"""
|
||||
if connections_df.empty:
|
||||
return
|
||||
|
||||
# 为盈利和亏损的连接线分别创建轨迹
|
||||
profit_lines = []
|
||||
loss_lines = []
|
||||
|
||||
for _, conn in connections_df.iterrows():
|
||||
# 获取开仓点和平仓点的坐标
|
||||
open_point = odf[odf["order_id"] == conn["open_order_id"]].iloc[0]
|
||||
close_point = odf[odf["order_id"] == conn["close_order_id"]].iloc[0]
|
||||
|
||||
line_data = {
|
||||
"x": [open_point["时间_x"], close_point["时间_x"]],
|
||||
"y": [open_point["价格"], close_point["价格"]],
|
||||
"pnl": conn["pnl"],
|
||||
"type": conn["type"],
|
||||
"open_order_id": conn["open_order_id"],
|
||||
"close_order_id": conn["close_order_id"]
|
||||
}
|
||||
|
||||
if conn["pnl"] >= 0:
|
||||
profit_lines.append(line_data)
|
||||
else:
|
||||
loss_lines.append(line_data)
|
||||
|
||||
# 添加盈利连接线(绿色)
|
||||
if profit_lines:
|
||||
x_profit = []
|
||||
y_profit = []
|
||||
customdata_profit = []
|
||||
|
||||
for line in profit_lines:
|
||||
x_profit.extend(line["x"])
|
||||
y_profit.extend(line["y"])
|
||||
x_profit.append(None)
|
||||
y_profit.append(None)
|
||||
|
||||
# 为每个点添加自定义数据
|
||||
customdata_profit.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_profit.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_profit.append(None)
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=x_profit,
|
||||
y=y_profit,
|
||||
mode="lines",
|
||||
name="盈利订单",
|
||||
line=dict(color="rgba(46, 204, 113, 0.7)", width=LINE_WIDTH),
|
||||
hoverinfo="text",
|
||||
text=[f"盈利: {d[2]:.2f}" if d else None for d in customdata_profit],
|
||||
customdata=customdata_profit,
|
||||
hovertemplate=(
|
||||
"<b>%{text}</b><br>"
|
||||
"类型: %{customdata[3]}<br>"
|
||||
"开仓订单ID: %{customdata[0]}<br>"
|
||||
"平仓订单ID: %{customdata[1]}<extra></extra>"
|
||||
)
|
||||
))
|
||||
|
||||
# 添加亏损连接线(红色)
|
||||
if loss_lines:
|
||||
x_loss = []
|
||||
y_loss = []
|
||||
customdata_loss = []
|
||||
|
||||
for line in loss_lines:
|
||||
x_loss.extend(line["x"])
|
||||
y_loss.extend(line["y"])
|
||||
x_loss.append(None)
|
||||
y_loss.append(None)
|
||||
|
||||
# 为每个点添加自定义数据
|
||||
customdata_loss.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_loss.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_loss.append(None)
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=x_loss,
|
||||
y=y_loss,
|
||||
mode="lines",
|
||||
name="亏损订单",
|
||||
line=dict(color="rgba(231, 76, 60, 0.7)", width=LINE_WIDTH),
|
||||
hoverinfo="text",
|
||||
text=[f"亏损: {abs(d[2]):.2f}" if d else None for d in customdata_loss],
|
||||
customdata=customdata_loss,
|
||||
hovertemplate=(
|
||||
"<b>%{text}</b><br>"
|
||||
"类型: %{customdata[3]}<br>"
|
||||
"开仓订单ID: %{customdata[0]}<极>"
|
||||
"平仓订单ID: %{customdata[1]}<extra></extra>"
|
||||
)
|
||||
))
|
||||
|
||||
|
||||
def generate_chart(kdf, odf, pnl_df, cum_realized, connections_df):
|
||||
"""生成K线图与交易标注"""
|
||||
fig = go.Figure()
|
||||
|
||||
# K线主图
|
||||
fig.add_trace(go.Candlestick(
|
||||
x=kdf["time"],
|
||||
open=kdf["open"],
|
||||
high=kdf["high"],
|
||||
low=kdf["low"],
|
||||
close=kdf["close"],
|
||||
name="K线",
|
||||
increasing_line_color="#2ecc71",
|
||||
decreasing_line_color="#e74c3c"
|
||||
))
|
||||
|
||||
# 添加交易点
|
||||
trade_types = [
|
||||
(odf[odf["side"] == "long_open"], "开多", "#2ecc71", "triangle-up"),
|
||||
(odf[odf["side"] == "long_close"], "平多", "#27ae60", "circle"),
|
||||
(odf[odf["side"] == "short_open"], "开空", "#e74c3c", "triangle-down"),
|
||||
(odf[odf["side"] == "short_close"], "平空", "#c0392b", "x")
|
||||
]
|
||||
|
||||
for data, name, color, symbol in trade_types:
|
||||
trace = create_trade_scatter(data, name, color, symbol)
|
||||
if trace:
|
||||
fig.add_trace(trace)
|
||||
|
||||
# 添加开平仓连接线
|
||||
add_trade_connections(fig, connections_df, odf)
|
||||
|
||||
# 计算时间范围,确保所有点都显示在图表中
|
||||
all_times = pd.concat([kdf["time"], odf["时间_x"]])
|
||||
min_time = all_times.min() - pd.Timedelta(minutes=10)
|
||||
max_time = all_times.max() + pd.Timedelta(minutes=10)
|
||||
|
||||
# 计算价格范围,确保所有点都显示在图表中
|
||||
min_price = min(kdf["low"].min(), odf["价格"].min()) * 0.99
|
||||
max_price = max(kdf["high"].max(), odf["价格"].max()) * 1.01
|
||||
|
||||
# 布局配置 - 更宽更扁的图表
|
||||
fig.update_layout(
|
||||
xaxis_title="时间",
|
||||
yaxis_title="价格 (USDT)",
|
||||
legend=dict(
|
||||
orientation="h",
|
||||
yanchor="bottom",
|
||||
y=1.02,
|
||||
xanchor="left",
|
||||
x=0,
|
||||
font=dict(size=FONT_SIZE, color=TEXT_COLOR) # 图例文字使用黑色
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
gridcolor="rgba(128, 128, 128, 0.2)",
|
||||
range=[min_time, max_time], # 设置时间范围
|
||||
title_font=dict(color=TEXT_COLOR), # 坐标轴标题使用黑色
|
||||
tickfont=dict(color=TEXT_COLOR) # 刻度标签使用黑色
|
||||
),
|
||||
yaxis=dict(
|
||||
gridcolor="rgba(128, 128, 128, 0.2)",
|
||||
range=[min_price, max_price], # 设置价格范围
|
||||
title_font=dict(color=TEXT_COLOR), # 坐标轴标题使用黑色
|
||||
tickfont=dict(color=TEXT_COLOR) # 刻度标签使用黑色
|
||||
),
|
||||
hovermode="x unified",
|
||||
hoverlabel=dict(
|
||||
namelength=-1,
|
||||
bgcolor="rgba(255, 255, 255, 0.9)",
|
||||
font_size=FONT_SIZE,
|
||||
font_color=TEXT_COLOR # 悬停标签文字使用黑色
|
||||
),
|
||||
margin=dict(l=50, r=50, t=80, b=50),
|
||||
plot_bgcolor="rgba(240, 240, 240, 1)",
|
||||
width=CHART_WIDTH, # 使用配置的宽度
|
||||
height=CHART_HEIGHT, # 使用配置的高度
|
||||
font=dict(size=FONT_SIZE, color=TEXT_COLOR), # 全局字体大小和颜色
|
||||
|
||||
# 增强交互性配置
|
||||
dragmode="pan", # 默认拖拽模式为平移
|
||||
clickmode="event+select", # 点击模式
|
||||
selectdirection="h", # 水平选择方向
|
||||
modebar=dict(
|
||||
orientation="h", # 水平方向工具栏
|
||||
bgcolor="rgba(255, 255, 255, 0.7)", # 半透明背景
|
||||
color="rgba(0, 0, 0, 0.7)", # 图标颜色
|
||||
activecolor="rgba(0, 0, 0, 0.9)" # 激活图标颜色
|
||||
)
|
||||
)
|
||||
|
||||
# 添加模式栏按钮
|
||||
fig.update_layout(
|
||||
modebar_add=[
|
||||
"zoom2d",
|
||||
"pan2d",
|
||||
"select2d",
|
||||
"lasso2d",
|
||||
"zoomIn2d",
|
||||
"zoomOut2d",
|
||||
"autoScale2d",
|
||||
"resetScale2d",
|
||||
"toImage"
|
||||
]
|
||||
)
|
||||
|
||||
# 配置缩放行为 - 确保滚轮缩放正常工作
|
||||
fig.update_xaxes(
|
||||
autorange=False,
|
||||
fixedrange=False, # 允许缩放
|
||||
constrain="domain", # 约束在域内
|
||||
rangeslider=dict(visible=False) # 禁用范围滑块
|
||||
)
|
||||
|
||||
fig.update_yaxes(
|
||||
autorange=False,
|
||||
fixedrange=False, # 允许缩放
|
||||
scaleanchor="x", # 保持纵横比
|
||||
scaleratio=1, # 缩放比例
|
||||
constrain="domain" # 约束在域内
|
||||
)
|
||||
|
||||
# 保存并打开结果 - 启用滚轮缩放
|
||||
fig.write_html(
|
||||
OUTPUT_HTML,
|
||||
include_plotlyjs="cdn",
|
||||
auto_open=True,
|
||||
config={
|
||||
'scrollZoom': True, # 启用滚轮缩放
|
||||
'displayModeBar': True, # 显示工具栏
|
||||
'displaylogo': False, # 隐藏Plotly标志
|
||||
'responsive': True # 响应式布局
|
||||
}
|
||||
)
|
||||
print(f"图表已生成: {OUTPUT_HTML}")
|
||||
|
||||
# 返回盈亏详情
|
||||
if not pnl_df.empty:
|
||||
pnl_df.to_csv("pnl_details.csv", index=False)
|
||||
print(f"盈亏详情已保存: pnl_details.csv")
|
||||
|
||||
if not connections_df.empty:
|
||||
connections_df.to_csv("trade_connections.csv", index=False)
|
||||
print(f"订单连接关系已保存: trade_connections.csv")
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
# ========== 主执行流程 ==========
|
||||
def main():
|
||||
print("开始处理数据...")
|
||||
|
||||
# 加载数据
|
||||
kdf, median_step = load_kline_data()
|
||||
odf = load_order_data()
|
||||
|
||||
print(f"加载K线数据: {len(kdf)}条")
|
||||
print(f"加载订单数据: {len(odf)}条")
|
||||
|
||||
# 对齐交易时间
|
||||
odf = align_trades_to_candles(kdf, odf, median_step)
|
||||
|
||||
# 检查时间范围
|
||||
kline_min_time = kdf["time"].min()
|
||||
kline_max_time = kdf["time"].max()
|
||||
order_min_time = odf["时间"].min()
|
||||
order_max_time = odf["时间"].max()
|
||||
|
||||
print(f"K线时间范围: {kline_min_time} 至 {kline_max_time}")
|
||||
print(f"订单时间范围: {order_min_time} 至 {order_max_time}")
|
||||
|
||||
# 检查是否有订单在K线时间范围外
|
||||
outside_orders = odf[(odf["时间"] < kline_min_time) | (odf["时间"] > kline_max_time)]
|
||||
if not outside_orders.empty:
|
||||
print(f"警告: 有 {len(outside_orders)} 个订单在K线时间范围外")
|
||||
print(outside_orders[["时间", "方向", "价格"]])
|
||||
|
||||
# 计算盈亏和订单连接关系
|
||||
pnl_df, cum_realized, connections_df = calculate_pnl(odf)
|
||||
print(f"累计已实现盈亏: {cum_realized:.2f} USDT")
|
||||
print(f"订单连接关系: {len(connections_df)}条")
|
||||
|
||||
# 生成图表
|
||||
generate_chart(kdf, odf, pnl_df, cum_realized, connections_df)
|
||||
print("处理完成")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
main()
|
||||
713
价格展示/test3.py
713
价格展示/test3.py
@@ -1,713 +0,0 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import warnings
|
||||
import os
|
||||
import uuid
|
||||
|
||||
# ========== 配置 ==========
|
||||
KLINE_XLSX = "kline_data.xlsx" # K线数据文件名
|
||||
ORDERS_XLSX = "做市策略.xls" # 订单数据文件名
|
||||
OUTPUT_HTML = "kline_with_trades.html"
|
||||
SYMBOL = "ETH-USDT" # 交易对筛选
|
||||
|
||||
# 时间与对齐配置
|
||||
ORDERS_TIME_IS_LOCAL_ASIA_SH = True # 订单时间是否为东八区时间
|
||||
SNAP_TRADES_TO_NEAREST_CANDLE = True # 对齐交易点到最近的K线时间
|
||||
SNAP_TOLERANCE_MULTIPLIER = 1.5 # 对齐容忍度倍数
|
||||
|
||||
# 图表尺寸配置 - 更宽更扁
|
||||
CHART_WIDTH = 2200 # 更宽的图表
|
||||
CHART_HEIGHT = 600 # 更矮的图表
|
||||
FONT_SIZE = 12 # 字体大小
|
||||
ANNOTATION_FONT_SIZE = 10 # 标注字体大小
|
||||
MARKER_SIZE = 10 # 标记大小
|
||||
LINE_WIDTH = 1.5 # 连接线宽度
|
||||
|
||||
# 颜色配置 - 所有文本使用黑色
|
||||
TEXT_COLOR = "black" # 所有文本使用黑色
|
||||
TEXT_OFFSET = 10 # 文本偏移量(像素)
|
||||
|
||||
|
||||
# ========== 工具函数 ==========
|
||||
def parse_numeric(x):
|
||||
"""高效解析数值类型,支持多种格式"""
|
||||
if pd.isna(x):
|
||||
return np.nan
|
||||
try:
|
||||
# 尝试直接转换(大多数情况)
|
||||
return float(x)
|
||||
except:
|
||||
# 处理特殊格式
|
||||
s = str(x).replace(",", "").replace("USDT", "").replace("张", "").strip()
|
||||
if s.endswith("%"):
|
||||
s = s[:-1]
|
||||
return float(s) if s else np.nan
|
||||
|
||||
|
||||
def epoch_to_dt(x):
|
||||
"""将时间戳转换为上海时区时间"""
|
||||
try:
|
||||
return pd.to_datetime(int(x), unit="s", utc=True).tz_convert("Asia/Shanghai")
|
||||
except:
|
||||
return pd.NaT
|
||||
|
||||
|
||||
def zh_side(row):
|
||||
"""解析交易方向"""
|
||||
direction = str(row.get("方向", "")).strip()
|
||||
if "开多" in direction: return "long_open"
|
||||
if "平多" in direction: return "long_close"
|
||||
if "开空" in direction: return "short_open"
|
||||
if "平空" in direction: return "short_close"
|
||||
return "unknown"
|
||||
|
||||
|
||||
# ========== 数据加载与预处理 ==========
|
||||
def load_kline_data():
|
||||
"""加载并预处理K线数据"""
|
||||
if not os.path.exists(KLINE_XLSX):
|
||||
raise FileNotFoundError(f"K线数据文件不存在: {KLINE_XLSX}")
|
||||
|
||||
kdf = pd.read_excel(KLINE_XLSX, dtype=str)
|
||||
kdf.columns = [str(c).strip().lower() for c in kdf.columns]
|
||||
|
||||
# 验证必要列
|
||||
required_cols = {"id", "open", "close", "low", "high"}
|
||||
missing = required_cols - set(kdf.columns)
|
||||
if missing:
|
||||
raise ValueError(f"K线表缺少列: {missing}")
|
||||
|
||||
# 时间转换 - 确保id是秒级时间戳
|
||||
kdf["time"] = kdf["id"].apply(epoch_to_dt)
|
||||
|
||||
# 数值转换(向量化操作提升性能)
|
||||
for col in ["open", "close", "low", "high"]:
|
||||
kdf[col] = pd.to_numeric(kdf[col].apply(parse_numeric), errors="coerce")
|
||||
|
||||
# 清理无效数据
|
||||
kdf = kdf.dropna(subset=["time", "open", "close", "low", "high"])
|
||||
kdf = kdf.sort_values("time").reset_index(drop=True)
|
||||
|
||||
# 计算K线周期(用于交易点对齐)
|
||||
if len(kdf) >= 3:
|
||||
median_step = kdf["time"].diff().median()
|
||||
else:
|
||||
median_step = pd.Timedelta(minutes=1)
|
||||
|
||||
return kdf, median_step
|
||||
|
||||
|
||||
def load_order_data():
|
||||
"""加载并预处理订单数据"""
|
||||
if not os.path.exists(ORDERS_XLSX):
|
||||
raise FileNotFoundError(f"订单数据文件不存在: {ORDERS_XLSX}")
|
||||
|
||||
odf = pd.read_excel(ORDERS_XLSX, dtype=str)
|
||||
|
||||
# 验证必要列
|
||||
need_order_cols = ["时间", "交易对", "方向", "模式", "数量(张)", "成交价", "交易额", "消耗手续费", "用户盈亏"]
|
||||
missing = set(need_order_cols) - set(odf.columns)
|
||||
if missing:
|
||||
raise ValueError(f"订单表缺少列: {missing}")
|
||||
|
||||
# 筛选交易对
|
||||
if SYMBOL and "交易对" in odf.columns:
|
||||
odf = odf[odf["交易对"].astype(str).str.strip() == SYMBOL]
|
||||
|
||||
# 时间处理 - 确保时间格式正确
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
if ORDERS_TIME_IS_LOCAL_ASIA_SH:
|
||||
# 尝试多种格式解析时间
|
||||
odf["时间"] = pd.to_datetime(odf["时间"], errors="coerce", format="mixed")
|
||||
# 本地化为上海时区
|
||||
odf["时间"] = odf["时间"].dt.tz_localize("Asia/Shanghai", ambiguous="NaT", nonexistent="shift_forward")
|
||||
else:
|
||||
# 如果Excel时间已经是UTC
|
||||
odf["时间"] = pd.to_datetime(odf["时间"], utc=True, errors="coerce").dt.tz_convert("Asia/Shanghai")
|
||||
|
||||
# 数值转换
|
||||
numeric_cols = {
|
||||
"数量(张)": "数量",
|
||||
"成交价": "价格",
|
||||
"交易额": "交易额_num",
|
||||
"消耗手续费": "手续费",
|
||||
"用户盈亏": "盈亏"
|
||||
}
|
||||
|
||||
for src, dest in numeric_cols.items():
|
||||
odf[dest] = pd.to_numeric(odf[src].apply(parse_numeric), errors="coerce")
|
||||
|
||||
# 解析交易方向
|
||||
odf["side"] = odf.apply(zh_side, axis=1)
|
||||
|
||||
# 为每个订单生成唯一ID
|
||||
odf["order_id"] = [str(uuid.uuid4()) for _ in range(len(odf))]
|
||||
|
||||
# 计算本金(数量 * 价格)
|
||||
odf["本金"] = odf["数量"] * odf["价格"]
|
||||
|
||||
# 清理无效数据
|
||||
odf = odf.dropna(subset=["时间", "价格"])
|
||||
odf = odf.sort_values("时间").reset_index(drop=True)
|
||||
|
||||
return odf
|
||||
|
||||
|
||||
def align_trades_to_candles(kdf, odf, median_step):
|
||||
"""将交易点对齐到最近的K线时间"""
|
||||
if not SNAP_TRADES_TO_NEAREST_CANDLE or kdf.empty or odf.empty:
|
||||
return odf.assign(时间_x=odf["时间"])
|
||||
|
||||
snap_tolerance = pd.Timedelta(seconds=max(1, int(median_step.total_seconds() * SNAP_TOLERANCE_MULTIPLIER)))
|
||||
|
||||
# 使用merge_asof高效对齐 - 使用方向为'backward'确保交易点对齐到前一个K线
|
||||
anchor = kdf[["time"]].copy().rename(columns={"time": "k_time"})
|
||||
odf_sorted = odf.sort_values("时间")
|
||||
|
||||
# 关键优化:使用'backward'方向确保交易点对齐到前一个K线
|
||||
aligned = pd.merge_asof(
|
||||
odf_sorted,
|
||||
anchor,
|
||||
left_on="时间",
|
||||
right_on="k_time",
|
||||
direction="backward", # 使用'backward'确保交易点对齐到前一个K线
|
||||
tolerance=snap_tolerance
|
||||
)
|
||||
|
||||
# 保留原始时间作为参考
|
||||
aligned["原始时间"] = aligned["时间"]
|
||||
aligned["时间_x"] = aligned["k_time"].fillna(aligned["时间"])
|
||||
|
||||
return aligned
|
||||
|
||||
|
||||
# ========== 持仓跟踪与盈亏计算 ==========
|
||||
class PositionTracker:
|
||||
"""FIFO持仓跟踪器,支持订单走向可视化"""
|
||||
|
||||
def __init__(self):
|
||||
self.long_lots = [] # (数量, 价格, 时间, 手续费, 订单ID)
|
||||
self.short_lots = [] # (数量, 价格, 时间, 手续费, 订单ID)
|
||||
self.realized_pnl = 0.0
|
||||
self.history = [] # 记录所有交易历史
|
||||
self.trade_connections = [] # 记录开平仓连接关系
|
||||
|
||||
def open_long(self, qty, price, time, fee, order_id):
|
||||
"""开多仓"""
|
||||
if qty > 1e-9:
|
||||
self.long_lots.append((qty, price, time, fee, order_id))
|
||||
|
||||
def close_long(self, qty, price, time, fee, order_id):
|
||||
"""平多仓"""
|
||||
remaining = qty
|
||||
local_pnl = 0.0
|
||||
connections = [] # 本次平仓的连接关系
|
||||
|
||||
while remaining > 1e-9 and self.long_lots:
|
||||
lot_qty, lot_price, lot_time, lot_fee, open_order_id = self.long_lots[0]
|
||||
take = min(lot_qty, remaining)
|
||||
pnl = (price - lot_price) * take
|
||||
local_pnl += pnl
|
||||
lot_qty -= take
|
||||
remaining -= take
|
||||
|
||||
# 记录开平仓连接
|
||||
connection = {
|
||||
"open_time": lot_time,
|
||||
"close_time": time,
|
||||
"open_price": lot_price,
|
||||
"close_price": price,
|
||||
"qty": take,
|
||||
"pnl": pnl,
|
||||
"type": "long",
|
||||
"open_order_id": open_order_id,
|
||||
"close_order_id": order_id
|
||||
}
|
||||
self.trade_connections.append(connection)
|
||||
connections.append(connection)
|
||||
|
||||
# 记录平仓详情
|
||||
self.history.append({
|
||||
"开仓时间": lot_time,
|
||||
"平仓时间": time,
|
||||
"数量": take,
|
||||
"开仓价": lot_price,
|
||||
"平仓价": price,
|
||||
"盈亏": pnl,
|
||||
"类型": "平多",
|
||||
"开仓订单ID": open_order_id,
|
||||
"平仓订单ID": order_id
|
||||
})
|
||||
|
||||
if lot_qty <= 1e-9:
|
||||
self.long_lots.pop(0)
|
||||
else:
|
||||
self.long_lots[0] = (lot_qty, lot_price, lot_time, lot_fee, open_order_id)
|
||||
|
||||
local_pnl -= fee
|
||||
self.realized_pnl += local_pnl
|
||||
return local_pnl, connections
|
||||
|
||||
def open_short(self, qty, price, time, fee, order_id):
|
||||
"""开空仓"""
|
||||
if qty > 1e-9:
|
||||
self.short_lots.append((qty, price, time, fee, order_id))
|
||||
|
||||
def close_short(self, qty, price, time, fee, order_id):
|
||||
"""平空仓"""
|
||||
remaining = qty
|
||||
local_pnl = 0.0
|
||||
connections = [] # 本次平仓的连接关系
|
||||
|
||||
while remaining > 1e-9 and self.short_lots:
|
||||
lot_qty, lot_price, lot_time, lot_fee, open_order_id = self.short_lots[0]
|
||||
take = min(lot_qty, remaining)
|
||||
pnl = (lot_price - price) * take
|
||||
local_pnl += pnl
|
||||
lot_qty -= take
|
||||
remaining -= take
|
||||
|
||||
# 记录开平仓连接
|
||||
connection = {
|
||||
"open_time": lot_time,
|
||||
"close_time": time,
|
||||
"open_price": lot_price,
|
||||
"close_price": price,
|
||||
"q极": take,
|
||||
"pnl": pnl,
|
||||
"type": "short",
|
||||
"open_order_id": open_order_id,
|
||||
"close_order_id": order_id
|
||||
}
|
||||
self.trade_connections.append(connection)
|
||||
connections.append(connection)
|
||||
|
||||
# 记录平仓详情
|
||||
self.history.append({
|
||||
"开仓时间": lot_time,
|
||||
"平仓时间": time,
|
||||
"数量": take,
|
||||
"开仓价": lot_price,
|
||||
"平仓价": price,
|
||||
"盈亏": pnl,
|
||||
"类型": "平空",
|
||||
"开仓订单ID": open_order_id,
|
||||
"平仓订单ID": order_id
|
||||
})
|
||||
|
||||
if lot_qty <= 1e-9:
|
||||
self.short_lots.pop(0)
|
||||
else:
|
||||
self.short_lots[0] = (lot_qty, lot_price, lot_time, lot_fee, open_order_id)
|
||||
|
||||
local_pnl -= fee
|
||||
self.realized_pnl += local_pnl
|
||||
return local_pnl, connections
|
||||
|
||||
|
||||
def calculate_pnl(odf):
|
||||
"""计算持仓盈亏和订单连接关系"""
|
||||
tracker = PositionTracker()
|
||||
all_connections = []
|
||||
|
||||
for idx, r in odf.iterrows():
|
||||
qty = r["数量"]
|
||||
price = r["价格"]
|
||||
ts = r["时间"]
|
||||
fee = r["手续费"]
|
||||
side = r["side"]
|
||||
order_id = r["order_id"]
|
||||
|
||||
if side == "long_open":
|
||||
tracker.open_long(qty, price, ts, fee, order_id)
|
||||
elif side == "long_close":
|
||||
_, connections = tracker.close_long(qty, price, ts, fee, order_id)
|
||||
all_connections.extend(connections)
|
||||
elif side == "short_open":
|
||||
tracker.open_short(qty, price, ts, fee, order_id)
|
||||
elif side == "short_close":
|
||||
_, connections = tracker.close_short(qty, price, ts, fee, order_id)
|
||||
all_connections.extend(connections)
|
||||
|
||||
# 创建盈亏DataFrame
|
||||
if tracker.history:
|
||||
pnl_df = pd.DataFrame(tracker.history)
|
||||
# 添加对齐后的时间
|
||||
pnl_df["时间_x"] = pnl_df["平仓时间"].apply(
|
||||
lambda x: odf.loc[odf["时间"] == x, "时间_x"].values[0] if not odf.empty else x
|
||||
)
|
||||
else:
|
||||
pnl_df = pd.DataFrame()
|
||||
|
||||
# 创建连接关系DataFrame
|
||||
connections_df = pd.DataFrame(all_connections) if all_connections else pd.DataFrame()
|
||||
|
||||
return pnl_df, tracker.realized_pnl, connections_df
|
||||
|
||||
|
||||
# ========== 可视化 ==========
|
||||
def create_trade_scatter(df, name, color, symbol):
|
||||
"""创建交易点散点图"""
|
||||
if df.empty:
|
||||
return None
|
||||
|
||||
# 为不同类型的交易点创建不同的文本标签
|
||||
if name == "开多":
|
||||
text = "开多\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["本金"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "平多":
|
||||
text = "平多\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["盈亏"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "开空":
|
||||
text = "开空\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["本金"].apply(lambda x: f"{x:.0f}")
|
||||
elif name == "平空":
|
||||
text = "平空\n" + df["价格"].apply(lambda x: f"{x:.2f}") + "\n" + df["盈亏"].apply(lambda x: f"{x:.0f}")
|
||||
else:
|
||||
text = name
|
||||
|
||||
return go.Scatter(
|
||||
x=df["时间_x"],
|
||||
y=df["价格"],
|
||||
mode="markers+text",
|
||||
name=name,
|
||||
text=text,
|
||||
textposition="middle right", # 文本放在右侧中间位置
|
||||
textfont=dict(size=ANNOTATION_FONT_SIZE, color=TEXT_COLOR), # 使用黑色文本
|
||||
marker=dict(
|
||||
size=MARKER_SIZE,
|
||||
color=color,
|
||||
symbol=symbol,
|
||||
line=dict(width=1.5, color="black")
|
||||
),
|
||||
customdata=np.stack([
|
||||
df["数量"].to_numpy(),
|
||||
df["价格"].to_numpy(),
|
||||
df["手续费"].to_numpy(),
|
||||
df.get("盈亏", np.nan).to_numpy(),
|
||||
df.get("原始时间", df["时间"]).dt.strftime("%Y-%m-%d %H:%M:%S").to_numpy(),
|
||||
df["order_id"].to_numpy(),
|
||||
df["本金"].to_numpy()
|
||||
], axis=-1),
|
||||
hovertemplate=(
|
||||
f"<b>{name}</b><br>"
|
||||
"数量: %{customdata[0]:.0f}张<br>"
|
||||
"价格: %{customdata[1]:.2f}<br>"
|
||||
"手续费: %{customdata[2]:.6f}<br>"
|
||||
"盈亏: %{customdata[3]:.4f}<br>"
|
||||
"本金: %{customdata[6]:.0f}<br>"
|
||||
"时间: %{customdata[4]}<br>"
|
||||
"订单ID: %{customdata[5]}<extra></extra>"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def add_trade_connections(fig, connections_df, odf):
|
||||
"""添加开平仓连接线"""
|
||||
if connections_df.empty:
|
||||
return
|
||||
|
||||
# 为盈利和亏损的连接线分别创建轨迹
|
||||
profit_lines = []
|
||||
loss_lines = []
|
||||
|
||||
for _, conn in connections_df.iterrows():
|
||||
# 获取开仓点和平仓点的坐标
|
||||
open_point = odf[odf["order_id"] == conn["open_order_id"]].iloc[0]
|
||||
close_point = odf[odf["order_id"] == conn["close_order_id"]].iloc[0]
|
||||
|
||||
line_data = {
|
||||
"x": [open_point["时间_x"], close_point["时间_x"]],
|
||||
"y": [open_point["价格"], close_point["价格"]],
|
||||
"pnl": conn["pnl"],
|
||||
"type": conn["type"],
|
||||
"open_order_id": conn["open_order_id"],
|
||||
"close_order_id": conn["close_order_id"]
|
||||
}
|
||||
|
||||
if conn["pnl"] >= 0:
|
||||
profit_lines.append(line_data)
|
||||
else:
|
||||
loss_lines.append(line_data)
|
||||
|
||||
# 添加盈利连接线(绿色)
|
||||
if profit_lines:
|
||||
x_profit = []
|
||||
y_profit = []
|
||||
customdata_profit = []
|
||||
|
||||
for line in profit_lines:
|
||||
x_profit.extend(line["x"])
|
||||
y_profit.extend(line["y"])
|
||||
x_profit.append(None)
|
||||
y_profit.append(None)
|
||||
|
||||
# 为每个点添加自定义数据
|
||||
customdata_profit.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_profit.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_profit.append(None)
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=x_profit,
|
||||
y=y_profit,
|
||||
mode="lines",
|
||||
name="盈利订单",
|
||||
line=dict(color="rgba(46, 204, 113, 0.7)", width=LINE_WIDTH),
|
||||
hoverinfo="text",
|
||||
text=[f"盈利: {d[2]:.2f}" if d else None for d in customdata_profit],
|
||||
customdata=customdata_profit,
|
||||
hovertemplate=(
|
||||
"<b>%{text}</b><br>"
|
||||
"类型: %{customdata[3]}<br>"
|
||||
"开仓订单ID: %{customdata[0]}<br>"
|
||||
"平仓订单ID: %{customdata[1]}<extra></extra>"
|
||||
)
|
||||
))
|
||||
|
||||
# 添加亏损连接线(红色)
|
||||
if loss_lines:
|
||||
x_loss = []
|
||||
y_loss = []
|
||||
customdata_loss = []
|
||||
|
||||
for line in loss_lines:
|
||||
x_loss.extend(line["x"])
|
||||
y_loss.extend(line["y"])
|
||||
x_loss.append(None)
|
||||
y_loss.append(None)
|
||||
|
||||
# 为每个点添加自定义数据
|
||||
customdata_loss.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_loss.append([
|
||||
line["open_order_id"],
|
||||
line["close_order_id"],
|
||||
line["pnl"],
|
||||
line["type"]
|
||||
])
|
||||
customdata_loss.append(None)
|
||||
|
||||
fig.add_trace(go.Scatter(
|
||||
x=x_loss,
|
||||
y=y_loss,
|
||||
mode="lines",
|
||||
name="亏损订单",
|
||||
line=dict(color="rgba(231, 76, 60, 0.7)", width=LINE_WIDTH),
|
||||
hoverinfo="text",
|
||||
text=[f"亏损: {abs(d[2]):.2f}" if d else None for d in customdata_loss],
|
||||
customdata=customdata_loss,
|
||||
hovertemplate=(
|
||||
"<b>%{text}</b><br>"
|
||||
"类型: %{customdata[3]}<br>"
|
||||
"开仓订单ID: %{customdata[0]}<极>"
|
||||
"平仓订单ID: %{customdata[1]}<extra></extra>"
|
||||
)
|
||||
))
|
||||
|
||||
|
||||
def generate_chart(kdf, odf, pnl_df, cum_realized, connections_df):
|
||||
"""生成K线图与交易标注"""
|
||||
fig = go.Figure()
|
||||
|
||||
# K线主图
|
||||
fig.add_trace(go.Candlestick(
|
||||
x=kdf["time"],
|
||||
open=kdf["open"],
|
||||
high=kdf["high"],
|
||||
low=kdf["low"],
|
||||
close=kdf["close"],
|
||||
name="K线",
|
||||
increasing_line_color="#2ecc71",
|
||||
decreasing_line_color="#e74c3c"
|
||||
))
|
||||
|
||||
# 添加交易点
|
||||
trade_types = [
|
||||
(odf[odf["side"] == "long_open"], "开多", "#2ecc71", "triangle-up"),
|
||||
(odf[odf["side"] == "long_close"], "平多", "#27ae60", "circle"),
|
||||
(odf[odf["side"] == "short_open"], "开空", "#e74c3c", "triangle-down"),
|
||||
(odf[odf["side"] == "short_close"], "平空", "#c0392b", "x")
|
||||
]
|
||||
|
||||
for data, name, color, symbol in trade_types:
|
||||
trace = create_trade_scatter(data, name, color, symbol)
|
||||
if trace:
|
||||
fig.add_trace(trace)
|
||||
|
||||
# 添加开平仓连接线
|
||||
add_trade_connections(fig, connections_df, odf)
|
||||
|
||||
# 计算时间范围,确保所有点都显示在图表中
|
||||
all_times = pd.concat([kdf["time"], odf["时间_x"]])
|
||||
min_time = all_times.min() - pd.Timedelta(minutes=10)
|
||||
max_time = all_times.max() + pd.Timedelta(minutes=10)
|
||||
|
||||
# 计算价格范围,确保所有点都显示在图表中
|
||||
min_price = min(kdf["low"].min(), odf["价格"].min()) * 0.999
|
||||
max_price = max(kdf["high"].max(), odf["价格"].max()) * 1.001
|
||||
|
||||
# 布局配置 - 更宽更扁的图表
|
||||
fig.update_layout(
|
||||
xaxis_title="时间",
|
||||
yaxis_title="价格 (USDT)",
|
||||
legend=dict(
|
||||
orientation="h",
|
||||
yanchor="bottom",
|
||||
y=1.02,
|
||||
xanchor="left",
|
||||
x=0,
|
||||
font=dict(size=FONT_SIZE, color=TEXT_COLOR) # 图例文字使用黑色
|
||||
),
|
||||
xaxis=dict(
|
||||
rangeslider=dict(visible=False),
|
||||
type="date",
|
||||
gridcolor="rgba(128, 128, 128, 0.2)",
|
||||
range=[min_time, max_time], # 设置时间范围
|
||||
title_font=dict(color=TEXT_COLOR), # 坐标轴标题使用黑色
|
||||
tickfont=dict(color=TEXT_COLOR) # 刻度标签使用黑色
|
||||
),
|
||||
yaxis=dict(
|
||||
gridcolor="rgba(128, 128, 128, 0.2)",
|
||||
range=[min_price, max_price], # 设置价格范围
|
||||
title_font=dict(color=TEXT_COLOR), # 坐标轴标题使用黑色
|
||||
tickfont=dict(color=TEXT_COLOR) # 刻度标签使用黑色
|
||||
),
|
||||
hovermode="x unified",
|
||||
hoverlabel=dict(
|
||||
namelength=-1,
|
||||
bgcolor="rgba(255, 255, 255, 0.9)",
|
||||
font_size=FONT_SIZE,
|
||||
font_color=TEXT_COLOR # 悬停标签文字使用黑色
|
||||
),
|
||||
margin=dict(l=50, r=50, t=80, b=50),
|
||||
plot_bgcolor="rgba(240, 240, 240, 1)",
|
||||
width=CHART_WIDTH, # 使用配置的宽度
|
||||
height=CHART_HEIGHT, # 使用配置的高度
|
||||
font=dict(size=FONT_SIZE, color=TEXT_COLOR), # 全局字体大小和颜色
|
||||
|
||||
# 增强交互性配置
|
||||
dragmode="pan", # 默认拖拽模式为平移
|
||||
clickmode="event+select", # 点击模式
|
||||
selectdirection="h", # 水平选择方向
|
||||
modebar=dict(
|
||||
orientation="h", # 水平方向工具栏
|
||||
bgcolor="rgba(255, 255, 255, 0.7)", # 半透明背景
|
||||
color="rgba(0, 0, 0, 0.7)", # 图标颜色
|
||||
activecolor="rgba(0, 0, 0, 0.9)" # 激活图标颜色
|
||||
)
|
||||
)
|
||||
|
||||
# 添加模式栏按钮
|
||||
fig.update_layout(
|
||||
modebar_add=[
|
||||
"zoom2d",
|
||||
"pan2d",
|
||||
"select2d",
|
||||
"lasso2d",
|
||||
"zoomIn2d",
|
||||
"zoomOut2d",
|
||||
"autoScale2d",
|
||||
"resetScale2d",
|
||||
"toImage"
|
||||
]
|
||||
)
|
||||
|
||||
# 配置缩放行为 - 确保滚轮缩放正常工作
|
||||
fig.update_xaxes(
|
||||
autorange=False,
|
||||
fixedrange=False, # 允许缩放
|
||||
constrain="domain", # 约束在域内
|
||||
rangeslider=dict(visible=False) # 禁用范围滑块
|
||||
)
|
||||
|
||||
fig.update_yaxes(
|
||||
autorange=False,
|
||||
fixedrange=False, # 允许缩放
|
||||
scaleanchor="x", # 保持纵横比
|
||||
scaleratio=1, # 缩放比例
|
||||
constrain="domain" # 约束在域内
|
||||
)
|
||||
|
||||
# 保存并打开结果 - 启用滚轮缩放
|
||||
fig.write_html(
|
||||
OUTPUT_HTML,
|
||||
include_plotlyjs="cdn",
|
||||
auto_open=True,
|
||||
config={
|
||||
'scrollZoom': True, # 启用滚轮缩放
|
||||
'displayModeBar': True, # 显示工具栏
|
||||
'displaylogo': False, # 隐藏Plotly标志
|
||||
'responsive': True # 响应式布局
|
||||
}
|
||||
)
|
||||
print(f"图表已生成: {OUTPUT_HTML}")
|
||||
|
||||
# 返回盈亏详情
|
||||
if not pnl_df.empty:
|
||||
pnl_df.to_csv("pnl_details.csv", index=False)
|
||||
print(f"盈亏详情已保存: pnl_details.csv")
|
||||
|
||||
if not connections_df.empty:
|
||||
connections_df.to_csv("trade_connections.csv", index=False)
|
||||
print(f"订单连接关系已保存: trade_connections.csv")
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
# ========== 主执行流程 ==========
|
||||
def main():
|
||||
print("开始处理数据...")
|
||||
|
||||
# 加载数据
|
||||
kdf, median_step = load_kline_data()
|
||||
odf = load_order_data()
|
||||
|
||||
print(f"加载K线数据: {len(kdf)}条")
|
||||
print(f"加载订单数据: {len(odf)}条")
|
||||
|
||||
# 对齐交易时间
|
||||
odf = align_trades_to_candles(kdf, odf, median_step)
|
||||
|
||||
# 检查时间范围
|
||||
kline_min_time = kdf["time"].min()
|
||||
kline_max_time = kdf["time"].max()
|
||||
order_min_time = odf["时间"].min()
|
||||
order_max_time = odf["时间"].max()
|
||||
|
||||
print(f"K线时间范围: {kline_min_time} 至 {kline_max_time}")
|
||||
print(f"订单时间范围: {order_min_time} 至 {order_max_time}")
|
||||
|
||||
# 检查是否有订单在K线时间范围外
|
||||
outside_orders = odf[(odf["时间"] < kline_min_time) | (odf["时间"] > kline_max_time)]
|
||||
if not outside_orders.empty:
|
||||
print(f"警告: 有 {len(outside_orders)} 个订单在K线时间范围外")
|
||||
print(outside_orders[["时间", "方向", "价格"]])
|
||||
|
||||
# 计算盈亏和订单连接关系
|
||||
pnl_df, cum_realized, connections_df = calculate_pnl(odf)
|
||||
print(f"累计已实现盈亏: {cum_realized:.2f} USDT")
|
||||
print(f"订单连接关系: {len(connections_df)}条")
|
||||
|
||||
# 生成图表
|
||||
generate_chart(kdf, odf, pnl_df, cum_realized, connections_df)
|
||||
print("处理完成")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
main()
|
||||
@@ -1,277 +0,0 @@
|
||||
open_time,close_time,open_price,close_price,q极,pnl,type,open_order_id,close_order_id,qty
|
||||
2025-08-28 09:35:18+08:00,2025-08-28 09:44:18+08:00,4514.69,4507.43,664.0,4820.639999999541,short,e1d3a2da-219e-436f-ac9e-a7ea93de15bd,abfa0508-9399-4f1b-90c7-7716c3e7f511,
|
||||
2025-08-28 10:06:42+08:00,2025-08-28 10:10:07+08:00,4519.88,4515.64,,-4689.439999999759,long,594bd10c-2120-4692-a8eb-9f5325e26ef0,bd643cab-14d4-414d-912e-e9257ddb96de,1106.0
|
||||
2025-08-28 10:45:07+08:00,2025-08-28 10:51:41+08:00,4512.13,4516.84,1108.0,-5218.68000000004,short,fc0c73ab-9fff-41b5-98c1-ef6f7d300256,206688da-848a-4822-9571-c05302ab7a29,
|
||||
2025-08-28 11:07:30+08:00,2025-08-28 11:13:19+08:00,4525.0,4529.25,,850.0,long,6197fb5c-6202-422f-9012-5395c5393fb3,59d8859f-2b64-4e8b-aefe-57998b198841,200.0
|
||||
2025-08-28 11:07:30+08:00,2025-08-28 11:13:19+08:00,4525.0,4529.25,,425.0,long,794c0925-6e48-4b9a-a162-d31d7a3c42cc,59d8859f-2b64-4e8b-aefe-57998b198841,100.0
|
||||
2025-08-28 11:07:31+08:00,2025-08-28 11:13:19+08:00,4525.0,4529.25,,2052.75,long,65f7d764-ad05-40c7-bce1-6b227ad5f5c0,59d8859f-2b64-4e8b-aefe-57998b198841,483.0
|
||||
2025-08-28 11:07:31+08:00,2025-08-28 11:13:19+08:00,4525.0,4529.25,,425.0,long,35e1c160-4129-425a-ae46-b8fb45d53ec8,59d8859f-2b64-4e8b-aefe-57998b198841,100.0
|
||||
2025-08-28 11:28:58+08:00,2025-08-28 11:34:21+08:00,4533.74,4537.34,,1440.0000000001455,long,a983b8f5-70d1-441b-87df-397c6c1ce843,3e161e4e-f367-421a-bc0d-8095f7b79675,400.0
|
||||
2025-08-28 11:28:58+08:00,2025-08-28 11:34:21+08:00,4533.74,4537.34,,1735.2000000001754,long,a983b8f5-70d1-441b-87df-397c6c1ce843,45ddb145-d637-4985-9c4b-3dee2fd189b5,482.0
|
||||
2025-08-28 12:22:53+08:00,2025-08-28 12:23:33+08:00,4550.8,4545.04,,-2753.2800000001043,long,37d97b24-c1fd-4a2d-ba0e-75b818062686,fcfe39c7-0bcf-47f6-9b48-a1dd70bd5444,478.0
|
||||
2025-08-28 12:22:53+08:00,2025-08-28 12:23:33+08:00,4550.8,4545.04,,-2304.0000000000873,long,37d97b24-c1fd-4a2d-ba0e-75b818062686,867b1b2c-0fe5-466b-a89f-ec8dce25bcda,400.0
|
||||
2025-08-28 12:26:39+08:00,2025-08-28 12:34:19+08:00,4549.6,4555.44,,5133.3599999993285,long,0a3a08a1-61c7-4997-b13c-4da05b2c5dbc,ad149e7f-7088-4dd9-9698-9b7bd615478e,879.0
|
||||
2025-08-28 12:46:19+08:00,2025-08-28 12:48:10+08:00,4561.8,4567.53,600.0,-3437.999999999738,short,18929fa1-2cc7-4e7f-8e76-c3937f16f877,8283a8b1-17f1-4818-843d-33877996af35,
|
||||
2025-08-28 12:46:19+08:00,2025-08-28 12:48:10+08:00,4561.8,4567.53,276.0,-1581.4799999998795,short,18929fa1-2cc7-4e7f-8e76-c3937f16f877,c0a1d021-0708-476e-a766-722bc73ab740,
|
||||
2025-08-28 12:53:40+08:00,2025-08-28 12:57:03+08:00,4577.05,4569.51,873.0,6582.419999999968,short,16f8970d-ef44-4026-ba19-9c9ff6dc70e1,e1f9de7d-115e-4289-8acd-bbba16e04ff0,
|
||||
2025-08-28 12:58:03+08:00,2025-08-28 13:01:30+08:00,4567.45,4572.58,475.0,-2436.750000000052,short,8dbfcfc8-466c-4111-acdf-316a2a8c7428,3e0a22b4-7707-4332-b80d-87d3b07cf3cc,
|
||||
2025-08-28 12:58:03+08:00,2025-08-28 13:01:30+08:00,4567.45,4572.58,400.0,-2052.0000000000437,short,8dbfcfc8-466c-4111-acdf-316a2a8c7428,29775823-c3fc-4dbc-8afa-f9587f803f2a,
|
||||
2025-08-28 13:32:49+08:00,2025-08-28 13:41:18+08:00,4564.18,4565.88,,1225.6999999998689,long,6c0a64d8-b890-42bc-ae09-152919931d57,5bba77bd-b49f-4c6d-9a12-fe062c687cae,721.0
|
||||
2025-08-28 13:32:49+08:00,2025-08-28 13:41:18+08:00,4564.18,4565.88,,263.4999999999718,long,c0e17c5e-6a39-465c-a3b3-347f1af3d772,5bba77bd-b49f-4c6d-9a12-fe062c687cae,155.0
|
||||
2025-08-28 13:52:44+08:00,2025-08-28 13:57:25+08:00,4555.2,4559.95,400.0,-1900.0,short,8f641745-890a-4b9c-8d83-10163234429d,dd5f8c6a-74da-4984-aa24-eebfbceb6d22,
|
||||
2025-08-28 13:52:44+08:00,2025-08-28 13:57:25+08:00,4555.2,4559.95,478.0,-2270.5,short,66c5bcfb-02ef-4a8c-a314-bd9d672decb5,dd5f8c6a-74da-4984-aa24-eebfbceb6d22,
|
||||
2025-08-28 14:40:01+08:00,2025-08-28 14:41:47+08:00,4572.59,4578.98,874.0,-5584.859999999491,short,1b1efa90-7dd5-4b5f-a170-eb87df7fb7db,26b0c5e5-f4a4-4f47-9fdc-12c6ecd05704,
|
||||
2025-08-28 15:05:18+08:00,2025-08-28 15:07:11+08:00,4582.63,4577.98,,-279.00000000003274,long,61f7b24d-18b1-44ee-bc9e-60666e4b2346,e53afe94-f3fa-42c1-9e49-619cd77261ab,60.0
|
||||
2025-08-28 15:05:18+08:00,2025-08-28 15:07:11+08:00,4582.63,4578.03,,-2760.0000000002183,long,61f7b24d-18b1-44ee-bc9e-60666e4b2346,b9cf7c9c-d909-4dad-ae66-64aa0706566f,600.0
|
||||
2025-08-28 15:05:18+08:00,2025-08-28 15:07:11+08:00,4582.63,4577.52,,-1083.3199999999306,long,61f7b24d-18b1-44ee-bc9e-60666e4b2346,05d46d57-d923-4d18-8c18-5a49927ac910,212.0
|
||||
2025-08-28 15:22:41+08:00,2025-08-28 15:23:11+08:00,4572.88,4567.91,,-2982.000000000153,long,184c5b21-ed98-43c2-8003-8cc5f8c1bb0a,10d151ec-2891-44fe-8527-89643b3f3b46,600.0
|
||||
2025-08-28 15:22:41+08:00,2025-08-28 15:23:11+08:00,4572.88,4567.53,,-1465.9000000000997,long,184c5b21-ed98-43c2-8003-8cc5f8c1bb0a,2a5ef255-f800-4e98-a10c-ca3ed33bb372,274.0
|
||||
2025-08-28 15:57:55+08:00,2025-08-28 16:02:09+08:00,4589.48,4607.47,,9840.530000000377,long,6fdd3eb9-13a9-4e2a-90b6-78e7d71f3fbc,81bc2d5e-8099-4d48-b968-d695a7f1fee4,547.0
|
||||
2025-08-28 15:57:55+08:00,2025-08-28 16:02:09+08:00,4589.48,4607.47,,5828.760000000224,long,aec18848-d3b5-4786-8c51-a4fdfc454ee2,81bc2d5e-8099-4d48-b968-d695a7f1fee4,324.0
|
||||
2025-08-28 16:05:54+08:00,2025-08-28 16:09:19+08:00,4614.86,4624.89,,8685.980000000567,long,8e6d3e81-c813-4759-b608-9ed4e547b24d,25509f73-848d-4620-b662-f7f93d76becc,866.0
|
||||
2025-08-28 16:09:50+08:00,2025-08-28 16:15:00+08:00,4621.56,4617.66,,-3373.500000000472,long,29739f4f-b289-4a1e-a5e7-f6925dd72b4c,7e4c74ac-902f-47c5-9e0a-b2ef07f31e8e,865.0
|
||||
2025-08-28 16:23:59+08:00,2025-08-28 16:28:31+08:00,4609.17,4602.01,867.0,6207.719999999874,short,6e8dc270-512a-4826-84c9-cfdf82c6cd59,0df8cd52-9db6-4532-99e1-923fcd9c134d,
|
||||
2025-08-28 16:54:47+08:00,2025-08-28 16:58:41+08:00,4595.28,4599.69,870.0,-3836.6999999998734,short,35598fc3-567f-4ee2-a9fe-db14aab8228b,9777e233-baa8-4238-8f48-ef3e303ff883,
|
||||
2025-08-28 17:06:53+08:00,2025-08-28 17:15:11+08:00,4595.19,4592.66,870.0,2201.0999999997784,short,3432672f-ced3-401f-981d-7686ddd396e6,bf9ffdef-56bd-4d35-9eef-f92a668d5829,
|
||||
2025-08-28 17:43:59+08:00,2025-08-28 17:49:12+08:00,4584.79,4581.95,258.0,732.7200000000375,short,c0b26494-b8c4-434e-bc49-e3b317c1fa88,ffbc0ba2-aa7f-4377-bade-cf31e3697b19,
|
||||
2025-08-28 17:43:59+08:00,2025-08-28 17:49:12+08:00,4584.79,4581.95,114.0,323.7600000000166,short,6fdb9fc7-e0f4-4033-896b-a13160b4fd18,ffbc0ba2-aa7f-4377-bade-cf31e3697b19,
|
||||
2025-08-28 17:43:59+08:00,2025-08-28 17:49:12+08:00,4584.79,4581.95,500.0,1420.0000000000728,short,b064c485-653b-4f88-a0d1-86961284dc70,ffbc0ba2-aa7f-4377-bade-cf31e3697b19,
|
||||
2025-08-28 17:49:44+08:00,2025-08-28 17:50:54+08:00,4579.81,4584.85,500.0,-2519.999999999982,short,1f97429c-1de1-467a-8985-8f9bb17000b5,cf389028-3cfb-4521-bf8c-59c3d9782e85,
|
||||
2025-08-28 17:49:44+08:00,2025-08-28 17:50:54+08:00,4579.81,4584.85,139.0,-700.559999999995,short,989c44a3-a81f-4939-ab13-d9884d845a65,cf389028-3cfb-4521-bf8c-59c3d9782e85,
|
||||
2025-08-28 17:49:44+08:00,2025-08-28 17:50:54+08:00,4579.58,4584.85,234.0,-1233.1800000001022,short,0b83f7ef-0985-4168-a211-d009f6c6ff1a,cf389028-3cfb-4521-bf8c-59c3d9782e85,
|
||||
2025-08-28 18:36:51+08:00,2025-08-28 18:43:09+08:00,4609.97,4604.0,,-3182.0100000001357,long,b8391ca4-74b4-41bc-85cd-b4c82cf6e8f3,2926d3da-8a19-4fae-938f-e2556e994376,533.0
|
||||
2025-08-28 18:36:51+08:00,2025-08-28 18:43:09+08:00,4610.0,4604.0,,-504.0,long,e441f55d-693a-4301-b6c9-10d54ab10357,2926d3da-8a19-4fae-938f-e2556e994376,84.0
|
||||
2025-08-28 18:36:51+08:00,2025-08-28 18:43:09+08:00,4609.97,4604.0,,-197.0100000000084,long,9dbce008-1d12-422c-80f8-a2c928610074,2926d3da-8a19-4fae-938f-e2556e994376,33.0
|
||||
2025-08-28 19:46:14+08:00,2025-08-28 19:54:40+08:00,4586.91,4591.36,654.0,-2910.299999999881,short,005b10f4-329d-4d34-8263-b8f63f526ac8,ea07e34c-cd41-4c80-b13f-6b8308257b92,
|
||||
2025-08-28 20:30:04+08:00,2025-08-28 20:30:34+08:00,4587.25,4588.6,,697.9500000001881,long,26fe1b1b-f4f0-4c49-b6d0-f8445d4e996e,9011ad7e-ef83-4c62-ac90-3e538965ad3f,517.0
|
||||
2025-08-28 20:30:05+08:00,2025-08-28 20:30:34+08:00,4587.44,4588.6,,410.64000000027045,long,32f1db9d-6a9b-413f-abba-3f698a83ec65,9011ad7e-ef83-4c62-ac90-3e538965ad3f,354.0
|
||||
2025-08-28 20:31:22+08:00,2025-08-28 20:31:28+08:00,4583.96,4585.9,400.0,-775.9999999998399,short,dd7d8a55-c4bf-4ff8-82b9-ec8591888b0d,ca1e611c-1ba6-4418-9f86-8e492a7726c5,
|
||||
2025-08-28 20:31:22+08:00,2025-08-28 20:31:29+08:00,4583.96,4585.9,472.0,-915.6799999998111,short,dd7d8a55-c4bf-4ff8-82b9-ec8591888b0d,04792496-7da0-4a9b-95cf-293b41d8292a,
|
||||
2025-08-28 20:43:41+08:00,2025-08-28 20:46:26+08:00,4609.77,4603.6,,-567.6400000000067,long,da034c24-f333-4197-ae9d-7dfa8639be45,9d59670d-a478-4432-8ec0-e3a8c4ec4d8a,92.0
|
||||
2025-08-28 20:43:41+08:00,2025-08-28 20:46:26+08:00,4609.77,4603.6,,-3702.0000000000437,long,da034c24-f333-4197-ae9d-7dfa8639be45,e83ff9d3-3078-4f5c-8e6a-fa5750c97b55,600.0
|
||||
2025-08-28 20:43:41+08:00,2025-08-28 20:46:27+08:00,4609.77,4604.08,,-995.7500000000891,long,da034c24-f333-4197-ae9d-7dfa8639be45,433dc03b-8b85-4951-9f21-dfaa5d6a0de5,175.0
|
||||
2025-08-29 09:45:36+08:00,2025-08-29 09:49:22+08:00,4466.85,4458.15,447.0,3888.9000000003252,short,87d823b2-675d-4501-bb36-b40eef344b7c,28bb7706-f2ce-480e-b606-fc5073ce4153,
|
||||
2025-08-29 10:13:26+08:00,2025-08-29 10:18:05+08:00,4470.7,4473.08,,4.760000000000218,long,6d7fe294-0553-46ac-9179-e22446e727dc,b50fd6a4-865b-4cd4-8b83-015dde307912,2.0
|
||||
2025-08-29 10:13:27+08:00,2025-08-29 10:18:05+08:00,4470.71,4473.08,,1054.6499999999514,long,3d441a61-f852-4d4d-88d9-a2c4494e3a5f,b50fd6a4-865b-4cd4-8b83-015dde307912,445.0
|
||||
2025-08-29 10:21:04+08:00,2025-08-29 10:25:10+08:00,4479.68,4483.07,,1511.9399999997404,long,9ac71ec9-58a8-43af-83ca-cac772d44dce,7db1d0e0-cdda-45a4-af09-9ec2f6a830a5,446.0
|
||||
2025-08-29 11:08:08+08:00,2025-08-29 11:10:24+08:00,4477.82,4481.35,,1574.380000000292,long,4c447405-283c-40f7-bc05-42ae2aeaaaeb,de3c12d5-ba88-4b3d-8e8a-2158228465cb,446.0
|
||||
2025-08-29 11:25:26+08:00,2025-08-29 11:31:14+08:00,4478.16,4482.39,,1886.580000000211,long,877eb021-8a47-4d55-8efd-789fde87699c,33addd17-0151-418d-b11e-d68a26c179e6,446.0
|
||||
2025-08-29 11:35:59+08:00,2025-08-29 11:43:49+08:00,4484.05,4494.74,,4767.7399999998215,long,913408fa-2541-48c8-a8a0-d352b8b74a93,a7fd376e-dd1c-4350-8c58-1913d130902c,446.0
|
||||
2025-08-29 11:55:41+08:00,2025-08-29 12:00:52+08:00,4498.11,4489.03,444.0,4031.5199999999677,short,6450e48b-f901-4907-b065-18e073f86534,7cfe2e33-9b17-4785-9e45-dd5654c483c2,
|
||||
2025-08-29 12:54:59+08:00,2025-08-29 12:58:10+08:00,4477.15,4471.43,670.0,3832.3999999995613,short,13bb04ff-529a-48f4-a062-bdbda01abe9a,01a6ebd4-6c63-4e58-9f52-4317a83a6c27,
|
||||
2025-08-29 13:05:31+08:00,2025-08-29 13:06:05+08:00,4466.57,4472.34,671.0,-3871.670000000293,short,5c439227-9432-48c1-8c70-184e65586599,398e954e-0534-4f4c-bdda-5d14d858afae,
|
||||
2025-08-29 13:17:39+08:00,2025-08-29 13:19:35+08:00,4461.42,4468.3,448.0,-3082.240000000049,short,d770f2c0-6aed-4bc2-bee2-f98209775454,14ce65ce-8af2-4c07-8441-bdeea724197e,
|
||||
2025-08-29 13:59:25+08:00,2025-08-29 14:01:39+08:00,4476.01,4484.33,,382.7199999999866,long,1f80cf00-8910-42af-a7ac-dd2199579d04,96e160f6-0a35-4ac4-9f3e-295065a56092,46.0
|
||||
2025-08-29 13:59:25+08:00,2025-08-29 14:01:39+08:00,4476.01,4484.33,,3327.9999999998836,long,1f80cf00-8910-42af-a7ac-dd2199579d04,db2ea541-8ece-4876-baa0-f803629d4675,400.0
|
||||
2025-08-29 14:15:38+08:00,2025-08-29 14:18:12+08:00,4457.41,4451.26,448.0,2755.199999999837,short,2616d754-2ae8-43c0-a382-46901e0dba21,913e0886-23e4-467d-932d-79e960df2947,
|
||||
2025-08-29 14:37:31+08:00,2025-08-29 14:39:31+08:00,4451.44,4451.34,,-44.89999999975498,long,9f225f1e-cdc5-42cc-8c22-6d39f68da6d6,db5c616d-faf7-4183-8e46-b95f09dbad99,449.0
|
||||
2025-08-29 14:41:21+08:00,2025-08-29 14:44:17+08:00,4451.53,4455.7,,1872.3300000000327,long,5ba24885-5747-405a-8fa9-6a9749dbe6b4,f7e02df2-857b-4e3a-a6aa-c0d7362cb5bb,449.0
|
||||
2025-08-29 15:01:14+08:00,2025-08-29 15:02:16+08:00,4448.83,4428.34,449.0,9200.009999999902,short,a605bd22-e12e-44a4-9e1c-87c145bc5aa4,365ff72f-891a-4314-a005-d320d2074d5f,
|
||||
2025-08-29 15:12:00+08:00,2025-08-29 15:15:04+08:00,4412.37,4393.89,453.0,8371.439999999802,short,03169227-80cc-4c41-89e5-7cbc59174c90,7939f969-7566-453d-a718-dfa6b96ee987,
|
||||
2025-08-29 15:44:10+08:00,2025-08-29 15:51:01+08:00,4387.22,4391.19,683.0,-2711.5099999995527,short,e2151c19-3f56-4239-8368-77af88a0c2c1,f90b5024-cd5b-44d5-a140-4d6253d49d6a,
|
||||
2025-08-29 15:57:12+08:00,2025-08-29 16:00:46+08:00,4383.78,4387.09,400.0,-1324.00000000016,short,f2affaff-2beb-481f-b346-c9453422f536,70cc7f5d-b472-40a1-b00b-7e4b032722fc,
|
||||
2025-08-29 15:57:12+08:00,2025-08-29 16:00:46+08:00,4383.78,4387.09,284.0,-940.0400000001137,short,f2affaff-2beb-481f-b346-c9453422f536,676c76cd-7afa-40db-a0a7-89004bb9387b,
|
||||
2025-08-29 16:08:15+08:00,2025-08-29 16:11:24+08:00,4386.41,4376.76,383.0,3695.9499999998607,short,da4365c6-5bed-4bff-824c-580b3be527bc,0fdf84b4-d9bd-419e-8fd0-037e8631eefd,
|
||||
2025-08-29 16:08:15+08:00,2025-08-29 16:11:24+08:00,4386.5,4376.76,300.0,2921.9999999999345,short,940d2d22-8103-4bdd-8eaa-ade1277111da,0fdf84b4-d9bd-419e-8fd0-037e8631eefd,
|
||||
2025-08-29 16:17:21+08:00,2025-08-29 16:17:29+08:00,4364.59,4365.41,187.0,-153.33999999994558,short,89b93201-dfd2-408c-a5de-b6ecd72424cd,db922d70-7a90-4cf4-9a17-a598fe213f8b,
|
||||
2025-08-29 16:17:21+08:00,2025-08-29 16:17:29+08:00,4364.59,4365.41,400.0,-327.9999999998836,short,89b93201-dfd2-408c-a5de-b6ecd72424cd,574a0e60-b26e-46bf-a696-cae5868b7af2,
|
||||
2025-08-29 16:17:21+08:00,2025-08-29 16:17:29+08:00,4365.0,4365.41,100.0,-40.99999999998545,short,e6be51d9-e746-45dd-8522-877cf7eafd33,574a0e60-b26e-46bf-a696-cae5868b7af2,
|
||||
2025-08-29 16:20:18+08:00,2025-08-29 16:20:36+08:00,4359.92,4362.29,688.0,-1630.559999999925,short,d302895c-7318-4851-838d-396e7100fc09,719a6eff-a759-4947-8f0a-45f2ac94dfc4,
|
||||
2025-08-29 16:29:12+08:00,2025-08-29 16:33:21+08:00,4355.55,4362.63,,3731.1599999999617,long,f587dcce-e557-4b34-a55f-bf449234faca,e833feb6-8ba7-4fa5-adea-678f690e16b3,527.0
|
||||
2025-08-29 16:29:12+08:00,2025-08-29 16:33:21+08:00,4355.55,4362.63,,1139.8799999999883,long,22c48502-01b1-4efd-9809-3bc8487b0a9b,e833feb6-8ba7-4fa5-adea-678f690e16b3,161.0
|
||||
2025-08-29 16:37:39+08:00,2025-08-29 16:40:08+08:00,4356.21,4352.88,688.0,2291.03999999995,short,b209c4ae-8c33-4ba5-ad48-7559d1eafc6a,f7b8931f-1568-4387-a132-8ac6f7f34891,
|
||||
2025-08-29 16:40:40+08:00,2025-08-29 16:41:12+08:00,4348.53,4353.19,140.0,-652.3999999999796,short,9f88809c-8f0c-4dd4-b83c-552c6b78da3a,b2c3aa90-dcc1-469d-bf31-6c74b8e4099b,
|
||||
2025-08-29 16:40:40+08:00,2025-08-29 16:41:12+08:00,4348.53,4353.19,549.0,-2558.33999999992,short,3efb68fb-7389-44fd-b48a-d749af57e70f,b2c3aa90-dcc1-469d-bf31-6c74b8e4099b,
|
||||
2025-08-29 18:24:56+08:00,2025-08-29 18:28:40+08:00,4354.11,4357.4,,1644.9999999999818,long,5b3555c7-8c22-45ad-a4b5-ef6b586335d5,88db631a-7f3b-4b85-9887-94f906259583,500.0
|
||||
2025-08-29 18:24:56+08:00,2025-08-29 18:28:40+08:00,4354.11,4357.4,,621.8099999999931,long,f6c1a4f1-6aa0-4645-b991-503a320913be,6132d1a3-6d55-4517-b5ba-115198948b85,189.0
|
||||
2025-08-29 18:30:45+08:00,2025-08-29 18:49:35+08:00,4351.51,4343.72,189.0,1472.3099999999931,short,b67b7a3a-cf62-4731-bc9c-1d333f42e639,2d4f2adb-d806-4f37-8ff1-a46eddccc6b1,
|
||||
2025-08-29 18:30:45+08:00,2025-08-29 18:49:35+08:00,4351.51,4343.72,500.0,3894.999999999982,short,0acabab2-2ab4-4921-82a0-df806d4eb40d,2d4f2adb-d806-4f37-8ff1-a46eddccc6b1,
|
||||
2025-08-29 18:58:56+08:00,2025-08-29 19:02:25+08:00,4345.89,4340.99,,-3381.0000000003765,long,b525febe-c7eb-4db9-a4f8-4a09406c9f7d,51ddc961-238a-4c9d-b985-e6ad6ef2aac7,690.0
|
||||
2025-08-29 19:14:13+08:00,2025-08-29 19:18:33+08:00,4351.49,4346.64,,-3341.649999999624,long,da8dccbd-f79c-476b-9471-d28b719da66f,cfbfe97f-e085-41db-8679-58574bd165fd,689.0
|
||||
2025-08-29 19:49:41+08:00,2025-08-29 19:51:55+08:00,4345.68,4340.66,,-3463.800000000301,long,98c03fb1-dca8-43ef-9ca7-0fe6d842946b,e0c7b8be-c842-412e-bb4f-e43b51fa53bb,690.0
|
||||
2025-08-29 20:22:08+08:00,2025-08-29 20:30:35+08:00,4365.85,4411.23,,31176.059999999452,long,7ab49fe4-9bd7-4368-bb45-ffe9970e4999,c3d6e447-288e-48d1-8977-ccceda6a2e41,687.0
|
||||
2025-08-29 20:31:05+08:00,2025-08-29 20:31:21+08:00,4439.0,4448.35,,149.60000000000582,long,76a58764-fd12-4fb4-8ee9-407aa2213a12,e3f58dbc-6a93-4c16-9dc3-7e351493f00f,16.0
|
||||
2025-08-29 20:31:05+08:00,2025-08-29 20:31:21+08:00,4439.0,4448.35,,6161.65000000024,long,cdd4a741-4195-4fac-9896-35ee5c09a7b9,e3f58dbc-6a93-4c16-9dc3-7e351493f00f,659.0
|
||||
2025-08-29 20:31:46+08:00,2025-08-29 20:33:05+08:00,4409.9,4406.3,,-647.9999999999018,long,a47f68a7-9565-4ea7-b3ab-770d376ffcea,46e071a8-871b-4373-92a5-8cd6cd46cc56,180.0
|
||||
2025-08-29 20:31:46+08:00,2025-08-29 20:33:05+08:00,4409.9,4406.3,,-1799.9999999997272,long,a47f68a7-9565-4ea7-b3ab-770d376ffcea,c67209e2-0598-443d-85fa-752bdae6059b,500.0
|
||||
2025-08-29 20:43:25+08:00,2025-08-29 20:44:07+08:00,4382.62,4386.13,684.0,-2400.8400000001493,short,af3305f0-ec39-48fe-bf71-ac9acb281b25,7556c209-5046-4e8b-8682-99dc36803a62,
|
||||
2025-08-29 20:45:44+08:00,2025-08-29 20:49:11+08:00,4389.13,4407.06,,1828.8600000000297,long,a19435e4-fbf9-4217-bc5d-d7fc07d9818f,cc0577e9-e4b6-45fd-bf5a-47ccf6aec320,102.0
|
||||
2025-08-29 20:45:44+08:00,2025-08-29 20:49:11+08:00,4389.13,4407.06,,10417.33000000017,long,a19435e4-fbf9-4217-bc5d-d7fc07d9818f,4fa96e6c-6692-426b-beca-c138c8505a82,581.0
|
||||
2025-08-29 20:49:46+08:00,2025-08-29 20:53:14+08:00,4406.49,4406.02,500.0,234.99999999967258,short,d11f3a55-a1d8-41ff-a979-778c232459c9,b2de5ff9-9281-4c2e-86bd-a071a5e1e864,
|
||||
2025-08-29 20:49:46+08:00,2025-08-29 20:53:14+08:00,4406.49,4406.02,180.0,84.59999999988213,short,d11f3a55-a1d8-41ff-a979-778c232459c9,a4e3c4e5-4280-417c-8018-8386a63c318a,
|
||||
2025-08-29 21:00:24+08:00,2025-08-29 21:00:33+08:00,4408.35,4405.11,,-2203.20000000047,long,26e94954-9b31-48a9-9b2b-cb08da0f8e3e,14772435-8e33-4ee5-a87c-81342a7f8449,680.0
|
||||
2025-08-29 21:01:47+08:00,2025-08-29 21:06:54+08:00,4406.15,4391.41,39.0,574.8599999999915,short,8665f24a-3e13-4d3f-ad28-a702c79c73ed,7addab21-fcf3-4921-97ab-d77462067550,
|
||||
2025-08-29 21:01:47+08:00,2025-08-29 21:06:54+08:00,4406.15,4391.41,141.0,2078.339999999969,short,0170663d-0cf1-43a4-8595-4511c59b3107,7addab21-fcf3-4921-97ab-d77462067550,
|
||||
2025-08-29 21:01:47+08:00,2025-08-29 21:06:54+08:00,4406.15,4391.41,500.0,7369.999999999891,short,0170663d-0cf1-43a4-8595-4511c59b3107,3b754022-7c7c-4b14-8251-c2da4478df27,
|
||||
2025-08-29 21:10:41+08:00,2025-08-29 21:11:27+08:00,4400.55,4392.88,,-107.38000000000102,long,37e79409-f3ed-4e62-811c-6cc437396a41,f3a43d61-4d64-4d82-aada-31b38d441baa,14.0
|
||||
2025-08-29 21:10:41+08:00,2025-08-29 21:11:27+08:00,4400.55,4392.79,,-519.9200000000146,long,37e79409-f3ed-4e62-811c-6cc437396a41,86bfa728-af41-4e41-9e87-d6ef88b1a063,67.0
|
||||
2025-08-29 21:10:41+08:00,2025-08-29 21:11:27+08:00,4400.55,4392.88,,-4602.000000000044,long,37e79409-f3ed-4e62-811c-6cc437396a41,af01f0d0-8844-4267-8476-4000d0b2dd8b,600.0
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,4386.05,4373.6,72.0,896.3999999999869,short,fecb81e1-3b36-4ae6-91ad-b6a2616ee608,aee7c322-ba5d-4b89-8b93-1c50ce5be75b,
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,4386.05,4372.44,211.0,2871.710000000123,short,fecb81e1-3b36-4ae6-91ad-b6a2616ee608,e20951ed-ff4e-4a06-8bb5-8112a72e38bf,
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,4386.05,4372.44,99.0,1347.3900000000576,short,fecb81e1-3b36-4ae6-91ad-b6a2616ee608,16329965-69b4-42a4-8145-79d1a0d2e92b,
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,4386.29,4372.44,101.0,1398.8500000000367,short,f1e4b54c-d392-4dc4-8f73-40a8733b85fe,16329965-69b4-42a4-8145-79d1a0d2e92b,
|
||||
2025-08-29 21:30:11+08:00,2025-08-29 21:34:27+08:00,4386.29,4372.44,200.0,2770.0000000000728,short,f1e4b54c-d392-4dc4-8f73-40a8733b85fe,1b66a876-041a-480e-a9d6-428a3e301ec4,
|
||||
2025-08-30 08:15:30+08:00,2025-08-30 08:25:24+08:00,4343.62,4355.47,,4740.0000000001455,long,fb636839-9fb2-4e2f-bdb0-14bd57ade58a,6b8d4b73-132c-4c86-92f9-2a5df18943fd,400.0
|
||||
2025-08-30 08:15:30+08:00,2025-08-30 08:25:24+08:00,4343.62,4355.47,,3436.5000000001055,long,fb636839-9fb2-4e2f-bdb0-14bd57ade58a,a744bd39-3c81-4cb1-a153-60ed71d6748f,290.0
|
||||
2025-08-30 08:32:12+08:00,2025-08-30 08:35:26+08:00,4342.48,4337.98,,-288.0,long,e6185c1c-debd-4a9c-928c-edd28b360578,401ccb35-f82c-4df2-9a29-f9cd57481767,64.0
|
||||
2025-08-30 08:32:12+08:00,2025-08-30 08:35:26+08:00,4342.48,4335.64,,-3850.91999999957,long,e6185c1c-debd-4a9c-928c-edd28b360578,69d6c400-d534-47b6-b2a8-8da09b2986aa,563.0
|
||||
2025-08-30 08:32:12+08:00,2025-08-30 08:35:26+08:00,4342.48,4335.73,,-425.25,long,e6185c1c-debd-4a9c-928c-edd28b360578,4aa4a001-f8b0-4aea-93c3-546c71b81ee0,63.0
|
||||
2025-08-30 08:37:25+08:00,2025-08-30 08:41:59+08:00,4336.85,4332.05,691.0,3316.8000000001257,short,5cc6dd95-ec96-4864-a085-95b875673f0f,07f9b455-a026-41df-95c6-f9cf37f23a90,
|
||||
2025-08-30 09:26:28+08:00,2025-08-30 09:29:48+08:00,4326.54,4324.64,293.0,556.6999999998934,short,ce7db548-7de1-448e-92c9-25139a5f23a4,70062cb1-9074-49a9-8e7f-b5b7bae639d4,
|
||||
2025-08-30 09:26:28+08:00,2025-08-30 09:29:48+08:00,4326.54,4323.46,400.0,1231.999999999971,short,ce7db548-7de1-448e-92c9-25139a5f23a4,6145a0a3-248c-4822-8e8c-5efb6daf7d24,
|
||||
2025-08-30 09:51:41+08:00,2025-08-30 09:55:09+08:00,4312.57,4289.19,177.0,4138.260000000019,short,4b786a41-410d-4c93-b1c6-30b118f9e7f2,f78be76a-092e-4e0e-b8c3-26867d78c1de,
|
||||
2025-08-30 09:51:41+08:00,2025-08-30 09:55:09+08:00,4312.57,4289.19,518.0,12110.840000000057,short,96fbc46a-1dd3-4fc6-9e14-4a032e9464bf,f78be76a-092e-4e0e-b8c3-26867d78c1de,
|
||||
2025-08-30 09:55:49+08:00,2025-08-30 09:55:50+08:00,4282.58,4282.19,,-273.0000000002292,long,201dbee0-8f0d-427d-95d5-ef84e08a8aab,37ff5d0c-e10e-480c-91ca-0506e61330b6,700.0
|
||||
2025-08-30 09:58:19+08:00,2025-08-30 09:58:59+08:00,4281.59,4280.23,,-952.0000000004075,long,0e651112-db3b-402d-a268-1ea200cf8729,65b07548-3473-4570-be22-0172c49bdb94,700.0
|
||||
2025-08-30 10:00:09+08:00,2025-08-30 10:00:52+08:00,4264.04,4268.35,703.0,-3029.9300000002813,short,d9703fdd-ea02-410d-8303-d1d13b94748f,17fca37e-fc89-48a9-92ed-671ab0b9088e,
|
||||
2025-08-30 10:01:23+08:00,2025-08-30 10:02:02+08:00,4268.54,4271.97,110.0,-377.300000000032,short,022ce461-b78f-4127-9157-8c34a5e9272e,89468587-89ce-49cc-a9e3-343e24cb8036,
|
||||
2025-08-30 10:01:23+08:00,2025-08-30 10:02:02+08:00,4268.54,4271.97,592.0,-2030.5600000001723,short,9bdcfab5-9b8e-447c-b4ac-a82724e2774d,89468587-89ce-49cc-a9e3-343e24cb8036,
|
||||
2025-08-30 10:02:55+08:00,2025-08-30 10:04:26+08:00,4275.14,4280.56,400.0,-2168.000000000029,short,1b938bf4-dc99-4d2f-9e0b-c2fa5092d19d,4e6c40d8-e7c7-45ac-800e-5f71611479f1,
|
||||
2025-08-30 10:02:55+08:00,2025-08-30 10:04:26+08:00,4275.14,4280.56,141.0,-764.2200000000103,short,1b938bf4-dc99-4d2f-9e0b-c2fa5092d19d,864390a3-33dd-4a47-a3c2-471b8a0104f1,
|
||||
2025-08-30 10:02:55+08:00,2025-08-30 10:04:26+08:00,4275.14,4280.56,160.0,-867.2000000000116,short,7eb3eaf4-30fa-495d-a74c-61d725625c42,864390a3-33dd-4a47-a3c2-471b8a0104f1,
|
||||
2025-08-30 10:11:35+08:00,2025-08-30 10:18:43+08:00,4296.88,4306.91,,2326.959999999941,long,b1a002d6-951f-4912-b8cf-19bfc98f61ba,743a1e0f-48f3-43fe-9b6a-47536fa12ce1,232.0
|
||||
2025-08-30 10:11:35+08:00,2025-08-30 10:18:43+08:00,4296.91,4306.91,,4660.0,long,94664c85-6be6-42d8-984a-f3293ed9750f,743a1e0f-48f3-43fe-9b6a-47536fa12ce1,466.0
|
||||
2025-08-30 10:21:54+08:00,2025-08-30 10:27:07+08:00,4307.47,4306.32,696.0,800.4000000003798,short,641e7574-db24-4c78-8782-566b13de1e39,817cbe73-8f5f-4c27-893c-ddfe59e1890d,
|
||||
2025-08-30 10:28:32+08:00,2025-08-30 10:35:18+08:00,4305.75,4311.46,,2855.000000000018,long,dee31b34-3e7f-4b74-8774-ccd70f3cb124,d37eec35-4b90-42c7-82e7-7a56e909d268,500.0
|
||||
2025-08-30 10:28:32+08:00,2025-08-30 10:35:18+08:00,4305.75,4311.25,,1078.0,long,dee31b34-3e7f-4b74-8774-ccd70f3cb124,9cbf242a-7e93-4a3c-b798-6e3f7a88025d,196.0
|
||||
2025-08-30 10:48:32+08:00,2025-08-30 10:57:53+08:00,4313.05,4303.81,695.0,6421.799999999848,short,0efcab97-a0b6-4cd6-ad7e-22911ea32fa2,57fa177a-12c5-4127-91c4-289668a191ae,
|
||||
2025-08-30 11:03:45+08:00,2025-08-30 11:10:25+08:00,4308.11,4322.99,,4404.480000000032,long,36e3b573-d4be-4d54-9af3-8190731448c9,1e9213a7-3443-4209-a26a-d3e0feeaf325,296.0
|
||||
2025-08-30 11:03:45+08:00,2025-08-30 11:10:25+08:00,4308.11,4322.99,,5952.000000000044,long,36e3b573-d4be-4d54-9af3-8190731448c9,a344d092-7b22-4230-8ba9-0702dd353041,400.0
|
||||
2025-08-30 11:11:04+08:00,2025-08-30 11:17:45+08:00,4323.24,4328.87,,3901.5900000000756,long,c2e861bb-6de2-4dc0-aed3-7a385c0f34b8,d786c18b-8199-4335-ad6b-6115f39e089f,693.0
|
||||
2025-08-30 11:18:36+08:00,2025-08-30 11:23:02+08:00,4333.76,4358.0,,9695.999999999913,long,3c5242f4-5844-46a7-8b84-a6c73fed27c7,d71ce99c-8d8e-4f15-be2e-85d21e853298,400.0
|
||||
2025-08-30 11:18:36+08:00,2025-08-30 11:23:02+08:00,4333.76,4358.0,,3248.1599999999708,long,3c5242f4-5844-46a7-8b84-a6c73fed27c7,fe3607bd-9257-42ce-a0b3-06d882cb74af,134.0
|
||||
2025-08-30 11:18:36+08:00,2025-08-30 11:23:02+08:00,4333.76,4358.0,,3829.9199999999655,long,ce9f82f0-70a9-44b1-9175-4ee31dbc8fd6,fe3607bd-9257-42ce-a0b3-06d882cb74af,158.0
|
||||
2025-08-30 11:52:34+08:00,2025-08-30 11:57:42+08:00,4352.62,4352.32,,-20.70000000001255,long,a7a38452-b2e4-46c1-a14c-d85c0a79b250,078499df-67ff-4bdd-acaf-4e4fcf4457c0,69.0
|
||||
2025-08-30 11:52:34+08:00,2025-08-30 11:57:42+08:00,4352.62,4352.32,,-186.00000000011278,long,76c6328b-ae56-4313-9b73-3bc43f644ef4,078499df-67ff-4bdd-acaf-4e4fcf4457c0,620.0
|
||||
2025-08-30 12:01:25+08:00,2025-08-30 12:07:56+08:00,4351.41,4357.44,500.0,-3014.9999999998727,short,2228c2d3-6d7b-4835-8065-42ce6ad3a5af,3544a077-b511-45ef-b088-c380928048ee,
|
||||
2025-08-30 12:01:25+08:00,2025-08-30 12:07:56+08:00,4351.7,4357.44,121.0,-694.5399999999736,short,e33d8985-328c-4a44-829e-b8ebf6127582,3544a077-b511-45ef-b088-c380928048ee,
|
||||
2025-08-30 12:01:25+08:00,2025-08-30 12:07:56+08:00,4351.41,4357.44,527.0,-3177.809999999866,short,6f169ccd-3d5a-4e35-9653-7efd0a1580dc,3544a077-b511-45ef-b088-c380928048ee,
|
||||
2025-08-30 13:01:32+08:00,2025-08-30 13:07:24+08:00,4353.26,4358.47,577.0,-3006.170000000021,short,27412351-b7db-41d2-941e-014723b46c1a,f65cc5f5-8a78-4a59-bb58-cf0af4df8448,
|
||||
2025-08-30 13:01:33+08:00,2025-08-30 13:07:24+08:00,4353.26,4358.47,298.0,-1552.5800000000108,short,cab58243-ce8d-4149-b279-3417eac36fcd,f65cc5f5-8a78-4a59-bb58-cf0af4df8448,
|
||||
2025-08-30 13:01:33+08:00,2025-08-30 13:07:24+08:00,4353.26,4359.1,273.0,-1594.3200000000397,short,cab58243-ce8d-4149-b279-3417eac36fcd,c28367f5-fcb9-4345-82f2-c56451e2e462,
|
||||
2025-08-30 13:20:13+08:00,2025-08-30 13:32:38+08:00,4373.03,4377.0,1143.0,-4537.710000000291,short,0f73205b-a08a-4fad-9e77-f5d2fbbf1aee,6d5e0a1c-53e6-4fac-a76a-77cb3341a976,
|
||||
2025-08-30 13:33:46+08:00,2025-08-30 13:42:14+08:00,4385.31,4391.97,,7592.399999999834,long,11f0898c-7cba-49da-91d9-1fc6e9b57ab2,e7744350-f030-4566-bdeb-8a76060f8066,1140.0
|
||||
2025-08-30 13:48:01+08:00,2025-08-30 13:56:36+08:00,4395.74,4382.93,1137.0,14564.96999999942,short,d00ac585-7cde-477d-af47-a5889aa5ef70,e99ef7df-85e7-46b4-bc8a-4a6f2fc811fe,
|
||||
2025-08-30 13:58:04+08:00,2025-08-30 14:03:08+08:00,4381.43,4385.01,1141.0,-4084.779999999917,short,76216a51-657b-4659-86cb-2295aa642dac,8a9ab27e-38f2-4ec3-bbaf-642afd20a6e5,
|
||||
2025-08-30 14:10:44+08:00,2025-08-30 14:15:13+08:00,4383.15,4377.63,601.0,3317.5199999997158,short,587020d7-a3eb-4e44-b6c7-5c95b8c5451a,8ecfa693-c528-405b-8db4-0b02ccc7b9b3,
|
||||
2025-08-30 14:10:44+08:00,2025-08-30 14:15:13+08:00,4383.15,4377.63,500.0,2759.9999999997635,short,587020d7-a3eb-4e44-b6c7-5c95b8c5451a,babe1585-607c-4004-9aa9-19e1f5374ac1,
|
||||
2025-08-30 14:10:44+08:00,2025-08-30 14:15:13+08:00,4383.15,4377.63,39.0,215.27999999998156,short,587020d7-a3eb-4e44-b6c7-5c95b8c5451a,ab4e9d81-4133-42b0-9460-9a2efd36ec0b,
|
||||
2025-08-30 14:26:36+08:00,2025-08-30 14:33:14+08:00,4384.3,4391.39,,8082.600000000166,long,f703a5cc-3428-4796-a288-bfa6442800cb,a4e60efd-9afc-48b1-8850-6054e1330780,1140.0
|
||||
2025-08-30 14:45:13+08:00,2025-08-30 14:52:41+08:00,4395.22,4390.35,1137.0,5537.189999999876,short,b1f16517-7b15-43a4-a3e7-8f0b60f9f732,0445e9e3-c80c-4beb-a121-18b6094f52eb,
|
||||
2025-08-30 15:20:15+08:00,2025-08-30 15:40:40+08:00,4392.17,4398.92,52.0,-351.0,short,504ad303-3016-470e-9deb-a717d25a761c,9a648f36-ef00-45a4-8e56-868ab74acce6,
|
||||
2025-08-30 15:20:15+08:00,2025-08-30 15:40:40+08:00,4392.17,4398.92,500.0,-3375.0,short,59f674e3-aff1-4b79-b8b0-c650b299d27d,9a648f36-ef00-45a4-8e56-868ab74acce6,
|
||||
2025-08-30 15:20:15+08:00,2025-08-30 15:40:40+08:00,4392.17,4398.92,586.0,-3955.5,short,77548f6a-59ae-4ac2-880c-ea1f5679f507,9a648f36-ef00-45a4-8e56-868ab74acce6,
|
||||
2025-08-30 15:46:04+08:00,2025-08-30 15:50:09+08:00,4408.51,4402.78,,-6497.820000000536,long,76eb4bce-ff36-4d4c-b97a-cda6d9f7b544,63f7184c-7211-417f-b594-e39436d14940,1134.0
|
||||
2025-08-30 16:11:35+08:00,2025-08-30 16:32:13+08:00,4393.35,4389.12,400.0,1692.0000000001892,short,be9ff2ce-0d9e-4184-8d43-c49b33c71c95,754db465-b458-43a3-b2b5-3fb4f9fc2aaf,
|
||||
2025-08-30 16:11:35+08:00,2025-08-30 16:32:13+08:00,4393.35,4389.12,500.0,2115.0000000002365,short,cdf388db-68a2-47ea-ba94-431f02dadeac,754db465-b458-43a3-b2b5-3fb4f9fc2aaf,
|
||||
2025-08-30 16:11:35+08:00,2025-08-30 16:32:13+08:00,4393.35,4389.12,237.0,1002.5100000001121,short,e6f8d379-c20f-4a9d-90a4-7a5904bff1f9,754db465-b458-43a3-b2b5-3fb4f9fc2aaf,
|
||||
2025-08-30 17:05:10+08:00,2025-08-30 17:11:26+08:00,4390.54,4387.01,133.0,469.48999999996613,short,d4db2208-ba63-42c7-af51-9cec92bfe6f1,46858333-121c-4020-9567-bc44d63dec01,
|
||||
2025-08-30 17:05:10+08:00,2025-08-30 17:11:26+08:00,4390.54,4387.01,190.0,670.6999999999516,short,d4db2208-ba63-42c7-af51-9cec92bfe6f1,4d19376a-cfb7-4da9-8859-982e073624d1,
|
||||
2025-08-30 17:05:10+08:00,2025-08-30 17:11:26+08:00,4390.54,4387.01,310.0,1094.299999999921,short,00dda8f5-7156-4d25-9ea0-29e5fb2a4aca,4d19376a-cfb7-4da9-8859-982e073624d1,
|
||||
2025-08-30 17:05:10+08:00,2025-08-30 17:11:26+08:00,4390.54,4387.01,505.0,1782.6499999998714,short,00dda8f5-7156-4d25-9ea0-29e5fb2a4aca,e28b9838-7c48-4c78-a369-8259844cc3f4,
|
||||
2025-08-30 17:18:39+08:00,2025-08-30 17:28:37+08:00,4389.73,4385.08,,-5296.349999999586,long,2789c169-8a5d-47da-a6f9-6800e72ec26e,cd9481fb-33de-4e75-beb8-119ace92b5f6,1139.0
|
||||
2025-08-30 17:31:17+08:00,2025-08-30 17:37:08+08:00,4379.77,4384.13,1141.0,-4974.759999999626,short,434fc13a-925d-4abd-8677-0cd2b1a412f0,1e63ca1f-7f77-4ed9-a885-159f5dac641e,
|
||||
2025-08-30 17:44:10+08:00,2025-08-30 17:48:40+08:00,4384.13,4388.17,1140.0,-4605.5999999999585,short,288b4dc7-affd-4b36-897a-e02a024dbae0,70c0e310-219d-45d2-a91d-6b685af4a065,
|
||||
2025-08-30 17:50:34+08:00,2025-08-30 17:54:02+08:00,4389.94,4393.0,,3482.2800000004554,long,b78acf6a-d3b3-44c2-b484-ad95dfedd167,9473c228-7657-44cd-9038-38aeb833c3ab,1138.0
|
||||
2025-08-30 18:05:09+08:00,2025-08-30 18:07:30+08:00,4392.24,4394.52,,2594.640000000745,long,44cd4f02-5250-47d0-8e71-a66f856405ba,81abc1fa-1789-4f69-aaaa-2da1f3f34186,1138.0
|
||||
2025-08-31 10:28:55+08:00,2025-08-31 10:34:47+08:00,4450.3,4446.25,,-1320.3000000000593,long,0fe00795-d7d9-4586-b864-e445017c7f9d,716e265c-d39a-46b3-92e2-99f5d8a6b45f,326.0
|
||||
2025-08-31 10:28:55+08:00,2025-08-31 10:34:47+08:00,4450.3,4446.25,,-2316.600000000104,long,0fe00795-d7d9-4586-b864-e445017c7f9d,e7b360d6-da94-4cb7-b8c7-0b10db53a03b,572.0
|
||||
2025-08-31 11:08:10+08:00,2025-08-31 11:12:01+08:00,4454.6,4458.68,897.0,-3659.7599999999347,short,c31d66e2-a558-4b86-a520-3a9eb9d55346,f737efdc-5818-4bc5-b3db-1da76e7caf71,
|
||||
2025-08-31 11:24:10+08:00,2025-08-31 11:27:33+08:00,4457.8,4453.59,,-2138.6800000000185,long,38413547-9d48-43fc-a2be-12d2a8e0cd9f,ce5c15c0-d938-4b48-ab08-807531847f2e,508.0
|
||||
2025-08-31 11:24:10+08:00,2025-08-31 11:27:33+08:00,4457.8,4453.59,,-1637.6900000000142,long,816792ec-d409-4688-b7fd-2ee3e3f4491e,ce5c15c0-d938-4b48-ab08-807531847f2e,389.0
|
||||
2025-08-31 11:49:43+08:00,2025-08-31 11:53:50+08:00,4462.28,4469.74,,3730.000000000018,long,b1e2c3bf-db41-4762-b152-7a6a469c8cdb,52c185f1-3f74-42b6-afe5-6032df9dcb11,500.0
|
||||
2025-08-31 11:49:43+08:00,2025-08-31 11:53:50+08:00,4462.28,4469.74,,2954.1600000000144,long,b1e2c3bf-db41-4762-b152-7a6a469c8cdb,5fbbe739-8d3c-4aab-8333-7860bdca1d69,396.0
|
||||
2025-08-31 11:54:26+08:00,2025-08-31 12:00:20+08:00,4475.79,4483.53,,2267.819999999936,long,614c6489-c5a5-451d-a2a0-699e38d5a695,5da7f47f-33e3-45b8-9dfe-8232807978fc,293.0
|
||||
2025-08-31 11:54:26+08:00,2025-08-31 12:00:20+08:00,4475.79,4483.53,,1547.9999999999563,long,c443995b-f986-4a55-b6d9-59c43fa71fd2,5da7f47f-33e3-45b8-9dfe-8232807978fc,200.0
|
||||
2025-08-31 11:54:26+08:00,2025-08-31 12:00:20+08:00,4475.79,4483.53,,3095.9999999999127,long,c443995b-f986-4a55-b6d9-59c43fa71fd2,7d96c3c9-b227-48f3-b9fe-9a5c08871cfe,400.0
|
||||
2025-08-31 12:15:25+08:00,2025-08-31 12:25:03+08:00,4473.35,4467.05,894.0,5632.200000000163,short,d5064049-73a7-4a01-8d14-3d0ba056d572,b4537133-4056-4afc-80c2-7656e28cff88,
|
||||
2025-08-31 12:30:21+08:00,2025-08-31 12:33:51+08:00,4461.85,4465.99,500.0,-2069.999999999709,short,516d9754-0fe4-4f2e-9289-5827765f7c67,13f52cac-de10-4bba-93d5-8376ba2c5139,
|
||||
2025-08-31 12:30:21+08:00,2025-08-31 12:33:51+08:00,4461.85,4465.88,100.0,-402.99999999997453,short,516d9754-0fe4-4f2e-9289-5827765f7c67,a77196b2-cc47-482b-8fb0-a140d25da936,
|
||||
2025-08-31 12:30:21+08:00,2025-08-31 12:33:51+08:00,4461.85,4465.99,296.0,-1225.4399999998277,short,516d9754-0fe4-4f2e-9289-5827765f7c67,c529dbf2-2dd4-46aa-b5b6-dd0ed4f473e2,
|
||||
2025-08-31 12:42:50+08:00,2025-08-31 12:48:13+08:00,4464.2,4468.3,661.0,-2710.1000000002405,short,0f3ef65f-e754-4229-87e6-e0f102020ce9,bce7cdc5-2c37-4bda-9b2a-05c865dd69ba,
|
||||
2025-08-31 12:42:50+08:00,2025-08-31 12:48:13+08:00,4464.2,4468.3,235.0,-963.5000000000855,short,10e40e0b-a920-4e80-8d17-ea6f73d645e3,bce7cdc5-2c37-4bda-9b2a-05c865dd69ba,
|
||||
2025-08-31 13:31:35+08:00,2025-08-31 13:38:28+08:00,4453.99,4450.23,,-376.0000000000218,long,e65553ad-4738-4b5f-af08-c7373301aef8,ce3b836f-f3c8-4e27-bdd5-33b9e1ba2bc9,100.0
|
||||
2025-08-31 13:31:35+08:00,2025-08-31 13:38:28+08:00,4454.98,4450.23,,-2850.0,long,57f64aba-942f-4563-a3c7-9dfdfeaf98e4,ce3b836f-f3c8-4e27-bdd5-33b9e1ba2bc9,600.0
|
||||
2025-08-31 13:31:35+08:00,2025-08-31 13:38:28+08:00,4454.98,4450.23,,-940.5,long,597f272b-ce3c-45bf-99ee-4abe2f630972,ce3b836f-f3c8-4e27-bdd5-33b9e1ba2bc9,198.0
|
||||
2025-08-31 14:22:05+08:00,2025-08-31 14:23:54+08:00,4441.46,4446.28,275.0,-1325.49999999992,short,eafee2f8-8601-47c7-bfe0-2f0a35bd1411,3744b6fa-5971-44d1-885f-7c7f93da8e70,
|
||||
2025-08-31 14:22:05+08:00,2025-08-31 14:23:54+08:00,4442.21,4446.28,100.0,-406.9999999999709,short,702cd777-dc0b-4602-8c19-618520cf46f4,3744b6fa-5971-44d1-885f-7c7f93da8e70,
|
||||
2025-08-31 14:22:05+08:00,2025-08-31 14:23:54+08:00,4441.46,4446.28,525.0,-2530.499999999847,short,be46bffb-290a-477a-81d9-fd4e7c1d2f3c,3744b6fa-5971-44d1-885f-7c7f93da8e70,
|
||||
2025-08-31 14:33:49+08:00,2025-08-31 14:39:00+08:00,4439.81,4444.22,900.0,-3968.999999999869,short,f25e9b2a-1bb7-4bc5-852e-5c953f222f61,2d0c6c6c-070a-498b-ade7-0235a6d3e644,
|
||||
2025-08-31 14:40:14+08:00,2025-08-31 14:47:51+08:00,4444.0,4439.28,,-1939.9200000001047,long,46e5c712-8dff-423d-805d-d09da5e43571,9a2c96f9-3c09-4cca-8539-7251e96e99df,411.0
|
||||
2025-08-31 14:40:14+08:00,2025-08-31 14:47:51+08:00,4444.0,4439.13,,-1947.9999999999563,long,46e5c712-8dff-423d-805d-d09da5e43571,f92ff1ec-cfa2-4576-bcbb-978910e5200c,400.0
|
||||
2025-08-31 14:40:14+08:00,2025-08-31 14:47:51+08:00,4444.0,4439.13,,-433.4299999999903,long,46e5c712-8dff-423d-805d-d09da5e43571,5f572e4d-e1a7-45cf-bc1b-b55ce56f12b4,89.0
|
||||
2025-08-31 14:51:09+08:00,2025-08-31 14:57:17+08:00,4436.57,4435.16,450.0,634.4999999999345,short,3d5b8a22-edc9-4076-8d61-23f8ed561202,9c959838-3125-426e-abaf-d09fc309fda2,
|
||||
2025-08-31 15:30:46+08:00,2025-08-31 15:33:17+08:00,4450.96,4446.74,,-1894.7800000001143,long,45ada65c-b8e3-4316-9414-5faf1dcad582,57564f53-121e-47ac-8804-e448bb39c82c,449.0
|
||||
2025-08-31 16:05:31+08:00,2025-08-31 16:08:48+08:00,4461.21,4457.34,,-1733.759999999951,long,050937e7-f1e4-459e-982a-8d5263e718cb,ad5c9d31-f050-4981-9f51-7852dbf0df37,448.0
|
||||
2025-08-31 16:20:45+08:00,2025-08-31 16:24:22+08:00,4463.0,4462.93,,-3.35999999998603,long,e41030f3-095f-4e36-b465-e7f7d20b2972,83b7259d-91f7-485a-92ee-37c94790c7f4,48.0
|
||||
2025-08-31 16:20:45+08:00,2025-08-31 16:24:22+08:00,4463.0,4462.93,,-27.999999999883585,long,e41030f3-095f-4e36-b465-e7f7d20b2972,de316218-6bb2-48c0-b540-fc4babf5ce9b,400.0
|
||||
2025-08-31 17:24:37+08:00,2025-08-31 17:27:28+08:00,4473.73,4467.56,447.0,2757.989999999626,short,b8d42b12-b7c8-465d-a67b-56085ceae48f,74ed9f75-4d45-47a8-a636-5a1cbe5511ce,
|
||||
2025-08-31 17:48:57+08:00,2025-08-31 17:52:50+08:00,4463.45,4470.31,,3073.2800000002608,long,9b9c8688-3c74-48c3-b1bf-6bbc162b3edf,70a47e37-f8fb-440f-9ea6-50ffbf8055f6,448.0
|
||||
2025-08-31 17:59:19+08:00,2025-08-31 18:00:09+08:00,4461.24,4465.19,152.0,-600.3999999999724,short,28a00768-c1e1-42d0-b75e-f7b6b8608b60,9263141f-f5ac-4173-b3c2-b8e2f7ba0985,
|
||||
2025-08-31 17:59:19+08:00,2025-08-31 18:00:09+08:00,4461.24,4465.19,520.0,-2053.9999999999054,short,28a00768-c1e1-42d0-b75e-f7b6b8608b60,32d89f55-ae9c-49a6-8e2a-e47b5f4167ab,
|
||||
2025-08-31 18:07:59+08:00,2025-08-31 18:12:30+08:00,4459.1,4452.94,672.0,4139.520000000513,short,765d6b9a-0683-4dca-b76c-6e9a2ea65cd5,7f047d14-dac5-4fca-94ed-c0fdfec63578,
|
||||
2025-08-31 18:17:18+08:00,2025-08-31 18:29:14+08:00,4451.84,4455.42,,2409.339999999951,long,755ad988-d66e-4bc8-9907-b69112e3a11a,775a3323-bbd4-4e5c-ae56-d68e178eb05e,673.0
|
||||
2025-08-31 22:07:31+08:00,2025-08-31 22:13:15+08:00,4453.82,4450.0,1122.0,4286.0399999996735,short,c3c54d31-6a99-45ad-8139-546ac61bed34,ef4df9cc-3394-4d53-8fed-7e261a8b0c60,
|
||||
2025-08-31 22:14:11+08:00,2025-08-31 22:20:53+08:00,4453.63,4459.25,,2809.9999999999454,long,18042f61-552b-41bb-b228-8d8031ee3c14,ad7b6056-101b-49e4-9b9f-dea68880cd52,500.0
|
||||
2025-08-31 22:14:11+08:00,2025-08-31 22:20:53+08:00,4453.63,4459.25,,685.6399999999867,long,208d18b8-941b-4ce3-aa29-2f5fb074cc01,ad7b6056-101b-49e4-9b9f-dea68880cd52,122.0
|
||||
2025-08-31 22:14:11+08:00,2025-08-31 22:20:53+08:00,4453.63,4459.25,,2809.9999999999454,long,4e557687-e4c4-4f2d-85b6-364f6e5fd752,ad7b6056-101b-49e4-9b9f-dea68880cd52,500.0
|
||||
2025-08-31 23:05:00+08:00,2025-08-31 23:09:16+08:00,4473.11,4471.27,,-2055.2799999991466,long,41d06c42-ac03-418f-9451-6f5a5fdda71a,5cd65f94-490b-4bb2-bd42-d1a64942d201,1117.0
|
||||
2025-09-01 09:07:41+08:00,2025-09-01 09:11:05+08:00,4394.0,4383.39,400.0,4243.999999999869,short,092adf53-a461-4de7-8e04-9bfe1de8c387,b3e26049-21f9-495e-9a96-7498f06dcd82,
|
||||
2025-09-01 09:07:41+08:00,2025-09-01 09:11:05+08:00,4394.0,4383.39,55.0,583.549999999982,short,092adf53-a461-4de7-8e04-9bfe1de8c387,0614bd80-655c-49d4-b895-798f56f54803,
|
||||
2025-09-01 09:20:32+08:00,2025-09-01 09:25:17+08:00,4399.41,4396.45,,-1343.8400000000165,long,ef1554ec-bb75-4b5d-a3fd-03325d8ce248,11fb1482-9f6a-4b3f-aec4-e0e9f46ec4eb,454.0
|
||||
2025-09-01 09:31:04+08:00,2025-09-01 09:32:03+08:00,4409.02,4405.26,,-1703.2800000000989,long,8050d64e-1c53-40e1-89e7-68481d09e689,079730bc-d8ce-413a-a77f-a889bed73972,453.0
|
||||
2025-09-01 09:34:47+08:00,2025-09-01 09:37:04+08:00,4405.92,4417.68,,5327.280000000099,long,4f3b316a-37b6-43b9-a742-28760ce5267b,18f19970-4048-485b-b95a-00b3e804d0d9,453.0
|
||||
2025-09-01 10:11:29+08:00,2025-09-01 10:16:05+08:00,4400.12,4391.86,400.0,3304.0000000000873,short,9b7a45aa-4ed0-4fa2-9c77-ee71e8319ecf,6adc05fa-6727-4961-8ae9-ef7f248ce06f,
|
||||
2025-09-01 10:11:30+08:00,2025-09-01 10:16:05+08:00,4400.12,4391.86,54.0,446.0400000000118,short,a2b8e0df-1a70-4cbe-b513-734c169a45e5,6adc05fa-6727-4961-8ae9-ef7f248ce06f,
|
||||
2025-09-01 10:29:03+08:00,2025-09-01 10:30:26+08:00,4389.33,4386.21,,-2130.9599999999255,long,9295d00e-a050-472f-b008-426e40eb0f71,73e0f83a-583a-4ae6-8dc3-41c2f37bb58e,683.0
|
||||
2025-09-01 10:35:00+08:00,2025-09-01 10:36:12+08:00,4375.45,4379.38,685.0,-2692.0500000001994,short,8e5b463d-f4fe-47f3-b39b-ec3721c79e7a,2f96c834-2272-491f-8bc1-8cbe6c4ea452,
|
||||
2025-09-01 11:03:26+08:00,2025-09-01 11:05:42+08:00,4383.62,4391.35,,5287.3200000003235,long,ad2ab2b0-b070-4027-ba77-039ae801c30b,ab66e717-8f29-4ebe-80c7-165f36806e5f,684.0
|
||||
2025-09-01 11:12:18+08:00,2025-09-01 11:15:56+08:00,4382.88,4374.85,184.0,1477.5199999999531,short,60e70f9e-3ed0-402a-a33e-0d9104ac17b6,2c200cb7-b183-474a-b78f-bba9e592c8c0,
|
||||
2025-09-01 11:12:18+08:00,2025-09-01 11:15:56+08:00,4382.88,4374.85,500.0,4014.9999999998727,short,4f57713c-6a4c-42d4-aa6b-c03bae934835,2c200cb7-b183-474a-b78f-bba9e592c8c0,
|
||||
2025-09-01 11:34:38+08:00,2025-09-01 11:41:09+08:00,4370.18,4382.21,,3440.579999999927,long,7716be61-be89-4b30-8939-ff0c2c42b4e2,15dadfe0-75d8-4cc7-838d-931a1d21ccf8,286.0
|
||||
2025-09-01 11:34:38+08:00,2025-09-01 11:41:09+08:00,4370.18,4382.21,,4811.999999999898,long,7716be61-be89-4b30-8939-ff0c2c42b4e2,9ae0cd54-0bfb-4a24-8c7a-1a247e078eb8,400.0
|
||||
2025-09-01 11:54:59+08:00,2025-09-01 11:58:53+08:00,4396.0,4403.97,,79.70000000000255,long,b0876036-7e56-46cb-bddd-ccabb6519f7c,600628e2-049f-44c8-a905-46ea1135e601,10.0
|
||||
2025-09-01 11:54:59+08:00,2025-09-01 11:58:53+08:00,4396.47,4403.97,,1087.5,long,4bc131e8-70e0-4140-a2c8-b054e0507acd,600628e2-049f-44c8-a905-46ea1135e601,145.0
|
||||
2025-09-01 11:54:59+08:00,2025-09-01 11:58:53+08:00,4396.47,4403.97,,2662.5,long,4bc131e8-70e0-4140-a2c8-b054e0507acd,9d3eca96-2443-4c6e-b40e-1020bde26895,355.0
|
||||
2025-09-01 11:54:59+08:00,2025-09-01 11:58:53+08:00,4396.47,4403.97,,1290.0,long,86ab0723-57a8-4e31-9000-9b73f49ad308,9d3eca96-2443-4c6e-b40e-1020bde26895,172.0
|
||||
2025-09-01 12:19:34+08:00,2025-09-01 12:23:09+08:00,4389.68,4388.07,500.0,805.000000000291,short,2aec9027-7a84-45c2-83d3-8c5871d90106,95aa0191-4e59-46b0-b538-e0a3fd31a029,
|
||||
2025-09-01 12:19:34+08:00,2025-09-01 12:23:09+08:00,4389.68,4388.0,87.0,146.16000000002532,short,2aec9027-7a84-45c2-83d3-8c5871d90106,861ff638-8ec8-4a6a-bafa-74206b093040,
|
||||
2025-09-01 12:19:34+08:00,2025-09-01 12:23:09+08:00,4389.68,4388.07,52.0,83.72000000003027,short,2aec9027-7a84-45c2-83d3-8c5871d90106,6cff4ab5-d999-4dda-87e2-067c9adbd240,
|
||||
2025-09-01 12:19:34+08:00,2025-09-01 12:23:09+08:00,4389.68,4388.07,500.0,805.000000000291,short,2aec9027-7a84-45c2-83d3-8c5871d90106,4dc3eeb9-b54d-4820-bc73-2172e8e5fd66,
|
||||
2025-09-01 12:52:26+08:00,2025-09-01 12:56:26+08:00,4381.97,4385.85,,4427.0800000001245,long,a3ef0945-101d-4f17-9cda-711fe44e2cf1,ab314993-0d4e-4632-8bcd-66aaaef57066,1141.0
|
||||
2025-09-01 13:11:24+08:00,2025-09-01 13:12:28+08:00,4363.3,4368.36,475.0,-2403.499999999758,short,4d1d7cf6-26fe-44f0-be21-d10a9871ae1a,53f10353-21b1-41c1-8554-18c5bac4991a,
|
||||
2025-09-01 13:11:24+08:00,2025-09-01 13:12:28+08:00,4363.3,4368.36,400.0,-2023.9999999997963,short,63978423-878f-4bc0-8ac6-7526d1aa3b24,53f10353-21b1-41c1-8554-18c5bac4991a,
|
||||
2025-09-01 13:11:24+08:00,2025-09-01 13:12:28+08:00,4363.3,4368.36,500.0,-2529.9999999997453,short,9736d4ed-bfd4-4fb4-a2b9-7a335070a480,53f10353-21b1-41c1-8554-18c5bac4991a,
|
||||
2025-09-01 13:19:17+08:00,2025-09-01 13:20:04+08:00,4357.3,4364.42,1376.0,-9797.11999999985,short,9d7b259b-c1ec-4d54-83c5-e1588347d99f,a63b68c2-d467-4b4b-992a-175e562a59cd,
|
||||
2025-09-01 13:23:06+08:00,2025-09-01 13:33:39+08:00,4390.89,4384.71,,-1798.3800000000847,long,348b7ba0-df18-4d21-b652-1c54dc5b4635,2a709f71-d330-49c0-9759-3b65a140431c,291.0
|
||||
2025-09-01 13:23:06+08:00,2025-09-01 13:33:39+08:00,4390.89,4384.71,,-1854.0000000000873,long,c8c60f9e-a45b-4f5a-b000-a1a436757403,2a709f71-d330-49c0-9759-3b65a140431c,300.0
|
||||
2025-09-01 13:23:06+08:00,2025-09-01 13:33:39+08:00,4390.89,4384.71,,-1854.0000000000873,long,970891fc-d2ec-4c54-a196-7a450a88c968,2a709f71-d330-49c0-9759-3b65a140431c,300.0
|
||||
2025-09-01 13:23:06+08:00,2025-09-01 13:33:39+08:00,4390.01,4384.71,,-106.00000000000364,long,71cd000e-8c95-4f6f-a32f-1440db6e8c0d,2a709f71-d330-49c0-9759-3b65a140431c,20.0
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,4396.35,4396.21,,-40.18000000009397,long,83a1a14a-a4e3-40f1-86bd-915987501b35,4c298f23-9a76-4a0f-8bc1-f8ed23f12ede,287.0
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,4396.35,4396.21,,-28.000000000065484,long,83a1a14a-a4e3-40f1-86bd-915987501b35,46728c29-6645-408a-bba2-07f01cd5d06d,200.0
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,4396.35,4396.21,,-1.8200000000042564,long,83a1a14a-a4e3-40f1-86bd-915987501b35,c4cb8611-b4dd-41fd-95c2-c9455c1237e6,13.0
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,4396.35,4396.21,,-26.180000000061227,long,47230a4e-8ee9-4d68-aa51-01753afc623b,c4cb8611-b4dd-41fd-95c2-c9455c1237e6,187.0
|
||||
2025-09-01 13:49:13+08:00,2025-09-01 13:54:15+08:00,4396.35,4396.13,,-46.86000000005424,long,47230a4e-8ee9-4d68-aa51-01753afc623b,e06fcad9-5742-4c94-82eb-2f34884e3f4a,213.0
|
||||
2025-09-01 13:49:14+08:00,2025-09-01 13:54:15+08:00,4396.35,4396.13,,-1.980000000002292,long,1743d43c-c642-434b-940f-b6d33e6718b9,e06fcad9-5742-4c94-82eb-2f34884e3f4a,9.0
|
||||
2025-09-01 15:34:19+08:00,2025-09-01 15:35:08+08:00,4429.5,4426.01,,-1406.469999999912,long,e9dad14a-702d-4396-a259-38e6c3b62876,76c3e1c9-aeee-41bd-b8d4-37e8948af1c4,403.0
|
||||
2025-09-01 15:34:19+08:00,2025-09-01 15:35:08+08:00,4429.5,4426.01,,-1744.9999999998909,long,431b7e4b-b81e-4626-a6de-f5acd5a7cf20,76c3e1c9-aeee-41bd-b8d4-37e8948af1c4,500.0
|
||||
2025-09-01 15:36:02+08:00,2025-09-01 15:36:51+08:00,4424.4,4419.44,,-3868.8000000000284,long,d7ba8670-d0d7-4817-b30f-ed7d2ba4fd3c,74c07207-3acc-4f9c-b98b-ae54e4085d96,780.0
|
||||
2025-09-01 15:36:02+08:00,2025-09-01 15:36:51+08:00,4424.4,4420.0,,-545.5999999999549,long,d7ba8670-d0d7-4817-b30f-ed7d2ba4fd3c,a4bea0b9-d2b0-42bf-9e3b-371747ea4fa3,124.0
|
||||
2025-09-01 15:44:38+08:00,2025-09-01 15:47:21+08:00,4420.39,4426.94,,4427.799999999508,long,17564d45-1b57-4822-91dd-7bf3a162e7b1,131e056d-1779-4de8-bb12-1663936c3ffe,676.0
|
||||
2025-09-01 15:44:38+08:00,2025-09-01 15:47:21+08:00,4420.0,4426.94,,152.6799999999912,long,7e280213-31c8-4848-b164-57ce0f3a30c3,131e056d-1779-4de8-bb12-1663936c3ffe,22.0
|
||||
2025-09-01 15:44:38+08:00,2025-09-01 15:47:21+08:00,4420.39,4426.94,,1349.2999999998501,long,37898b4e-9a13-4bc0-a9e6-3fa908bd1e80,131e056d-1779-4de8-bb12-1663936c3ffe,206.0
|
||||
2025-09-01 15:51:28+08:00,2025-09-01 15:54:46+08:00,4434.86,4450.54,,6068.160000000113,long,a62d6355-e666-4b24-a9fe-1a8efd802cd4,c5b380bf-78c7-4dfd-a1b8-f212e0562e5d,387.0
|
||||
2025-09-01 15:51:28+08:00,2025-09-01 15:54:46+08:00,4434.86,4450.54,,8059.52000000015,long,9383482c-67d4-4a52-9c9a-bf106f8a66ed,c5b380bf-78c7-4dfd-a1b8-f212e0562e5d,514.0
|
||||
2025-09-01 15:59:35+08:00,2025-09-01 16:02:01+08:00,4454.21,4472.89,,16774.64000000026,long,b4194b4d-5b4a-4387-879f-b208a173d23e,0b3a10ad-d3bc-49e7-bc76-95d3f3f7b012,898.0
|
||||
2025-09-01 16:05:00+08:00,2025-09-01 16:05:43+08:00,4486.3,4479.29,,-6245.9100000001945,long,62035134-6e2c-4e78-a57e-90883d6fd125,e148f8b3-d2b0-49a8-9702-bf0ef6d70a11,891.0
|
||||
2025-09-01 16:06:22+08:00,2025-09-01 16:13:08+08:00,4478.53,4480.64,893.0,-1884.2300000005198,short,d901988b-b5b6-4a5e-a03c-83cedbdbbbda,dcc81b54-5253-4a49-a426-f28316341b68,
|
||||
2025-09-01 16:18:38+08:00,2025-09-01 16:19:03+08:00,4467.53,4470.68,895.0,-2819.2500000004884,short,2a066536-51d7-425d-ae9e-83613597c612,41ad454f-5790-480c-9f16-80b478841763,
|
||||
2025-09-01 16:20:05+08:00,2025-09-01 16:22:22+08:00,4473.83,4478.12,894.0,-3835.2599999999675,short,93df10c4-453d-405e-aa78-3870c5c5f78f,5aeedf17-a10d-4388-b16e-e18251d6e1e5,
|
||||
2025-09-01 16:23:14+08:00,2025-09-01 16:27:37+08:00,4475.22,4469.41,893.0,5188.330000000357,short,d9277a5f-844a-4afc-b784-0343d373a87e,7def0a70-6cc3-4880-9da3-16671ec6a296,
|
||||
2025-09-01 16:33:42+08:00,2025-09-01 16:41:25+08:00,4470.45,4469.46,894.0,885.0599999998049,short,9e7bf3df-d22c-413b-8b29-024b9c830f6b,3e80d0e3-3466-4e63-b75d-c86b67b6eb0b,
|
||||
2025-09-01 16:55:38+08:00,2025-09-01 17:04:46+08:00,4474.93,4476.7,893.0,-1580.6099999995777,short,aca7bfae-9112-4a7a-8a95-7b7661be7d13,20115f47-253a-435a-9b43-1cfcbdc9c12e,
|
||||
2025-09-01 17:10:25+08:00,2025-09-01 17:15:02+08:00,4472.69,4480.01,,6544.080000000553,long,44c66b7e-5da3-4773-b5a0-73b1cd1c13b9,d8403012-f909-414d-bb21-a012dc5722c2,894.0
|
||||
2025-09-01 17:35:29+08:00,2025-09-01 17:38:36+08:00,4473.01,4460.17,300.0,3852.0000000000437,short,67981b9f-af13-4595-9a36-80db526ac0e4,de9b300a-19de-4cc4-b86e-e5d8ade4eb2f,
|
||||
2025-09-01 17:35:29+08:00,2025-09-01 17:38:36+08:00,4473.01,4460.17,70.0,898.8000000000102,short,67981b9f-af13-4595-9a36-80db526ac0e4,f7f5c314-6e7b-498a-99cf-a2c868021b32,
|
||||
2025-09-01 17:35:29+08:00,2025-09-01 17:38:36+08:00,4473.01,4460.17,236.0,3030.2400000000343,short,67981b9f-af13-4595-9a36-80db526ac0e4,f34ae213-3bb5-4bc5-96b5-1efb92648427,
|
||||
2025-09-01 17:35:29+08:00,2025-09-01 17:38:36+08:00,4473.01,4460.17,287.0,3685.0800000000418,short,da23b541-1bde-4417-8875-4a52000d3183,f34ae213-3bb5-4bc5-96b5-1efb92648427,
|
||||
|
BIN
价格展示/做市策略.xls
BIN
价格展示/做市策略.xls
Binary file not shown.
177
回测数据/test2.py
177
回测数据/test2.py
@@ -1,177 +0,0 @@
|
||||
import datetime
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
# 请求头,用于伪装成浏览器访问接口
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'zh,zh-CN;q=0.9,zh-HK;q=0.8,en;q=0.7',
|
||||
'cache-control': 'no-cache',
|
||||
'origin': 'https://www.websea.com',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.websea.com/',
|
||||
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
|
||||
}
|
||||
|
||||
|
||||
def fetch_kline(day: int):
|
||||
"""获取某一天的分钟级 K线数据"""
|
||||
# 构造该日的起止时间戳
|
||||
time_ser = datetime.datetime(2025, 6, day)
|
||||
start_of_day = time_ser.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end_of_day = time_ser.replace(hour=23, minute=59, second=59, microsecond=0)
|
||||
params = {
|
||||
'symbol': 'ETH-USDT', # 交易对
|
||||
'period': '1min', # 分钟级K线
|
||||
'start': int(start_of_day.timestamp()), # 开始时间
|
||||
'end': int(end_of_day.timestamp()), # 结束时间
|
||||
}
|
||||
# 请求 API 获取数据
|
||||
response = requests.get('https://capi.websea.com/webApi/market/getKline', params=params, headers=headers)
|
||||
data = response.json()['result']['data']
|
||||
# 按 id 排序(保证时间顺序)
|
||||
return sorted(data, key=lambda x: x['id'])
|
||||
|
||||
|
||||
# ================= 辅助函数 =================
|
||||
|
||||
def is_bullish(candle):
|
||||
"""判断是否是阳线(收盘价 > 开盘价)"""
|
||||
return float(candle['close']) > float(candle['open'])
|
||||
|
||||
|
||||
def is_bearish(candle):
|
||||
"""判断是否是阴线(收盘价 < 开盘价)"""
|
||||
return float(candle['close']) < float(candle['open'])
|
||||
|
||||
|
||||
def check_signal(prev, curr):
|
||||
"""
|
||||
判断是否出现反包形态:
|
||||
1. 前一根阴线,后一根阳线,并且阳线包住前一根阴线 -> 做多信号
|
||||
2. 前一根阳线,后一根阴线,并且阴线包住前一根阳线 -> 做空信号
|
||||
"""
|
||||
p_open, p_close = float(prev['open']), float(prev['close'])
|
||||
c_open, c_close = float(curr['open']), float(curr['close'])
|
||||
|
||||
# 情况1:前阴后阳,且阳线包住前阴
|
||||
if is_bullish(curr) and is_bearish(prev):
|
||||
if c_open < p_close and c_close > p_open:
|
||||
return "long"
|
||||
|
||||
# 情况2:前阳后阴,且阴线包住前阳
|
||||
if is_bearish(curr) and is_bullish(prev):
|
||||
if c_open > p_close and c_close < p_open:
|
||||
return "short"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def simulate_trade(direction, entry_price, future_candles, capital=10000, take_profit_ratio=5,
|
||||
stop_loss_ratio=-2):
|
||||
"""
|
||||
模拟交易(逐根K线回测)
|
||||
使用资金金额来控制止盈止损:
|
||||
- 盈利达到 capital * take_profit_ratio 就止盈
|
||||
- 亏损达到 capital * stop_loss_ratio 就止损
|
||||
"""
|
||||
|
||||
for candle in future_candles:
|
||||
high, low, close = float(candle['high']), float(candle['low']), float(candle['close'])
|
||||
|
||||
if direction == "long":
|
||||
if high - entry_price >= take_profit_ratio:
|
||||
return high, take_profit_ratio, candle['id'] # 止盈
|
||||
if low - entry_price <= stop_loss_ratio:
|
||||
return low, stop_loss_ratio, candle['id'] # 止损
|
||||
|
||||
elif direction == "short":
|
||||
if entry_price - low >= take_profit_ratio:
|
||||
return high, take_profit_ratio, candle['id'] # 止盈
|
||||
if entry_price - low <= stop_loss_ratio:
|
||||
return low, stop_loss_ratio, candle['id'] # 止损
|
||||
|
||||
# 如果未来都没触发,最后一根收盘平仓
|
||||
final_price = float(future_candles[-1]['close'])
|
||||
if direction == "long":
|
||||
diff_money = (final_price - entry_price) / entry_price * capital
|
||||
else:
|
||||
diff_money = (entry_price - final_price) / entry_price * capital
|
||||
|
||||
return final_price, diff_money, future_candles[-1]['id']
|
||||
|
||||
|
||||
# ================= 主程序 =================
|
||||
if __name__ == '__main__':
|
||||
if __name__ == '__main__':
|
||||
zh_project = 0 # 累计盈亏
|
||||
all_trades = [] # 保存所有交易明细
|
||||
|
||||
# 遍历 9月1日 ~ 9月27日
|
||||
for i in range(1, 30):
|
||||
sorted_data = fetch_kline(i) # 获取数据
|
||||
|
||||
signals = wins = 0
|
||||
lig_price = low_price = 0 # 分别记录多/空的净收益
|
||||
|
||||
# 遍历每根K线,寻找信号
|
||||
for idx in range(1, len(sorted_data) - 2): # 留出未来K线
|
||||
prev, curr = sorted_data[idx - 1], sorted_data[idx] # 前一笔,当前一笔
|
||||
entry_candle = sorted_data[idx + 1] # 下一根开盘价作为入场价
|
||||
future_candles = sorted_data[idx + 2:] # 未来行情
|
||||
|
||||
entry_open = float(entry_candle['open']) # 开仓价格
|
||||
direction = check_signal(prev, curr) # 判断开仓方向
|
||||
|
||||
if direction:
|
||||
signals += 1 # 总共信号数
|
||||
exit_price, diff, exit_time = simulate_trade(direction, entry_open, future_candles)
|
||||
|
||||
# 统计多单/空单盈亏情况
|
||||
if direction == "long":
|
||||
lig_price += diff
|
||||
if diff > 0: wins += 1
|
||||
else:
|
||||
low_price += diff
|
||||
if diff > 0: wins += 1
|
||||
|
||||
# 保存交易详情
|
||||
all_trades.append((f"{i}号", entry_candle["id"], "做多" if direction == "long" else "做空",
|
||||
entry_open, exit_price, diff, exit_time))
|
||||
|
||||
# 输出每日统计结果
|
||||
if signals > 0:
|
||||
logger.info(
|
||||
f"日期:{i}号,信号数={signals}, 胜率={wins / signals * 100:.2f}%,"
|
||||
f"上涨方向={lig_price:.2f},下跌方向={low_price:.2f},综合={lig_price + low_price:.2f}"
|
||||
)
|
||||
else:
|
||||
logger.info(f"日期:{i}号,没有信号")
|
||||
|
||||
# 累计盈亏
|
||||
zh_project += (lig_price + low_price)
|
||||
|
||||
logger.success(f"综合价格:{zh_project:.2f}")
|
||||
|
||||
# ===== 输出每笔交易详情 =====
|
||||
logger.info("===== 每笔交易详情 =====")
|
||||
n = n1 = 0 # n = 总盈利,n1 = 总手续费
|
||||
for date, time_str, direction, entry, exit, diff, end_time in all_trades:
|
||||
logger.info(
|
||||
f"{date} {time_str} {direction} 入场={entry:.2f} 出场={exit:.2f} 出场时间={end_time} "
|
||||
f"差价={diff:.2f} 盈利={diff / entry * 10000:.2f} "
|
||||
f"开仓手续费=5u 平仓手续费={10000 / entry * exit * 0.0005:.2f}"
|
||||
)
|
||||
n1 += 5 + (10000 / entry * exit * 0.0005)
|
||||
n += (diff / entry) * 10000
|
||||
|
||||
print(f'一共笔数:{len(all_trades)}')
|
||||
print(f"一共盈利:{n:.2f}")
|
||||
print(f'一共手续费:{n1:.2f}')
|
||||
214
回测数据/推测策略,回测.py
214
回测数据/推测策略,回测.py
@@ -1,214 +0,0 @@
|
||||
import datetime
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
# 请求头,用于伪装成浏览器访问接口
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'zh,zh-CN;q=0.9,zh-HK;q=0.8,en;q=0.7',
|
||||
'cache-control': 'no-cache',
|
||||
'origin': 'https://www.websea.com',
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': 'https://www.websea.com/',
|
||||
'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
|
||||
}
|
||||
|
||||
|
||||
def fetch_kline(day: int, year: int = 2025, month: int = 9, period=1):
|
||||
"""获取某一天的分钟级 K线数据"""
|
||||
# 构造该日的起止时间戳
|
||||
time_ser = datetime.datetime(year, month, day) # 修正为2024年9月
|
||||
start_of_day = time_ser.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end_of_day = time_ser.replace(hour=23, minute=59, second=59, microsecond=0)
|
||||
params = {
|
||||
'symbol': 'ETH-USDT', # 交易对
|
||||
'period': f'{period}min', # 分钟级K线
|
||||
'start': int(start_of_day.timestamp()), # 开始时间
|
||||
'end': int(end_of_day.timestamp()), # 结束时间
|
||||
}
|
||||
# 请求 API 获取数据
|
||||
response = requests.get('https://capi.websea.com/webApi/market/getKline', params=params, headers=headers)
|
||||
data = response.json()['result']['data']
|
||||
# 按 id 排序(保证时间顺序)
|
||||
return sorted(data, key=lambda x: x['id'])
|
||||
|
||||
|
||||
# ================= 辅助函数 =================
|
||||
|
||||
def is_bullish(candle):
|
||||
"""判断是否是阳线(开盘价 < 收盘价,即涨)"""
|
||||
return float(candle['open']) < float(candle['close'])
|
||||
|
||||
|
||||
def is_bearish(candle):
|
||||
"""判断是否是阴线(开盘价 > 收盘价,即跌)"""
|
||||
return float(candle['open']) > float(candle['close'])
|
||||
|
||||
|
||||
def check_signal(prev, curr):
|
||||
"""
|
||||
判断是否出现包住形态,返回信号类型和方向:
|
||||
1. 前跌后涨包住 -> 做多信号 (bear_bull_engulf)
|
||||
2. 前涨后跌包住 -> 做空信号 (bull_bear_engulf)
|
||||
3. 前涨后涨包住 -> 做多信号 (bull_bull_engulf)
|
||||
4. 前跌后跌包住 -> 做空信号 (bear_bear_engulf)
|
||||
"""
|
||||
p_open, p_close = float(prev['open']), float(prev['close']) # 前一笔
|
||||
c_open, c_close = float(curr['open']), float(curr['close']) # 当前一笔
|
||||
|
||||
# 情况1:前跌后涨,且涨线包住前跌线 -> 做多信号
|
||||
if is_bullish(curr) and is_bearish(prev):
|
||||
if c_open < p_close and c_close > p_open:
|
||||
return "long", "bear_bull_engulf"
|
||||
|
||||
# 情况2:前涨后跌,且跌线包住前涨线 -> 做空信号
|
||||
if is_bearish(curr) and is_bullish(prev):
|
||||
if c_open > p_close and c_close < p_open:
|
||||
return "short", "bull_bear_engulf"
|
||||
|
||||
# # 情况3:前涨后涨,且后涨线包住前涨线 -> 做多信号
|
||||
# if is_bullish(curr) and is_bullish(prev):
|
||||
# if c_open < p_open and c_close > p_close:
|
||||
# return "long", "bull_bull_engulf"
|
||||
|
||||
# # 情况4:前跌后跌,且后跌线包住前跌线 -> 做空信号
|
||||
# if is_bearish(curr) and is_bearish(prev):
|
||||
# if c_open > p_open and c_close < p_close:
|
||||
# return "short", "bear_bear_engulf"
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def simulate_trade(direction, entry_price, future_candles, take_profit_diff=30, stop_loss_diff=-10):
|
||||
"""
|
||||
模拟交易(逐根K线回测)
|
||||
使用价差来控制止盈止损:
|
||||
- 盈利达到 take_profit_diff 就止盈
|
||||
- 亏损达到 stop_loss_diff 就止损
|
||||
|
||||
direction:信号类型
|
||||
entry_price:开盘价格
|
||||
future_candles:未来的行情数据
|
||||
|
||||
"""
|
||||
|
||||
for candle in future_candles:
|
||||
high, low, close = float(candle['high']), float(candle['low']), float(candle['close'])
|
||||
|
||||
if direction == "long":
|
||||
# 做多:检查最高价是否达到止盈,最低价是否触及止损
|
||||
if high >= entry_price + take_profit_diff:
|
||||
return entry_price + take_profit_diff, take_profit_diff, candle['id'] # 止盈
|
||||
if low <= entry_price + stop_loss_diff:
|
||||
return entry_price + stop_loss_diff, stop_loss_diff, candle['id'] # 止损
|
||||
|
||||
elif direction == "short":
|
||||
# 做空:检查最低价是否达到止盈,最高价是否触及止损
|
||||
if low <= entry_price - take_profit_diff:
|
||||
return entry_price - take_profit_diff, take_profit_diff, candle['id'] # 止盈
|
||||
if high >= entry_price - stop_loss_diff:
|
||||
return entry_price - stop_loss_diff, stop_loss_diff, candle['id'] # 止损
|
||||
|
||||
# 如果未来都没触发,最后一根收盘平仓
|
||||
final_price = float(future_candles[-1]['close'])
|
||||
if direction == "long":
|
||||
diff_money = final_price - entry_price
|
||||
else:
|
||||
diff_money = entry_price - final_price
|
||||
|
||||
return final_price, diff_money, future_candles[-1]['id']
|
||||
|
||||
|
||||
# ================= 主程序 =================
|
||||
if __name__ == '__main__':
|
||||
zh_project = 0 # 累计盈亏
|
||||
all_trades = [] # 保存所有交易明细
|
||||
|
||||
# 四种信号类型的统计
|
||||
signal_stats = {
|
||||
"bear_bull_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "涨包跌"},
|
||||
"bull_bear_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "跌包涨"},
|
||||
# "bull_bull_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "涨包涨"},
|
||||
# "bear_bear_engulf": {"count": 0, "wins": 0, "total_profit": 0, "name": "跌包跌"}
|
||||
}
|
||||
|
||||
datas = []
|
||||
# 遍历 9月1日 ~ 9月27日
|
||||
for i in range(1, 31):
|
||||
logger.info(i)
|
||||
sorted_data = fetch_kline(year=2025, month=9, day=i, period=15) # 获取数据
|
||||
|
||||
datas.extend(sorted_data)
|
||||
|
||||
datas = sorted(datas, key=lambda x: x["id"])
|
||||
print(datas)
|
||||
daily_signals = 0 # 信号总数
|
||||
daily_wins = 0
|
||||
daily_profit = 0 # 价差总和
|
||||
|
||||
# 遍历每根K线,寻找信号
|
||||
for idx in range(1, len(datas) - 2): # 留出未来K线
|
||||
prev, curr = datas[idx - 1], datas[idx] # 前一笔,当前一笔
|
||||
entry_candle = datas[idx + 1] # 下一根开盘价作为入场价
|
||||
future_candles = datas[idx + 2:] # 未来行情
|
||||
|
||||
entry_open = float(entry_candle['open']) # 开仓价格
|
||||
direction, signal_type = check_signal(prev, curr) # 判断开仓方向和信号类型
|
||||
|
||||
if direction and signal_type:
|
||||
daily_signals += 1
|
||||
|
||||
exit_price, diff, exit_time = simulate_trade(direction, entry_open, future_candles, take_profit_diff=30,
|
||||
stop_loss_diff=-2)
|
||||
|
||||
# 统计该信号类型的表现
|
||||
signal_stats[signal_type]["count"] += 1
|
||||
signal_stats[signal_type]["total_profit"] += diff
|
||||
if diff > 0:
|
||||
signal_stats[signal_type]["wins"] += 1
|
||||
daily_wins += 1
|
||||
|
||||
daily_profit += diff
|
||||
|
||||
# 将时间戳转换为本地时间
|
||||
local_time = datetime.datetime.fromtimestamp(entry_candle['id'])
|
||||
formatted_time = local_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# 保存交易详情
|
||||
all_trades.append(
|
||||
(
|
||||
f"{formatted_time}号",
|
||||
"做多" if direction == "long" else "做空",
|
||||
signal_stats[signal_type]["name"],
|
||||
entry_open,
|
||||
exit_price,
|
||||
diff,
|
||||
exit_time
|
||||
)
|
||||
)
|
||||
|
||||
# ===== 输出每笔交易详情 =====
|
||||
logger.info("===== 每笔交易详情 =====")
|
||||
n = n1 = 0 # n = 总盈利,n1 = 总手续费
|
||||
for date, direction, signal_name, entry, exit, diff, end_time in all_trades:
|
||||
profit_amount = diff / entry * 10000 # 计算盈利金额
|
||||
close_fee = 10000 / entry * exit * 0.0005 # 平仓手续费
|
||||
|
||||
logger.info(
|
||||
f"{date} {direction}({signal_name}) 入场={entry:.2f} 出场={exit:.2f} 出场时间={end_time} "
|
||||
f"差价={diff:.2f} 盈利={profit_amount:.2f} "
|
||||
f"开仓手续费=5u 平仓手续费={close_fee:.2f}"
|
||||
)
|
||||
n1 += 5 + close_fee
|
||||
n += profit_amount
|
||||
|
||||
print(f'一共笔数:{len(all_trades)}')
|
||||
print(f"一共盈利:{n:.2f}")
|
||||
print(f'一共手续费:{n1:.2f}')
|
||||
Reference in New Issue
Block a user