vntrader 自带的移仓助手相当不错,不过细节少了些。
我结合实际使用,使用向导界面,在丰富了一些细节,在原理代码上做了一个新版移仓助手。
第一页界面
输入移仓合约后,跳出需要移仓的策略。
这里可以通过打勾选择需要移仓的策略,如果策略不用移仓可以取消。这里增加了一些错误判断,比如目标合约不能是带移仓合约。
第二页界面
下一步后进入第二页界面,上面是统计策略持仓和账户实际持仓;如果不一致,则会报错;当然如果上一页某些策略取消,则可能不一致,可以改成warning message。
另外这里代码是按照净仓模式来处理,如果是按照默认模式,需要改代码。
下半部页面是移仓发单操作;因为有可能移仓手数太多,这个相当一个小型算法交易,定义了每笔手数,如果是10,对于25就是拆分成三笔。
下面是超价,如果想使用市价单,把超价改为-1即可。
最后是每笔成交后,等待时间,如果3秒,则等上笔交易完成3秒后再发下一笔。
第三页界面
上版部分是移仓指令的信息汇总,中间是启动移仓,下面是log报告
这里面改动的地方是,新仓位的开仓价是原来开仓价加上新合约开仓价和旧合约平仓价的差值。比如旧合约开仓价格50,现在平仓价格70,新合约开仓价格80,那么新合约的持仓价格是50+(80-70) = 60,包含了旧持仓20的盈利点位。
同时会再策略级别生成一个虚拟平仓单和虚拟开仓单,方便历史统计计算。
代码如下,因为改动东西比较多,仅作参考。
from datetime import datetime
from threading import Timer
from time import sleep
from typing import TYPE_CHECKING
# from vnpy.trader.ui import QtWidgets
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import (QMessageBox, QFrame,
QWizard, QWizardPage, QVBoxLayout, QGridLayout,
QLabel)
from vnpy.trader.constant import OrderType
from vnpy.trader.converter import OffsetConverter, PositionHolding
from vnpy.trader.database import database_manager
from vnpy.trader.engine import MainEngine
from vnpy.trader.event import (
EVENT_TRADE
)
from vnpy.trader.object import ContractData, OrderRequest, SubscribeRequest, TickData
from vnpy.trader.object import Direction, Offset
from vnpy.trader.utility import round_to
from ..base import StopOrder, StopOrderStatus
from ..engine import CtaEngine, APP_NAME
from ..template import CtaTemplate
if TYPE_CHECKING:
from .widget import CtaManager
class WizardPage1(QWizardPage):
def __init__(self, parent=None):
super(WizardPage1, self).__init__(parent)
self.parent = parent
self.cta_manager: "CtaManager" = parent.cta_manager
self.cta_engine: CtaEngine = parent.cta_manager.cta_engine
self.main_engine: MainEngine = parent.cta_manager.main_engine
self.init_ui()
def init_ui(self):
""""""
self.setTitle("选择移仓合约和策略")
old_symbols = []
for vt_symbol, strategies in self.cta_engine.symbol_strategy_map.items():
if strategies:
old_symbols.append(vt_symbol)
self.old_symbol_combo = QtWidgets.QComboBox()
self.old_symbol_combo.addItems(old_symbols)
self.new_symbol_line = QtWidgets.QLineEdit()
self.message_line = QtWidgets.QLabel()
self.strategy_table = StrategyMonitor()
self.strategy_table.setMinimumWidth(500)
button = QtWidgets.QPushButton("待移仓策略")
button.clicked.connect(self.display_traget_strategies)
button.setFixedHeight(button.sizeHint().height() * 2)
form = QtWidgets.QFormLayout()
form.addRow("移仓合约", self.old_symbol_combo)
form.addRow("目标合约", self.new_symbol_line)
form.addRow(self.message_line)
form.addRow(button)
hbox = QtWidgets.QHBoxLayout()
hbox.addLayout(form)
hbox.addWidget(self.strategy_table)
self.setLayout(hbox)
def validatePage(self):
self.strategy_table.actived_strategyies()
if self.strategy_table.rollover_strategies_name:
# (lambda x: x * x, [y for y in range(10)])
self.parent.rollover_strategies = [self.cta_engine.strategies[x] for x in
self.strategy_table.rollover_strategies_name]
self.parent.old_symbol = self.old_symbol_combo.currentText()
if not self.new_symbol_line.text():
QMessageBox.warning(self, '信息', '移仓目标合约为空')
return False
elif self.parent.old_symbol == self.new_symbol_line.text():
QMessageBox.warning(self, '信息', '移仓目标重复')
return False
new_symbol = self.new_symbol_line.text()
# 确认是否有这个合约
self.parent.subscribe(new_symbol)
sleep(1)
new_tick = self.main_engine.get_tick(new_symbol)
if not new_tick:
# self.setTitle(f"无法获取目标合约{new_symbol}的盘口数据,请先订阅行情")
QMessageBox.warning(self, '信息', f"无法获取目标合约{new_symbol}的盘口数据,请先订阅行情")
return False
contract = self.main_engine.get_contract(new_symbol)
self.parent.priceTick = contract.pricetick
self.parent.new_symbol = new_symbol
return True
else:
# self.setTitle("没有策略被选中")
QMessageBox.warning(self, '信息', f"没有策略被选中")
return False
def display_traget_strategies(self):
selected_vt_symbol = self.old_symbol_combo.currentText()
strategies = self.cta_engine.symbol_strategy_map[selected_vt_symbol]
self.strategy_table.setRowCount(0)
self.strategy_table.update_data(strategies)
class StrategyMonitor(QtWidgets.QTableWidget):
"""
Table monitor for parameters and variables.
"""
def __init__(self):
""""""
super(StrategyMonitor, self).__init__()
self.rollover_strategies_name = []
self.init_ui()
def init_ui(self):
""""""
labels = list(["选择", "策略名称", "持仓数"])
self.setColumnCount(len(labels))
self.setHorizontalHeaderLabels(labels)
# self.setMaximumWidth(100)
self.verticalHeader().setVisible(False)
# self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
self.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
def update_data(self, strategies):
if strategies:
for iterable, strategy in enumerate(strategies):
self.insertRow(iterable)
item_checked = QtWidgets.QTableWidgetItem()
item_checked.setCheckState(Qt.Checked)
item_checked.checkState()
self.setItem(iterable, 0, item_checked)
self.setItem(iterable, 1, QtWidgets.QTableWidgetItem(strategy.strategy_name))
self.setItem(iterable, 2, QtWidgets.QTableWidgetItem(str(strategy.pos)))
def actived_strategyies(self):
""""""
rowCount = self.rowCount()
if rowCount > 0:
self.rollover_strategies_name = []
for rowId in range(rowCount):
if self.item(rowId, 0).checkState():
# strategy = self.parent.cta_engine.strategies[self.item(rowId,1)]
self.rollover_strategies_name.append(self.item(rowId, 1).text())
return self.rollover_strategies_name
class WizardPage2(QWizardPage):
def __init__(self, parent=None):
super(WizardPage2, self).__init__(parent)
self.parent = parent
self.strategy_positive_pos = 0
self.strategy_negative_pos = 0
self.strategy_net_pos = 0
self.account_positive_pos = 0
self.account_negative_pos = 0
self.account_net_pos = 0
self._conclusion_list = {
0: "持仓待更新",
1: "策略净持仓和账户合约净持仓相等, 账户单向持仓,净仓模式",
2: "策略净持仓和账户合约净持仓相等, 账户双向持仓",
3: "策略净持仓和账户合约净持仓存在差异"
}
self.conclusion = 0
self.init_ui()
def init_ui(self):
""""""
self.setTitle('移仓数量和账户持仓数据')
AllvLayout = QVBoxLayout()
# 中间窗口部分显示的内容
vLayout = QGridLayout()
self.strategy_info = QLabel()
self.strategy_positive_info = QLabel()
self.strategy_negative_info = QLabel()
self.strategy_net_info = QLabel()
vLayout.addWidget(self.strategy_info, 0, 0)
vLayout.addWidget(self.strategy_positive_info, 1, 0)
vLayout.addWidget(self.strategy_negative_info, 2, 0)
vLayout.addWidget(self.strategy_net_info, 3, 0)
frame = QFrame() #
frame.setFrameStyle(QFrame.Box)
self.accnout_info = QLabel()
self.accnout_positive_info = QLabel()
self.accnout_negative_info = QLabel()
self.accnout_net_info = QLabel()
vLayout.addWidget(self.accnout_info, 0, 1)
vLayout.addWidget(self.accnout_positive_info, 1, 1)
vLayout.addWidget(self.accnout_negative_info, 2, 1)
vLayout.addWidget(self.accnout_net_info, 3, 1)
frame.setLayout(vLayout)
self.conclusion_info = QLabel()
self.conclusion_info.setFont(QFont(self.conclusion_info.font().family(), 14))
self.conclusion_info.setAlignment(Qt.AlignCenter)
frame2 = QFrame()
form = QtWidgets.QFormLayout()
self.max_order_count_spin = QtWidgets.QSpinBox()
self.max_order_count_spin.setRange(1,5000)
self.max_order_count_spin.setValue(100)
self.wait_time_spin = QtWidgets.QSpinBox()
self.wait_time_spin.setRange(0, 10000)
self.payup_spin = QtWidgets.QSpinBox()
self.payup_spin.setRange(-1,50000)
self.payup_spin.setValue(2)
self.wait_time_spin.setValue(3)
form.addRow("交易单笔限额 ", self.max_order_count_spin)
form.addRow("移仓发单超价", self.payup_spin)
form.addRow(QLabel("超价设为 -1 时,按市价单发单"))
form.addRow("单笔成交后等待(秒)", self.wait_time_spin)
frame2.setLayout(form)
AllvLayout.addWidget(frame)
AllvLayout.addWidget(self.conclusion_info)
AllvLayout.addWidget(frame2)
self.setLayout(AllvLayout)
self.setButtonText(QWizard.NextButton, '确定移仓')
def initializePage(self):
self.strategy_positive_pos = 0
self.strategy_negative_pos = 0
for strategy in self.parent.rollover_strategies:
if strategy.pos > 0:
self.strategy_positive_pos += strategy.pos
elif strategy.pos < 0:
self.strategy_negative_pos += strategy.pos
self.strategy_net_pos = self.strategy_positive_pos + self.strategy_negative_pos
self.strategy_info.setText(f"选取 {self.parent.old_symbol} 策略共: {len(self.parent.rollover_strategies)} 个")
self.strategy_positive_info.setText(f"策略多仓共: {self.strategy_positive_pos}")
self.strategy_negative_info.setText(f"策略空仓共: {abs(self.strategy_negative_pos)}")
self.strategy_net_info.setText(f"策略净持仓共: {self.strategy_net_pos}")
current_holding = self.parent.current_position()
self.account_positive_pos = current_holding.long_pos
self.account_negative_pos = -current_holding.short_pos
self.account_net_pos = self.account_positive_pos + self.account_negative_pos
self.accnout_info.setText(f"账户当前合约: {self.parent.old_symbol}")
self.accnout_positive_info.setText(f"账户多仓共: {self.account_positive_pos}")
self.accnout_negative_info.setText(f"账户空仓共: {abs(self.account_negative_pos)}")
self.accnout_net_info.setText(f"账户净持仓共: {self.account_net_pos}")
if self.account_net_pos == self.strategy_net_pos and (
self.account_negative_pos == 0 or self.account_positive_pos == 0):
self.conclusion = 1
self.parent.move_position = self.strategy_net_pos
elif self.account_net_pos == self.strategy_net_pos and self.account_negative_pos != 0 and self.account_positive_pos != 0:
self.conclusion = 2
elif self.account_net_pos != self.strategy_net_pos:
self.conclusion = 3
self.conclusion_info.setText(self._conclusion_list[self.conclusion])
def validatePage(self):
if self.conclusion == 1:
self.parent.ROLL_OVER_MAX = self.max_order_count_spin.value()
self.parent.trade_wait_time = self.wait_time_spin.value()
self.parent.payup = self.payup_spin.value()
return True
else:
QMessageBox.warning(self, '信息', self._conclusion_list[self.conclusion])
return False
class WizardPage3(QWizardPage):
def __init__(self, parent=None):
super(WizardPage3, self).__init__(parent)
self.parent = parent
self.init_ui()
def init_ui(self):
self.setTitle("移仓交易")
self.setMinimumHeight(600)
vboxLayout = QtWidgets.QVBoxLayout()
self.old_symbol_info = QLabel()
self.new_symbol_info = QLabel()
self.move_pistion = QLabel()
self.move_strategy = QLabel()
self.roll_over_info = QLabel()
form = QtWidgets.QFormLayout()
form.addRow("移仓合约 ", self.old_symbol_info)
form.addRow("目标合约 ", self.new_symbol_info)
form.addRow("委托移仓数量 ", self.move_pistion)
form.addRow("更新策略 ", self.move_strategy)
form.addRow("交易说明 ", self.roll_over_info)
button = QtWidgets.QPushButton("移仓启动")
button.clicked.connect(self.parent.roll_all)
button.setFixedHeight(button.sizeHint().height() * 2)
self.log_edit = QtWidgets.QTextEdit()
self.log_edit.setReadOnly(True)
self.log_edit.setMinimumWidth(500)
self.log_edit.setMinimumHeight(200)
vboxLayout.addLayout(form)
vboxLayout.addWidget(button)
vboxLayout.addWidget(self.log_edit)
self.setLayout(vboxLayout)
def initializePage(self):
self.old_symbol_info.setText(self.parent.old_symbol)
self.new_symbol_info.setText(self.parent.new_symbol)
self.move_pistion.setText(str(self.parent.move_position))
textline = ""
for strategy in self.parent.rollover_strategies:
textline += f"策略: {strategy.strategy_name} 仓位: {strategy.pos}" + "\n"
self.move_strategy.setText(textline)
roundCount = abs(self.parent.move_position) // self.parent.ROLL_OVER_MAX
self.parent.request_split_list = [self.parent.ROLL_OVER_MAX for x in range(roundCount)]
lastAmount = abs(self.parent.move_position) - self.parent.ROLL_OVER_MAX * roundCount
if lastAmount != 0:
self.parent.request_split_list.append(abs(self.parent.move_position) - self.parent.ROLL_OVER_MAX * roundCount)
self.parent.send_count = len(self.parent.request_split_list)
sendMessage = "按照市价" if self.parent.payup < 0 else "市场价格加超价" + str(self.parent.payup)
if self.parent.send_count > 0:
self.roll_over_info.setText(f"移仓手数: {self.parent.move_position}; 发单限额: {self.parent.ROLL_OVER_MAX} \n" +
f"交易将会分: {self.parent.send_count} 次发出; 每次笔数为 {self.parent.request_split_list} \n"+
sendMessage + f"发单, 每个交易完成后等待{self.parent.trade_wait_time}秒\n")
elif self.parent.move_position !=0:
self.roll_over_info.setText(f"交易将会单次发出; 按当前"
+ sendMessage + f"发单")
elif self.parent.move_position ==0:
self.roll_over_info.setText(f"无需交易,仅作策略合约切换")
class RolloverTool(QWizard):
""""""
log_signal: QtCore.pyqtSignal = QtCore.pyqtSignal(str)
# trade_signal: QtCore.pyqtSignal = QtCore.pyqtSignal(Event)
def __init__(self, cta_manager: "CtaManager") -> None:
""""""
super().__init__()
self.cta_manager: "CtaManager" = cta_manager
self.event_engine = cta_manager.event_engine
self.cta_engine: CtaEngine = cta_manager.cta_engine
self.main_engine: MainEngine = cta_manager.main_engine
self.rollover_strategies = []
self.new_strategies = []
self.old_symbol = ""
self.new_symbol = ""
self.move_position = 0
self.ROLL_OVER_MAX = 100
self.send_count = 0
self.request_split_list = []
self.request_count = 0
self.payup = 0
self.trade_wait_time = 3
self.deal_volume = 0
self.priceTick = 0
self.old_completed_traders = 0
self.new_completed_traders = 0
self.new_vt_orderids = []
self.old_vt_orderids = []
self.new_vt_traders = []
self.old_vt_traders = []
self.total_new_vt_traders = []
self.total_old_vt_traders = []
self.page1 = WizardPage1(self)
self.page2 = WizardPage2(self)
self.page3 = WizardPage3(self)
self.register_event()
self.log_signal.connect(self.log_txt_append)
self.init_ui()
def register_event(self):
""""""
# self.trade_signal.connect(self.process_trade_event)
self.event_engine.register(EVENT_TRADE, self.process_trade_event)
def process_trade_event(self, event):
trade = event.data
if trade.vt_orderid in self.old_vt_orderids:
self.old_vt_traders.append((trade.volume,trade.price))
self.old_completed_traders +=trade.volume
elif trade.vt_orderid in self.new_vt_orderids:
self.new_vt_traders.append((trade.volume,trade.price))
self.new_completed_traders +=trade.volume
else:
return
if self.new_completed_traders == 0 or self.old_completed_traders == 0:
return
if self.send_count == 0 and self.old_completed_traders == self.deal_volume and self.new_completed_traders == self.deal_volume:
self.update_strategies(self.old_vt_traders,self.new_vt_traders)
elif self.send_count != 0 and self.old_completed_traders == self.deal_volume and self.new_completed_traders == self.deal_volume:
self.total_old_vt_traders.extend(self.old_vt_traders)
self.total_new_vt_traders.extend(self.new_vt_traders)
self.write_log(f"第({self.request_count+1})笔移仓交易完成,移仓数量:{self.deal_volume}")
if self.request_count == self.send_count:
self.update_strategies(self.total_old_vt_traders,self.total_new_vt_traders)
return
# sleep(self.trade_wait_time)
wait_Timer = Timer(self.trade_wait_time,self.next_trade_request)
self.write_log(f"等待{self.trade_wait_time}秒后启动下一个笔交易")
wait_Timer.start()
def next_trade_request(self):
self.old_vt_traders = []
self.new_vt_traders = []
self.old_completed_traders = 0
self.new_completed_traders = 0
self.deal_volume = self.request_split_list[self.request_count]
self.roll_position(self.old_symbol, self.new_symbol, self.payup)
self.request_count += 1
def init_ui(self):
self.setPage(0, self.page1)
self.setPage(1, self.page2)
self.setPage(2, self.page3)
# 去掉帮助按钮
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
# 窗口最小化
self.setWindowFlags(Qt.WindowMinimizeButtonHint|Qt.WindowCloseButtonHint)
# 设置导航样式
self.setWizardStyle(QWizard.ModernStyle)
# 设置导航窗口标题
self.setWindowTitle("移仓助手")
# 去掉页面的一些按钮
self.setOption(QWizard.NoBackButtonOnStartPage) # 首页没有回退按钮
# self.setOption(QWizard.NoBackButtonOnLastPage) # 最后一页没有回退按钮
self.setOption(QWizard.NoCancelButton) # 没有取消按钮
# 设置按钮的显示名称
self.setButtonText(QWizard.NextButton, '下一步')
self.setButtonText(QWizard.BackButton, '上一步')
self.setButtonText(QWizard.FinishButton, '完成')
def write_log(self, text: str) -> None:
""""""
now = datetime.now()
text = now.strftime("%H:%M:%S\t") + text
self.log_signal.emit(text)
self.main_engine.write_log(text)
def log_txt_append(self,text):
self.page3.log_edit.append(text)
self.page3.log_edit.moveCursor(QtGui.QTextCursor.End)
def subscribe(self, vt_symbol: str) -> None:
""""""
contract = self.main_engine.get_contract(vt_symbol)
if not contract:
return
req = SubscribeRequest(contract.symbol, contract.exchange)
self.main_engine.subscribe(req, contract.gateway_name)
def roll_all(self) -> None:
""""""
# Check all strategies inited (pos data loaded from disk json file)
for strategy in self.rollover_strategies:
if not strategy.inited:
self.write_log(f"策略{strategy.strategy_name}尚未初始化,无法执行移仓")
return
if strategy.trading:
self.write_log(f"策略{strategy.strategy_name}是交易状态,将停止交易")
self.cta_engine.stop_strategy(strategy.strategy_name)
self.write_log(f"策略{strategy.strategy_name}已经停止交易")
# self.setOption(QWizard.NoBackButtonOnLastPage)
# self.setEnabled(False)
# Roll position first
if self.send_count == 0:
self.deal_volume = abs(self.move_position)
self.roll_position(self.old_symbol, self.new_symbol, self.payup)
else:
self.deal_volume = self.request_split_list[self.request_count]
self.roll_position(self.old_symbol, self.new_symbol, self.payup)
self.request_count +=1
# Disable self
def rollover_price_diff(self,old_tuplelist, new_tuplelist):
old_average_price = self.calculte_average(old_tuplelist)
self.write_log(f"旧合约平仓均价:{round_to(old_average_price,self.priceTick)}")
new_average_price = self.calculte_average(new_tuplelist)
self.write_log(f"目标合约开仓均价:{round_to(new_average_price,self.priceTick)}")
price_diff = new_average_price - old_average_price
self.write_log(f"新旧移仓差价:{price_diff}")
return price_diff
def calculte_average(self, tuplelist):
total_volume= 0
total_price = 0
for tupleOne in tuplelist:
total_volume += tupleOne[0]
total_price += tupleOne[0]*tupleOne[1]
return total_price/total_volume
def current_position(self) -> PositionHolding:
""""""
converter = self.cta_engine.offset_converter
holding: PositionHolding = converter.get_position_holding(self.old_symbol)
return holding
def roll_position(self, old_symbol: str, new_symbol: str, payup: int) -> None:
""""""
converter = self.cta_engine.offset_converter
holding: PositionHolding = converter.get_position_holding(old_symbol)
if self.deal_volume == 0:
self.write_log(f"无需交易,移仓数量:{self.deal_volume}")
self.update_strategies(self.old_vt_traders,self.new_vt_traders)
return
if holding.long_pos:
# if holding.long_pos != self.move_position:
# QMessageBox.warning(self, '信息', f"账户持仓数量改变,请退回")
# return
self.old_vt_orderids = self.send_order(
old_symbol,
Direction.SHORT,
Offset.CLOSE,
payup,
self.deal_volume
)
self.new_vt_orderids = self.send_order(
new_symbol,
Direction.LONG,
Offset.OPEN,
payup,
self.deal_volume
)
# Roll short postiion
if holding.short_pos:
# if holding.short_pos != self.move_position:
# QMessageBox.warning(self, '信息', f"账户持仓数量改变,请退回")
# return
self.old_vt_orderids = self.send_order(
old_symbol,
Direction.LONG,
Offset.CLOSE,
payup,
self.deal_volume
)
self.new_vt_orderids = self.send_order(
new_symbol,
Direction.SHORT,
Offset.OPEN,
payup,
self.deal_volume
)
self.write_log(f"第({self.request_count+1})笔移仓交易请求发出,移仓数量:{self.deal_volume}")
def update_strategies(self,old_vt_traders,new_vt_traders):
"""when deal complete, update_strategies"""
self.write_log(f"===========持仓更新完成,开始更新策略, 请完成后再关闭窗口============")
if old_vt_traders and new_vt_traders:
price_diff = self.rollover_price_diff(old_vt_traders,new_vt_traders)
else:
old_tick: TickData = self.main_engine.get_tick(self.old_symbol)
new_tick: TickData = self.main_engine.get_tick(self.new_symbol)
price_diff = new_tick.last_price - old_tick.last_price
# Then roll strategy
for strategy in self.rollover_strategies:
self.roll_strategy(strategy, self.new_symbol, price_diff)
check_Timer = Timer(5, self.check_update_strategies)
check_Timer.start()
def check_update_strategies(self):
if self.new_strategies:
for new_strategy in self.new_strategies:
if new_strategy.inited:
self.cta_engine.start_strategy(new_strategy.strategy_name)
self.write_log(f"更新策略 [{new_strategy.strategy_name}] 初始化完成,启动完成")
self.new_strategies.remove(new_strategy)
check_Timer = Timer(5,self.check_update_strategies)
check_Timer.start()
else:
self.write_log(f"==========={self.old_symbol} -> {self.new_symbol} 移仓完成 请关闭窗口 ============")
self.setEnabled(True)
def roll_strategy(self, strategy: CtaTemplate, vt_symbol: str, price_diff) -> None:
""""""
if not strategy.inited:
self.cta_engine._init_strategy(strategy.strategy_name)
# Save data of old strategy
pos = strategy.pos
name = strategy.strategy_name
vt_local = strategy.vt_local
parameters = strategy.get_parameters()
if pos !=0:
new_price = round_to(strategy.PosPrice + price_diff, self.priceTick)
else:
new_price = 0
# Remove old strategy
result = self.cta_engine.remove_strategy(name)
if result:
self.cta_manager.remove_strategy(name)
self.write_log(f"移除老策略 [{name}] [{strategy.vt_symbol}],仓位:{strategy.pos}, 原价格:{strategy.PosPrice}")
# Add new strategy
if ("init_pos" in parameters) and ("init_entry_price" in parameters):
parameters["init_entry_price"] = new_price
# Add new strategy
self.cta_engine.add_strategy(
strategy.__class__.__name__,
name,
vt_symbol,
vt_local,
parameters
)
# Init new strategy
self.cta_engine.init_strategy(name)
# Update pos to new strategy
new_strategy: CtaTemplate = self.cta_engine.strategies[name]
new_strategy.pos = pos
new_strategy.PosPrice = new_price
self.cta_engine.sync_strategy_data(new_strategy)
self.cta_engine.put_strategy_event(new_strategy)
self.new_strategies.append(new_strategy)
self.write_log(f"更新策略 [{name}] [{vt_symbol}]完成,仓位:{new_strategy.pos}, 价格:{new_price}")
# Save close and open dummy deal to database
if strategy.pos == 0:
return
elif strategy.pos > 0:
close_old_direction = Direction.SHORT
open_new_direction = Direction.LONG
elif strategy.pos < 0:
close_old_direction = Direction.LONG
open_new_direction = Direction.SHORT
dummy_close_old_strategy_order = StopOrder(
vt_symbol= strategy.vt_symbol,
direction = close_old_direction,
offset = Offset.CLOSE,
price = strategy.PosPrice,
volume = pos,
stop_orderid = f"{name}_{strategy.vt_symbol}_{vt_symbol}_Rollover",
strategy_name = name,
datetime = datetime.now(),
lock = False,
net = False,
vt_orderids = [],
status = StopOrderStatus.TRIGGERED
)
dummy_close_old_strategy_order.completed_volume = pos
dummy_close_old_strategy_order.average_price = strategy.PosPrice
dummy_close_old_strategy_order.first_price = strategy.PosPrice
dummy_close_old_strategy_order.triggered_price = strategy.PosPrice
database_manager.save_triggered_stop_order_data(dummy_close_old_strategy_order)
dummy_open_new_strategy_order = StopOrder(
vt_symbol= vt_symbol,
direction = open_new_direction,
offset = Offset.OPEN,
price = new_price,
volume = pos,
stop_orderid = f"{name}_{strategy.vt_symbol}_{vt_symbol}_Rollover",
strategy_name = name,
datetime = datetime.now(),
lock = False,
net = False,
vt_orderids = [],
status = StopOrderStatus.TRIGGERED
)
dummy_open_new_strategy_order.completed_volume = pos
dummy_open_new_strategy_order.average_price = new_price
dummy_open_new_strategy_order.first_price = new_price
dummy_open_new_strategy_order.triggered_price = new_price
database_manager.save_triggered_stop_order_data(dummy_open_new_strategy_order)
self.write_log(f"虚拟平仓单已经录入数据库")
def send_order(
self,
vt_symbol: str,
direction: Direction,
offset: Offset,
payup: int,
volume: float,
):
"""
Send a new order to server.
"""
contract: ContractData = self.main_engine.get_contract(vt_symbol)
tick: TickData = self.main_engine.get_tick(vt_symbol)
offset_converter: OffsetConverter = self.cta_engine.offset_converter
if self.payup >= 0:
if direction == Direction.LONG:
price = tick.ask_price_1 + contract.pricetick * payup
else:
price = tick.bid_price_1 - contract.pricetick * payup
else:
if direction == Direction.LONG:
if tick.limit_up:
price = tick.limit_up
else:
price = tick.ask_price_5
else:
if tick.limit_down:
price = tick.limit_down
else:
price = tick.bid_price_5
original_req: OrderRequest = OrderRequest(
symbol=contract.symbol,
exchange=contract.exchange,
direction=direction,
offset=offset,
type=OrderType.LIMIT,
price=price,
volume=volume,
reference=f"{APP_NAME}_Rollover"
)
req_list = offset_converter.convert_order_request(original_req, False, False)
vt_orderids = []
for req in req_list:
vt_orderid = self.main_engine.send_order(req, contract.gateway_name)
if not vt_orderid:
continue
vt_orderids.append(vt_orderid)
offset_converter.update_order_request(req, vt_orderid)
msg = f"发出委托{vt_symbol},{direction.value} {offset.value},{volume}@{price}"
self.write_log(msg)
return vt_orderids