[Add] backtesting function for spread trading
This commit is contained in:
parent
ddbf62d47a
commit
687bdbc66d
120
examples/spread_backtesting/backtesting.ipynb
Normal file
120
examples/spread_backtesting/backtesting.ipynb
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"#%%\n",
|
||||||
|
"from vnpy.app.spread_trading.backtesting import BacktestingEngine\n",
|
||||||
|
"from vnpy.app.spread_trading.strategies.statistical_arbitrage_strategy import (\n",
|
||||||
|
" StatisticalArbitrageStrategy\n",
|
||||||
|
")\n",
|
||||||
|
"from vnpy.app.spread_trading.base import LegData, SpreadData\n",
|
||||||
|
"from datetime import datetime"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"spread = SpreadData(\n",
|
||||||
|
" name=\"IF-Spread\",\n",
|
||||||
|
" legs=[LegData(\"IF1911.CFFEX\"), LegData(\"IF1912.CFFEX\")],\n",
|
||||||
|
" price_multipliers={\"IF1911.CFFEX\": 1, \"IF1912.CFFEX\": -1},\n",
|
||||||
|
" trading_multipliers={\"IF1911.CFFEX\": 1, \"IF1912.CFFEX\": -1},\n",
|
||||||
|
" active_symbol=\"IF1911.CFFEX\",\n",
|
||||||
|
" inverse_contracts={\"IF1911.CFFEX\": False, \"IF1912.CFFEX\": False},\n",
|
||||||
|
" min_volume=1\n",
|
||||||
|
")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"#%%\n",
|
||||||
|
"engine = BacktestingEngine()\n",
|
||||||
|
"engine.set_parameters(\n",
|
||||||
|
" spread=spread,\n",
|
||||||
|
" interval=\"1m\",\n",
|
||||||
|
" start=datetime(2019, 6, 10),\n",
|
||||||
|
" end=datetime(2019, 11, 10),\n",
|
||||||
|
" rate=0,\n",
|
||||||
|
" slippage=0,\n",
|
||||||
|
" size=300,\n",
|
||||||
|
" pricetick=0.2,\n",
|
||||||
|
" capital=1_000_000,\n",
|
||||||
|
")\n",
|
||||||
|
"engine.add_strategy(StatisticalArbitrageStrategy, {})"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {
|
||||||
|
"scrolled": false
|
||||||
|
},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"2019-11-10 16:09:03.822440\t开始加载历史数据\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"#%%\n",
|
||||||
|
"engine.load_data()\n",
|
||||||
|
"engine.run_backtesting()\n",
|
||||||
|
"df = engine.calculate_result()\n",
|
||||||
|
"engine.calculate_statistics()\n",
|
||||||
|
"engine.show_chart()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"for trade in engine.trades.values():\n",
|
||||||
|
" print(trade)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.7.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 2
|
||||||
|
}
|
@ -3,7 +3,9 @@ from pathlib import Path
|
|||||||
from vnpy.trader.app import BaseApp
|
from vnpy.trader.app import BaseApp
|
||||||
from vnpy.trader.object import (
|
from vnpy.trader.object import (
|
||||||
OrderData,
|
OrderData,
|
||||||
TradeData
|
TradeData,
|
||||||
|
TickData,
|
||||||
|
BarData
|
||||||
)
|
)
|
||||||
|
|
||||||
from .engine import (
|
from .engine import (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from typing import Callable
|
from typing import Callable, Type, Dict, List
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -14,7 +14,7 @@ from vnpy.trader.database import database_manager
|
|||||||
from vnpy.trader.object import TradeData, BarData, TickData
|
from vnpy.trader.object import TradeData, BarData, TickData
|
||||||
from vnpy.trader.utility import round_to
|
from vnpy.trader.utility import round_to
|
||||||
|
|
||||||
from .template import SpreadStrategyTemplate
|
from .template import SpreadStrategyTemplate, SpreadAlgoTemplate
|
||||||
from .base import SpreadData, BacktestingMode
|
from .base import SpreadData, BacktestingMode
|
||||||
|
|
||||||
sns.set_style("whitegrid")
|
sns.set_style("whitegrid")
|
||||||
@ -38,8 +38,8 @@ class BacktestingEngine:
|
|||||||
self.capital = 1_000_000
|
self.capital = 1_000_000
|
||||||
self.mode = BacktestingMode.BAR
|
self.mode = BacktestingMode.BAR
|
||||||
|
|
||||||
self.strategy_class = None
|
self.strategy_class: Type[SpreadStrategyTemplate] = None
|
||||||
self.strategy = None
|
self.strategy: SpreadStrategyTemplate = None
|
||||||
self.tick: TickData = None
|
self.tick: TickData = None
|
||||||
self.bar: BarData = None
|
self.bar: BarData = None
|
||||||
self.datetime = None
|
self.datetime = None
|
||||||
@ -138,7 +138,8 @@ class BacktestingEngine:
|
|||||||
self.spread,
|
self.spread,
|
||||||
self.interval,
|
self.interval,
|
||||||
self.start,
|
self.start,
|
||||||
self.end
|
self.end,
|
||||||
|
self.pricetick
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.history_datas = load_tick_data(
|
self.history_datas = load_tick_data(
|
||||||
@ -208,8 +209,7 @@ class BacktestingEngine:
|
|||||||
start_pos,
|
start_pos,
|
||||||
self.size,
|
self.size,
|
||||||
self.rate,
|
self.rate,
|
||||||
self.slippage,
|
self.slippage
|
||||||
self.inverse
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pre_close = daily_result.close_price
|
pre_close = daily_result.close_price
|
||||||
@ -427,10 +427,9 @@ class BacktestingEngine:
|
|||||||
""""""
|
""""""
|
||||||
self.bar = bar
|
self.bar = bar
|
||||||
self.datetime = bar.datetime
|
self.datetime = bar.datetime
|
||||||
|
self.cross_algo()
|
||||||
|
|
||||||
self.cross_limit_order()
|
self.strategy.on_spread_bar(bar)
|
||||||
self.cross_stop_order()
|
|
||||||
self.strategy.on_bar(bar)
|
|
||||||
|
|
||||||
self.update_daily_close(bar.close_price)
|
self.update_daily_close(bar.close_price)
|
||||||
|
|
||||||
@ -438,44 +437,39 @@ class BacktestingEngine:
|
|||||||
""""""
|
""""""
|
||||||
self.tick = tick
|
self.tick = tick
|
||||||
self.datetime = tick.datetime
|
self.datetime = tick.datetime
|
||||||
|
self.cross_algo()
|
||||||
|
|
||||||
self.cross_limit_order()
|
self.spread.bid_price = tick.bid_price_1
|
||||||
self.cross_stop_order()
|
self.spread.bid_volume = tick.bid_volume_1
|
||||||
self.strategy.on_tick(tick)
|
self.spread.ask_price = tick.ask_price_1
|
||||||
|
self.spread.ask_volume = tick.ask_volume_1
|
||||||
|
|
||||||
|
self.strategy.on_spread_data()
|
||||||
|
|
||||||
self.update_daily_close(tick.last_price)
|
self.update_daily_close(tick.last_price)
|
||||||
|
|
||||||
def cross_limit_order(self):
|
def cross_algo(self):
|
||||||
"""
|
"""
|
||||||
Cross limit order with last bar/tick data.
|
Cross limit order with last bar/tick data.
|
||||||
"""
|
"""
|
||||||
if self.mode == BacktestingMode.BAR:
|
if self.mode == BacktestingMode.BAR:
|
||||||
long_cross_price = self.bar.low_price
|
long_cross_price = self.bar.close_price
|
||||||
short_cross_price = self.bar.high_price
|
short_cross_price = self.bar.close_price
|
||||||
long_best_price = self.bar.open_price
|
|
||||||
short_best_price = self.bar.open_price
|
|
||||||
else:
|
else:
|
||||||
long_cross_price = self.tick.ask_price_1
|
long_cross_price = self.tick.ask_price_1
|
||||||
short_cross_price = self.tick.bid_price_1
|
short_cross_price = self.tick.bid_price_1
|
||||||
long_best_price = long_cross_price
|
|
||||||
short_best_price = short_cross_price
|
|
||||||
|
|
||||||
for order in list(self.active_limit_orders.values()):
|
|
||||||
# Push order update with status "not traded" (pending).
|
|
||||||
if order.status == Status.SUBMITTING:
|
|
||||||
order.status = Status.NOTTRADED
|
|
||||||
self.strategy.on_order(order)
|
|
||||||
|
|
||||||
|
for algo in list(self.active_algos.values()):
|
||||||
# Check whether limit orders can be filled.
|
# Check whether limit orders can be filled.
|
||||||
long_cross = (
|
long_cross = (
|
||||||
order.direction == Direction.LONG
|
algo.direction == Direction.LONG
|
||||||
and order.price >= long_cross_price
|
and algo.price >= long_cross_price
|
||||||
and long_cross_price > 0
|
and long_cross_price > 0
|
||||||
)
|
)
|
||||||
|
|
||||||
short_cross = (
|
short_cross = (
|
||||||
order.direction == Direction.SHORT
|
algo.direction == Direction.SHORT
|
||||||
and order.price <= short_cross_price
|
and algo.price <= short_cross_price
|
||||||
and short_cross_price > 0
|
and short_cross_price > 0
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -483,49 +477,49 @@ class BacktestingEngine:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Push order udpate with status "all traded" (filled).
|
# Push order udpate with status "all traded" (filled).
|
||||||
order.traded = order.volume
|
algo.traded = algo.volume
|
||||||
order.status = Status.ALLTRADED
|
algo.status = Status.ALLTRADED
|
||||||
self.strategy.on_order(order)
|
self.strategy.update_spread_algo(algo)
|
||||||
|
|
||||||
self.active_limit_orders.pop(order.vt_orderid)
|
self.active_algos.pop(algo.algoid)
|
||||||
|
|
||||||
# Push trade update
|
# Push trade update
|
||||||
self.trade_count += 1
|
self.trade_count += 1
|
||||||
|
|
||||||
if long_cross:
|
if long_cross:
|
||||||
trade_price = min(order.price, long_best_price)
|
trade_price = long_cross_price
|
||||||
pos_change = order.volume
|
pos_change = algo.volume
|
||||||
else:
|
else:
|
||||||
trade_price = max(order.price, short_best_price)
|
trade_price = short_cross_price
|
||||||
pos_change = -order.volume
|
pos_change = -algo.volume
|
||||||
|
|
||||||
trade = TradeData(
|
trade = TradeData(
|
||||||
symbol=order.symbol,
|
symbol=self.spread.name,
|
||||||
exchange=order.exchange,
|
exchange=Exchange.LOCAL,
|
||||||
orderid=order.orderid,
|
orderid=algo.algoid,
|
||||||
tradeid=str(self.trade_count),
|
tradeid=str(self.trade_count),
|
||||||
direction=order.direction,
|
direction=algo.direction,
|
||||||
offset=order.offset,
|
offset=algo.offset,
|
||||||
price=trade_price,
|
price=trade_price,
|
||||||
volume=order.volume,
|
volume=algo.volume,
|
||||||
time=self.datetime.strftime("%H:%M:%S"),
|
time=self.datetime.strftime("%H:%M:%S"),
|
||||||
gateway_name=self.gateway_name,
|
gateway_name=self.gateway_name,
|
||||||
)
|
)
|
||||||
trade.datetime = self.datetime
|
trade.datetime = self.datetime
|
||||||
|
|
||||||
self.strategy.pos += pos_change
|
self.spread.net_pos += pos_change
|
||||||
self.strategy.on_trade(trade)
|
self.strategy.on_spread_pos()
|
||||||
|
|
||||||
self.trades[trade.vt_tradeid] = trade
|
self.trades[trade.vt_tradeid] = trade
|
||||||
|
|
||||||
def load_bar(
|
def load_bar(
|
||||||
self, spread: str, days: int, interval: Interval, callback: Callable
|
self, spread: SpreadData, days: int, interval: Interval, callback: Callable
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
self.days = days
|
self.days = days
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|
||||||
def load_tick(self, spread: str, days: int, callback: Callable):
|
def load_tick(self, spread: SpreadData, days: int, callback: Callable):
|
||||||
""""""
|
""""""
|
||||||
self.days = days
|
self.days = days
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
@ -543,14 +537,39 @@ class BacktestingEngine:
|
|||||||
lock: bool
|
lock: bool
|
||||||
) -> str:
|
) -> str:
|
||||||
""""""
|
""""""
|
||||||
pass
|
self.algo_count += 1
|
||||||
|
algoid = str(self.algo_count)
|
||||||
|
|
||||||
|
algo = SpreadAlgoTemplate(
|
||||||
|
self,
|
||||||
|
algoid,
|
||||||
|
self.spread,
|
||||||
|
direction,
|
||||||
|
offset,
|
||||||
|
price,
|
||||||
|
volume,
|
||||||
|
payup,
|
||||||
|
interval,
|
||||||
|
lock
|
||||||
|
)
|
||||||
|
|
||||||
|
self.algos[algoid] = algo
|
||||||
|
self.active_algos[algoid] = algo
|
||||||
|
|
||||||
|
return algoid
|
||||||
|
|
||||||
def stop_algo(
|
def stop_algo(
|
||||||
self,
|
self,
|
||||||
|
strategy: SpreadStrategyTemplate,
|
||||||
algoid: str
|
algoid: str
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
pass
|
if algoid not in self.active_algos:
|
||||||
|
return
|
||||||
|
algo = self.active_algos.pop(algoid)
|
||||||
|
|
||||||
|
algo.status = Status.CANCELLED
|
||||||
|
self.strategy.update_spread_algo(algo)
|
||||||
|
|
||||||
def send_order(
|
def send_order(
|
||||||
self,
|
self,
|
||||||
@ -563,23 +582,15 @@ class BacktestingEngine:
|
|||||||
lock: bool
|
lock: bool
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
price = round_to(price, self.pricetick)
|
pass
|
||||||
if stop:
|
|
||||||
vt_orderid = self.send_stop_order(direction, offset, price, volume)
|
|
||||||
else:
|
|
||||||
vt_orderid = self.send_limit_order(direction, offset, price, volume)
|
|
||||||
return [vt_orderid]
|
|
||||||
|
|
||||||
def cancel_order(self, strategy: SpreadStrategyTemplate, vt_orderid: str):
|
def cancel_order(self, strategy: SpreadStrategyTemplate, vt_orderid: str):
|
||||||
"""
|
"""
|
||||||
Cancel order by vt_orderid.
|
Cancel order by vt_orderid.
|
||||||
"""
|
"""
|
||||||
if vt_orderid.startswith(STOPORDER_PREFIX):
|
pass
|
||||||
self.cancel_stop_order(strategy, vt_orderid)
|
|
||||||
else:
|
|
||||||
self.cancel_limit_order(strategy, vt_orderid)
|
|
||||||
|
|
||||||
def write_log(self, msg: str, strategy: SpreadStrategyTemplate = None):
|
def write_strategy_log(self, strategy: SpreadStrategyTemplate, msg: str):
|
||||||
"""
|
"""
|
||||||
Write log message.
|
Write log message.
|
||||||
"""
|
"""
|
||||||
@ -598,6 +609,10 @@ class BacktestingEngine:
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def write_algo_log(self, algo: SpreadAlgoTemplate, msg: str):
|
||||||
|
""""""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DailyResult:
|
class DailyResult:
|
||||||
""""""
|
""""""
|
||||||
@ -633,8 +648,7 @@ class DailyResult:
|
|||||||
start_pos: float,
|
start_pos: float,
|
||||||
size: int,
|
size: int,
|
||||||
rate: float,
|
rate: float,
|
||||||
slippage: float,
|
slippage: float
|
||||||
inverse: bool
|
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
# If no pre_close provided on the first day,
|
# If no pre_close provided on the first day,
|
||||||
@ -648,12 +662,7 @@ class DailyResult:
|
|||||||
self.start_pos = start_pos
|
self.start_pos = start_pos
|
||||||
self.end_pos = start_pos
|
self.end_pos = start_pos
|
||||||
|
|
||||||
if not inverse: # For normal contract
|
self.holding_pnl = self.start_pos * (self.close_price - self.pre_close) * size
|
||||||
self.holding_pnl = self.start_pos * \
|
|
||||||
(self.close_price - self.pre_close) * size
|
|
||||||
else: # For crypto currency inverse contract
|
|
||||||
self.holding_pnl = self.start_pos * \
|
|
||||||
(1 / self.pre_close - 1 / self.close_price) * size
|
|
||||||
|
|
||||||
# Trading pnl is the pnl from new trade during the day
|
# Trading pnl is the pnl from new trade during the day
|
||||||
self.trade_count = len(self.trades)
|
self.trade_count = len(self.trades)
|
||||||
@ -666,18 +675,10 @@ class DailyResult:
|
|||||||
|
|
||||||
self.end_pos += pos_change
|
self.end_pos += pos_change
|
||||||
|
|
||||||
# For normal contract
|
turnover = trade.volume * size * trade.price
|
||||||
if not inverse:
|
self.trading_pnl += pos_change * \
|
||||||
turnover = trade.volume * size * trade.price
|
(self.close_price - trade.price) * size
|
||||||
self.trading_pnl += pos_change * \
|
self.slippage += trade.volume * size * slippage
|
||||||
(self.close_price - trade.price) * size
|
|
||||||
self.slippage += trade.volume * size * slippage
|
|
||||||
# For crypto currency inverse contract
|
|
||||||
else:
|
|
||||||
turnover = trade.volume * size / trade.price
|
|
||||||
self.trading_pnl += pos_change * \
|
|
||||||
(1 / trade.price - 1 / self.close_price) * size
|
|
||||||
self.slippage += trade.volume * size * slippage / (trade.price ** 2)
|
|
||||||
|
|
||||||
self.turnover += turnover
|
self.turnover += turnover
|
||||||
self.commission += turnover * rate
|
self.commission += turnover * rate
|
||||||
@ -687,30 +688,72 @@ class DailyResult:
|
|||||||
self.net_pnl = self.total_pnl - self.commission - self.slippage
|
self.net_pnl = self.total_pnl - self.commission - self.slippage
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=999)
|
@lru_cache(maxsize=999)
|
||||||
def load_bar_data(
|
def load_bar_data(
|
||||||
symbol: str,
|
spread: SpreadData,
|
||||||
exchange: Exchange,
|
|
||||||
interval: Interval,
|
interval: Interval,
|
||||||
start: datetime,
|
start: datetime,
|
||||||
end: datetime
|
end: datetime,
|
||||||
|
pricetick: float
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
return database_manager.load_bar_data(
|
# Load bar data of each spread leg
|
||||||
symbol, exchange, interval, start, end
|
leg_bars: Dict[str, Dict] = {}
|
||||||
)
|
|
||||||
|
for vt_symbol in spread.legs.keys():
|
||||||
|
symbol, exchange_str = vt_symbol.split(".")
|
||||||
|
exchange = Exchange(exchange_str)
|
||||||
|
|
||||||
|
bar_data: List[BarData] = database_manager.load_bar_data(
|
||||||
|
symbol, exchange, interval, start, end
|
||||||
|
)
|
||||||
|
|
||||||
|
bars: Dict[datetime, BarData] = {bar.datetime: bar for bar in bar_data}
|
||||||
|
leg_bars[vt_symbol] = bars
|
||||||
|
|
||||||
|
# Calculate spread bar data
|
||||||
|
spread_bars: List[BarData] = []
|
||||||
|
|
||||||
|
for dt in bars.keys():
|
||||||
|
spread_price = 0
|
||||||
|
spread_available = True
|
||||||
|
|
||||||
|
for leg in spread.legs.values():
|
||||||
|
leg_bar = leg_bars[leg.vt_symbol].get(dt, None)
|
||||||
|
|
||||||
|
if leg_bar:
|
||||||
|
price_multiplier = spread.price_multipliers[leg.vt_symbol]
|
||||||
|
spread_price += price_multiplier * leg_bar.close_price
|
||||||
|
else:
|
||||||
|
spread_available = False
|
||||||
|
|
||||||
|
if spread_available:
|
||||||
|
spread_price = round_to(spread_price, pricetick)
|
||||||
|
|
||||||
|
spread_bar = BarData(
|
||||||
|
symbol=spread.name,
|
||||||
|
exchange=exchange.LOCAL,
|
||||||
|
datetime=dt,
|
||||||
|
interval=interval,
|
||||||
|
open_price=spread_price,
|
||||||
|
high_price=spread_price,
|
||||||
|
low_price=spread_price,
|
||||||
|
close_price=spread_price,
|
||||||
|
gateway_name=BacktestingEngine.gateway_name,
|
||||||
|
)
|
||||||
|
spread_bars.append(spread_bar)
|
||||||
|
|
||||||
|
return spread_bars
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=999)
|
@lru_cache(maxsize=999)
|
||||||
def load_tick_data(
|
def load_tick_data(
|
||||||
symbol: str,
|
spread: SpreadData,
|
||||||
exchange: Exchange,
|
|
||||||
start: datetime,
|
start: datetime,
|
||||||
end: datetime
|
end: datetime
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
return database_manager.load_tick_data(
|
return database_manager.load_tick_data(
|
||||||
symbol, exchange, start, end
|
spread.name, Exchange.LOCAL, start, end
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from vnpy.trader.object import TickData, PositionData, TradeData, ContractData
|
from vnpy.trader.object import TickData, PositionData, TradeData, ContractData
|
||||||
from vnpy.trader.constant import Direction, Offset, Exchange
|
from vnpy.trader.constant import Direction, Offset, Exchange
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set, Callable
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
from vnpy.trader.object import TickData, TradeData, OrderData, ContractData
|
from vnpy.trader.object import (
|
||||||
from vnpy.trader.constant import Direction, Status, Offset
|
TickData, TradeData, OrderData, ContractData, BarData
|
||||||
|
)
|
||||||
|
from vnpy.trader.constant import Direction, Status, Offset, Interval
|
||||||
from vnpy.trader.utility import virtual, floor_to, ceil_to, round_to
|
from vnpy.trader.utility import virtual, floor_to, ceil_to, round_to
|
||||||
|
|
||||||
from .base import SpreadData, calculate_inverse_volume
|
from .base import SpreadData, calculate_inverse_volume
|
||||||
@ -434,6 +436,20 @@ class SpreadStrategyTemplate:
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@virtual
|
||||||
|
def on_spread_tick(self, tick: TickData):
|
||||||
|
"""
|
||||||
|
Callback when new spread tick data is generated.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@virtual
|
||||||
|
def on_spread_bar(self, bar: BarData):
|
||||||
|
"""
|
||||||
|
Callback when new spread bar data is generated.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@virtual
|
@virtual
|
||||||
def on_spread_pos(self):
|
def on_spread_pos(self):
|
||||||
"""
|
"""
|
||||||
@ -635,3 +651,23 @@ class SpreadStrategyTemplate:
|
|||||||
"""
|
"""
|
||||||
if self.inited:
|
if self.inited:
|
||||||
self.strategy_engine.send_email(msg, self)
|
self.strategy_engine.send_email(msg, self)
|
||||||
|
|
||||||
|
def load_bar(
|
||||||
|
self,
|
||||||
|
days: int,
|
||||||
|
interval: Interval = Interval.MINUTE,
|
||||||
|
callback: Callable = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Load historical bar data for initializing strategy.
|
||||||
|
"""
|
||||||
|
if not callback:
|
||||||
|
callback = self.on_spread_bar
|
||||||
|
|
||||||
|
self.strategy_engine.load_bar(self.spread, days, interval, callback)
|
||||||
|
|
||||||
|
def load_tick(self, days: int):
|
||||||
|
"""
|
||||||
|
Load historical tick data for initializing strategy.
|
||||||
|
"""
|
||||||
|
self.strategy_engine.load_tick(self.spread, days, self.on_spread_tick)
|
||||||
|
Loading…
Reference in New Issue
Block a user