[新增] 股票ETF期权CTA引擎
This commit is contained in:
parent
c2ed8bfba1
commit
7070140197
37
vnpy/app/cta_option/__init__.py
Normal file
37
vnpy/app/cta_option/__init__.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from vnpy.trader.app import BaseApp
|
||||||
|
from .base import APP_NAME
|
||||||
|
|
||||||
|
# 期权CTA策略引擎
|
||||||
|
from .engine import CtaOptionEngine
|
||||||
|
|
||||||
|
from .template import (
|
||||||
|
Direction,
|
||||||
|
Offset,
|
||||||
|
Exchange,
|
||||||
|
Status,
|
||||||
|
Color,
|
||||||
|
ContractData,
|
||||||
|
HistoryRequest,
|
||||||
|
TickData,
|
||||||
|
BarData,
|
||||||
|
TradeData,
|
||||||
|
OrderData,
|
||||||
|
CtaTemplate,
|
||||||
|
CtaOptionTemplate,
|
||||||
|
CtaOptionPolicy
|
||||||
|
) # noqa
|
||||||
|
from vnpy.trader.utility import BarGenerator, ArrayManager # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class CtaOptionApp(BaseApp):
|
||||||
|
"""期权引擎App"""
|
||||||
|
|
||||||
|
app_name = APP_NAME
|
||||||
|
app_module = __module__
|
||||||
|
app_path = Path(__file__).parent
|
||||||
|
display_name = "CTA期权策略"
|
||||||
|
engine_class = CtaOptionEngine
|
||||||
|
widget_name = "CtaOption"
|
||||||
|
icon_name = "cta.ico"
|
53
vnpy/app/cta_option/base.py
Normal file
53
vnpy/app/cta_option/base.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"""
|
||||||
|
Defines constants and objects used in CtaStrategyPro App.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum
|
||||||
|
from datetime import timedelta
|
||||||
|
from vnpy.trader.constant import Direction, Offset, Interval
|
||||||
|
|
||||||
|
APP_NAME = "CtaOption"
|
||||||
|
STOPORDER_PREFIX = "STOP"
|
||||||
|
|
||||||
|
|
||||||
|
class StopOrderStatus(Enum):
|
||||||
|
WAITING = "等待中"
|
||||||
|
CANCELLED = "已撤销"
|
||||||
|
TRIGGERED = "已触发"
|
||||||
|
|
||||||
|
|
||||||
|
class EngineType(Enum):
|
||||||
|
LIVE = "实盘"
|
||||||
|
BACKTESTING = "回测"
|
||||||
|
|
||||||
|
|
||||||
|
class BacktestingMode(Enum):
|
||||||
|
BAR = 1
|
||||||
|
TICK = 2
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StopOrder:
|
||||||
|
vt_symbol: str
|
||||||
|
direction: Direction
|
||||||
|
offset: Offset
|
||||||
|
price: float
|
||||||
|
volume: float
|
||||||
|
stop_orderid: str
|
||||||
|
strategy_name: str
|
||||||
|
lock: bool = False
|
||||||
|
vt_orderids: list = field(default_factory=list)
|
||||||
|
status: StopOrderStatus = StopOrderStatus.WAITING
|
||||||
|
gateway_name: str = None
|
||||||
|
|
||||||
|
|
||||||
|
EVENT_CTA_LOG = "eCtaLog"
|
||||||
|
EVENT_CTA_OPTION = "eCtaOption"
|
||||||
|
EVENT_CTA_STOPORDER = "eCtaStopOrder"
|
||||||
|
|
||||||
|
INTERVAL_DELTA_MAP = {
|
||||||
|
Interval.MINUTE: timedelta(minutes=1),
|
||||||
|
Interval.HOUR: timedelta(hours=1),
|
||||||
|
Interval.DAILY: timedelta(days=1),
|
||||||
|
}
|
2379
vnpy/app/cta_option/engine.py
Normal file
2379
vnpy/app/cta_option/engine.py
Normal file
File diff suppressed because it is too large
Load Diff
120
vnpy/app/cta_option/option_utility.py
Normal file
120
vnpy/app/cta_option/option_utility.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy import stats
|
||||||
|
|
||||||
|
#################
|
||||||
|
# BSM模型相关
|
||||||
|
def get_option_d(s, k, t, r, sigma, q):
|
||||||
|
d1 = (np.log(s/k) + (r - q + 0.5*sigma**2)*t)/(sigma*np.sqrt(t))
|
||||||
|
d2 = (np.log(s/k) + (r - q - 0.5*sigma**2)*t)/(sigma*np.sqrt(t))
|
||||||
|
return d1, d2
|
||||||
|
|
||||||
|
def get_option_greeks(cp, s, k, t, r, sigma, q):
|
||||||
|
"""
|
||||||
|
计算期权希腊值
|
||||||
|
:param cp:
|
||||||
|
:param s:
|
||||||
|
:param k:
|
||||||
|
:param t:
|
||||||
|
:param r:
|
||||||
|
:param sigma:
|
||||||
|
:param q:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
d1, d2 = get_option_d(s, k, t, r, sigma, q)
|
||||||
|
delta = cp * stats.norm.cdf(cp * d1)
|
||||||
|
gamma = stats.norm.pdf(d1) / (s * sigma * np.sqrt(t))
|
||||||
|
vega = (s * stats.norm.pdf(d1) * np.sqrt(t))
|
||||||
|
theta = (-1 * (s * stats.norm.pdf(d1) * sigma) / (2 * np.sqrt(t)) - cp * r * k * np.exp(-r * t) * stats.norm.cdf(cp * d2))
|
||||||
|
return delta, gamma, vega, theta
|
||||||
|
|
||||||
|
def bsm_value(cp, s, k, t, r, sigma, q):
|
||||||
|
d1, d2 = get_option_d(s, k, t, r, sigma, q)
|
||||||
|
if cp > 0:
|
||||||
|
value = (
|
||||||
|
s*np.exp(-q*t)*stats.norm.cdf(d1) -
|
||||||
|
k*np.exp(-r*t)*stats.norm.cdf(d2)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
value = (
|
||||||
|
k * np.exp(-r * t) * stats.norm.cdf(-d2) -
|
||||||
|
s*np.exp(-q*t) * stats.norm.cdf(-d1)
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
##############
|
||||||
|
|
||||||
|
# 二分法迭代计算隐波
|
||||||
|
def calculate_single_option_iv_by_bsm(
|
||||||
|
cp, s, k, c, t, r, q,
|
||||||
|
initial_iv=0.5, # 迭代起始值,如果上一个分钟有计算过隐波,这里把上一分钟的结果输入进来,有助于加快收敛
|
||||||
|
):
|
||||||
|
|
||||||
|
c_est = 0 # 期权价格估计值
|
||||||
|
top = 1 # 波动率上限
|
||||||
|
floor = 0 # 波动率下限
|
||||||
|
sigma = initial_iv # 波动率初始值
|
||||||
|
count = 0 # 计数器
|
||||||
|
best_result = 0
|
||||||
|
error = abs(c - c_est)
|
||||||
|
last_error = error
|
||||||
|
while error > 0.0001:
|
||||||
|
c_est = bsm_value(cp, s, k, t, r, sigma, q)
|
||||||
|
error = abs(c - c_est)
|
||||||
|
if error < last_error:
|
||||||
|
best_result = sigma
|
||||||
|
|
||||||
|
# 根据价格判断波动率是被低估还是高估,并对波动率做修正
|
||||||
|
count += 1
|
||||||
|
if count > 100: # 时间价值为0的期权是算不出隐含波动率的,因此迭代到一定次数就不再迭代了
|
||||||
|
sigma = 0
|
||||||
|
break
|
||||||
|
|
||||||
|
if c - c_est > 0: # f(x)>0
|
||||||
|
floor = sigma
|
||||||
|
sigma = (sigma + top)/2
|
||||||
|
else:
|
||||||
|
top = sigma
|
||||||
|
sigma = (sigma + floor)/2
|
||||||
|
return best_result
|
||||||
|
|
||||||
|
# 计算隐含分红率
|
||||||
|
# 我们目前不计算这个
|
||||||
|
def calculate_dividend_rate(
|
||||||
|
underlying_price, # 当前标的价格
|
||||||
|
call_price,
|
||||||
|
put_price,
|
||||||
|
rest_days, # 剩余时间
|
||||||
|
exercise_price, # 行权价
|
||||||
|
free_rate,
|
||||||
|
):
|
||||||
|
c = call_price
|
||||||
|
c_p = put_price
|
||||||
|
r = free_rate
|
||||||
|
t = rest_days / 360
|
||||||
|
k = exercise_price
|
||||||
|
s = underlying_price
|
||||||
|
q = -np.log((c+k*np.exp(-r*t)-c_p)/(s))/t
|
||||||
|
return q
|
||||||
|
|
||||||
|
# 计算隐波和Greeks
|
||||||
|
def calculate_single_option_greeks(
|
||||||
|
underlying_price, # 当前标的价格
|
||||||
|
option_price, # 期权价格
|
||||||
|
call_put, # 期权方向, CALL=1 PUT=-1
|
||||||
|
rest_days, # 剩余时间,按自然日计算,也可以用小数来表示不完整的日子
|
||||||
|
exercise_price, # 行权价
|
||||||
|
free_rate = 0.03, # 无风险利率,如果没有数据,指定为3%
|
||||||
|
dividend_rate = 0, # 分红率,目前指定为0
|
||||||
|
initial_iv = 0.5, # 初始迭代的隐波
|
||||||
|
):
|
||||||
|
cp = call_put
|
||||||
|
s = underlying_price
|
||||||
|
r = free_rate
|
||||||
|
k = exercise_price
|
||||||
|
t = rest_days / 360
|
||||||
|
c = option_price
|
||||||
|
q = dividend_rate
|
||||||
|
sigma = calculate_single_option_iv_by_bsm(cp, s, k, c, t, r, q, initial_iv)
|
||||||
|
delta, gamma, vega, theta = get_option_greeks(cp, s, k, t, r, sigma, q)
|
||||||
|
# sigma就是iv
|
||||||
|
return sigma, delta, gamma, vega, theta
|
0
vnpy/app/cta_option/strategies/__init__.py
Normal file
0
vnpy/app/cta_option/strategies/__init__.py
Normal file
1068
vnpy/app/cta_option/template.py
Normal file
1068
vnpy/app/cta_option/template.py
Normal file
File diff suppressed because it is too large
Load Diff
1
vnpy/app/cta_option/ui/__init__.py
Normal file
1
vnpy/app/cta_option/ui/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .widget import CtaOption
|
BIN
vnpy/app/cta_option/ui/cta.ico
Normal file
BIN
vnpy/app/cta_option/ui/cta.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
552
vnpy/app/cta_option/ui/widget.py
Normal file
552
vnpy/app/cta_option/ui/widget.py
Normal file
@ -0,0 +1,552 @@
|
|||||||
|
from vnpy.event import Event, EventEngine
|
||||||
|
from vnpy.trader.engine import MainEngine
|
||||||
|
from vnpy.trader.ui import QtCore, QtGui, QtWidgets
|
||||||
|
from vnpy.trader.ui.widget import (
|
||||||
|
BaseCell,
|
||||||
|
EnumCell,
|
||||||
|
MsgCell,
|
||||||
|
TimeCell,
|
||||||
|
BaseMonitor
|
||||||
|
)
|
||||||
|
from vnpy.trader.ui.kline.ui_snapshot import UiSnapshot
|
||||||
|
from ..base import (
|
||||||
|
APP_NAME,
|
||||||
|
EVENT_CTA_LOG,
|
||||||
|
EVENT_CTA_STOPORDER,
|
||||||
|
EVENT_CTA_OPTION
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..engine import CtaOptionEngine
|
||||||
|
|
||||||
|
|
||||||
|
class CtaOption(QtWidgets.QWidget):
|
||||||
|
""""""
|
||||||
|
|
||||||
|
signal_log = QtCore.pyqtSignal(Event)
|
||||||
|
signal_strategy = QtCore.pyqtSignal(Event)
|
||||||
|
|
||||||
|
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||||
|
super(CtaOption, self).__init__()
|
||||||
|
|
||||||
|
self.main_engine = main_engine
|
||||||
|
self.event_engine = event_engine
|
||||||
|
self.cta_engine = main_engine.get_engine(APP_NAME)
|
||||||
|
|
||||||
|
self.managers = {}
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
self.register_event()
|
||||||
|
self.cta_engine.init_engine()
|
||||||
|
self.update_class_combo()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
""""""
|
||||||
|
self.setWindowTitle("CTA策略")
|
||||||
|
|
||||||
|
# Create widgets
|
||||||
|
self.class_combo = QtWidgets.QComboBox()
|
||||||
|
|
||||||
|
add_button = QtWidgets.QPushButton("添加策略")
|
||||||
|
add_button.clicked.connect(self.add_strategy)
|
||||||
|
|
||||||
|
init_button = QtWidgets.QPushButton("全部初始化")
|
||||||
|
init_button.clicked.connect(self.cta_engine.init_all_strategies)
|
||||||
|
|
||||||
|
start_button = QtWidgets.QPushButton("全部启动")
|
||||||
|
start_button.clicked.connect(self.cta_engine.start_all_strategies)
|
||||||
|
|
||||||
|
stop_button = QtWidgets.QPushButton("全部停止")
|
||||||
|
stop_button.clicked.connect(self.cta_engine.stop_all_strategies)
|
||||||
|
|
||||||
|
clear_button = QtWidgets.QPushButton("清空日志")
|
||||||
|
clear_button.clicked.connect(self.clear_log)
|
||||||
|
|
||||||
|
self.scroll_layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.scroll_layout.addStretch()
|
||||||
|
|
||||||
|
scroll_widget = QtWidgets.QWidget()
|
||||||
|
scroll_widget.setLayout(self.scroll_layout)
|
||||||
|
|
||||||
|
scroll_area = QtWidgets.QScrollArea()
|
||||||
|
scroll_area.setWidgetResizable(True)
|
||||||
|
scroll_area.setWidget(scroll_widget)
|
||||||
|
|
||||||
|
self.log_monitor = LogMonitor(self.main_engine, self.event_engine)
|
||||||
|
|
||||||
|
self.stop_order_monitor = StopOrderMonitor(
|
||||||
|
self.main_engine, self.event_engine
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set layout
|
||||||
|
hbox1 = QtWidgets.QHBoxLayout()
|
||||||
|
hbox1.addWidget(self.class_combo)
|
||||||
|
hbox1.addWidget(add_button)
|
||||||
|
hbox1.addStretch()
|
||||||
|
hbox1.addWidget(init_button)
|
||||||
|
hbox1.addWidget(start_button)
|
||||||
|
hbox1.addWidget(stop_button)
|
||||||
|
hbox1.addWidget(clear_button)
|
||||||
|
|
||||||
|
grid = QtWidgets.QGridLayout()
|
||||||
|
grid.addWidget(scroll_area, 0, 0, 2, 1)
|
||||||
|
grid.addWidget(self.stop_order_monitor, 0, 1)
|
||||||
|
grid.addWidget(self.log_monitor, 1, 1)
|
||||||
|
|
||||||
|
vbox = QtWidgets.QVBoxLayout()
|
||||||
|
vbox.addLayout(hbox1)
|
||||||
|
vbox.addLayout(grid)
|
||||||
|
|
||||||
|
self.setLayout(vbox)
|
||||||
|
|
||||||
|
def update_class_combo(self):
|
||||||
|
""""""
|
||||||
|
self.class_combo.addItems(
|
||||||
|
self.cta_engine.get_all_strategy_class_names()
|
||||||
|
)
|
||||||
|
|
||||||
|
def register_event(self):
|
||||||
|
""""""
|
||||||
|
self.signal_strategy.connect(self.process_strategy_event)
|
||||||
|
|
||||||
|
self.event_engine.register(
|
||||||
|
EVENT_CTA_OPTION, self.signal_strategy.emit
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_strategy_event(self, event):
|
||||||
|
"""
|
||||||
|
Update strategy status onto its monitor.
|
||||||
|
"""
|
||||||
|
data = event.data
|
||||||
|
strategy_name = data["strategy_name"]
|
||||||
|
|
||||||
|
if strategy_name in self.managers:
|
||||||
|
manager = self.managers[strategy_name]
|
||||||
|
manager.update_data(data)
|
||||||
|
else:
|
||||||
|
manager = StrategyManager(self, self.cta_engine, data)
|
||||||
|
self.scroll_layout.insertWidget(0, manager)
|
||||||
|
self.managers[strategy_name] = manager
|
||||||
|
|
||||||
|
def remove_strategy(self, strategy_name):
|
||||||
|
""""""
|
||||||
|
manager = self.managers.pop(strategy_name)
|
||||||
|
manager.deleteLater()
|
||||||
|
|
||||||
|
def add_strategy(self):
|
||||||
|
""""""
|
||||||
|
class_name = str(self.class_combo.currentText())
|
||||||
|
if not class_name:
|
||||||
|
return
|
||||||
|
|
||||||
|
parameters = self.cta_engine.get_strategy_class_parameters(class_name)
|
||||||
|
editor = SettingEditor(parameters, class_name=class_name)
|
||||||
|
n = editor.exec_()
|
||||||
|
|
||||||
|
if n == editor.Accepted:
|
||||||
|
setting = editor.get_setting()
|
||||||
|
vt_symbols = setting.pop("vt_symbols").split(",")
|
||||||
|
strategy_name = setting.pop("strategy_name")
|
||||||
|
auto_init = setting.pop("auto_init", False)
|
||||||
|
auto_start = setting.pop("auto_start", False)
|
||||||
|
self.cta_engine.add_strategy(
|
||||||
|
class_name, strategy_name, vt_symbols, setting, auto_init, auto_start
|
||||||
|
)
|
||||||
|
|
||||||
|
def clear_log(self):
|
||||||
|
""""""
|
||||||
|
self.log_monitor.setRowCount(0)
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
""""""
|
||||||
|
self.showMaximized()
|
||||||
|
|
||||||
|
|
||||||
|
class StrategyManager(QtWidgets.QFrame):
|
||||||
|
"""
|
||||||
|
Manager for a strategy
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, cta_manager: CtaOption, cta_engine: CtaOptionEngine, data: dict
|
||||||
|
):
|
||||||
|
""""""
|
||||||
|
super(StrategyManager, self).__init__()
|
||||||
|
|
||||||
|
self.cta_manager = cta_manager
|
||||||
|
self.cta_engine = cta_engine
|
||||||
|
|
||||||
|
self.strategy_name = data["strategy_name"]
|
||||||
|
self._data = data
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
""""""
|
||||||
|
self.setFixedHeight(300)
|
||||||
|
self.setFrameShape(self.Box)
|
||||||
|
self.setLineWidth(1)
|
||||||
|
|
||||||
|
init_button = QtWidgets.QPushButton("初始化")
|
||||||
|
init_button.clicked.connect(self.init_strategy)
|
||||||
|
|
||||||
|
start_button = QtWidgets.QPushButton("启动")
|
||||||
|
start_button.clicked.connect(self.start_strategy)
|
||||||
|
|
||||||
|
stop_button = QtWidgets.QPushButton("停止")
|
||||||
|
stop_button.clicked.connect(self.stop_strategy)
|
||||||
|
|
||||||
|
edit_button = QtWidgets.QPushButton("编辑")
|
||||||
|
edit_button.clicked.connect(self.edit_strategy)
|
||||||
|
|
||||||
|
remove_button = QtWidgets.QPushButton("移除")
|
||||||
|
remove_button.clicked.connect(self.remove_strategy)
|
||||||
|
|
||||||
|
reload_button = QtWidgets.QPushButton("重载")
|
||||||
|
reload_button.clicked.connect(self.reload_strategy)
|
||||||
|
|
||||||
|
save_button = QtWidgets.QPushButton("保存")
|
||||||
|
save_button.clicked.connect(self.save_strategy)
|
||||||
|
|
||||||
|
view_button = QtWidgets.QPushButton("K线")
|
||||||
|
view_button.clicked.connect(self.view_strategy_snapshot)
|
||||||
|
|
||||||
|
strategy_name = self._data["strategy_name"]
|
||||||
|
#vt_symbol = self._data["vt_symbol"]
|
||||||
|
class_name = self._data["class_name"]
|
||||||
|
author = self._data["author"]
|
||||||
|
|
||||||
|
label_text = (
|
||||||
|
f"{strategy_name} - ({class_name} by {author})"
|
||||||
|
)
|
||||||
|
label = QtWidgets.QLabel(label_text)
|
||||||
|
label.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
|
||||||
|
self.parameters_monitor = DataMonitor(self._data["parameters"])
|
||||||
|
self.variables_monitor = DataMonitor(self._data["variables"])
|
||||||
|
|
||||||
|
hbox = QtWidgets.QHBoxLayout()
|
||||||
|
hbox.addWidget(init_button)
|
||||||
|
hbox.addWidget(start_button)
|
||||||
|
hbox.addWidget(stop_button)
|
||||||
|
hbox.addWidget(edit_button)
|
||||||
|
hbox.addWidget(remove_button)
|
||||||
|
hbox.addWidget(reload_button)
|
||||||
|
hbox.addWidget(save_button)
|
||||||
|
hbox.addWidget(view_button)
|
||||||
|
|
||||||
|
vbox = QtWidgets.QVBoxLayout()
|
||||||
|
vbox.addWidget(label)
|
||||||
|
vbox.addLayout(hbox)
|
||||||
|
vbox.addWidget(self.parameters_monitor)
|
||||||
|
vbox.addWidget(self.variables_monitor)
|
||||||
|
self.setLayout(vbox)
|
||||||
|
|
||||||
|
def update_data(self, data: dict):
|
||||||
|
""""""
|
||||||
|
self._data = data
|
||||||
|
|
||||||
|
self.parameters_monitor.update_data(data["parameters"])
|
||||||
|
self.variables_monitor.update_data(data["variables"])
|
||||||
|
|
||||||
|
def init_strategy(self):
|
||||||
|
""""""
|
||||||
|
self.cta_engine.init_strategy(self.strategy_name)
|
||||||
|
|
||||||
|
def start_strategy(self):
|
||||||
|
""""""
|
||||||
|
self.cta_engine.start_strategy(self.strategy_name)
|
||||||
|
|
||||||
|
def stop_strategy(self):
|
||||||
|
""""""
|
||||||
|
self.cta_engine.stop_strategy(self.strategy_name)
|
||||||
|
|
||||||
|
def edit_strategy(self):
|
||||||
|
""""""
|
||||||
|
strategy_name = self._data["strategy_name"]
|
||||||
|
|
||||||
|
parameters = self.cta_engine.get_strategy_parameters(strategy_name)
|
||||||
|
editor = SettingEditor(parameters, strategy_name=strategy_name)
|
||||||
|
n = editor.exec_()
|
||||||
|
|
||||||
|
if n == editor.Accepted:
|
||||||
|
setting = editor.get_setting()
|
||||||
|
self.cta_engine.edit_strategy(strategy_name, setting)
|
||||||
|
|
||||||
|
def remove_strategy(self):
|
||||||
|
""""""
|
||||||
|
result = self.cta_engine.remove_strategy(self.strategy_name)
|
||||||
|
|
||||||
|
# Only remove strategy gui manager if it has been removed from engine
|
||||||
|
if result:
|
||||||
|
self.cta_manager.remove_strategy(self.strategy_name)
|
||||||
|
|
||||||
|
def reload_strategy(self):
|
||||||
|
"""重新加载策略"""
|
||||||
|
self.cta_engine.reload_strategy(self.strategy_name)
|
||||||
|
|
||||||
|
def save_strategy(self):
|
||||||
|
"""保存策略缓存数据"""
|
||||||
|
self.cta_engine.save_strategy_data(self.strategy_name)
|
||||||
|
|
||||||
|
def view_strategy_snapshot(self):
|
||||||
|
"""实时查看策略切片"""
|
||||||
|
kline_info = self.cta_engine.get_strategy_kline_names(self.strategy_name)
|
||||||
|
|
||||||
|
selector = KlineSelectDialog(kline_info,self.strategy_name)
|
||||||
|
n = selector.exec_()
|
||||||
|
|
||||||
|
if n == selector.Accepted:
|
||||||
|
klines = selector.get_klines()
|
||||||
|
if len(klines) > 0:
|
||||||
|
snapshot = self.cta_engine.get_strategy_snapshot(self.strategy_name,klines)
|
||||||
|
if snapshot is None:
|
||||||
|
return
|
||||||
|
ui_snapshot = UiSnapshot()
|
||||||
|
ui_snapshot.show(snapshot_file="", d=snapshot)
|
||||||
|
|
||||||
|
class DataMonitor(QtWidgets.QTableWidget):
|
||||||
|
"""
|
||||||
|
Table monitor for parameters and variables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data: dict):
|
||||||
|
""""""
|
||||||
|
super(DataMonitor, self).__init__()
|
||||||
|
|
||||||
|
self._data = data
|
||||||
|
self.cells = {}
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
""""""
|
||||||
|
labels = list(self._data.keys())
|
||||||
|
self.setColumnCount(len(labels))
|
||||||
|
self.setHorizontalHeaderLabels(labels)
|
||||||
|
|
||||||
|
self.setRowCount(1)
|
||||||
|
self.verticalHeader().setSectionResizeMode(
|
||||||
|
QtWidgets.QHeaderView.Stretch
|
||||||
|
)
|
||||||
|
self.verticalHeader().setVisible(False)
|
||||||
|
self.setEditTriggers(self.NoEditTriggers)
|
||||||
|
|
||||||
|
for column, name in enumerate(self._data.keys()):
|
||||||
|
value = self._data[name]
|
||||||
|
|
||||||
|
cell = QtWidgets.QTableWidgetItem(str(value))
|
||||||
|
cell.setTextAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
|
||||||
|
self.setItem(0, column, cell)
|
||||||
|
self.cells[name] = cell
|
||||||
|
|
||||||
|
def update_data(self, data: dict):
|
||||||
|
""""""
|
||||||
|
for name, value in data.items():
|
||||||
|
cell = self.cells[name]
|
||||||
|
cell.setText(str(value))
|
||||||
|
|
||||||
|
|
||||||
|
class StopOrderMonitor(BaseMonitor):
|
||||||
|
"""
|
||||||
|
Monitor for local stop order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
event_type = EVENT_CTA_STOPORDER
|
||||||
|
data_key = "stop_orderid"
|
||||||
|
sorting = True
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"stop_orderid": {
|
||||||
|
"display": "停止委托号",
|
||||||
|
"cell": BaseCell,
|
||||||
|
"update": False,
|
||||||
|
},
|
||||||
|
"vt_orderids": {"display": "限价委托号", "cell": BaseCell, "update": True},
|
||||||
|
"vt_symbol": {"display": "本地代码", "cell": BaseCell, "update": False},
|
||||||
|
"direction": {"display": "方向", "cell": EnumCell, "update": False},
|
||||||
|
"offset": {"display": "开平", "cell": EnumCell, "update": False},
|
||||||
|
"price": {"display": "价格", "cell": BaseCell, "update": False},
|
||||||
|
"volume": {"display": "数量", "cell": BaseCell, "update": False},
|
||||||
|
"status": {"display": "状态", "cell": EnumCell, "update": True},
|
||||||
|
"lock": {"display": "锁仓", "cell": BaseCell, "update": False},
|
||||||
|
"strategy_name": {"display": "策略名", "cell": BaseCell, "update": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LogMonitor(BaseMonitor):
|
||||||
|
"""
|
||||||
|
Monitor for log data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
event_type = EVENT_CTA_LOG
|
||||||
|
data_key = ""
|
||||||
|
sorting = False
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"time": {"display": "时间", "cell": TimeCell, "update": False},
|
||||||
|
"msg": {"display": "信息", "cell": MsgCell, "update": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
"""
|
||||||
|
Stretch last column.
|
||||||
|
"""
|
||||||
|
super(LogMonitor, self).init_ui()
|
||||||
|
|
||||||
|
self.horizontalHeader().setSectionResizeMode(
|
||||||
|
1, QtWidgets.QHeaderView.Stretch
|
||||||
|
)
|
||||||
|
|
||||||
|
def insert_new_row(self, data):
|
||||||
|
"""
|
||||||
|
Insert a new row at the top of table.
|
||||||
|
"""
|
||||||
|
super(LogMonitor, self).insert_new_row(data)
|
||||||
|
self.resizeRowToContents(0)
|
||||||
|
|
||||||
|
class KlineSelectDialog(QtWidgets.QDialog):
|
||||||
|
"""
|
||||||
|
多K线选择窗口
|
||||||
|
"""
|
||||||
|
def __init__(
|
||||||
|
self, info: dict, strategy_name:str
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
构造函数
|
||||||
|
:param info: 所有k线的配置
|
||||||
|
:param strategy_name:
|
||||||
|
"""
|
||||||
|
super(KlineSelectDialog, self).__init__()
|
||||||
|
|
||||||
|
self.info = info
|
||||||
|
self.strategy_name = strategy_name
|
||||||
|
self.t = None
|
||||||
|
self.select_names = []
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
""""""
|
||||||
|
form = QtWidgets.QFormLayout()
|
||||||
|
self.t = QtWidgets.QTableWidget(len(self.info), 2)
|
||||||
|
|
||||||
|
self.t.setHorizontalHeaderLabels(['股票', 'K线'])
|
||||||
|
row = 0
|
||||||
|
for k, v in self.info.items():
|
||||||
|
|
||||||
|
item = QtWidgets.QTableWidgetItem()
|
||||||
|
item.setText(k)
|
||||||
|
self.t.setItem(row, 0, item)
|
||||||
|
|
||||||
|
klines = QtWidgets.QTableWidgetItem()
|
||||||
|
klines.setText(','.join(v))
|
||||||
|
self.t.setItem(row,1, klines)
|
||||||
|
row +=1
|
||||||
|
|
||||||
|
# 单选
|
||||||
|
self.t.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||||
|
# self.t.cellPressed.conect(self.cell_select)
|
||||||
|
form.addWidget(self.t)
|
||||||
|
button = QtWidgets.QPushButton('确定')
|
||||||
|
button.clicked.connect(self.accept)
|
||||||
|
form.addRow(button)
|
||||||
|
self.setLayout(form)
|
||||||
|
|
||||||
|
def cell_select(self,row,col):
|
||||||
|
try:
|
||||||
|
content = self.t.item(row,0).text()
|
||||||
|
self.select_names = self.info.get(content,[])
|
||||||
|
except Exception as ex:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_klines(self):
|
||||||
|
""""""
|
||||||
|
selectedItems = self.t.selectedItems()
|
||||||
|
for item in selectedItems:
|
||||||
|
cur_row = item.row()
|
||||||
|
content = item.text()
|
||||||
|
self.select_names = self.info.get(content, [])
|
||||||
|
if len(self.select_names) > 0:
|
||||||
|
return self.select_names
|
||||||
|
return self.select_names
|
||||||
|
|
||||||
|
|
||||||
|
class SettingEditor(QtWidgets.QDialog):
|
||||||
|
"""
|
||||||
|
For creating new strategy and editing strategy parameters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, parameters: dict, strategy_name: str = "", class_name: str = ""
|
||||||
|
):
|
||||||
|
""""""
|
||||||
|
super(SettingEditor, self).__init__()
|
||||||
|
|
||||||
|
self.parameters = parameters
|
||||||
|
self.strategy_name = strategy_name
|
||||||
|
self.class_name = class_name
|
||||||
|
|
||||||
|
self.edits = {}
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
""""""
|
||||||
|
form = QtWidgets.QFormLayout()
|
||||||
|
|
||||||
|
# Add vt_symbol and name edit if add new strategy
|
||||||
|
if self.class_name:
|
||||||
|
self.setWindowTitle(f"添加策略:{self.class_name}")
|
||||||
|
button_text = "添加"
|
||||||
|
parameters = {"strategy_name": "", "vt_symbols": "", "auto_init": True, "auto_start": True}
|
||||||
|
parameters.update(self.parameters)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.setWindowTitle(f"参数编辑:{self.strategy_name}")
|
||||||
|
button_text = "确定"
|
||||||
|
parameters = self.parameters
|
||||||
|
|
||||||
|
for name, value in parameters.items():
|
||||||
|
type_ = type(value)
|
||||||
|
|
||||||
|
edit = QtWidgets.QLineEdit(str(value))
|
||||||
|
if type_ is int:
|
||||||
|
validator = QtGui.QIntValidator()
|
||||||
|
edit.setValidator(validator)
|
||||||
|
elif type_ is float:
|
||||||
|
validator = QtGui.QDoubleValidator()
|
||||||
|
edit.setValidator(validator)
|
||||||
|
|
||||||
|
form.addRow(f"{name} {type_}", edit)
|
||||||
|
|
||||||
|
self.edits[name] = (edit, type_)
|
||||||
|
|
||||||
|
button = QtWidgets.QPushButton(button_text)
|
||||||
|
button.clicked.connect(self.accept)
|
||||||
|
form.addRow(button)
|
||||||
|
|
||||||
|
self.setLayout(form)
|
||||||
|
|
||||||
|
def get_setting(self):
|
||||||
|
""""""
|
||||||
|
setting = {}
|
||||||
|
|
||||||
|
if self.class_name:
|
||||||
|
setting["class_name"] = self.class_name
|
||||||
|
|
||||||
|
for name, tp in self.edits.items():
|
||||||
|
edit, type_ = tp
|
||||||
|
value_text = edit.text()
|
||||||
|
|
||||||
|
if type_ == bool:
|
||||||
|
if value_text == "True":
|
||||||
|
value = True
|
||||||
|
else:
|
||||||
|
value = False
|
||||||
|
else:
|
||||||
|
value = type_(value_text)
|
||||||
|
|
||||||
|
setting[name] = value
|
||||||
|
|
||||||
|
return setting
|
Loading…
Reference in New Issue
Block a user