使用ctpplus,然后增加回测模块
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,836 @@
|
||||
"""
|
||||
订单流回测模块
|
||||
支持两种回测模式:
|
||||
1. Mode ofdata:读取 ofdata.json 历史数据重放
|
||||
2. Mode tick: 读取原始 tick CSV 重放完整流程
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import csv
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import datetime, timedelta
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from config import SYSTEM_CONFIG, TRADING_PARAMS
|
||||
|
||||
|
||||
# ============ 隔离数据存储 ============
|
||||
|
||||
class BacktestDataStore:
|
||||
"""回测数据存储(隔离实例,避免与实盘共享状态)"""
|
||||
tickdata = {}
|
||||
quote = {}
|
||||
ofdata = {}
|
||||
trade_dfs = {}
|
||||
prev_volume = {}
|
||||
last_tick = {}
|
||||
|
||||
|
||||
# ============ 交易记录数据结构 ============
|
||||
|
||||
@dataclass
|
||||
class TradeRecord:
|
||||
"""交易记录"""
|
||||
trade_id: int
|
||||
entry_datetime: str
|
||||
entry_price: float
|
||||
direction: str # "long" / "short"
|
||||
exit_datetime: Optional[str] = None
|
||||
exit_price: Optional[float] = None
|
||||
lots: int = 1
|
||||
pnl: float = 0.0
|
||||
pnl_pct: float = 0.0
|
||||
status: str = "open" # "open" / "closed"
|
||||
|
||||
|
||||
# ============ 权益跟踪器 ============
|
||||
|
||||
class EquityTracker:
|
||||
"""跟踪权益曲线和回撤"""
|
||||
|
||||
def __init__(self, initial_equity: float = 100000.0):
|
||||
self.initial_equity = initial_equity
|
||||
self.current_equity = initial_equity
|
||||
self.peak_equity = initial_equity
|
||||
self.max_drawdown = 0.0
|
||||
self.max_drawdown_pct = 0.0
|
||||
|
||||
# 权益曲线记录
|
||||
self.equity_curve = [] # [(datetime, equity, drawdown, drawdown_pct), ...]
|
||||
self.trades = [] # TradeRecord list
|
||||
self.trade_id_counter = 0
|
||||
|
||||
def update_equity(self, datetime_str: str, price: float, position: int):
|
||||
"""更新权益曲线(每个 bar 更新一次)"""
|
||||
if position != 0:
|
||||
# 有持仓时不计入权益(按结算价计算 unrealized P&L)
|
||||
return
|
||||
|
||||
# 无持仓时权益不变
|
||||
drawdown = self.current_equity - self.peak_equity
|
||||
drawdown_pct = (drawdown / self.peak_equity * 100) if self.peak_equity > 0 else 0
|
||||
|
||||
self.equity_curve.append({
|
||||
"datetime": datetime_str,
|
||||
"equity": self.current_equity,
|
||||
"drawdown": drawdown,
|
||||
"drawdown_pct": round(drawdown_pct, 2)
|
||||
})
|
||||
|
||||
def record_trade(self, trade: TradeRecord):
|
||||
"""记录交易"""
|
||||
self.trades.append(trade)
|
||||
|
||||
def close_trade(self, trade_id: int, exit_datetime: str, exit_price: float,
|
||||
multiplier: float, lots: int):
|
||||
"""平仓并计算 P&L"""
|
||||
for trade in self.trades:
|
||||
if trade.trade_id == trade_id and trade.status == "open":
|
||||
trade.exit_datetime = exit_datetime
|
||||
trade.exit_price = exit_price
|
||||
trade.status = "closed"
|
||||
|
||||
if trade.direction == "long":
|
||||
trade.pnl = (exit_price - trade.entry_price) * multiplier * lots
|
||||
else: # short
|
||||
trade.pnl = (trade.entry_price - exit_price) * multiplier * lots
|
||||
|
||||
trade.pnl_pct = (trade.pnl / self.initial_equity * 100)
|
||||
|
||||
# 更新权益
|
||||
self.current_equity += trade.pnl
|
||||
if self.current_equity > self.peak_equity:
|
||||
self.peak_equity = self.current_equity
|
||||
|
||||
return trade
|
||||
return None
|
||||
|
||||
def get_metrics(self) -> dict:
|
||||
"""计算性能指标"""
|
||||
closed_trades = [t for t in self.trades if t.status == "closed"]
|
||||
winning_trades = [t for t in closed_trades if t.pnl > 0]
|
||||
losing_trades = [t for t in closed_trades if t.pnl <= 0]
|
||||
|
||||
total_return = self.current_equity - self.initial_equity
|
||||
total_return_pct = (total_return / self.initial_equity * 100)
|
||||
|
||||
# 计算最大回撤
|
||||
equity_df = pd.DataFrame(self.equity_curve)
|
||||
if not equity_df.empty and "equity" in equity_df.columns:
|
||||
rolling_max = equity_df["equity"].cummax()
|
||||
drawdowns = equity_df["equity"] - rolling_max
|
||||
self.max_drawdown = drawdowns.min()
|
||||
self.max_drawdown_pct = (drawdowns.min() / rolling_max.max() * 100) if rolling_max.max() > 0 else 0
|
||||
|
||||
avg_win = np.mean([t.pnl for t in winning_trades]) if winning_trades else 0
|
||||
avg_loss = np.mean([t.pnl for t in losing_trades]) if losing_trades else 0
|
||||
total_win = sum([t.pnl for t in winning_trades])
|
||||
total_loss = abs(sum([t.pnl for t in losing_trades]))
|
||||
|
||||
return {
|
||||
"total_return": round(total_return, 2),
|
||||
"total_return_pct": round(total_return_pct, 2),
|
||||
"max_drawdown": round(self.max_drawdown, 2),
|
||||
"max_drawdown_pct": round(self.max_drawdown_pct, 2),
|
||||
"win_rate": round(len(winning_trades) / len(closed_trades), 4) if closed_trades else 0,
|
||||
"total_trades": len(closed_trades),
|
||||
"winning_trades": len(winning_trades),
|
||||
"losing_trades": len(losing_trades),
|
||||
"avg_win": round(avg_win, 2),
|
||||
"avg_loss": round(avg_loss, 2),
|
||||
"profit_factor": round(total_win / total_loss, 2) if total_loss > 0 else 0,
|
||||
"final_equity": round(self.current_equity, 2),
|
||||
}
|
||||
|
||||
def save_results(self, symbol: str):
|
||||
"""保存回测结果到文件"""
|
||||
results_dir = SYSTEM_CONFIG["backtest_results_dir"]
|
||||
os.makedirs(results_dir, exist_ok=True)
|
||||
|
||||
prefix = f"{results_dir}/{symbol}"
|
||||
|
||||
# 保存权益曲线 CSV
|
||||
if self.equity_curve:
|
||||
equity_df = pd.DataFrame(self.equity_curve)
|
||||
equity_df.to_csv(f"{prefix}_equity.csv", index=False)
|
||||
|
||||
# 保存交易记录 CSV
|
||||
if self.trades:
|
||||
trades_data = []
|
||||
for t in self.trades:
|
||||
trades_data.append({
|
||||
"trade_id": t.trade_id,
|
||||
"entry_datetime": t.entry_datetime,
|
||||
"entry_price": t.entry_price,
|
||||
"direction": t.direction,
|
||||
"exit_datetime": t.exit_datetime or "",
|
||||
"exit_price": t.exit_price or "",
|
||||
"lots": t.lots,
|
||||
"pnl": round(t.pnl, 2),
|
||||
"pnl_pct": round(t.pnl_pct, 2),
|
||||
"status": t.status
|
||||
})
|
||||
trades_df = pd.DataFrame(trades_data)
|
||||
trades_df.to_csv(f"{prefix}_trades.csv", index=False)
|
||||
|
||||
# 保存性能指标 JSON
|
||||
metrics = self.get_metrics()
|
||||
with open(f"{prefix}_metrics.json", 'w', encoding='utf-8') as f:
|
||||
json.dump(metrics, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
# ============ 回测交易执行器 ============
|
||||
|
||||
class BacktestTrader:
|
||||
"""回测交易执行器(复用信号计算逻辑)"""
|
||||
|
||||
def __init__(self, param_dict: dict, equity_tracker: EquityTracker):
|
||||
self.param_dict = param_dict
|
||||
self.equity_tracker = equity_tracker
|
||||
self.current_symbol = " "
|
||||
|
||||
def set_symbol(self, symbol: str):
|
||||
"""设置当前合约"""
|
||||
self.current_symbol = symbol
|
||||
|
||||
def execute_open_long(self, trade_df: pd.DataFrame, param, current_price: float):
|
||||
"""开多信号 — 记录待买入价格和对应的止损价(模拟实盘 pending order模式)"""
|
||||
if param.position != 0:
|
||||
return None
|
||||
|
||||
param.pending_long_price = trade_df['dj_price_high'].iloc[-1] if 'dj_price_high' in trade_df.columns else current_price
|
||||
param.pending_sl_long_price = trade_df['dj_price_low'].iloc[-1] if 'dj_price_low' in trade_df.columns else 0
|
||||
print(f"开多信号: 记录待买入价={param.pending_long_price}, datetime={trade_df['datetime'].iloc[-1]}")
|
||||
|
||||
def execute_open_short(self, trade_df: pd.DataFrame, param, current_price: float):
|
||||
"""开空信号 — 记录待卖出价格和对应的止损价(模拟实盘 pending order 模式)"""
|
||||
if param.position != 0:
|
||||
return None
|
||||
|
||||
param.pending_short_price = trade_df['dj_price_low'].iloc[-1] if 'dj_price_low' in trade_df.columns else current_price
|
||||
param.pending_sl_short_price = trade_df['dj_price_high'].iloc[-1] if 'dj_price_high' in trade_df.columns else 0
|
||||
print(f"开空信号: 记录待卖出价={param.pending_short_price}, datetime={trade_df['datetime'].iloc[-1]}")
|
||||
|
||||
def check_pending_orders(self, trade_df: pd.DataFrame, param, current_price: float):
|
||||
"""检查并执行待成交订单(模拟实盘 check_pending_orders)"""
|
||||
# 检查待开多订单(价格回落到堆积区间高价时买入)
|
||||
if param.position == 0 and param.pending_long_price > 0:
|
||||
if current_price <= param.pending_long_price:
|
||||
price = current_price + param.price_offset
|
||||
self._do_open_long(trade_df, param, price)
|
||||
param.pending_long_price = 0
|
||||
param.pending_sl_long_price = 0
|
||||
|
||||
# 检查待开空订单(价格反弹到堆积区间低价时卖出)
|
||||
if param.position == 0 and param.pending_short_price > 0:
|
||||
if current_price >= param.pending_short_price:
|
||||
price = current_price - param.price_offset
|
||||
self._do_open_short(trade_df, param, price)
|
||||
param.pending_short_price = 0
|
||||
param.pending_sl_short_price = 0
|
||||
|
||||
def _do_open_long(self, trade_df: pd.DataFrame, param, price: float):
|
||||
"""执行开多(内部方法)"""
|
||||
trade_id = self.equity_tracker.trade_id_counter + 1
|
||||
self.equity_tracker.trade_id_counter += 1
|
||||
|
||||
trade = TradeRecord(
|
||||
trade_id=trade_id,
|
||||
entry_datetime=trade_df["datetime"].iloc[-1],
|
||||
entry_price=price,
|
||||
direction="long",
|
||||
lots=param.lots,
|
||||
status="open"
|
||||
)
|
||||
self.equity_tracker.record_trade(trade)
|
||||
|
||||
param.position = 1
|
||||
param.sl_long_price = param.pending_sl_long_price if param.pending_sl_long_price > 0 else trade_df['dj_price_low'].iloc[-1] if 'dj_price_low' in trade_df.columns else 0
|
||||
print(f"执行开多: price={price}, sl={param.sl_long_price}, datetime={trade_df['datetime'].iloc[-1]}")
|
||||
|
||||
return trade
|
||||
|
||||
def _do_open_short(self, trade_df: pd.DataFrame, param, price: float):
|
||||
"""执行开空(内部方法)"""
|
||||
trade_id = self.equity_tracker.trade_id_counter + 1
|
||||
self.equity_tracker.trade_id_counter += 1
|
||||
|
||||
trade = TradeRecord(
|
||||
trade_id=trade_id,
|
||||
entry_datetime=trade_df["datetime"].iloc[-1],
|
||||
entry_price=price,
|
||||
direction="short",
|
||||
lots=param.lots,
|
||||
status="open"
|
||||
)
|
||||
self.equity_tracker.record_trade(trade)
|
||||
|
||||
param.position = -1
|
||||
param.sl_short_price = param.pending_sl_short_price if param.pending_sl_short_price > 0 else trade_df['dj_price_high'].iloc[-1] if 'dj_price_high' in trade_df.columns else 0
|
||||
print(f"执行开空: price={price}, sl={param.sl_short_price}, datetime={trade_df['datetime'].iloc[-1]}")
|
||||
|
||||
return trade
|
||||
|
||||
def execute_close_long(self, trade_df: pd.DataFrame, param, current_price: float):
|
||||
"""平多"""
|
||||
if param.position != 1:
|
||||
return None
|
||||
|
||||
#找到对应的 open trade
|
||||
for trade in self.equity_tracker.trades:
|
||||
if trade.direction == "long" and trade.status == "open":
|
||||
trade.exit_datetime = trade_df["datetime"].iloc[-1]
|
||||
trade.exit_price = current_price
|
||||
trade.status = "closed"
|
||||
|
||||
multiplier = SYSTEM_CONFIG["contract_multiplier"].get(
|
||||
param.symbol, 1
|
||||
)
|
||||
trade.pnl = (current_price - trade.entry_price) * multiplier * param.lots
|
||||
trade.pnl_pct = (trade.pnl / self.equity_tracker.initial_equity * 100)
|
||||
|
||||
self.equity_tracker.current_equity += trade.pnl
|
||||
if self.equity_tracker.current_equity > self.equity_tracker.peak_equity:
|
||||
self.equity_tracker.peak_equity = self.equity_tracker.current_equity
|
||||
|
||||
param.position = 0
|
||||
param.sl_long_price = 0
|
||||
param.pending_long_price = 0
|
||||
|
||||
return trade
|
||||
return None
|
||||
|
||||
def execute_close_short(self, trade_df: pd.DataFrame, param, current_price: float):
|
||||
"""平空"""
|
||||
if param.position != -1:
|
||||
return None
|
||||
|
||||
for trade in self.equity_tracker.trades:
|
||||
if trade.direction == "short" and trade.status == "open":
|
||||
trade.exit_datetime = trade_df["datetime"].iloc[-1]
|
||||
trade.exit_price = current_price
|
||||
trade.status = "closed"
|
||||
|
||||
multiplier = SYSTEM_CONFIG["contract_multiplier"].get(
|
||||
param.symbol, 1
|
||||
)
|
||||
trade.pnl = (trade.entry_price - current_price) * multiplier * param.lots
|
||||
trade.pnl_pct = (trade.pnl / self.equity_tracker.initial_equity * 100)
|
||||
|
||||
self.equity_tracker.current_equity += trade.pnl
|
||||
if self.equity_tracker.current_equity > self.equity_tracker.peak_equity:
|
||||
self.equity_tracker.peak_equity = self.equity_tracker.current_equity
|
||||
|
||||
param.position = 0
|
||||
param.sl_short_price = 0
|
||||
param.pending_short_price = 0
|
||||
|
||||
return trade
|
||||
return None
|
||||
|
||||
def check_stop_loss(self, trade_df: pd.DataFrame, param, current_price: float) -> bool:
|
||||
"""检查止损"""
|
||||
if param.position > 0 and param.sl_long_price > 0:
|
||||
if current_price < param.sl_long_price:
|
||||
self.execute_close_long(trade_df, param, current_price)
|
||||
return True
|
||||
elif param.position < 0 and param.sl_short_price > 0:
|
||||
if current_price > param.sl_short_price:
|
||||
self.execute_close_short(trade_df, param, current_price)
|
||||
return True
|
||||
return False
|
||||
|
||||
def calculate_signals(self, trade_df: pd.DataFrame, param) -> Tuple[bool, bool, float, float]:
|
||||
"""计算交易信号(复用 OrderFlowTrader 逻辑)"""
|
||||
if len(trade_df) < 2:
|
||||
return False, False, 0, 0
|
||||
|
||||
dj_last = trade_df["dj"].iloc[-1]
|
||||
delta_last = trade_df["delta"].iloc[-1]
|
||||
|
||||
bull_signal = dj_last >= param.accumulation_threshold and \
|
||||
delta_last >= param.delta_threshold
|
||||
bear_signal = dj_last <= -param.accumulation_threshold and \
|
||||
delta_last <= -param.delta_threshold
|
||||
|
||||
return bull_signal, bear_signal, dj_last, delta_last
|
||||
|
||||
def calculate_delta_cumulative(self, trade_df: pd.DataFrame, param):
|
||||
"""计算 Delta 累计"""
|
||||
if trade_df["delta"].dtype == object:
|
||||
trade_df["delta"] = trade_df["delta"].astype(float)
|
||||
|
||||
trade_df["datetime"] = pd.to_datetime(trade_df["datetime"])
|
||||
|
||||
last_dt = pd.to_datetime(trade_df["datetime"].iloc[-1])
|
||||
hour = last_dt.hour
|
||||
if hour >= 21:
|
||||
trading_day = (last_dt + pd.Timedelta(days=1)).date()
|
||||
elif hour < 15:
|
||||
trading_day = last_dt.date()
|
||||
else:
|
||||
trading_day = last_dt.date()
|
||||
|
||||
trade_df["trading_day"] = str(trading_day)
|
||||
trade_df["delta累计"] = trade_df.groupby("trading_day")["delta"].cumsum()
|
||||
|
||||
trade_df["datetime"] = trade_df["datetime"].dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
# ============ ofdata 回放器 (Mode 1) ============
|
||||
|
||||
class OfdataReplayer:
|
||||
"""Mode 1: 回放 ofdata.json 数据"""
|
||||
|
||||
def __init__(self, backtester: 'Backtester', symbol: str):
|
||||
self.backtester = backtester
|
||||
self.symbol = symbol
|
||||
self.param = backtester.param_dict[symbol]
|
||||
self.trader = backtester.trader
|
||||
self.equity_tracker = backtester.equity_tracker
|
||||
self.data_store = backtester.data_store
|
||||
|
||||
def replay(self):
|
||||
"""执行 ofdata 回放"""
|
||||
data_dir = SYSTEM_CONFIG["data_dir"]
|
||||
json_path = f"{data_dir}/{self.symbol}_ofdata.json"
|
||||
|
||||
if not os.path.exists(json_path):
|
||||
print(f"ofdata 文件不存在: {json_path}")
|
||||
return
|
||||
|
||||
# 加载 ofdata.json
|
||||
trade_df = pd.read_json(json_path, lines=True)
|
||||
if trade_df.empty:
|
||||
print("ofdata 数据为空")
|
||||
return
|
||||
|
||||
print(f"加载 ofdata: {json_path}, 共 {len(trade_df)} 条")
|
||||
|
||||
self.data_store.trade_dfs[self.symbol] = trade_df.copy()
|
||||
self.trader.set_symbol(self.symbol)
|
||||
|
||||
param = self.param
|
||||
param.position = 0
|
||||
param.processed_rows = 0
|
||||
|
||||
# 逐行处理
|
||||
for idx in range(len(trade_df)):
|
||||
# 构建单行 DataFrame
|
||||
row_df = trade_df.iloc[[idx]].copy()
|
||||
current_dt = row_df["datetime"].iloc[0]
|
||||
current_price = row_df["close"].iloc[0]
|
||||
|
||||
# 计算 delta 累计
|
||||
if idx > 0:
|
||||
# 重新计算到当前行
|
||||
temp_df = trade_df.iloc[:idx+1].copy()
|
||||
self.trader.calculate_delta_cumulative(temp_df, param)
|
||||
signal_df = temp_df
|
||||
else:
|
||||
signal_df = trade_df.iloc[[idx]].copy()
|
||||
|
||||
# 更新 DataStore
|
||||
self.data_store.trade_dfs[self.symbol] = trade_df.iloc[[idx]].copy()
|
||||
|
||||
# 检查止损(已有持仓的情况下)
|
||||
self.trader.check_stop_loss(signal_df, param, current_price)
|
||||
|
||||
# 检查待成交订单(价格触达 pending 则开仓)
|
||||
self.trader.check_pending_orders(signal_df, param, current_price)
|
||||
|
||||
# 计算信号(仅在无持仓时触发 pending order)
|
||||
bull_signal, bear_signal, dj_last, delta_last = self.trader.calculate_signals(signal_df, param)
|
||||
|
||||
if bull_signal or bear_signal:
|
||||
# 平仓信号
|
||||
if param.position < 0 and (bear_signal or bull_signal):
|
||||
self.trader.execute_close_short(row_df, param, current_price)
|
||||
elif param.position > 0 and (bull_signal or bear_signal):
|
||||
self.trader.execute_close_long(row_df, param, current_price)
|
||||
|
||||
# 开仓信号(设置 pending price,不立即开仓)
|
||||
if bull_signal and param.position == 0:
|
||||
self.trader.execute_open_long(row_df, param, current_price)
|
||||
if bear_signal and param.position == 0:
|
||||
self.trader.execute_open_short(row_df, param, current_price)
|
||||
|
||||
# 更新权益曲线(每行)
|
||||
self.equity_tracker.update_equity(current_dt, current_price, param.position)
|
||||
|
||||
param.processed_rows = idx + 1
|
||||
|
||||
print(f"回放完成: {len(trade_df)} 条, 最终持仓: {param.position}")
|
||||
|
||||
|
||||
# ============ tick 回放器 (Mode 2) ============
|
||||
|
||||
class TickReplayer:
|
||||
"""Mode 2: 回放原始 tick CSV 数据"""
|
||||
|
||||
def __init__(self, backtester: 'Backtester', symbol: str):
|
||||
self.backtester = backtester
|
||||
self.symbol = symbol
|
||||
self.param = backtester.param_dict[symbol]
|
||||
self.trader = backtester.trader
|
||||
self.equity_tracker = backtester.equity_tracker
|
||||
self.data_store = backtester.data_store
|
||||
|
||||
# tick CSV 路径
|
||||
data_dir = SYSTEM_CONFIG["data_dir"]
|
||||
self.tick_csv_path = f"{data_dir}/{symbol}_tick.csv"
|
||||
|
||||
def replay(self):
|
||||
"""执行 tick 回放"""
|
||||
if not os.path.exists(self.tick_csv_path):
|
||||
print(f"tick CSV 不存在: {self.tick_csv_path}")
|
||||
return
|
||||
|
||||
# 读取 tick CSV
|
||||
tick_df = pd.read_csv(self.tick_csv_path)
|
||||
if tick_df.empty:
|
||||
print("tick 数据为空")
|
||||
return
|
||||
|
||||
print(f"加载 tick: {self.tick_csv_path}, 共 {len(tick_df)} 条")
|
||||
|
||||
self.trader.set_symbol(self.symbol)
|
||||
param = self.param
|
||||
param.position = 0
|
||||
param.processed_rows = 0
|
||||
|
||||
prev_bartime = None
|
||||
|
||||
for idx, row in tick_df.iterrows():
|
||||
# 构建 tick 数据
|
||||
tick_data = {
|
||||
"InstrumentID": row["InstrumentID"].encode() if isinstance(row["InstrumentID"], str) else row["InstrumentID"],
|
||||
"ActionDay": str(row["ActionDay"]).encode() if isinstance(row["ActionDay"], (int, str)) else row["ActionDay"],
|
||||
"UpdateTime": str(row["UpdateTime"]).encode() if isinstance(row["UpdateTime"], str) else row["UpdateTime"],
|
||||
"UpdateMillisec": int(row["UpdateMillisec"]) if "UpdateMillisec" in row else 0,
|
||||
"LastPrice": float(row["LastPrice"]),
|
||||
"Volume": int(row["Volume"]),
|
||||
"BidPrice1": float(row["BidPrice1"]),
|
||||
"BidVolume1": int(row["BidVolume1"]),
|
||||
"AskPrice1": float(row["AskPrice1"]),
|
||||
"AskVolume1": int(row["AskVolume1"]),
|
||||
"UpperLimitPrice": float(row["UpperLimitPrice"]) if "UpperLimitPrice" in row else 0,
|
||||
"LowerLimitPrice": float(row["LowerLimitPrice"]) if "LowerLimitPrice" in row else 0,
|
||||
"TradingDay": str(row["TradingDay"]).encode() if isinstance(row["TradingDay"], str) else row["TradingDay"],
|
||||
"Turnover": float(row["Turnover"]) if "Turnover" in row else 0,
|
||||
"OpenInterest": int(row["OpenInterest"]) if "OpenInterest" in row else 0,
|
||||
}
|
||||
|
||||
# 处理 tick
|
||||
self._process_tick(tick_data, param, prev_bartime)
|
||||
prev_bartime = self.data_store.tickdata.get(self.symbol, {}).get("bartime", prev_bartime)
|
||||
|
||||
print(f"tick 回放完成: {len(tick_df)} 条, 最终持仓: {param.position}")
|
||||
|
||||
def _process_tick(self, data: dict, param, prev_bartime: str):
|
||||
"""处理单个 tick(复用 run.py 的 tick 处理逻辑)"""
|
||||
instrument_id = data["InstrumentID"].decode() if isinstance(data["InstrumentID"], bytes) else data["InstrumentID"]
|
||||
action_day = str(data["ActionDay"].decode() if isinstance(data["ActionDay"], bytes) else data["ActionDay"])
|
||||
update_time = str(data["UpdateTime"].decode() if isinstance(data["UpdateTime"], bytes) else data["UpdateTime"])
|
||||
|
||||
action_day_fmt = f"{action_day[:4]}-{action_day[4:6]}-{action_day[6:]}"
|
||||
created_at = datetime.strptime(
|
||||
f"{action_day_fmt} {update_time}.{data['UpdateMillisec']}",
|
||||
"%Y-%m-%d %H:%M:%S.%f"
|
||||
)
|
||||
|
||||
# 计算瞬时成交量
|
||||
prev_vol = self.data_store.prev_volume.get(instrument_id, 0)
|
||||
curr_vol = int(data["Volume"])
|
||||
last_vol = curr_vol - prev_vol if prev_vol != 0 else 0
|
||||
self.data_store.prev_volume[instrument_id] = curr_vol
|
||||
|
||||
if last_vol <= 0:
|
||||
return
|
||||
|
||||
# 构建 tick
|
||||
tick = {
|
||||
"symbol": instrument_id,
|
||||
"created_at": created_at,
|
||||
"price": float(data["LastPrice"]),
|
||||
"last_volume": last_vol,
|
||||
"bid_p": float(data["BidPrice1"]),
|
||||
"bid_v": int(data["BidVolume1"]),
|
||||
"ask_p": float(data["AskPrice1"]),
|
||||
"ask_v": int(data["AskVolume1"]),
|
||||
"upper_limit": float(data["UpperLimitPrice"]),
|
||||
"lower_limit": float(data["LowerLimitPrice"]),
|
||||
"trading_day": data["TradingDay"].decode() if isinstance(data["TradingDay"], bytes) else str(data["TradingDay"]),
|
||||
"cum_volume": curr_vol,
|
||||
"cum_amount": float(data["Turnover"]),
|
||||
"cum_position": int(data["OpenInterest"]),
|
||||
}
|
||||
self._on_tick(tick, param, prev_bartime)
|
||||
|
||||
def _on_tick(self, tick: dict, param, prev_bartime: str):
|
||||
"""处理 tick(简化版)"""
|
||||
tsymbol = tick["symbol"]
|
||||
|
||||
# 获取上条 tick
|
||||
prev_tick = self.data_store.last_tick.get(tsymbol)
|
||||
if prev_tick:
|
||||
bid_p = prev_tick["bid_p"]
|
||||
ask_p = prev_tick["ask_p"]
|
||||
bid_v = prev_tick["bid_v"]
|
||||
ask_v = prev_tick["ask_v"]
|
||||
else:
|
||||
bid_p = tick["bid_p"]
|
||||
ask_p = tick["ask_p"]
|
||||
bid_v = tick["bid_v"]
|
||||
ask_v = tick["ask_v"]
|
||||
|
||||
self.data_store.last_tick[tsymbol] = tick
|
||||
|
||||
timetick = tick["created_at"].strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
||||
|
||||
# 构建 DataFrame
|
||||
tick_dt = pd.DataFrame({
|
||||
"datetime": timetick,
|
||||
"symbol": tick["symbol"],
|
||||
"mainsym": tick["symbol"].rstrip("0123456789").upper(),
|
||||
"lastprice": tick["price"],
|
||||
"vol": tick["last_volume"],
|
||||
"bid_p": bid_p,
|
||||
"ask_p": ask_p,
|
||||
"bid_v": bid_v,
|
||||
"ask_v": ask_v,
|
||||
}, index=[0])
|
||||
|
||||
self._tickdata(tick_dt, tsymbol, param, prev_bartime)
|
||||
|
||||
def _tickdata(self, df: pd.DataFrame, symbol: str, param, prev_bartime: str):
|
||||
"""K线聚合(简化版)"""
|
||||
from run import OrderFlowTrader
|
||||
|
||||
tickdata = df.copy()
|
||||
tickdata["bartime"] = pd.to_datetime(tickdata["datetime"])
|
||||
|
||||
rdf = self.data_store.tickdata.get(symbol)
|
||||
|
||||
if rdf is not None:
|
||||
rdftm = pd.to_datetime(rdf["bartime"][0]).strftime("%Y-%m-%d %H:%M:%S")
|
||||
now_bartime = pd.to_datetime(tickdata["bartime"][0]).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if now_bartime > rdftm:
|
||||
# 新 K 线开始
|
||||
with type('Lock', (), {'__enter__': lambda s: None, '__exit__': lambda s, *a: None})():
|
||||
of = self.data_store.ofdata.pop(symbol, None)
|
||||
if of is not None:
|
||||
# 追加到 trade_dfs
|
||||
existing = self.data_store.trade_dfs.get(symbol)
|
||||
if existing is None:
|
||||
self.data_store.trade_dfs[symbol] = of
|
||||
else:
|
||||
self.data_store.trade_dfs[symbol] = pd.concat([existing, of], ignore_index=True)
|
||||
|
||||
self.data_store.quote.pop(symbol, None)
|
||||
self.data_store.tickdata.pop(symbol, None)
|
||||
|
||||
# 处理上一根 K 线的信号
|
||||
trade_df = self.data_store.trade_dfs.get(symbol)
|
||||
if trade_df is not None and len(trade_df) > 0:
|
||||
last_row = trade_df.iloc[[-1]].copy()
|
||||
current_price = last_row["close"].iloc[0]
|
||||
|
||||
# 计算 delta 累计
|
||||
self.trader.calculate_delta_cumulative(trade_df, param)
|
||||
|
||||
# 检查止损
|
||||
self.trader.check_stop_loss(last_row, param, current_price)
|
||||
|
||||
# 计算信号
|
||||
bull_signal, bear_signal, dj_last, delta_last = self.trader.calculate_signals(trade_df, param)
|
||||
|
||||
if bull_signal or bear_signal:
|
||||
if param.position < 0 and (bear_signal or bull_signal):
|
||||
self.trader.execute_close_short(last_row, param, current_price)
|
||||
elif param.position > 0 and (bull_signal or bear_signal):
|
||||
self.trader.execute_close_long(last_row, param, current_price)
|
||||
if bull_signal and param.position == 0:
|
||||
self.trader.execute_open_long(last_row, param, current_price)
|
||||
if bear_signal and param.position == 0:
|
||||
self.trader.execute_open_short(last_row, param, current_price)
|
||||
|
||||
# 更新权益曲线
|
||||
self.equity_tracker.update_equity(
|
||||
last_row["datetime"].iloc[0],
|
||||
current_price,
|
||||
param.position
|
||||
)
|
||||
|
||||
# 初始化新 K 线
|
||||
tickdata["open"] = tickdata["lastprice"]
|
||||
tickdata["high"] = tickdata["lastprice"]
|
||||
tickdata["low"] = tickdata["lastprice"]
|
||||
tickdata["close"] = tickdata["lastprice"]
|
||||
tickdata["starttime"] = tickdata["datetime"]
|
||||
else:
|
||||
# 同一 K 线
|
||||
tickdata["bartime"] = rdf["bartime"]
|
||||
tickdata["open"] = rdf["open"]
|
||||
lastprice_val = float(tickdata["lastprice"].values[0])
|
||||
tickdata["high"] = max(lastprice_val, float(rdf["high"].values[0]))
|
||||
tickdata["low"] = min(lastprice_val, float(rdf["low"].values[0]))
|
||||
tickdata["close"] = tickdata["lastprice"]
|
||||
tickdata["vol"] = df["vol"].values + rdf["vol"].values
|
||||
tickdata["starttime"] = rdf["starttime"]
|
||||
else:
|
||||
# 首条 tick
|
||||
tickdata["open"] = tickdata["lastprice"]
|
||||
tickdata["high"] = tickdata["lastprice"]
|
||||
tickdata["low"] = tickdata["lastprice"]
|
||||
tickdata["close"] = tickdata["lastprice"]
|
||||
tickdata["starttime"] = tickdata["datetime"]
|
||||
|
||||
self.data_store.tickdata[symbol] = tickdata
|
||||
|
||||
# 生成订单流数据
|
||||
self._orderflow_df_new(tickdata, symbol, param)
|
||||
|
||||
def _orderflow_df_new(self, tickdata: pd.DataFrame, symbol: str, param):
|
||||
"""生成订单流(简化版)"""
|
||||
bar_vol = int(tickdata["vol"].values[0])
|
||||
bar_close = float(tickdata["close"].values[0])
|
||||
bar_open = float(tickdata["open"].values[0])
|
||||
bar_low = float(tickdata["low"].values[0])
|
||||
bar_high = float(tickdata["high"].values[0])
|
||||
dt = pd.to_datetime(tickdata["bartime"].values[0]).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
bid_p = float(tickdata["bid_p"].values[0])
|
||||
ask_p = float(tickdata["ask_p"].values[0])
|
||||
last_price = float(tickdata["lastprice"].values[0])
|
||||
|
||||
bid_dict = {}
|
||||
ask_dict = {}
|
||||
|
||||
if last_price >= ask_p:
|
||||
ask_dict[str(last_price)] = ask_dict.get(str(last_price), 0) + bar_vol
|
||||
if last_price <= bid_p:
|
||||
bid_dict[str(last_price)] = bid_dict.get(str(last_price), 0) + bar_vol
|
||||
|
||||
# 合并买卖盘
|
||||
bid_result, ask_result = self._process_quote(bid_dict, ask_dict, symbol)
|
||||
|
||||
# 价格排序
|
||||
price_list = sorted(bid_result.keys())
|
||||
ask_list = [ask_result.get(p, 0) for p in price_list]
|
||||
bid_list = [bid_result.get(p, 0) for p in price_list]
|
||||
|
||||
delta = sum(ask_list) - sum(bid_list)
|
||||
|
||||
# 构建 DataFrame
|
||||
df = pd.DataFrame({
|
||||
"price": [price_list],
|
||||
"Ask": [ask_list],
|
||||
"Bid": [bid_list],
|
||||
})
|
||||
df["symbol"] = symbol
|
||||
df["datetime"] = dt
|
||||
df["delta"] = delta
|
||||
df["close"] = bar_close
|
||||
df["open"] = bar_open
|
||||
df["high"] = bar_high
|
||||
df["low"] = bar_low
|
||||
df["vol"] = bar_vol
|
||||
df["dj"] = 0
|
||||
df["dj_price_high"] = 0
|
||||
df["dj_price_low"] = 0
|
||||
|
||||
self.data_store.ofdata[symbol] = df
|
||||
|
||||
def _process_quote(self, bid_dict: dict, ask_dict: dict, symbol: str):
|
||||
"""合并买卖盘"""
|
||||
dic = self.data_store.quote.get(symbol)
|
||||
if dic:
|
||||
bid_result = dic["bid_result"].copy()
|
||||
ask_result = dic["ask_result"].copy()
|
||||
else:
|
||||
bid_result, ask_result = {}, {}
|
||||
|
||||
price_list = sorted(set(bid_dict.keys()) | set(ask_dict.keys()))
|
||||
|
||||
for price in price_list:
|
||||
bid_val = int(bid_dict.get(price, 0))
|
||||
ask_val = int(ask_dict.get(price, 0))
|
||||
|
||||
if bid_val:
|
||||
bid_result[price] = bid_result.get(price, 0) + bid_val
|
||||
ask_result.setdefault(price, 0)
|
||||
if ask_val:
|
||||
ask_result[price] = ask_result.get(price, 0) + ask_val
|
||||
bid_result.setdefault(price, 0)
|
||||
|
||||
self.data_store.quote[symbol] = {"bid_result": bid_result, "ask_result": ask_result}
|
||||
return bid_result, ask_result
|
||||
|
||||
|
||||
# ============ 回测主类 ============
|
||||
|
||||
class Backtester:
|
||||
"""回测主协调器"""
|
||||
|
||||
def __init__(self, param_dict: dict):
|
||||
self.param_dict = param_dict
|
||||
self.data_store = BacktestDataStore()
|
||||
self.equity_tracker = EquityTracker(SYSTEM_CONFIG["initial_equity"])
|
||||
self.trader = BacktestTrader(param_dict, self.equity_tracker)
|
||||
self.results = {}
|
||||
|
||||
def run(self):
|
||||
"""执行回测"""
|
||||
mode = SYSTEM_CONFIG["backtest_mode"]
|
||||
|
||||
for symbol in self.param_dict.keys():
|
||||
print(f"\n{'='*50}")
|
||||
print(f"回测合约: {symbol},模式: {mode}")
|
||||
print(f"{'='*50}")
|
||||
|
||||
# 每次重新初始化
|
||||
self.data_store = BacktestDataStore()
|
||||
self.equity_tracker = EquityTracker(SYSTEM_CONFIG["initial_equity"])
|
||||
self.trader = BacktestTrader(self.param_dict, self.equity_tracker)
|
||||
|
||||
if mode == "ofdata":
|
||||
replayer = OfdataReplayer(self, symbol)
|
||||
else:
|
||||
replayer = TickReplayer(self, symbol)
|
||||
|
||||
replayer.replay()
|
||||
|
||||
# 保存结果
|
||||
self.equity_tracker.save_results(symbol)
|
||||
|
||||
# 打印指标
|
||||
metrics = self.equity_tracker.get_metrics()
|
||||
print(f"\n回测结果:")
|
||||
print(f" 总收益率: {metrics['total_return_pct']}%")
|
||||
print(f" 最大回撤: {metrics['max_drawdown_pct']}%")
|
||||
print(f" 胜率: {metrics['win_rate']}%")
|
||||
print(f" 总交易次数: {metrics['total_trades']}")
|
||||
print(f" 盈利交易: {metrics['winning_trades']}, 亏损交易: {metrics['losing_trades']}")
|
||||
print(f" 最终权益: {metrics['final_equity']}")
|
||||
|
||||
self.results[symbol] = metrics
|
||||
|
||||
return self.results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试回测模块
|
||||
from run import TradingParam
|
||||
|
||||
param_dict = {}
|
||||
for symbol, config in TRADING_PARAMS.items():
|
||||
p = TradingParam.from_config(symbol, config)
|
||||
p.symbol = symbol
|
||||
param_dict[symbol] = p
|
||||
|
||||
backtester = Backtester(param_dict)
|
||||
backtester.run()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"total_return": -29.2,
|
||||
"total_return_pct": -0.0,
|
||||
"max_drawdown": -110.8,
|
||||
"max_drawdown_pct": -0.01,
|
||||
"win_rate": 0.1765,
|
||||
"total_trades": 17,
|
||||
"winning_trades": 3,
|
||||
"losing_trades": 14,
|
||||
"avg_win": 45.67,
|
||||
"avg_loss": -11.87,
|
||||
"profit_factor": 0.82,
|
||||
"final_equity": 999970.8
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
trade_id,entry_datetime,entry_price,direction,exit_datetime,exit_price,lots,pnl,pnl_pct,status
|
||||
1,2026-05-29 10:07:00,8418.8,short,2026-05-29 10:16:00,8393.0,1,25.8,0.0,closed
|
||||
2,2026-05-29 10:17:00,8397.8,short,2026-05-29 10:22:00,8409.8,1,-12.0,-0.0,closed
|
||||
3,2026-05-29 13:06:00,8374.4,short,2026-05-29 13:07:00,8383.4,1,-9.0,-0.0,closed
|
||||
4,2026-05-29 14:25:00,8311.2,long,2026-06-01 09:39:00,8351.6,1,40.4,0.0,closed
|
||||
5,2026-06-01 09:41:00,8356.6,long,2026-06-01 09:47:00,8346.0,1,-10.6,-0.0,closed
|
||||
6,2026-06-01 09:49:00,8368.0,short,2026-06-01 09:50:00,8373.2,1,-5.2,-0.0,closed
|
||||
7,2026-06-01 13:16:00,8415.4,long,2026-06-01 13:17:00,8406.0,1,-9.4,-0.0,closed
|
||||
8,2026-06-02 09:41:00,8309.2,long,2026-06-02 09:45:00,8271.0,1,-38.2,-0.0,closed
|
||||
9,2026-06-02 10:01:00,8227.4,short,2026-06-02 10:02:00,8239.0,1,-11.6,-0.0,closed
|
||||
10,2026-06-02 14:11:00,8325.4,short,2026-06-02 14:19:00,8333.4,1,-8.0,-0.0,closed
|
||||
11,2026-06-03 11:15:00,8468.2,long,2026-06-03 11:16:00,8456.2,1,-12.0,-0.0,closed
|
||||
12,2026-06-03 13:36:00,8421.0,short,2026-06-03 13:37:00,8436.8,1,-15.8,-0.0,closed
|
||||
13,2026-06-03 14:14:00,8395.8,short,2026-06-03 14:32:00,8325.0,1,70.8,0.01,closed
|
||||
14,2026-06-03 14:43:00,8339.0,short,2026-06-03 14:48:00,8348.0,1,-9.0,-0.0,closed
|
||||
15,2026-06-05 09:33:00,8267.2,short,2026-06-05 09:33:00,8272.2,1,-5.0,-0.0,closed
|
||||
16,2026-06-05 09:38:00,8254.8,long,2026-06-05 09:39:00,8248.4,1,-6.4,-0.0,closed
|
||||
17,2026-06-04 16:21:00,8378.0,short,2026-06-04 16:24:00,8392.0,1,-14.0,-0.0,closed
|
||||
|
@@ -39,32 +39,32 @@ TRADING_PARAMS = {
|
||||
"IM2606": {
|
||||
"lots": 1,
|
||||
"price_offset": 5,
|
||||
"delta_threshold": 300,
|
||||
"delta_threshold": 200,
|
||||
"imbalance_ratio": 3,
|
||||
"accumulation_threshold": 3,
|
||||
"period": "3min",
|
||||
"min_volume": 10,
|
||||
"period": "1min",
|
||||
"min_volume": 20,
|
||||
"merge_price": 5,
|
||||
"mini_price": 0.2,
|
||||
},
|
||||
"jm2609": {
|
||||
"lots": 1,
|
||||
"price_offset": 1, # 黄金波动较小,价格偏移也小
|
||||
"delta_threshold": 300,
|
||||
"imbalance_ratio": 3,
|
||||
"accumulation_threshold": 3,
|
||||
"period": "3min",
|
||||
"min_volume": 5, # 黄金成交量较大,可适当调高
|
||||
"merge_price": 2,
|
||||
"mini_price": 0.5, # 黄金最小变动价位
|
||||
},
|
||||
# "jm2609": {
|
||||
# "lots": 1,
|
||||
# "price_offset": 1, # 黄金波动较小,价格偏移也小
|
||||
# "delta_threshold": 300,
|
||||
# "imbalance_ratio": 3,
|
||||
# "accumulation_threshold": 3,
|
||||
# "period": "1min",
|
||||
# "min_volume": 10, # 黄金成交量较大,可适当调高
|
||||
# "merge_price": 2,
|
||||
# "mini_price": 0.5, # 黄金最小变动价位
|
||||
# },
|
||||
}
|
||||
|
||||
# ============ SimNow模拟账户配置 ============
|
||||
SIMNOW_CONFIG = {
|
||||
"investor_id": "223828",
|
||||
"password": os.getenv("SIMNOW_PASSWORD", "Zj1234!@#%"),
|
||||
"server_name": "电信1", # 交易服务器(电信1、电信2、移动、TEST、N视界、TEST环境)
|
||||
"server_name": "TEST", # 交易服务器(电信1、电信2、移动、TEST、N视界、TEST环境)
|
||||
}
|
||||
|
||||
# ============ 实盘账户配置(注释备用) ============
|
||||
@@ -88,6 +88,18 @@ SYSTEM_CONFIG = {
|
||||
"json_records_limit": 20,
|
||||
"stops_load_interval": 60,
|
||||
"data_dir": "traderdata",
|
||||
# === 回测/录制配置 ===
|
||||
"mode": "backtest", # "live" | "backtest"
|
||||
"backtest_mode": "ofdata", # "ofdata" | "tick"
|
||||
"backtest_results_dir": "backtest_results",
|
||||
"initial_equity": 1000000.0,
|
||||
"contract_multiplier": {
|
||||
"im2606": 300,
|
||||
"jm2609": 60, # 焦煤:吨/手
|
||||
"au2608": 1000, # 黄金:克/手
|
||||
},
|
||||
"record_tick": True, # 是否录制 tick 数据
|
||||
"tick_record_interval": 1, # 录制间隔(每N个tick录一个)
|
||||
}
|
||||
|
||||
# ============ 夜盘收盘时间字典 ============
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,161 @@
|
||||
# 订单流交易系统
|
||||
|
||||
## 概述
|
||||
|
||||
订单流交易策略系统,支持实盘交易和回测。
|
||||
|
||||
## 模式切换
|
||||
|
||||
通过 `config.py` 中的 `SYSTEM_CONFIG["mode"]` 切换:
|
||||
|
||||
```python
|
||||
SYSTEM_CONFIG = {
|
||||
"mode": "live", # "live" | "backtest"
|
||||
}
|
||||
```
|
||||
|
||||
## 实盘/模拟模式 (mode="live")
|
||||
|
||||
运行 SimNow 模拟交易或实盘交易:
|
||||
|
||||
```bash
|
||||
python run.py
|
||||
```
|
||||
|
||||
### Tick 数据录制
|
||||
|
||||
实盘运行时可录制 tick 数据供回测使用:
|
||||
|
||||
```python
|
||||
SYSTEM_CONFIG = {
|
||||
"record_tick": True, # 开启录制
|
||||
"tick_record_interval": 1, # 每N个tick录一个
|
||||
}
|
||||
```
|
||||
|
||||
录制的数据保存在 `traderdata/{symbol}_tick.csv`。
|
||||
|
||||
## 回测模式 (mode="backtest")
|
||||
|
||||
### 配置
|
||||
|
||||
```python
|
||||
SYSTEM_CONFIG = {
|
||||
"mode": "backtest",
|
||||
"backtest_mode": "ofdata", # "ofdata" | "tick"
|
||||
"backtest_results_dir": "backtest_results",
|
||||
"initial_equity": 100000.0,
|
||||
"contract_multiplier": {
|
||||
"jm2609": 60, # 焦煤:吨/手
|
||||
"au2608": 1000, # 黄金:克/手
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 回测模式 1: ofdata.json 回放
|
||||
|
||||
读取已有的 `ofdata.json` 历史订单流数据进行回测。
|
||||
|
||||
数据源:`traderdata/{symbol}_ofdata.json`
|
||||
|
||||
```python
|
||||
"backtest_mode": "ofdata"
|
||||
```
|
||||
|
||||
### 回测模式 2: 原始 tick 回放
|
||||
|
||||
读取原始 tick CSV 文件,完整重放 tick → K线 → 订单流流程。
|
||||
|
||||
数据源:`traderdata/{symbol}_tick.csv`
|
||||
|
||||
```python
|
||||
"backtest_mode": "tick"
|
||||
```
|
||||
|
||||
### 运行回测
|
||||
|
||||
```bash
|
||||
python run.py
|
||||
```
|
||||
|
||||
### 输出文件
|
||||
|
||||
回测结果保存在 `backtest_results/` 目录:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `{symbol}_equity.csv` | 权益曲线 |
|
||||
| `{symbol}_trades.csv` | 交易记录 |
|
||||
| `{symbol}_metrics.json` | 性能指标 |
|
||||
|
||||
### 性能指标
|
||||
|
||||
```json
|
||||
{
|
||||
"total_return": 12500.0,
|
||||
"total_return_pct": 12.5,
|
||||
"max_drawdown": -3200.0,
|
||||
"max_drawdown_pct": -3.2,
|
||||
"win_rate": 0.62,
|
||||
"total_trades": 50,
|
||||
"winning_trades": 31,
|
||||
"losing_trades": 19,
|
||||
"avg_win": 850.0,
|
||||
"avg_loss": -420.0,
|
||||
"profit_factor": 2.1,
|
||||
"final_equity": 112500.0
|
||||
}
|
||||
```
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
4.orderflow/
|
||||
├── run.py # 实盘/模拟交易主程序
|
||||
├── config.py # 配置文件
|
||||
├── backtest.py # 回测模块
|
||||
├── traderdata/ # 实盘数据
|
||||
│ ├── jm2609_ofdata.json
|
||||
│ └── jm2609_tick.csv # 录制生成
|
||||
├── backtest_results/ # 回测输出
|
||||
│ ├── jm2609_equity.csv
|
||||
│ ├── jm2609_trades.csv
|
||||
│ └── jm2609_metrics.json
|
||||
└── readme.md
|
||||
```
|
||||
|
||||
## tick CSV 格式 (Mode 2 回测数据)
|
||||
|
||||
|字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| InstrumentID | string | 合约代码 |
|
||||
| ActionDay | string | YYYYMMDD |
|
||||
| UpdateTime | string | HH:MM:SS |
|
||||
| UpdateMillisec | int | 毫秒 |
|
||||
| LastPrice | float | 最新价 |
|
||||
| Volume | int | 累计成交量 |
|
||||
| BidPrice1 | float | 买一价 |
|
||||
| BidVolume1 | int | 买一量 |
|
||||
| AskPrice1 | float | 卖一价 |
|
||||
| AskVolume1 | int | 卖一量 |
|
||||
| UpperLimitPrice | float | 涨停价 |
|
||||
| LowerLimitPrice | float | 跌停价 |
|
||||
| TradingDay | string | YYYYMMDD |
|
||||
| Turnover | float | 累计成交额 |
|
||||
| OpenInterest | int | 持仓量 |
|
||||
|
||||
## 历史修改记录
|
||||
|
||||
### 2026-06-05: ofdata.json 保存时机改善
|
||||
|
||||
**问题**:`ofdata.json` 中价格数据相对于实时 tick 有延迟
|
||||
|
||||
**修改**:在 `tickdata` 方法中,新 K 线开始时立即保存 `ofdata.json`,移除 `cal_sig` 中的重复保存
|
||||
|
||||
### 2026-06-05: 回测模块
|
||||
|
||||
**新增功能**:
|
||||
- 支持 `mode` 切换实盘/回测
|
||||
- Mode 1: ofdata.json 回放
|
||||
- Mode 2: 原始 tick CSV 回放
|
||||
- Tick 数据录制功能
|
||||
+85
-37
@@ -7,6 +7,7 @@ import threading # 线程模块
|
||||
import os # 操作系统模块
|
||||
import json # JSON模块
|
||||
import re # 正则表达式模块
|
||||
import csv # CSV模块
|
||||
import time as time_module # 标准库时间模块
|
||||
from datetime import datetime, time as datetime_time # datetime时间对象
|
||||
from multiprocessing import Process, Queue # 多进程模块
|
||||
@@ -167,12 +168,23 @@ class OrderFlowTrader(TraderApiBase):
|
||||
# 数据存储
|
||||
self._json_save_counter = {} # JSON保存计数器
|
||||
self.md_queue = md_queue # 行情队列
|
||||
self._tick_record_counter = {} # tick录制计数器 {symbol: count}
|
||||
|
||||
# ============ 行情数据处理 ============
|
||||
|
||||
def tickcome(self, md_queue):
|
||||
"""接收并处理Tick行情数据"""
|
||||
data = md_queue # 从队列获取行情数据
|
||||
|
||||
# === tick录制(实盘模式) ===
|
||||
if SYSTEM_CONFIG.get("record_tick", False):
|
||||
instrument_id_raw = data["InstrumentID"]
|
||||
symbol = instrument_id_raw.decode() if isinstance(instrument_id_raw, bytes) else instrument_id_raw
|
||||
self._tick_record_counter[symbol] = self._tick_record_counter.get(symbol, 0) + 1
|
||||
interval = SYSTEM_CONFIG.get("tick_record_interval", 1)
|
||||
if self._tick_record_counter[symbol] % interval == 0:
|
||||
self._record_tick_to_csv(data, symbol)
|
||||
|
||||
instrument_id = data["InstrumentID"].decode() # 解码合约代码
|
||||
action_day = data["ActionDay"].decode() # 解码交易日期
|
||||
update_time = data["UpdateTime"].decode() # 解码更新时间
|
||||
@@ -215,6 +227,36 @@ class OrderFlowTrader(TraderApiBase):
|
||||
}
|
||||
self.on_tick(tick) # 调用Tick处理回调
|
||||
|
||||
def _record_tick_to_csv(self, data: dict, symbol: str):
|
||||
"""录制 tick 数据到 CSV"""
|
||||
data_dir = SYSTEM_CONFIG["data_dir"]
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
csv_path = f"{data_dir}/{symbol}_tick.csv"
|
||||
|
||||
# CSV 字段
|
||||
fields = [
|
||||
"InstrumentID", "ActionDay", "UpdateTime", "UpdateMillisec",
|
||||
"LastPrice", "Volume", "BidPrice1", "BidVolume1",
|
||||
"AskPrice1", "AskVolume1", "UpperLimitPrice", "LowerLimitPrice",
|
||||
"TradingDay", "Turnover", "OpenInterest"
|
||||
]
|
||||
|
||||
# 写入字段(bytes 解码)
|
||||
row = {}
|
||||
for field in fields:
|
||||
val = data.get(field)
|
||||
if isinstance(val, bytes):
|
||||
row[field] = val.decode()
|
||||
else:
|
||||
row[field] = val
|
||||
|
||||
file_exists = os.path.exists(csv_path)
|
||||
with open(csv_path, 'a', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fields)
|
||||
if not file_exists:
|
||||
writer.writeheader()
|
||||
writer.writerow(row)
|
||||
|
||||
def on_tick(self, tick):
|
||||
"""处理单个Tick数据"""
|
||||
if tick["last_volume"] == 0: # 无成交量则返回
|
||||
@@ -330,6 +372,8 @@ class OrderFlowTrader(TraderApiBase):
|
||||
of = DataStore.ofdata.pop(symbol, None)
|
||||
if of is not None:
|
||||
self.data_of(symbol, of) # 写入交易DataFrame
|
||||
# 保存 ofdata.json(改善时间戳对齐:旧K线完成时即保存)
|
||||
self.save_ofdata_json(symbol)
|
||||
|
||||
# 清空旧K线的报价数据
|
||||
with self._quote_lock:
|
||||
@@ -1033,9 +1077,6 @@ class OrderFlowTrader(TraderApiBase):
|
||||
# 计算Delta累计(必须在保存之前)
|
||||
self.calculate_delta_cumulative(trade_df, param)
|
||||
|
||||
# 保存 ofdata.json(计算 delta累计 之后)
|
||||
self.save_ofdata_json(instrument_id)
|
||||
|
||||
# 计算交易信号
|
||||
bull_signal, bear_signal, dj_last, delta_last = self.calculate_signals(trade_df, param)
|
||||
|
||||
@@ -1146,44 +1187,51 @@ if __name__ == "__main__":
|
||||
for symbol, config in TRADING_PARAMS.items():
|
||||
param_dict[symbol] = TradingParam.from_config(symbol, config)
|
||||
|
||||
# 连接SimNow模拟交易
|
||||
future_account = get_simulate_account(
|
||||
investor_id=SIMNOW_CONFIG["investor_id"],
|
||||
password=SIMNOW_CONFIG["password"],
|
||||
server_name=SIMNOW_CONFIG["server_name"],
|
||||
subscribe_list=list(param_dict.keys()),
|
||||
)
|
||||
# ===模式切换:回测模式 ===
|
||||
if SYSTEM_CONFIG.get("mode", "live") == "backtest":
|
||||
from backtest import Backtester
|
||||
backtester = Backtester(param_dict)
|
||||
backtester.run()
|
||||
else:
|
||||
# === 实盘/模拟模式 ===
|
||||
# 连接SimNow模拟交易
|
||||
future_account = get_simulate_account(
|
||||
investor_id=SIMNOW_CONFIG["investor_id"],
|
||||
password=SIMNOW_CONFIG["password"],
|
||||
server_name=SIMNOW_CONFIG["server_name"],
|
||||
subscribe_list=list(param_dict.keys()),
|
||||
)
|
||||
|
||||
# 连接实盘(备用)
|
||||
# from CtpPlus.CTP.FutureAccount import FutureAccount
|
||||
# future_account = FutureAccount(...)
|
||||
# 连接实盘(备用)
|
||||
# from CtpPlus.CTP.FutureAccount import FutureAccount
|
||||
# future_account = FutureAccount(...)
|
||||
|
||||
print("开始", len(future_account.subscribe_list))
|
||||
print("开始", len(future_account.subscribe_list))
|
||||
|
||||
# 创建共享队列
|
||||
share_queue = Queue(maxsize=SYSTEM_CONFIG["queue_share_size"])
|
||||
# 创建共享队列
|
||||
share_queue = Queue(maxsize=SYSTEM_CONFIG["queue_share_size"])
|
||||
|
||||
# 启动行情进程
|
||||
md_process = Process(target=run_tick_engine, args=(future_account, [share_queue]))
|
||||
# 启动行情进程
|
||||
md_process = Process(target=run_tick_engine, args=(future_account, [share_queue]))
|
||||
|
||||
# 启动交易进程
|
||||
trader_process = Process(
|
||||
target=run_trader,
|
||||
args=(
|
||||
param_dict,
|
||||
future_account.broker_id,
|
||||
future_account.server_dict["TDServer"],
|
||||
future_account.investor_id,
|
||||
future_account.password,
|
||||
future_account.app_id,
|
||||
future_account.auth_code,
|
||||
share_queue,
|
||||
future_account.td_flow_path,
|
||||
),
|
||||
)
|
||||
# 启动交易进程
|
||||
trader_process = Process(
|
||||
target=run_trader,
|
||||
args=(
|
||||
param_dict,
|
||||
future_account.broker_id,
|
||||
future_account.server_dict["TDServer"],
|
||||
future_account.investor_id,
|
||||
future_account.password,
|
||||
future_account.app_id,
|
||||
future_account.auth_code,
|
||||
share_queue,
|
||||
future_account.td_flow_path,
|
||||
),
|
||||
)
|
||||
|
||||
md_process.start() # 启动行情进程
|
||||
trader_process.start() # 启动交易进程
|
||||
md_process.start() # 启动行情进程
|
||||
trader_process.start() # 启动交易进程
|
||||
|
||||
md_process.join() # 等待行情进程结束
|
||||
trader_process.join() # 等待交易进程结束
|
||||
md_process.join() # 等待行情进程结束
|
||||
trader_process.join() # 等待交易进程结束
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
{"price":["8356.2","8357.2","8358.2","8359.2","8360.2","8361.2","8362.2","8363.2","8364.2","8365.2","8366.2","8367.2","8368.2","8369.2"],"Ask":[5,45,109,12,0,0,0,44,27,0,0,12,6,7],"Bid":[46,53,31,0,45,5,46,74,8,2,1,22,10,0],"symbol":"IM2606","datetime":"2026-06-04 16:30:00","delta":-76,"close":8356.6,"open":8364.6,"high":8370.0,"low":8356.2,"vol":754,"dj":0,"dj_price_high":0,"dj_price_low":0}
|
||||
@@ -0,0 +1,276 @@
|
||||
IM2606,20260604,16:28:36,500,8364.8,137610,8364.6,1,8364.8,3,9185.6,7515.6,20260605,229747788760.0,188013.0
|
||||
IM2606,20260604,16:28:37,0,8364.6,137612,8364.0,1,8364.8,1,9185.6,7515.6,20260605,229751134600.0,188014.0
|
||||
IM2606,20260604,16:28:37,500,8364.6,137614,8364.0,1,8364.8,1,9185.6,7515.6,20260605,229754480440.0,188016.0
|
||||
IM2606,20260604,16:28:38,0,8364.6,137614,8364.6,1,8364.8,1,9185.6,7515.6,20260605,229754480440.0,188016.0
|
||||
IM2606,20260604,16:28:38,500,8364.0,137621,8363.4,1,8364.0,2,9185.6,7515.6,20260605,229766190560.0,188015.0
|
||||
IM2606,20260604,16:28:39,0,8365.0,137624,8364.2,2,8365.0,4,9185.6,7515.6,20260605,229771209160.0,188014.0
|
||||
IM2606,20260604,16:28:39,500,8363.4,137630,8364.0,1,8364.2,1,9185.6,7515.6,20260605,229781245920.0,188014.0
|
||||
IM2606,20260604,16:28:40,0,8364.0,137633,8363.2,1,8364.0,1,9185.6,7515.6,20260605,229786264320.0,188012.0
|
||||
IM2606,20260604,16:28:40,500,8364.6,137636,8363.4,1,8364.6,1,9185.6,7515.6,20260605,229791282880.0,188014.0
|
||||
IM2606,20260604,16:28:41,0,8363.6,137640,8363.6,3,8364.0,4,9185.6,7515.6,20260605,229797974160.0,188015.0
|
||||
IM2606,20260604,16:28:41,500,8364.0,137646,8363.6,3,8364.0,2,9185.6,7515.6,20260605,229808010960.0,188016.0
|
||||
IM2606,20260604,16:28:42,0,8364.0,137649,8364.0,2,8365.0,4,9185.6,7515.6,20260605,229813029360.0,188018.0
|
||||
IM2606,20260604,16:28:42,500,8365.0,137653,8363.8,1,8365.0,2,9185.6,7515.6,20260605,229819720960.0,188020.0
|
||||
IM2606,20260604,16:28:43,0,8365.0,137654,8364.0,5,8365.0,1,9185.6,7515.6,20260605,229821393960.0,188020.0
|
||||
IM2606,20260604,16:28:43,500,8364.2,137656,8364.0,3,8365.0,2,9185.6,7515.6,20260605,229824739640.0,188018.0
|
||||
IM2606,20260604,16:28:44,0,8363.8,137660,8363.6,3,8363.8,1,9185.6,7515.6,20260605,229831430960.0,188018.0
|
||||
IM2606,20260604,16:28:44,500,8363.8,137661,8363.6,3,8364.0,1,9185.6,7515.6,20260605,229833103720.0,188017.0
|
||||
IM2606,20260604,16:28:45,0,8363.6,137662,8363.6,2,8364.0,1,9185.6,7515.6,20260605,229834776440.0,188018.0
|
||||
IM2606,20260604,16:28:45,500,8363.6,137662,8363.8,1,8364.0,1,9185.6,7515.6,20260605,229834776440.0,188018.0
|
||||
IM2606,20260604,16:28:46,0,8363.6,137665,8363.4,2,8363.8,1,9185.6,7515.6,20260605,229839794680.0,188019.0
|
||||
IM2606,20260604,16:28:46,500,8364.0,137667,8363.4,2,8364.0,1,9185.6,7515.6,20260605,229843140240.0,188020.0
|
||||
IM2606,20260604,16:28:47,0,8364.0,137674,8364.0,7,8365.4,2,9185.6,7515.6,20260605,229854850040.0,188025.0
|
||||
IM2606,20260604,16:28:47,500,8364.0,137674,8364.2,2,8365.4,2,9185.6,7515.6,20260605,229854850040.0,188025.0
|
||||
IM2606,20260604,16:28:48,0,8364.0,137675,8364.0,5,8365.4,3,9185.6,7515.6,20260605,229856522840.0,188026.0
|
||||
IM2606,20260604,16:28:48,500,8364.0,137676,8364.0,5,8365.0,1,9185.6,7515.6,20260605,229858195640.0,188027.0
|
||||
IM2606,20260604,16:28:49,0,8365.0,137677,8364.0,5,8364.8,1,9185.6,7515.6,20260605,229859868640.0,188026.0
|
||||
IM2606,20260604,16:28:49,500,8364.8,137678,8365.0,1,8365.4,5,9185.6,7515.6,20260605,229861541600.0,188027.0
|
||||
IM2606,20260604,16:28:50,500,8364.0,137680,8364.0,4,8365.4,5,9185.6,7515.6,20260605,229864887360.0,188028.0
|
||||
IM2606,20260604,16:28:51,0,8364.0,137680,8364.0,5,8365.0,3,9185.6,7515.6,20260605,229864887360.0,188028.0
|
||||
IM2606,20260604,16:28:51,500,8365.0,137681,8364.0,5,8365.0,2,9185.6,7515.6,20260605,229866560360.0,188027.0
|
||||
IM2606,20260604,16:28:52,0,8365.0,137681,8364.0,5,8364.4,1,9185.6,7515.6,20260605,229866560360.0,188027.0
|
||||
IM2606,20260604,16:28:52,500,8364.4,137685,8364.0,2,8365.0,2,9185.6,7515.6,20260605,229873251640.0,188031.0
|
||||
IM2606,20260604,16:28:53,0,8365.0,137692,8365.0,4,8365.4,2,9185.6,7515.6,20260605,229884962440.0,188029.0
|
||||
IM2606,20260604,16:28:53,500,8364.0,137701,8364.0,1,8364.8,6,9185.6,7515.6,20260605,229900019480.0,188033.0
|
||||
IM2606,20260604,16:28:54,0,8364.0,137703,8363.6,1,8363.8,6,9185.6,7515.6,20260605,229903365160.0,188033.0
|
||||
IM2606,20260604,16:28:54,500,8363.4,137714,8363.0,19,8363.4,2,9185.6,7515.6,20260605,229921765120.0,188036.0
|
||||
IM2606,20260604,16:28:55,0,8363.0,137720,8363.0,14,8364.0,3,9185.6,7515.6,20260605,229931800720.0,188041.0
|
||||
IM2606,20260604,16:28:55,500,8363.2,137724,8363.2,2,8363.8,2,9185.6,7515.6,20260605,229938491400.0,188041.0
|
||||
IM2606,20260604,16:28:56,0,8363.8,137731,8363.0,12,8363.8,2,9185.6,7515.6,20260605,229950200160.0,188046.0
|
||||
IM2606,20260604,16:28:56,500,8363.8,137735,8363.8,4,8364.0,3,9185.6,7515.6,20260605,229956890920.0,188048.0
|
||||
IM2606,20260604,16:28:57,0,8364.0,137740,8363.8,5,8366.0,1,9185.6,7515.6,20260605,229965255120.0,188052.0
|
||||
IM2606,20260604,16:28:57,500,8364.2,137742,8364.0,5,8365.0,1,9185.6,7515.6,20260605,229968601040.0,188052.0
|
||||
IM2606,20260604,16:28:58,0,8363.8,137745,8363.8,4,8364.8,3,9185.6,7515.6,20260605,229973619400.0,188053.0
|
||||
IM2606,20260604,16:28:58,500,8363.8,137753,8363.0,12,8363.8,14,9185.6,7515.6,20260605,229987001600.0,188052.0
|
||||
IM2606,20260604,16:28:59,0,8363.8,137753,8363.0,13,8363.8,15,9185.6,7515.6,20260605,229987001600.0,188052.0
|
||||
IM2606,20260604,16:28:59,500,8363.8,137754,8363.0,13,8363.6,3,9185.6,7515.6,20260605,229988674360.0,188052.0
|
||||
IM2606,20260604,16:29:00,0,8363.0,137756,8363.0,12,8363.8,15,9185.6,7515.6,20260605,229992019600.0,188052.0
|
||||
IM2606,20260604,16:29:01,0,8363.6,137757,8363.0,13,8363.6,2,9185.6,7515.6,20260605,229993692320.0,188051.0
|
||||
IM2606,20260604,16:29:01,500,8363.0,137761,8363.0,9,8363.8,15,9185.6,7515.6,20260605,230000382720.0,188053.0
|
||||
IM2606,20260604,16:29:02,0,8363.0,137763,8363.0,12,8363.8,15,9185.6,7515.6,20260605,230003727920.0,188055.0
|
||||
IM2606,20260604,16:29:02,500,8363.0,137764,8363.0,12,8363.8,15,9185.6,7515.6,20260605,230005400520.0,188055.0
|
||||
IM2606,20260604,16:29:03,0,8363.2,137766,8363.0,12,8363.8,15,9185.6,7515.6,20260605,230008745800.0,188056.0
|
||||
IM2606,20260604,16:29:03,500,8363.8,137767,8363.0,12,8363.8,14,9185.6,7515.6,20260605,230010418560.0,188055.0
|
||||
IM2606,20260604,16:29:04,0,8363.0,137769,8363.2,1,8363.8,14,9185.6,7515.6,20260605,230013763920.0,188054.0
|
||||
IM2606,20260604,16:29:04,500,8363.0,137781,8363.0,15,8363.8,4,9185.6,7515.6,20260605,230033836760.0,188052.0
|
||||
IM2606,20260604,16:29:05,0,8363.8,137788,8363.8,2,8364.0,2,9185.6,7515.6,20260605,230045546120.0,188049.0
|
||||
IM2606,20260604,16:29:05,500,8364.6,137795,8364.0,5,8365.8,2,9185.6,7515.6,20260605,230057256640.0,188050.0
|
||||
IM2606,20260604,16:29:06,0,8364.6,137798,8364.2,3,8366.0,1,9185.6,7515.6,20260605,230062275600.0,188051.0
|
||||
IM2606,20260604,16:29:06,500,8365.2,137802,8364.6,1,8367.0,1,9185.6,7515.6,20260605,230068967960.0,188052.0
|
||||
IM2606,20260604,16:29:07,0,8365.2,137803,8365.0,1,8366.6,1,9185.6,7515.6,20260605,230070641000.0,188052.0
|
||||
IM2606,20260604,16:29:07,500,8366.2,137804,8366.0,1,8366.4,1,9185.6,7515.6,20260605,230072314240.0,188051.0
|
||||
IM2606,20260604,16:29:08,0,8366.0,137805,8366.6,1,8368.8,1,9185.6,7515.6,20260605,230073987440.0,188052.0
|
||||
IM2606,20260604,16:29:08,500,8364.4,137808,8364.4,2,8366.6,5,9185.6,7515.6,20260605,230079006920.0,188052.0
|
||||
IM2606,20260604,16:29:09,0,8364.4,137808,8364.8,2,8366.4,1,9185.6,7515.6,20260605,230079006920.0,188052.0
|
||||
IM2606,20260604,16:29:09,500,8366.0,137809,8365.0,3,8368.0,1,9185.6,7515.6,20260605,230080680120.0,188051.0
|
||||
IM2606,20260604,16:29:10,0,8365.4,137812,8365.2,5,8367.4,2,9185.6,7515.6,20260605,230085699920.0,188050.0
|
||||
IM2606,20260604,16:29:10,500,8365.4,137813,8365.4,5,8367.2,4,9185.6,7515.6,20260605,230087373000.0,188051.0
|
||||
IM2606,20260604,16:29:11,0,8365.4,137813,8365.6,3,8367.0,1,9185.6,7515.6,20260605,230087373000.0,188051.0
|
||||
IM2606,20260604,16:29:11,500,8365.4,137813,8365.4,5,8367.2,1,9185.6,7515.6,20260605,230087373000.0,188051.0
|
||||
IM2606,20260604,16:29:12,0,8367.0,137815,8365.6,2,8367.0,1,9185.6,7515.6,20260605,230090719480.0,188052.0
|
||||
IM2606,20260604,16:29:12,500,8367.4,137818,8365.8,4,8367.8,2,9185.6,7515.6,20260605,230095740080.0,188052.0
|
||||
IM2606,20260604,16:29:13,0,8367.8,137819,8366.0,4,8368.4,1,9185.6,7515.6,20260605,230097413640.0,188051.0
|
||||
IM2606,20260604,16:29:13,500,8368.2,137820,8366.4,1,8368.2,1,9185.6,7515.6,20260605,230099087280.0,188051.0
|
||||
IM2606,20260604,16:29:14,0,8366.6,137822,8366.6,2,8368.2,1,9185.6,7515.6,20260605,230102434240.0,188051.0
|
||||
IM2606,20260604,16:29:14,500,8366.6,137822,8366.8,3,8368.2,1,9185.6,7515.6,20260605,230102434240.0,188051.0
|
||||
IM2606,20260604,16:29:15,0,8367.6,137824,8367.2,1,8368.0,3,9185.6,7515.6,20260605,230105781080.0,188051.0
|
||||
IM2606,20260604,16:29:15,500,8367.6,137824,8367.2,4,8367.8,1,9185.6,7515.6,20260605,230105781080.0,188051.0
|
||||
IM2606,20260604,16:29:16,0,8367.8,137826,8367.2,3,8368.0,3,9185.6,7515.6,20260605,230109128080.0,188051.0
|
||||
IM2606,20260604,16:29:16,500,8368.0,137829,8367.4,1,8368.8,1,9185.6,7515.6,20260605,230114148880.0,188051.0
|
||||
IM2606,20260604,16:29:17,0,8368.0,137829,8368.0,1,8368.8,1,9185.6,7515.6,20260605,230114148880.0,188051.0
|
||||
IM2606,20260604,16:29:17,500,8368.2,137832,8368.2,1,8369.0,1,9185.6,7515.6,20260605,230119169920.0,188051.0
|
||||
IM2606,20260604,16:29:18,0,8367.8,137840,8368.0,1,8368.4,1,9185.6,7515.6,20260605,230132559120.0,188054.0
|
||||
IM2606,20260604,16:29:18,500,8367.8,137841,8368.0,1,8369.0,1,9185.6,7515.6,20260605,230134232680.0,188053.0
|
||||
IM2606,20260604,16:29:19,0,8368.8,137845,8367.2,2,8368.0,1,9185.6,7515.6,20260605,230140927280.0,188052.0
|
||||
IM2606,20260604,16:29:19,500,8368.0,137847,8367.2,2,8368.6,1,9185.6,7515.6,20260605,230144274480.0,188052.0
|
||||
IM2606,20260604,16:29:20,0,8367.8,137849,8367.4,4,8368.0,1,9185.6,7515.6,20260605,230147621600.0,188052.0
|
||||
IM2606,20260604,16:29:20,500,8368.2,137853,8368.0,2,8368.8,1,9185.6,7515.6,20260605,230154316240.0,188055.0
|
||||
IM2606,20260604,16:29:21,0,8368.2,137857,8368.0,2,8369.0,1,9185.6,7515.6,20260605,230161010800.0,188052.0
|
||||
IM2606,20260604,16:29:21,500,8368.2,137857,8368.8,1,8369.0,1,9185.6,7515.6,20260605,230161010800.0,188052.0
|
||||
IM2606,20260604,16:29:22,0,8369.0,137859,8368.8,2,8369.6,1,9185.6,7515.6,20260605,230164358400.0,188052.0
|
||||
IM2606,20260604,16:29:22,500,8369.0,137859,8369.0,3,8369.6,1,9185.6,7515.6,20260605,230164358400.0,188052.0
|
||||
IM2606,20260604,16:29:23,0,8370.0,137861,8369.0,3,8369.6,2,9185.6,7515.6,20260605,230167706320.0,188053.0
|
||||
IM2606,20260604,16:29:23,500,8369.8,137863,8369.0,1,8370.0,1,9185.6,7515.6,20260605,230171054240.0,188051.0
|
||||
IM2606,20260604,16:29:24,0,8369.0,137871,8369.0,1,8369.8,1,9185.6,7515.6,20260605,230184444960.0,188050.0
|
||||
IM2606,20260604,16:29:24,500,8369.6,137881,8369.0,5,8369.6,1,9185.6,7515.6,20260605,230201183000.0,188045.0
|
||||
IM2606,20260604,16:29:25,0,8369.4,137888,8369.0,1,8369.4,1,9185.6,7515.6,20260605,230212899840.0,188043.0
|
||||
IM2606,20260604,16:29:25,500,8369.2,137891,8369.2,1,8369.6,1,9185.6,7515.6,20260605,230217921440.0,188044.0
|
||||
IM2606,20260604,16:29:26,0,8369.4,137892,8369.4,1,8369.6,1,9185.6,7515.6,20260605,230219595320.0,188044.0
|
||||
IM2606,20260604,16:29:26,500,8369.6,137895,8369.2,1,8370.0,2,9185.6,7515.6,20260605,230224617000.0,188046.0
|
||||
IM2606,20260604,16:29:27,0,8369.0,137897,8368.6,10,8369.0,1,9185.6,7515.6,20260605,230227964640.0,188047.0
|
||||
IM2606,20260604,16:29:27,500,8368.0,137910,8367.2,1,8368.4,1,9185.6,7515.6,20260605,230249722640.0,188053.0
|
||||
IM2606,20260604,16:29:28,0,8367.6,137912,8367.0,1,8368.2,1,9185.6,7515.6,20260605,230253069600.0,188054.0
|
||||
IM2606,20260604,16:29:28,500,8367.0,137913,8366.6,2,8368.0,2,9185.6,7515.6,20260605,230254743000.0,188055.0
|
||||
IM2606,20260604,16:29:29,0,8367.0,137913,8366.6,2,8367.6,2,9185.6,7515.6,20260605,230254743000.0,188055.0
|
||||
IM2606,20260604,16:29:29,500,8367.4,137916,8365.2,1,8367.4,3,9185.6,7515.6,20260605,230259763120.0,188056.0
|
||||
IM2606,20260604,16:29:30,0,8367.4,137917,8365.4,2,8367.2,5,9185.6,7515.6,20260605,230261436600.0,188056.0
|
||||
IM2606,20260604,16:29:30,500,8367.2,137919,8365.2,1,8367.0,3,9185.6,7515.6,20260605,230264783520.0,188056.0
|
||||
IM2606,20260604,16:29:31,0,8365.2,137920,8365.0,4,8366.0,3,9185.6,7515.6,20260605,230266456560.0,188057.0
|
||||
IM2606,20260604,16:29:31,500,8365.8,137928,8365.0,3,8365.8,1,9185.6,7515.6,20260605,230279841560.0,188060.0
|
||||
IM2606,20260604,16:29:32,0,8365.0,137929,8365.0,2,8365.6,4,9185.6,7515.6,20260605,230281514560.0,188061.0
|
||||
IM2606,20260604,16:29:32,500,8365.0,137933,8364.0,2,8365.2,1,9185.6,7515.6,20260605,230288206680.0,188063.0
|
||||
IM2606,20260604,16:29:33,0,8365.0,137933,8364.0,2,8365.0,10,9185.6,7515.6,20260605,230288206680.0,188063.0
|
||||
IM2606,20260604,16:29:33,500,8364.0,137934,8364.0,1,8364.6,1,9185.6,7515.6,20260605,230289879480.0,188063.0
|
||||
IM2606,20260604,16:29:34,0,8363.8,137936,8363.8,4,8364.0,1,9185.6,7515.6,20260605,230293225040.0,188063.0
|
||||
IM2606,20260604,16:29:34,500,8363.8,137942,8363.6,1,8363.8,6,9185.6,7515.6,20260605,230303261600.0,188064.0
|
||||
IM2606,20260604,16:29:35,0,8363.4,137946,8363.0,4,8363.4,6,9185.6,7515.6,20260605,230309952280.0,188065.0
|
||||
IM2606,20260604,16:29:35,500,8362.6,137953,8362.4,1,8362.8,2,9185.6,7515.6,20260605,230321660360.0,188069.0
|
||||
IM2606,20260604,16:29:36,0,8362.2,137962,8362.2,1,8362.4,6,9185.6,7515.6,20260605,230336712400.0,188068.0
|
||||
IM2606,20260604,16:29:36,500,8362.2,137963,8362.0,2,8362.2,2,9185.6,7515.6,20260605,230338384840.0,188067.0
|
||||
IM2606,20260604,16:29:37,0,8361.8,137968,8361.2,1,8361.8,1,9185.6,7515.6,20260605,230346746720.0,188070.0
|
||||
IM2606,20260604,16:29:37,500,8361.6,137971,8361.0,4,8361.2,8,9185.6,7515.6,20260605,230351763600.0,188069.0
|
||||
IM2606,20260604,16:29:38,0,8361.0,137977,8360.6,1,8360.8,1,9185.6,7515.6,20260605,230361796800.0,188070.0
|
||||
IM2606,20260604,16:29:38,500,8360.2,137981,8360.2,13,8360.6,77,9185.6,7515.6,20260605,230368485280.0,188069.0
|
||||
IM2606,20260604,16:29:39,0,8360.2,138016,8360.0,13,8360.4,2,9185.6,7515.6,20260605,230427006040.0,188072.0
|
||||
IM2606,20260604,16:29:39,500,8358.6,138033,8358.6,1,8359.6,4,9185.6,7515.6,20260605,230455429320.0,188079.0
|
||||
IM2606,20260604,16:29:40,0,8358.0,138056,8358.0,4,8358.6,75,9185.6,7515.6,20260605,230493881040.0,188087.0
|
||||
IM2606,20260604,16:29:40,500,8358.6,138065,8357.8,1,8358.0,1,9185.6,7515.6,20260605,230508925560.0,188088.0
|
||||
IM2606,20260604,16:29:41,0,8358.2,138071,8358.0,3,8358.6,73,9185.6,7515.6,20260605,230518955400.0,188091.0
|
||||
IM2606,20260604,16:29:41,500,8356.8,138081,8356.0,7,8358.6,69,9185.6,7515.6,20260605,230535671600.0,188094.0
|
||||
IM2606,20260604,16:29:42,0,8358.6,138107,8357.8,1,8358.6,45,9185.6,7515.6,20260605,230579135960.0,188092.0
|
||||
IM2606,20260604,16:29:42,500,8358.6,138161,8358.6,3,8360.0,27,9185.6,7515.6,20260605,230669410120.0,188087.0
|
||||
IM2606,20260604,16:29:43,0,8360.0,138173,8359.8,1,8360.0,21,9185.6,7515.6,20260605,230689472920.0,188089.0
|
||||
IM2606,20260604,16:29:43,500,8358.8,138187,8357.8,1,8359.2,1,9185.6,7515.6,20260605,230712880160.0,188100.0
|
||||
IM2606,20260604,16:29:44,0,8358.8,138194,8358.0,1,8358.8,1,9185.6,7515.6,20260605,230724581760.0,188104.0
|
||||
IM2606,20260604,16:29:44,500,8356.2,138201,8356.2,2,8358.8,1,9185.6,7515.6,20260605,230736282400.0,188107.0
|
||||
IM2606,20260604,16:29:45,0,8356.4,138204,8357.0,1,8358.4,2,9185.6,7515.6,20260605,230741296520.0,188109.0
|
||||
IM2606,20260604,16:29:45,500,8357.2,138207,8357.2,1,8357.6,1,9185.6,7515.6,20260605,230746311080.0,188108.0
|
||||
IM2606,20260604,16:29:46,0,8357.8,138210,8357.6,1,8358.0,1,9185.6,7515.6,20260605,230751325600.0,188109.0
|
||||
IM2606,20260604,16:29:46,500,8356.2,138214,8356.2,1,8358.0,1,9185.6,7515.6,20260605,230758011200.0,188111.0
|
||||
IM2606,20260604,16:29:47,0,8357.0,138217,8357.6,1,8357.8,1,9185.6,7515.6,20260605,230763025240.0,188113.0
|
||||
IM2606,20260604,16:29:47,500,8357.6,138226,8357.2,1,8357.6,6,9185.6,7515.6,20260605,230778069360.0,188114.0
|
||||
IM2606,20260604,16:29:48,0,8357.6,138240,8358.6,1,8359.0,13,9185.6,7515.6,20260605,230801470480.0,188123.0
|
||||
IM2606,20260604,16:29:48,500,8356.4,138245,8356.2,4,8358.0,1,9185.6,7515.6,20260605,230809828120.0,188126.0
|
||||
IM2606,20260604,16:29:49,0,8356.4,138250,8356.4,2,8357.2,1,9185.6,7515.6,20260605,230818184880.0,188129.0
|
||||
IM2606,20260604,16:29:49,500,8357.0,138252,8356.4,1,8357.2,6,9185.6,7515.6,20260605,230821527640.0,188130.0
|
||||
IM2606,20260604,16:29:50,0,8357.2,138259,8356.4,1,8357.2,3,9185.6,7515.6,20260605,230833226920.0,188128.0
|
||||
IM2606,20260604,16:29:50,500,8357.2,138260,8356.4,1,8357.2,2,9185.6,7515.6,20260605,230834898360.0,188128.0
|
||||
IM2606,20260604,16:29:51,0,8357.2,138263,8357.2,6,8358.0,3,9185.6,7515.6,20260605,230839912680.0,188126.0
|
||||
IM2606,20260604,16:29:51,500,8357.2,138269,8356.8,1,8357.4,1,9185.6,7515.6,20260605,230849941320.0,188122.0
|
||||
IM2606,20260604,16:29:52,0,8356.8,138272,8356.4,3,8357.0,1,9185.6,7515.6,20260605,230854955560.0,188124.0
|
||||
IM2606,20260604,16:29:52,500,8357.0,138277,8357.0,2,8357.8,1,9185.6,7515.6,20260605,230863312760.0,188126.0
|
||||
IM2606,20260604,16:29:53,0,8357.0,138282,8356.6,2,8356.8,3,9185.6,7515.6,20260605,230871669840.0,188123.0
|
||||
IM2606,20260604,16:29:53,500,8356.6,138287,8356.4,3,8357.4,1,9185.6,7515.6,20260605,230880026560.0,188122.0
|
||||
IM2606,20260604,16:29:54,0,8356.4,138294,8356.0,9,8356.4,4,9185.6,7515.6,20260605,230891726200.0,188122.0
|
||||
IM2606,20260604,16:29:54,500,8356.2,138302,8356.0,10,8357.6,5,9185.6,7515.6,20260605,230905096520.0,188125.0
|
||||
IM2606,20260604,16:29:55,0,8357.6,138310,8356.0,11,8357.6,2,9185.6,7515.6,20260605,230918468000.0,188127.0
|
||||
IM2606,20260604,16:29:55,500,8357.4,138314,8356.4,2,8357.8,1,9185.6,7515.6,20260605,230925154000.0,188130.0
|
||||
IM2606,20260604,16:29:56,0,8357.0,138317,8356.6,4,8357.4,1,9185.6,7515.6,20260605,230930168560.0,188130.0
|
||||
IM2606,20260604,16:29:56,500,8357.4,138321,8357.4,1,8358.0,2,9185.6,7515.6,20260605,230936854640.0,188132.0
|
||||
IM2606,20260604,16:29:57,0,8358.0,138322,8357.4,1,8358.0,1,9185.6,7515.6,20260605,230938526240.0,188133.0
|
||||
IM2606,20260604,16:29:57,500,8358.0,138326,8358.0,7,8358.2,2,9185.6,7515.6,20260605,230945212640.0,188136.0
|
||||
IM2606,20260604,16:29:58,0,8358.2,138340,8358.0,8,8358.6,2,9185.6,7515.6,20260605,230968615600.0,188143.0
|
||||
IM2606,20260604,16:29:58,500,8358.0,138344,8358.0,4,8358.4,1,9185.6,7515.6,20260605,230975302000.0,188146.0
|
||||
IM2606,20260604,16:29:59,0,8357.6,138355,8356.4,4,8358.0,14,9185.6,7515.6,20260605,230993688840.0,188149.0
|
||||
IM2606,20260604,16:29:59,500,8357.6,138356,8356.4,5,8357.6,2,9185.6,7515.6,20260605,230995360360.0,188149.0
|
||||
IM2606,20260604,16:30:00,0,8356.6,138363,8356.4,5,8357.4,1,9185.6,7515.6,20260605,231007060000.0,188153.0
|
||||
IM2606,20260604,16:30:00,500,8356.6,138364,8356.4,5,8356.8,2,9185.6,7515.6,20260605,231008731320.0,188154.0
|
||||
IM2606,20260604,16:30:01,0,8356.4,138368,8356.4,4,8356.8,2,9185.6,7515.6,20260605,231015416480.0,188157.0
|
||||
IM2606,20260604,16:30:01,500,8356.4,138370,8356.4,3,8357.4,1,9185.6,7515.6,20260605,231018759120.0,188158.0
|
||||
IM2606,20260604,16:30:02,0,8356.4,138373,8356.4,1,8357.4,1,9185.6,7515.6,20260605,231023773120.0,188158.0
|
||||
IM2606,20260604,16:30:03,0,8356.6,138382,8356.6,2,8357.8,2,9185.6,7515.6,20260605,231038815400.0,188163.0
|
||||
IM2606,20260604,16:30:03,500,8356.4,138386,8356.4,1,8356.6,3,9185.6,7515.6,20260605,231045500600.0,188163.0
|
||||
IM2606,20260604,16:30:04,0,8356.4,138391,8356.2,3,8356.4,3,9185.6,7515.6,20260605,231053856920.0,188163.0
|
||||
IM2606,20260604,16:30:04,500,8356.2,138398,8356.0,8,8356.2,3,9185.6,7515.6,20260605,231065555560.0,188165.0
|
||||
IM2606,20260604,16:30:05,0,8356.0,138411,8355.6,3,8356.0,20,9185.6,7515.6,20260605,231087281040.0,188174.0
|
||||
IM2606,20260604,16:30:05,500,8355.4,138423,8355.0,22,8355.4,1,9185.6,7515.6,20260605,231107333760.0,188180.0
|
||||
IM2606,20260604,16:30:06,0,8356.0,138438,8355.0,9,8356.0,22,9185.6,7515.6,20260605,231132399000.0,188185.0
|
||||
IM2606,20260604,16:30:06,500,8355.0,138441,8355.2,1,8355.4,1,9185.6,7515.6,20260605,231137412000.0,188185.0
|
||||
IM2606,20260604,16:30:07,0,8355.4,138445,8355.0,7,8355.6,2,9185.6,7515.6,20260605,231144096160.0,188186.0
|
||||
IM2606,20260604,16:30:07,500,8355.0,138447,8355.0,7,8355.2,1,9185.6,7515.6,20260605,231147438240.0,188186.0
|
||||
IM2606,20260604,16:30:08,0,8355.4,138452,8355.0,5,8355.6,1,9185.6,7515.6,20260605,231155793360.0,188188.0
|
||||
IM2606,20260604,16:30:08,500,8355.0,138461,8354.8,13,8355.2,4,9185.6,7515.6,20260605,231170832480.0,188192.0
|
||||
IM2606,20260604,16:30:09,0,8355.2,138463,8354.8,13,8355.2,3,9185.6,7515.6,20260605,231174174560.0,188194.0
|
||||
IM2606,20260604,16:30:09,500,8355.0,138471,8354.8,8,8355.0,1,9185.6,7515.6,20260605,231187542440.0,188202.0
|
||||
IM2606,20260604,16:30:10,0,8355.8,138479,8355.2,2,8356.0,2,9185.6,7515.6,20260605,231200910920.0,188206.0
|
||||
IM2606,20260604,16:30:10,500,8356.2,138492,8355.4,4,8356.2,19,9185.6,7515.6,20260605,231222636480.0,188209.0
|
||||
IM2606,20260604,16:30:11,0,8355.2,138498,8355.2,4,8355.4,2,9185.6,7515.6,20260605,231232663240.0,188211.0
|
||||
IM2606,20260604,16:30:11,500,8355.0,138504,8355.0,10,8355.2,3,9185.6,7515.6,20260605,231242689480.0,188213.0
|
||||
IM2606,20260604,16:30:12,0,8355.0,138514,8355.0,1,8355.2,2,9185.6,7515.6,20260605,231259399520.0,188212.0
|
||||
IM2606,20260604,16:30:12,500,8355.0,138520,8354.8,8,8355.0,4,9185.6,7515.6,20260605,231269425520.0,188217.0
|
||||
IM2606,20260604,16:30:13,0,8354.8,138525,8354.8,4,8355.0,3,9185.6,7515.6,20260605,231277780360.0,188220.0
|
||||
IM2606,20260604,16:30:13,500,8355.0,138537,8354.4,3,8355.0,4,9185.6,7515.6,20260605,231297832240.0,188225.0
|
||||
IM2606,20260604,16:30:14,0,8355.0,138540,8354.4,1,8355.0,3,9185.6,7515.6,20260605,231302845000.0,188226.0
|
||||
IM2606,20260604,16:30:14,500,8355.0,138543,8354.4,1,8355.0,3,9185.6,7515.6,20260605,231307857880.0,188225.0
|
||||
IM2606,20260604,16:30:15,0,8354.4,138554,8354.8,1,8356.0,1,9185.6,7515.6,20260605,231326237800.0,188232.0
|
||||
IM2606,20260604,16:30:15,500,8354.6,138558,8353.4,1,8354.4,1,9185.6,7515.6,20260605,231332921360.0,188234.0
|
||||
IM2606,20260604,16:30:16,0,8354.8,138563,8353.6,4,8354.8,5,9185.6,7515.6,20260605,231341275960.0,188236.0
|
||||
IM2606,20260604,16:30:16,500,8353.6,138566,8353.4,2,8353.6,3,9185.6,7515.6,20260605,231346288360.0,188235.0
|
||||
IM2606,20260604,16:30:17,0,8354.6,138582,8353.4,2,8354.8,5,9185.6,7515.6,20260605,231373020560.0,188236.0
|
||||
IM2606,20260604,16:30:17,500,8353.4,138588,8353.2,1,8353.4,1,9185.6,7515.6,20260605,231383044840.0,188239.0
|
||||
IM2606,20260604,16:30:18,0,8353.2,138601,8353.0,10,8353.2,5,9185.6,7515.6,20260605,231404763200.0,188247.0
|
||||
IM2606,20260604,16:30:18,500,8351.4,138636,8351.4,6,8352.0,2,9185.6,7515.6,20260605,231463229880.0,188252.0
|
||||
IM2606,20260604,16:30:19,0,8351.4,138674,8351.0,26,8351.4,2,9185.6,7515.6,20260605,231526699960.0,188252.0
|
||||
IM2606,20260604,16:30:19,500,8352.0,138705,8351.0,16,8352.0,1,9185.6,7515.6,20260605,231578479200.0,188274.0
|
||||
IM2606,20260604,16:30:20,0,8352.0,138727,8351.2,4,8353.0,23,9185.6,7515.6,20260605,231615228800.0,188272.0
|
||||
IM2606,20260604,16:30:20,500,8352.8,138748,8352.6,2,8352.8,1,9185.6,7515.6,20260605,231650306440.0,188277.0
|
||||
IM2606,20260604,16:30:21,0,8353.0,138767,8352.2,2,8352.4,1,9185.6,7515.6,20260605,231682047120.0,188292.0
|
||||
IM2606,20260604,16:30:21,500,8353.0,138776,8352.6,1,8353.6,2,9185.6,7515.6,20260605,231697082480.0,188294.0
|
||||
IM2606,20260604,16:30:22,0,8353.4,138784,8352.4,3,8353.4,1,9185.6,7515.6,20260605,231710447360.0,188299.0
|
||||
IM2606,20260604,16:30:22,500,8354.8,138790,8353.4,1,8354.8,4,9185.6,7515.6,20260605,231720472160.0,188301.0
|
||||
IM2606,20260604,16:30:23,0,8352.8,138797,8353.0,1,8353.2,5,9185.6,7515.6,20260605,231732166280.0,188303.0
|
||||
IM2606,20260604,16:30:23,500,8353.0,138804,8352.8,1,8353.2,6,9185.6,7515.6,20260605,231743860320.0,188305.0
|
||||
IM2606,20260604,16:30:24,0,8352.8,138809,8352.4,3,8352.8,5,9185.6,7515.6,20260605,231752213040.0,188307.0
|
||||
IM2606,20260604,16:30:24,500,8352.4,138816,8352.2,1,8352.4,10,9185.6,7515.6,20260605,231763906360.0,188305.0
|
||||
IM2606,20260604,16:30:25,0,8351.0,138827,8351.0,14,8352.0,2,9185.6,7515.6,20260605,231782279800.0,188310.0
|
||||
IM2606,20260604,16:30:25,500,8351.0,138848,8350.8,4,8351.0,5,9185.6,7515.6,20260605,231817354160.0,188301.0
|
||||
IM2606,20260604,16:30:26,0,8350.6,138858,8350.6,15,8350.8,7,9185.6,7515.6,20260605,231834055720.0,188303.0
|
||||
IM2606,20260604,16:30:27,0,8350.2,138884,8350.0,58,8350.6,12,9185.6,7515.6,20260605,231877478640.0,188307.0
|
||||
IM2606,20260604,16:30:27,500,8350.0,138910,8350.0,33,8350.2,1,9185.6,7515.6,20260605,231920898840.0,188319.0
|
||||
IM2606,20260604,16:30:28,0,8348.8,138968,8348.8,1,8350.0,14,9185.6,7515.6,20260605,232017757040.0,188342.0
|
||||
IM2606,20260604,16:30:28,500,8348.0,138982,8348.0,1,8348.6,1,9185.6,7515.6,20260605,232041132800.0,188342.0
|
||||
IM2606,20260604,16:30:29,0,8346.6,139020,8346.0,1,8346.2,7,9185.6,7515.6,20260605,232104573640.0,188363.0
|
||||
IM2606,20260604,16:30:29,500,8346.4,139043,8346.4,4,8347.6,1,9185.6,7515.6,20260605,232142966960.0,188371.0
|
||||
IM2606,20260604,16:30:30,0,8346.4,139064,8346.4,1,8347.6,1,9185.6,7515.6,20260605,232178025000.0,188385.0
|
||||
IM2606,20260604,16:30:30,500,8346.2,139090,8346.2,6,8348.8,6,9185.6,7515.6,20260605,232221430960.0,188396.0
|
||||
IM2606,20260604,16:30:31,0,8346.2,139109,8346.2,5,8346.4,3,9185.6,7515.6,20260605,232253148040.0,188404.0
|
||||
IM2606,20260604,16:30:31,500,8347.0,139129,8347.0,1,8348.6,1,9185.6,7515.6,20260605,232286535440.0,188415.0
|
||||
IM2606,20260604,16:30:32,0,8347.0,139143,8346.4,5,8347.0,2,9185.6,7515.6,20260605,232309907120.0,188419.0
|
||||
IM2606,20260604,16:30:32,500,8346.2,139155,8346.2,1,8346.4,4,9185.6,7515.6,20260605,232329938840.0,188427.0
|
||||
IM2606,20260604,16:30:33,0,8346.2,139166,8346.0,3,8346.2,1,9185.6,7515.6,20260605,232348300720.0,188430.0
|
||||
IM2606,20260604,16:30:33,500,8346.2,139173,8346.0,3,8346.2,8,9185.6,7515.6,20260605,232359985480.0,188431.0
|
||||
IM2606,20260604,16:30:34,0,8346.2,139189,8345.2,1,8346.2,4,9185.6,7515.6,20260605,232386692920.0,188432.0
|
||||
IM2606,20260604,16:30:34,500,8345.0,139206,8345.0,8,8346.0,2,9185.6,7515.6,20260605,232415067120.0,188443.0
|
||||
IM2606,20260604,16:30:35,0,8345.8,139228,8344.8,3,8346.0,5,9185.6,7515.6,20260605,232451786120.0,188447.0
|
||||
IM2606,20260604,16:30:35,500,8345.0,139241,8344.8,3,8345.0,12,9185.6,7515.6,20260605,232473483720.0,188449.0
|
||||
IM2606,20260604,16:30:36,0,8345.0,139254,8344.4,2,8345.0,3,9185.6,7515.6,20260605,232495180600.0,188448.0
|
||||
IM2606,20260604,16:30:36,500,8346.0,139272,8345.0,1,8346.2,7,9185.6,7515.6,20260605,232525222200.0,188454.0
|
||||
IM2606,20260604,16:30:37,0,8343.0,139284,8343.0,2,8346.0,1,9185.6,7515.6,20260605,232545248400.0,188461.0
|
||||
IM2606,20260604,16:30:37,500,8342.2,139321,8342.2,3,8345.8,3,9185.6,7515.6,20260605,232606990680.0,188469.0
|
||||
IM2606,20260604,16:30:38,0,8342.4,139329,8342.4,3,8343.0,1,9185.6,7515.6,20260605,232620339040.0,188475.0
|
||||
IM2606,20260604,16:30:38,500,8343.4,139338,8343.4,1,8345.4,4,9185.6,7515.6,20260605,232635358120.0,188479.0
|
||||
IM2606,20260604,16:30:39,0,8345.0,139358,8343.0,4,8343.4,1,9185.6,7515.6,20260605,232668733240.0,188496.0
|
||||
IM2606,20260604,16:30:39,500,8343.8,139373,8343.4,17,8345.6,1,9185.6,7515.6,20260605,232693766240.0,188500.0
|
||||
IM2606,20260604,16:30:40,0,8343.4,139400,8343.6,2,8346.2,8,9185.6,7515.6,20260605,232738824880.0,188496.0
|
||||
IM2606,20260604,16:30:40,500,8343.8,139406,8344.0,1,8345.8,1,9185.6,7515.6,20260605,232748837920.0,188498.0
|
||||
IM2606,20260604,16:30:41,0,8345.6,139429,8343.0,3,8345.6,5,9185.6,7515.6,20260605,232787219400.0,188502.0
|
||||
IM2606,20260604,16:30:41,500,8343.4,139438,8343.2,2,8343.6,1,9185.6,7515.6,20260605,232802240720.0,188501.0
|
||||
IM2606,20260604,16:30:42,0,8343.8,139444,8343.6,2,8344.0,2,9185.6,7515.6,20260605,232812253560.0,188503.0
|
||||
IM2606,20260604,16:30:42,500,8344.6,139451,8344.6,2,8345.6,1,9185.6,7515.6,20260605,232823935600.0,188505.0
|
||||
IM2606,20260604,16:30:43,0,8345.8,139454,8345.8,3,8346.2,8,9185.6,7515.6,20260605,232828943080.0,188508.0
|
||||
IM2606,20260604,16:30:43,500,8346.2,139464,8345.8,2,8346.4,5,9185.6,7515.6,20260605,232845635440.0,188515.0
|
||||
IM2606,20260604,16:30:44,0,8346.6,139473,8346.8,1,8348.8,9,9185.6,7515.6,20260605,232860659120.0,188519.0
|
||||
IM2606,20260604,16:30:44,500,8346.4,139481,8346.2,9,8347.8,1,9185.6,7515.6,20260605,232874014120.0,188519.0
|
||||
IM2606,20260604,16:30:45,0,8346.2,139495,8346.4,1,8346.8,2,9185.6,7515.6,20260605,232897383120.0,188524.0
|
||||
IM2606,20260604,16:30:45,500,8345.4,139507,8344.0,1,8344.6,1,9185.6,7515.6,20260605,232917411440.0,188528.0
|
||||
IM2606,20260604,16:30:46,0,8344.6,139514,8344.6,6,8346.0,5,9185.6,7515.6,20260605,232929093920.0,188532.0
|
||||
IM2606,20260604,16:30:46,500,8345.8,139526,8344.6,5,8345.8,6,9185.6,7515.6,20260605,232949123080.0,188539.0
|
||||
IM2606,20260604,16:30:47,0,8345.6,139533,8344.6,7,8345.6,2,9185.6,7515.6,20260605,232960806640.0,188539.0
|
||||
IM2606,20260604,16:30:47,500,8344.8,139535,8345.6,1,8345.8,8,9185.6,7515.6,20260605,232964144720.0,188541.0
|
||||
IM2606,20260604,16:30:48,0,8345.8,139547,8345.6,2,8346.0,4,9185.6,7515.6,20260605,232984174400.0,188542.0
|
||||
IM2606,20260604,16:30:48,500,8345.6,139549,8345.6,4,8345.8,1,9185.6,7515.6,20260605,232987512640.0,188541.0
|
||||
IM2606,20260604,16:30:49,0,8344.8,139557,8344.6,7,8345.0,4,9185.6,7515.6,20260605,233000865080.0,188540.0
|
||||
IM2606,20260604,16:30:49,500,8344.8,139561,8344.6,7,8345.0,2,9185.6,7515.6,20260605,233007540960.0,188541.0
|
||||
IM2606,20260604,16:30:50,0,8345.6,139567,8344.6,6,8345.8,1,9185.6,7515.6,20260605,233017555240.0,188539.0
|
||||
IM2606,20260604,16:30:50,500,8344.8,139571,8344.6,6,8345.0,2,9185.6,7515.6,20260605,233024231560.0,188540.0
|
||||
IM2606,20260604,16:30:51,0,8345.8,139575,8345.8,1,8346.0,4,9185.6,7515.6,20260605,233030907880.0,188539.0
|
||||
IM2606,20260604,16:30:51,500,8345.8,139579,8345.6,1,8346.0,3,9185.6,7515.6,20260605,233037584560.0,188540.0
|
||||
IM2606,20260604,16:30:52,0,8346.0,139586,8345.0,3,8346.0,2,9185.6,7515.6,20260605,233049268600.0,188545.0
|
||||
IM2606,20260604,16:30:52,500,8346.0,139593,8345.8,1,8346.0,1,9185.6,7515.6,20260605,233060953000.0,188542.0
|
||||
IM2606,20260604,16:30:53,0,8346.0,139600,8346.0,4,8346.2,9,9185.6,7515.6,20260605,233072637240.0,188545.0
|
||||
IM2606,20260604,16:30:53,500,8346.0,139614,8345.8,1,8346.0,1,9185.6,7515.6,20260605,233096006160.0,188548.0
|
||||
IM2606,20260604,16:30:54,0,8346.2,139621,8346.0,2,8346.2,5,9185.6,7515.6,20260605,233107690320.0,188552.0
|
||||
IM2606,20260604,16:30:54,500,8346.0,139627,8345.0,6,8346.0,12,9185.6,7515.6,20260605,233117705480.0,188554.0
|
||||
IM2606,20260604,16:30:55,0,8346.0,139641,8345.0,4,8346.0,10,9185.6,7515.6,20260605,233141073440.0,188564.0
|
||||
IM2606,20260604,16:30:55,500,8346.0,139642,8345.0,4,8345.4,1,9185.6,7515.6,20260605,233142742640.0,188565.0
|
||||
IM2606,20260604,16:30:56,0,8345.0,139657,8344.0,4,8345.0,7,9185.6,7515.6,20260605,233167776520.0,188570.0
|
||||
|
Reference in New Issue
Block a user