You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1332 lines
45 KiB
1332 lines
45 KiB
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
from abc import ABC, ABCMeta
|
|
from typing import Any, Callable
|
|
from copy import copy
|
|
from ..common.constant import (
|
|
Interval,
|
|
OrderFlag, OrderType, Offset, Direction
|
|
)
|
|
from ..common.datastruct import (
|
|
TickData, BarData,
|
|
OrderRequest, OrderData,
|
|
CtpOrderField, PaperOrderField
|
|
)
|
|
from ..common.sqglobal import dotdict
|
|
from ..common.utility import extract_full_symbol, virtual
|
|
from ..api.ctp_constant import (
|
|
THOST_FTDC_D_Buy,
|
|
THOST_FTDC_D_Sell,
|
|
THOST_FTDC_OPT_LimitPrice,
|
|
THOST_FTDC_OPT_AnyPrice,
|
|
THOST_FTDC_OF_Open,
|
|
THOST_FTDC_OF_Close,
|
|
THOST_FTDC_HF_Speculation,
|
|
THOST_FTDC_CC_Immediately,
|
|
THOST_FTDC_TC_GFD,
|
|
THOST_FTDC_VC_AV,
|
|
THOST_FTDC_TC_IOC,
|
|
THOST_FTDC_VC_CV
|
|
)
|
|
|
|
|
|
class StrategyBase(metaclass=ABCMeta):
|
|
"""
|
|
Base strategy class
|
|
|
|
"""
|
|
# class id and name
|
|
ID = -1
|
|
NAME = "base"
|
|
|
|
# for rqquant use
|
|
context = dotdict()
|
|
|
|
# parameters and variables,for vnpy use
|
|
author = ""
|
|
account = ""
|
|
api = ""
|
|
autostart = False
|
|
parameters = ["api", "account"]
|
|
variables = []
|
|
|
|
def __init__(self,
|
|
strategy_engine: Any,
|
|
strategy_name: str,
|
|
full_symbol: str,
|
|
setting: dict):
|
|
"""
|
|
initialize trategy
|
|
:param
|
|
:variables
|
|
"""
|
|
self.full_symbol = full_symbol
|
|
sym, ex = extract_full_symbol(self.full_symbol)
|
|
self.symbol = sym
|
|
self.strategy_engine = strategy_engine
|
|
self.engine_id = self.strategy_engine.id
|
|
self.strategy_name = strategy_name
|
|
|
|
self.active = False
|
|
|
|
self.inited = False
|
|
self.trading = False
|
|
self.pos = 0
|
|
|
|
self.long_pos = 0
|
|
self.long_pos_frozen = 0
|
|
self.short_pos = 0
|
|
self.short_pos_frozen = 0
|
|
self.long_price = 0.0
|
|
self.short_price = 0.0
|
|
|
|
# Copy a new variables list here to avoid duplicate insert when multiple
|
|
# strategy instances are created with the same strategy class.
|
|
self.variables = copy(self.variables)
|
|
self.variables.insert(0, "inited")
|
|
self.variables.insert(1, "trading")
|
|
self.variables.insert(2, "pos")
|
|
|
|
self.update_setting(setting)
|
|
self.add_functions()
|
|
|
|
def add_functions(self):
|
|
self.get_position_holding = self.strategy_engine.get_position_holding
|
|
self.get_account = self.strategy_engine.get_account
|
|
self.get_order = self.strategy_engine.get_order
|
|
self.get_tick = self.strategy_engine.get_tick
|
|
self.get_trade = self.strategy_engine.get_trade
|
|
self.get_position = self.strategy_engine.get_position
|
|
self.get_contract = self.strategy_engine.get_contract
|
|
self.get_all_active_orders = self.strategy_engine.get_all_active_orders
|
|
|
|
def get_my_active_orderids(self):
|
|
oidset = self.strategy_engine.get_strategy_active_orderids(
|
|
self.strategy_name)
|
|
return oidset
|
|
|
|
def get_my_position_holding(self):
|
|
holding = self.get_position_holding(self.account, self.full_symbol)
|
|
self.long_pos = holding.long_pos
|
|
self.long_pos_frozen = holding.long_pos_frozen
|
|
self.short_pos = holding.short_pos
|
|
self.short_pos_frozen = holding.short_pos_frozen
|
|
self.long_price = holding.long_price
|
|
self.short_price = holding.short_price
|
|
|
|
def update_setting(self, setting: dict):
|
|
"""
|
|
Update strategy parameter wtih value in setting dict.
|
|
"""
|
|
for name in self.parameters:
|
|
if name in setting:
|
|
setattr(self, name, setting[name])
|
|
|
|
@classmethod
|
|
def get_class_parameters(cls):
|
|
"""
|
|
Get default parameters dict of strategy class.
|
|
"""
|
|
class_parameters = {}
|
|
for name in cls.parameters:
|
|
class_parameters[name] = getattr(cls, name)
|
|
return class_parameters
|
|
|
|
def get_parameters(self):
|
|
"""
|
|
Get strategy parameters dict.
|
|
"""
|
|
strategy_parameters = {}
|
|
for name in self.parameters:
|
|
strategy_parameters[name] = getattr(self, name)
|
|
return strategy_parameters
|
|
|
|
def get_variables(self):
|
|
"""
|
|
Get strategy variables dict.
|
|
"""
|
|
strategy_variables = {}
|
|
for name in self.variables:
|
|
strategy_variables[name] = getattr(self, name)
|
|
return strategy_variables
|
|
|
|
def get_data(self):
|
|
"""
|
|
Get strategy data.
|
|
"""
|
|
strategy_data = {
|
|
"engine_id": self.engine_id,
|
|
"strategy_name": self.strategy_name,
|
|
"full_symbol": self.full_symbol,
|
|
"class_name": self.__class__.__name__,
|
|
"author": self.author,
|
|
"parameters": self.get_parameters(),
|
|
"variables": self.get_variables(),
|
|
}
|
|
return strategy_data
|
|
|
|
@virtual
|
|
def on_init(self, params_dict=None):
|
|
pass
|
|
|
|
@virtual
|
|
def on_start(self):
|
|
self.active = True
|
|
|
|
@virtual
|
|
def on_stop(self):
|
|
self.active = False
|
|
|
|
@virtual
|
|
def on_reset(self):
|
|
pass
|
|
|
|
@virtual
|
|
def on_tick(self, tick):
|
|
"""
|
|
Respond to tick
|
|
"""
|
|
pass
|
|
|
|
@virtual
|
|
def on_bar(self, bar):
|
|
"""
|
|
Respond to bar
|
|
"""
|
|
pass
|
|
|
|
@virtual
|
|
def on_order_status(self, order):
|
|
"""
|
|
on order acknowledged
|
|
:return:
|
|
"""
|
|
#raise NotImplementedError("Should implement on_order()")
|
|
pass
|
|
|
|
@virtual
|
|
def on_order(self, order):
|
|
"""
|
|
on order
|
|
:return:
|
|
"""
|
|
self.on_order_status(order)
|
|
|
|
@virtual
|
|
def on_cancel(self, event):
|
|
"""
|
|
on order canceled
|
|
:return:
|
|
"""
|
|
pass
|
|
|
|
@virtual
|
|
def on_fill(self, trade):
|
|
"""
|
|
on order filled
|
|
:return:
|
|
"""
|
|
|
|
pass
|
|
|
|
def on_trade(self, trade):
|
|
self.on_fill(trade)
|
|
|
|
@virtual
|
|
def on_pos(self, position):
|
|
pass
|
|
|
|
@virtual
|
|
def on_acc(self, acc):
|
|
pass
|
|
|
|
@virtual
|
|
def on_contract(self, contract):
|
|
pass
|
|
|
|
@virtual
|
|
def on_info(self, info):
|
|
pass
|
|
|
|
@virtual
|
|
def on_req(self, req):
|
|
pass
|
|
|
|
@virtual
|
|
def on_headermsg(self, event):
|
|
pass
|
|
|
|
@virtual
|
|
def on_stop_order(self, stop_order):
|
|
"""
|
|
Callback of stop order update.
|
|
"""
|
|
pass
|
|
|
|
def cancel_order(self, oid):
|
|
if self.trading:
|
|
self.strategy_engine.cancel_order(self, oid)
|
|
|
|
def cancel_all(self):
|
|
"""
|
|
cancel all standing orders from this strategy
|
|
|
|
"""
|
|
if self.trading:
|
|
self.strategy_engine.cancel_all(self)
|
|
|
|
|
|
# wrapper function for easy use ,
|
|
# rqquant's use
|
|
|
|
|
|
def buy_open(self, price: float, size: int, type='lmt'):
|
|
if not self.trading:
|
|
return
|
|
if (type == 'mkt'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_AnyPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Buy,
|
|
CombOffsetFlag=THOST_FTDC_OF_Open,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_GFD,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.MKT,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.MKT,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.OPEN,
|
|
order_size=size
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.MKT,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'lmt'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Buy,
|
|
CombOffsetFlag=THOST_FTDC_OF_Open,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_GFD,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.LMT,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.LMT,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.OPEN,
|
|
order_size=size
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.LMT,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'fak'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Buy,
|
|
CombOffsetFlag=THOST_FTDC_OF_Open,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_IOC,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.FAK,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.FAK,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.OPEN,
|
|
order_size=size
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.FAK,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'fok'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Buy,
|
|
CombOffsetFlag=THOST_FTDC_OF_Open,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_IOC,
|
|
VolumeCondition=THOST_FTDC_VC_CV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.FOK,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.FOK,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.OPEN,
|
|
order_size=size
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.FOK,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
else:
|
|
print('order type not supported!')
|
|
|
|
def buy_close(self, price: float, size: int, type='lmt'):
|
|
if not self.trading:
|
|
return
|
|
if (type == 'mkt'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_AnyPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Buy,
|
|
CombOffsetFlag=THOST_FTDC_OF_Close,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_GFD,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.MKT,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.MKT,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.CLOSE,
|
|
order_size=size
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.MKT,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'lmt'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Buy,
|
|
CombOffsetFlag=THOST_FTDC_OF_Close,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_GFD,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.LMT,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.LMT,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.CLOSE,
|
|
order_size=size
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.LMT,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'fak'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Buy,
|
|
CombOffsetFlag=THOST_FTDC_OF_Close,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_IOC,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.FAK,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.FAK,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.CLOSE,
|
|
order_size=size
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.FAK,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'fok'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Buy,
|
|
CombOffsetFlag=THOST_FTDC_OF_Close,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_IOC,
|
|
VolumeCondition=THOST_FTDC_VC_CV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.FOK,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.FOK,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.CLOSE,
|
|
order_size=size
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.FOK,
|
|
direction=Direction.LONG,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
else:
|
|
print('order type not supported!')
|
|
|
|
def sell_open(self, price: float, size: int, type='lmt'):
|
|
if not self.trading:
|
|
return
|
|
if (type == 'mkt'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_AnyPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Sell,
|
|
CombOffsetFlag=THOST_FTDC_OF_Open,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_GFD,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.MKT,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.MKT,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.OPEN,
|
|
order_size=size * (-1)
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.MKT,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'lmt'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Sell,
|
|
CombOffsetFlag=THOST_FTDC_OF_Open,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_GFD,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.LMT,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.LMT,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.OPEN,
|
|
order_size=size * (-1)
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.LMT,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'fak'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Sell,
|
|
CombOffsetFlag=THOST_FTDC_OF_Open,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_IOC,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.FAK,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.FAK,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.OPEN,
|
|
order_size=size * (-1)
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.FAK,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'fok'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Sell,
|
|
CombOffsetFlag=THOST_FTDC_OF_Open,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_IOC,
|
|
VolumeCondition=THOST_FTDC_VC_CV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.FOK,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.FOK,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.OPEN,
|
|
order_size=size * (-1)
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.OPEN,
|
|
type=OrderType.FOK,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
else:
|
|
print('order type not supported!')
|
|
|
|
def sell_close(self, price: float, size: int, type='lmt'):
|
|
if not self.trading:
|
|
return
|
|
if (type == 'mkt'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_AnyPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Sell,
|
|
CombOffsetFlag=THOST_FTDC_OF_Close,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_GFD,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.MKT,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.MKT,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.CLOSE,
|
|
order_size=size * (-1)
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.MKT,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'lmt'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Sell,
|
|
CombOffsetFlag=THOST_FTDC_OF_Close,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_GFD,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.LMT,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.LMT,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.CLOSE,
|
|
order_size=size * (-1)
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.LMT,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'fak'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Sell,
|
|
CombOffsetFlag=THOST_FTDC_OF_Close,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_IOC,
|
|
VolumeCondition=THOST_FTDC_VC_AV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.FAK,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.FAK,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.CLOSE,
|
|
order_size=size * (-1)
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.FAK,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif (type == 'fok'):
|
|
if self.api == "CTP.TD":
|
|
of = CtpOrderField(
|
|
InstrumentID=self.symbol,
|
|
OrderPriceType=THOST_FTDC_OPT_LimitPrice,
|
|
LimitPrice=price,
|
|
Direction=THOST_FTDC_D_Sell,
|
|
CombOffsetFlag=THOST_FTDC_OF_Close,
|
|
CombHedgeFlag=THOST_FTDC_HF_Speculation,
|
|
VolumeTotalOriginal=size,
|
|
TimeCondition=THOST_FTDC_TC_IOC,
|
|
VolumeCondition=THOST_FTDC_VC_CV,
|
|
MinVolume=1,
|
|
ContingentCondition=THOST_FTDC_CC_Immediately
|
|
)
|
|
order = OrderRequest(
|
|
api="CTP.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.FOK,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
elif self.api == "PAPER.TD":
|
|
of = PaperOrderField(
|
|
order_type=OrderType.FOK,
|
|
limit_price=price,
|
|
full_symbol=self.full_symbol,
|
|
order_flag=OrderFlag.CLOSE,
|
|
order_size=size * (-1)
|
|
)
|
|
order = OrderRequest(
|
|
api="PAPER.TD",
|
|
account=self.account,
|
|
symbol=self.symbol,
|
|
full_symbol=self.full_symbol,
|
|
price=price,
|
|
volume=size,
|
|
offset=Offset.CLOSE,
|
|
type=OrderType.FOK,
|
|
direction=Direction.SHORT,
|
|
orderfield=of
|
|
)
|
|
self.strategy_engine.send_order(self, order)
|
|
else:
|
|
print('order type not supported!')
|
|
|
|
# vnpy's use
|
|
|
|
def buy(self, price: float, volume: float, stop: bool = False):
|
|
"""
|
|
Send buy order to open a long position.
|
|
|
|
"""
|
|
self.buy_open(price, volume)
|
|
|
|
pass
|
|
|
|
def sell(self, price: float, volume: float, stop: bool = False):
|
|
"""
|
|
Send sell order to close a long position.
|
|
"""
|
|
self.sell_close(price, volume)
|
|
pass
|
|
|
|
def short(self, price: float, volume: float, stop: bool = False):
|
|
"""
|
|
Send short order to open as short position.
|
|
"""
|
|
self.sell_open(price, volume)
|
|
pass
|
|
|
|
def cover(self, price: float, volume: float, stop: bool = False):
|
|
"""
|
|
Send cover order to close a short position.
|
|
"""
|
|
self.buy_close(price, volume)
|
|
pass
|
|
# end wrapper
|
|
|
|
def write_log(self, msg: str):
|
|
"""
|
|
Write a log message.
|
|
"""
|
|
# if self.inited:
|
|
# self.cta_engine.write_log(msg, self)
|
|
print(msg)
|
|
pass
|
|
|
|
def get_engine_type(self):
|
|
"""
|
|
Return whether the cta_engine is backtesting or live trading.
|
|
"""
|
|
return self.strategy_engine.engine_type
|
|
|
|
def load_bar(
|
|
self,
|
|
days: int,
|
|
interval: Interval = Interval.MINUTE,
|
|
callback: Callable = None,
|
|
datasource='DataBase'
|
|
):
|
|
"""
|
|
Load historical bar data for initializing strategy.
|
|
"""
|
|
if not callback:
|
|
callback = self.on_bar
|
|
self.strategy_engine.load_bar(
|
|
self.full_symbol, days, interval, callback, datasource)
|
|
|
|
def load_tick(self, days: int, datasource='DataBase'):
|
|
"""
|
|
Load historical tick data for initializing strategy.
|
|
"""
|
|
self.strategy_engine.load_tick(
|
|
self.full_symbol, days, self.on_tick, datasource)
|
|
pass
|
|
|
|
def put_event(self):
|
|
"""
|
|
Put an strategy data event for ui update.
|
|
"""
|
|
if self.inited:
|
|
self.strategy_engine.put_strategy_event(self)
|
|
|
|
def send_email(self, msg):
|
|
"""
|
|
Send email to default receiver.
|
|
"""
|
|
# if self.inited:
|
|
# self.cta_engine.send_email(msg, self)
|
|
pass
|
|
|
|
def sync_data(self):
|
|
"""
|
|
Sync strategy variables value into disk storage.
|
|
"""
|
|
if self.trading:
|
|
# print('begin syn data')
|
|
self.strategy_engine.sync_strategy_data(self)
|
|
pass
|
|
|
|
|
|
class CtaSignal(ABC):
|
|
""""""
|
|
|
|
def __init__(self):
|
|
""""""
|
|
self.signal_pos = 0
|
|
|
|
@virtual
|
|
def on_tick(self, tick: TickData):
|
|
"""
|
|
Callback of new tick data update.
|
|
"""
|
|
pass
|
|
|
|
@virtual
|
|
def on_bar(self, bar: BarData):
|
|
"""
|
|
Callback of new bar data update.
|
|
"""
|
|
pass
|
|
|
|
def set_signal_pos(self, pos):
|
|
""""""
|
|
self.signal_pos = pos
|
|
|
|
def get_signal_pos(self):
|
|
""""""
|
|
return self.signal_pos
|
|
|
|
|
|
CtaTemplate = StrategyBase
|
|
|
|
|
|
class TargetPosTemplate(CtaTemplate):
|
|
""""""
|
|
tick_add = 1
|
|
|
|
last_tick = None
|
|
last_bar = None
|
|
target_pos = 0
|
|
vt_orderids = []
|
|
|
|
def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
|
|
""""""
|
|
super().__init__(
|
|
cta_engine, strategy_name, vt_symbol, setting
|
|
)
|
|
self.variables.append("target_pos")
|
|
|
|
@virtual
|
|
def on_tick(self, tick: TickData):
|
|
"""
|
|
Callback of new tick data update.
|
|
"""
|
|
self.last_tick = tick
|
|
|
|
if self.trading:
|
|
self.trade()
|
|
|
|
@virtual
|
|
def on_bar(self, bar: BarData):
|
|
"""
|
|
Callback of new bar data update.
|
|
"""
|
|
self.last_bar = bar
|
|
|
|
@virtual
|
|
def on_order(self, order: OrderData):
|
|
"""
|
|
Callback of new order data update.
|
|
"""
|
|
vt_orderid = order.server_order_id
|
|
|
|
if not order.is_active() and vt_orderid in self.vt_orderids:
|
|
self.vt_orderids.remove(vt_orderid)
|
|
|
|
def set_target_pos(self, target_pos):
|
|
""""""
|
|
self.target_pos = target_pos
|
|
self.trade()
|
|
|
|
def trade(self):
|
|
""""""
|
|
self.cancel_all()
|
|
|
|
pos_change = self.target_pos - self.pos
|
|
if not pos_change:
|
|
return
|
|
|
|
long_price = 0
|
|
short_price = 0
|
|
|
|
if self.last_tick:
|
|
if pos_change > 0:
|
|
long_price = self.last_tick.ask_price_1 + self.tick_add
|
|
if self.last_tick.limit_up:
|
|
long_price = min(long_price, self.last_tick.limit_up)
|
|
else:
|
|
short_price = self.last_tick.bid_price_1 - self.tick_add
|
|
if self.last_tick.limit_down:
|
|
short_price = max(short_price, self.last_tick.limit_down)
|
|
|
|
else:
|
|
if pos_change > 0:
|
|
long_price = self.last_bar.close_price + self.tick_add
|
|
else:
|
|
short_price = self.last_bar.close_price - self.tick_add
|
|
|
|
# if self.get_engine_type() == EngineType.BACKTESTING:
|
|
# if pos_change > 0:
|
|
# vt_orderids = self.buy(long_price, abs(pos_change))
|
|
# else:
|
|
# vt_orderids = self.short(short_price, abs(pos_change))
|
|
# self.vt_orderids.extend(vt_orderids)
|
|
|
|
# else:
|
|
# if self.vt_orderids:
|
|
# return
|
|
|
|
# if pos_change > 0:
|
|
# if self.pos < 0:
|
|
# if pos_change < abs(self.pos):
|
|
# vt_orderids = self.cover(long_price, pos_change)
|
|
# else:
|
|
# vt_orderids = self.cover(long_price, abs(self.pos))
|
|
# else:
|
|
# vt_orderids = self.buy(long_price, abs(pos_change))
|
|
# else:
|
|
# if self.pos > 0:
|
|
# if abs(pos_change) < self.pos:
|
|
# vt_orderids = self.sell(short_price, abs(pos_change))
|
|
# else:
|
|
# vt_orderids = self.sell(short_price, abs(self.pos))
|
|
# else:
|
|
# vt_orderids = self.short(short_price, abs(pos_change))
|
|
# self.vt_orderids.extend(vt_orderids)
|
|
|
|
|
|
class Strategies(StrategyBase):
|
|
"""
|
|
Strategies is a collection of strategy
|
|
Usage e.g.: strategy = Strategies(strategyA, DisplayStrategy())
|
|
"""
|
|
|
|
def __init__(self, *strategies):
|
|
self._strategies_collection = strategies
|
|
|
|
def on_tick(self, event):
|
|
for strategy in self._strategies_collection:
|
|
strategy.on_tick(event)
|
|
|