[Mod]complete twap algo and app test
This commit is contained in:
parent
be62354c1f
commit
0646e1ccd2
@ -15,6 +15,7 @@ from vnpy.gateway.huobi import HuobiGateway
|
||||
|
||||
from vnpy.app.cta_strategy import CtaStrategyApp
|
||||
from vnpy.app.csv_loader import CsvLoaderApp
|
||||
from vnpy.app.algo_trading import AlgoTradingApp
|
||||
|
||||
|
||||
def main():
|
||||
@ -35,6 +36,7 @@ def main():
|
||||
|
||||
main_engine.add_app(CtaStrategyApp)
|
||||
main_engine.add_app(CsvLoaderApp)
|
||||
main_engine.add_app(AlgoTradingApp)
|
||||
|
||||
main_window = MainWindow(main_engine, event_engine)
|
||||
main_window.showMaximized()
|
||||
|
@ -3,9 +3,10 @@ from pathlib import Path
|
||||
from vnpy.trader.app import BaseApp
|
||||
|
||||
from .engine import AlgoEngine, APP_NAME
|
||||
from .template import AlgoTemplate
|
||||
|
||||
|
||||
class CtaStrategyApp(BaseApp):
|
||||
class AlgoTradingApp(BaseApp):
|
||||
""""""
|
||||
|
||||
app_name = APP_NAME
|
||||
|
@ -1,59 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
'''
|
||||
动态载入所有的策略类
|
||||
'''
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import importlib
|
||||
import traceback
|
||||
|
||||
|
||||
# 用来保存算法类和控件类的字典
|
||||
ALGO_DICT = {}
|
||||
WIDGET_DICT = {}
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def loadAlgoModule(path, prefix):
|
||||
"""使用importlib动态载入算法"""
|
||||
for root, subdirs, files in os.walk(path):
|
||||
for name in files:
|
||||
# 只有文件名以Algo.py结尾的才是算法文件
|
||||
if len(name)>7 and name[-7:] == 'Algo.py':
|
||||
try:
|
||||
# 模块名称需要模块路径前缀
|
||||
moduleName = prefix + name.replace('.py', '')
|
||||
module = importlib.import_module(moduleName)
|
||||
|
||||
# 获取算法类和控件类
|
||||
algo = None
|
||||
widget = None
|
||||
|
||||
for k in dir(module):
|
||||
# 以Algo结尾的类,是算法
|
||||
if k[-4:] == 'Algo':
|
||||
algo = module.__getattribute__(k)
|
||||
|
||||
# 以Widget结尾的类,是控件
|
||||
if k[-6:] == 'Widget':
|
||||
widget = module.__getattribute__(k)
|
||||
|
||||
# 保存到字典中
|
||||
if algo and widget:
|
||||
ALGO_DICT[algo.templateName] = algo
|
||||
WIDGET_DICT[algo.templateName] = widget
|
||||
except:
|
||||
print ('-' * 20)
|
||||
print ('Failed to import strategy file %s:' %moduleName)
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# 遍历algo目录下的文件
|
||||
path1 = os.path.abspath(os.path.dirname(__file__))
|
||||
loadAlgoModule(path1, 'vnpy.trader.app.algoTrading.algo.')
|
||||
|
||||
# 遍历工作目录下的文件
|
||||
path2 = os.getcwd()
|
||||
loadAlgoModule(path2, '')
|
@ -0,0 +1,112 @@
|
||||
from vnpy.trader.constant import Offset, Direction
|
||||
from vnpy.trader.object import TradeData
|
||||
from vnpy.trader.engine import BaseEngine
|
||||
|
||||
from vnpy.app.algo_trading import AlgoTemplate
|
||||
|
||||
|
||||
class TwapAlgo(AlgoTemplate):
|
||||
""""""
|
||||
|
||||
display_name = "TWAP 时间加权平均"
|
||||
|
||||
default_setting = {
|
||||
"vt_symbol": "",
|
||||
"direction": [Direction.LONG.value, Direction.SHORT.value],
|
||||
"price": 0.0,
|
||||
"volume": 0.0,
|
||||
"time": 600,
|
||||
"interval": 60,
|
||||
"offset": [
|
||||
Offset.NONE.value,
|
||||
Offset.OPEN.value,
|
||||
Offset.CLOSE.value,
|
||||
Offset.CLOSETODAY.value,
|
||||
Offset.CLOSEYESTERDAY.value
|
||||
]
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
algo_engine: BaseEngine,
|
||||
algo_name: str,
|
||||
setting: dict
|
||||
):
|
||||
""""""
|
||||
super().__init__(algo_engine, algo_name, setting)
|
||||
|
||||
# Parameters
|
||||
self.vt_symbol = setting["vt_symbol"]
|
||||
self.direction = Direction(setting["direction"])
|
||||
self.price = setting["price"]
|
||||
self.volume = setting["volume"]
|
||||
self.time = setting["time"]
|
||||
self.interval = setting["interval"]
|
||||
self.offset = Offset(setting["offset"])
|
||||
|
||||
# Variables
|
||||
self.order_volume = self.volume / (self.time / self.interval)
|
||||
self.timer_count = 0
|
||||
self.total_count = 0
|
||||
self.traded = 0
|
||||
|
||||
self.variables.extend([
|
||||
"traded",
|
||||
"order_volume",
|
||||
"timer_count",
|
||||
"total_count"
|
||||
])
|
||||
|
||||
self.subscribe(self.vt_symbol)
|
||||
self.put_parameters_event()
|
||||
self.put_variables_event()
|
||||
|
||||
def on_trade(self, trade: TradeData):
|
||||
""""""
|
||||
self.traded += trade.volume
|
||||
|
||||
if self.traded >= self.volume:
|
||||
self.stop()
|
||||
else:
|
||||
self.put_variables_event()
|
||||
|
||||
def on_timer(self):
|
||||
""""""
|
||||
self.timer_count += 1
|
||||
self.total_count += 1
|
||||
self.put_variables_event()
|
||||
|
||||
if self.total_count >= self.time:
|
||||
self.write_log("执行时间已结束,停止算法")
|
||||
self.stop()
|
||||
return
|
||||
|
||||
if self.timer_count < self.interval:
|
||||
return
|
||||
self.timer_count = 0
|
||||
|
||||
tick = self.get_tick(self.vt_symbol)
|
||||
if not tick:
|
||||
return
|
||||
|
||||
self.cancel_all()
|
||||
|
||||
left_volume = self.volume - self.traded
|
||||
order_volume = min(self.order_volume, left_volume)
|
||||
|
||||
if self.direction == Direction.LONG:
|
||||
if tick.ask_price_1 <= self.price:
|
||||
self.buy(self.vt_symbol, self.price,
|
||||
order_volume, offset=self.offset)
|
||||
self.write_log(
|
||||
f"委托买入{self.vt_symbol}:{order_volume}@{self.price}")
|
||||
else:
|
||||
if tick.bid_price_1 >= self.price:
|
||||
self.sell(self.vt_symbol, self.price,
|
||||
order_volume, offset=self.offset)
|
||||
self.write_log(
|
||||
f"委托卖出{self.vt_symbol}:{order_volume}@{self.price}")
|
||||
|
||||
def get_default_setting(self):
|
||||
""""""
|
||||
return self.default_setting
|
@ -1,65 +1,257 @@
|
||||
|
||||
from vnpy.event import EventEngine
|
||||
from vnpy.event import EventEngine, Event
|
||||
from vnpy.trader.engine import BaseEngine, MainEngine
|
||||
from vnpy.trader.event import (EVENT_TICK, EVENT_TIMER, EVENT_ORDER, EVENT_TRADE)
|
||||
from vnpy.trader.event import (
|
||||
EVENT_TICK, EVENT_TIMER, EVENT_ORDER, EVENT_TRADE)
|
||||
from vnpy.trader.constant import (Direction, Offset, OrderType)
|
||||
from vnpy.trader.object import (SubscribeRequest, OrderRequest)
|
||||
from vnpy.trader.utility import load_json, save_json
|
||||
|
||||
from .template import AlgoTemplate
|
||||
|
||||
|
||||
APP_NAME = "AlgoTrading"
|
||||
|
||||
EVENT_ALGO_LOG = "eAlgoLog"
|
||||
EVENT_ALGO_SETTING = "eAlgoSetting"
|
||||
EVENT_ALGO_VARIABLES = "eAlgoVariables"
|
||||
EVENT_ALGO_PARAMETERS = "eAlgoParameters"
|
||||
|
||||
|
||||
class AlgoEngine(BaseEngine):
|
||||
""""""
|
||||
setting_filename = "algo_trading_setting.json"
|
||||
|
||||
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||
"""Constructor"""
|
||||
super().__init__(main_engine, event_engine)
|
||||
|
||||
super().__init__(main_engine, event_engine, APP_NAME)
|
||||
|
||||
self.algos = {}
|
||||
self.symbol_algo_map = {}
|
||||
self.orderid_algo_map = {}
|
||||
|
||||
|
||||
self.algo_templates = {}
|
||||
self.algo_settings = {}
|
||||
|
||||
self.load_algo_template()
|
||||
self.register_event()
|
||||
|
||||
|
||||
def init_engine(self):
|
||||
""""""
|
||||
self.write_log("算法交易引擎启动")
|
||||
self.load_algo_setting()
|
||||
|
||||
def load_algo_template(self):
|
||||
""""""
|
||||
from .algos.twap_algo import TwapAlgo
|
||||
|
||||
self.algo_templates[TwapAlgo.__name__] = TwapAlgo
|
||||
|
||||
def load_algo_setting(self):
|
||||
""""""
|
||||
self.algo_settings = load_json(self.setting_filename)
|
||||
|
||||
for setting_name, setting in self.algo_settings.items():
|
||||
self.put_setting_event(setting_name, setting)
|
||||
|
||||
self.write_log("算法配置载入成功")
|
||||
|
||||
def save_algo_setting(self):
|
||||
""""""
|
||||
save_json(self.setting_filename, self.algo_settings)
|
||||
|
||||
def register_event(self):
|
||||
""""""
|
||||
self.event_engine.register(EVENT_TICK, self.process_tick_event)
|
||||
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
|
||||
self.event_engine.register(EVENT_ORDER, self.process_order_event)
|
||||
self.event_engine.register(EVENT_TRADE, self.process_trade_event)
|
||||
|
||||
def process_tick_event(self):
|
||||
|
||||
def process_tick_event(self, event: Event):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def process_timer_event(self):
|
||||
tick = event.data
|
||||
|
||||
algos = self.symbol_algo_map.get(tick.vt_symbol, None)
|
||||
if algos:
|
||||
for algo in algos:
|
||||
algo.update_tick(tick)
|
||||
|
||||
def process_timer_event(self, event: Event):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def process_trade_event(self):
|
||||
for algo in self.algos.values():
|
||||
algo.update_timer()
|
||||
|
||||
def process_trade_event(self, event: Event):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def process_order_event(self):
|
||||
trade = event.data
|
||||
|
||||
algo = self.orderid_algo_map.get(trade.vt_orderid, None)
|
||||
if algo:
|
||||
algo.update_trade(trade)
|
||||
|
||||
def process_order_event(self, event: Event):
|
||||
""""""
|
||||
pass
|
||||
|
||||
order = event.data
|
||||
|
||||
algo = self.orderid_algo_map.get(order.vt_orderid, None)
|
||||
if algo:
|
||||
algo.update_order(order)
|
||||
|
||||
def start_algo(self, setting: dict):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def stop_algo(self, algo_name: dict):
|
||||
template_name = setting["template_name"]
|
||||
algo_template = self.algo_templates[template_name]
|
||||
|
||||
algo = algo_template.new(self, setting)
|
||||
algo.start()
|
||||
|
||||
self.algos[algo.algo_name] = algo
|
||||
return algo.algo_name
|
||||
|
||||
def stop_algo(self, algo_name: str):
|
||||
""""""
|
||||
pass
|
||||
|
||||
algo = self.algos.get(algo_name, None)
|
||||
if algo:
|
||||
algo.stop()
|
||||
self.algos.pop(algo_name)
|
||||
|
||||
def stop_all(self):
|
||||
""""""
|
||||
pass
|
||||
|
||||
def subscribe(self, algo, vt_symbol):
|
||||
for algo_name in list(self.algos.keys()):
|
||||
self.stop_algo(algo_name)
|
||||
|
||||
def subscribe(self, algo: AlgoTemplate, vt_symbol: str):
|
||||
""""""
|
||||
pass
|
||||
|
||||
contract = self.main_engine.get_contract(vt_symbol)
|
||||
if not contract:
|
||||
self.write_log(f'订阅行情失败,找不到合约:{vt_symbol}', algo)
|
||||
return
|
||||
|
||||
algos = self.symbol_algo_map.setdefault(vt_symbol, set())
|
||||
|
||||
if not algos:
|
||||
req = SubscribeRequest(
|
||||
symbol=contract.symbol,
|
||||
exchange=contract.exchange
|
||||
)
|
||||
self.main_engine.subscribe(req, contract.gateway_name)
|
||||
|
||||
algos.add(algo)
|
||||
|
||||
def send_order(
|
||||
self,
|
||||
algo,
|
||||
vt_symbol
|
||||
self,
|
||||
algo: AlgoTemplate,
|
||||
vt_symbol: str,
|
||||
direction: Direction,
|
||||
price: float,
|
||||
volume: float,
|
||||
order_type: OrderType,
|
||||
offset: Offset
|
||||
):
|
||||
""""""
|
||||
pass
|
||||
contract = self.main_engine.get_contract(vt_symbol)
|
||||
if not contract:
|
||||
self.write_log(f'委托下单失败,找不到合约:{vt_symbol}', algo)
|
||||
return
|
||||
|
||||
req = OrderRequest(
|
||||
symbol=contract.symbol,
|
||||
exchange=contract.exchange,
|
||||
direction=direction,
|
||||
type=order_type,
|
||||
volume=volume,
|
||||
price=price,
|
||||
offset=offset
|
||||
)
|
||||
vt_orderid = self.main_engine.send_order(req, contract.gateway_name)
|
||||
|
||||
self.orderid_algo_map[vt_orderid] = algo
|
||||
return vt_orderid
|
||||
|
||||
def cancel_order(self, algo: AlgoTemplate, vt_orderid: str):
|
||||
""""""
|
||||
order = self.main_engine.get_order(vt_orderid)
|
||||
|
||||
if not order:
|
||||
self.write_log(f"委托撤单失败,找不到委托:{vt_orderid}", algo)
|
||||
return
|
||||
|
||||
req = order.create_cancel_request()
|
||||
self.main_engine.cancel_order(req, order.gateway_name)
|
||||
|
||||
def get_tick(self, algo: AlgoTemplate, vt_symbol: str):
|
||||
""""""
|
||||
tick = self.main_engine.get_tick(vt_symbol)
|
||||
|
||||
if not tick:
|
||||
self.write_log(f"查询行情失败,找不到行情:{vt_symbol}", algo)
|
||||
|
||||
return tick
|
||||
|
||||
def get_contract(self, algo: AlgoTemplate, vt_symbol: str):
|
||||
""""""
|
||||
contract = self.main_engine.get_contract(vt_symbol)
|
||||
|
||||
if not contract:
|
||||
self.write_log(f"查询合约失败,找不到合约:{vt_symbol}", algo)
|
||||
|
||||
return contract
|
||||
|
||||
def write_log(self, msg: str, algo: AlgoTemplate = None):
|
||||
""""""
|
||||
if algo:
|
||||
msg = f"{algo.algo_name}:{msg}"
|
||||
|
||||
event = Event(EVENT_ALGO_LOG)
|
||||
event.data = msg
|
||||
self.event_engine.put(event)
|
||||
|
||||
def put_setting_event(self, setting_name: str, setting: dict):
|
||||
""""""
|
||||
event = Event(EVENT_ALGO_SETTING)
|
||||
event.data = {
|
||||
"setting_name": setting_name,
|
||||
"setting": setting
|
||||
}
|
||||
self.event_engine.put(event)
|
||||
|
||||
def update_algo_setting(self, setting_name: str, setting: dict):
|
||||
""""""
|
||||
self.algo_settings[setting_name] = setting
|
||||
|
||||
self.save_algo_setting()
|
||||
|
||||
self.put_setting_event(setting_name, setting)
|
||||
|
||||
def remove_algo_setting(self, setting_name: str):
|
||||
""""""
|
||||
if setting_name not in self.algo_settings:
|
||||
return
|
||||
self.algo_settings.pop(setting_name)
|
||||
|
||||
event = Event(EVENT_ALGO_SETTING)
|
||||
event.data = {
|
||||
"setting_name": setting_name,
|
||||
"setting": None
|
||||
}
|
||||
self.event_engine.put(event)
|
||||
|
||||
self.save_algo_setting()
|
||||
|
||||
def put_parameters_event(self, algo: AlgoTemplate, parameters: dict):
|
||||
""""""
|
||||
event = Event(EVENT_ALGO_PARAMETERS)
|
||||
event.data = {
|
||||
"algo_name": algo.algo_name,
|
||||
"parameters": parameters
|
||||
}
|
||||
self.event_engine.put(event)
|
||||
|
||||
def put_variables_event(self, algo: AlgoTemplate, variables: dict):
|
||||
""""""
|
||||
event = Event(EVENT_ALGO_VARIABLES)
|
||||
event.data = {
|
||||
"algo_name": algo.algo_name,
|
||||
"variables": variables
|
||||
}
|
||||
self.event_engine.put(event)
|
||||
|
@ -1,30 +1,38 @@
|
||||
from vnpy.trader.engine import BaseEngine
|
||||
from vnpy.trader.object import TickData, OrderData, TradeData
|
||||
from vnpy.trader.constant import OrderType, Offset
|
||||
from vnpy.trader.constant import OrderType, Offset, Direction
|
||||
|
||||
|
||||
class AlgoTemplate:
|
||||
""""""
|
||||
count = 0
|
||||
|
||||
_count = 0
|
||||
display_name = ""
|
||||
default_setting = {}
|
||||
variables = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
algo_engine: BaseEngine,
|
||||
self,
|
||||
algo_engine: BaseEngine,
|
||||
algo_name: str,
|
||||
setting: dict
|
||||
):
|
||||
"""Constructor"""
|
||||
self.algo_engine = algo_engine
|
||||
self.algo_name = algo_name
|
||||
|
||||
self.active = False
|
||||
self.active_orders = {} # vt_orderid:order
|
||||
|
||||
@staticmethod
|
||||
def new(cls, algo_engine:BaseEngine, setting: dict):
|
||||
self.active = False
|
||||
self.active_orders = {} # vt_orderid:order
|
||||
|
||||
self.variables.insert(0, "active")
|
||||
|
||||
@classmethod
|
||||
def new(cls, algo_engine: BaseEngine, setting: dict):
|
||||
"""Create new algo instance"""
|
||||
cls.count += 1
|
||||
algo_name = f"{cls.__name__}_{cls.count}"
|
||||
cls._count += 1
|
||||
algo_name = f"{cls.__name__}_{cls._count}"
|
||||
algo = cls(algo_engine, algo_name, setting)
|
||||
return algo
|
||||
|
||||
def update_tick(self, tick: TickData):
|
||||
""""""
|
||||
@ -38,27 +46,27 @@ class AlgoTemplate:
|
||||
self.active_orders[order.vt_orderid] = order
|
||||
elif order.vt_orderid in self.active_orders:
|
||||
self.active_orders.pop(order.vt_orderid)
|
||||
|
||||
|
||||
self.on_order(order)
|
||||
|
||||
|
||||
def update_trade(self, trade: TradeData):
|
||||
""""""
|
||||
if self.active:
|
||||
self.on_trade(trade)
|
||||
|
||||
|
||||
def update_timer(self):
|
||||
""""""
|
||||
if self.active:
|
||||
self.on_timer()
|
||||
|
||||
|
||||
def on_start(self):
|
||||
""""""
|
||||
pass
|
||||
|
||||
|
||||
def on_stop(self):
|
||||
""""""
|
||||
pass
|
||||
|
||||
|
||||
def on_tick(self, tick: TickData):
|
||||
""""""
|
||||
pass
|
||||
@ -66,58 +74,106 @@ class AlgoTemplate:
|
||||
def on_order(self, order: OrderData):
|
||||
""""""
|
||||
pass
|
||||
|
||||
|
||||
def on_trade(self, trade: TradeData):
|
||||
""""""
|
||||
pass
|
||||
|
||||
|
||||
def on_timer(self):
|
||||
""""""
|
||||
pass
|
||||
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
""""""
|
||||
pass
|
||||
|
||||
self.active = True
|
||||
self.on_start()
|
||||
self.put_variables_event()
|
||||
|
||||
def stop(self):
|
||||
""""""
|
||||
pass
|
||||
|
||||
self.active = False
|
||||
self.cancel_all()
|
||||
self.on_stop()
|
||||
self.put_variables_event()
|
||||
|
||||
def subscribe(self, vt_symbol):
|
||||
""""""
|
||||
self.algo_engine.subscribe(self, vt_symbol)
|
||||
|
||||
def buy(
|
||||
self,
|
||||
vt_symbol,
|
||||
price,
|
||||
volume,
|
||||
self,
|
||||
vt_symbol,
|
||||
price,
|
||||
volume,
|
||||
order_type: OrderType = OrderType.LIMIT,
|
||||
offset: Offset = Offset.NONE
|
||||
):
|
||||
""""""
|
||||
return self.algo_engine.buy(
|
||||
return self.algo_engine.send_order(
|
||||
self,
|
||||
vt_symbol,
|
||||
Direction.LONG,
|
||||
price,
|
||||
volume,
|
||||
order_type,
|
||||
offset
|
||||
)
|
||||
|
||||
|
||||
def sell(
|
||||
self,
|
||||
vt_symbol,
|
||||
price,
|
||||
volume,
|
||||
self,
|
||||
vt_symbol,
|
||||
price,
|
||||
volume,
|
||||
order_type: OrderType = OrderType.LIMIT,
|
||||
offset: Offset = Offset.NONE
|
||||
):
|
||||
""""""
|
||||
return self.algo_engine.buy(
|
||||
return self.algo_engine.send_order(
|
||||
self,
|
||||
vt_symbol,
|
||||
Direction.SHORT,
|
||||
price,
|
||||
volume,
|
||||
order_type,
|
||||
offset
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
)
|
||||
|
||||
def cancel_order(self, vt_orderid: str):
|
||||
""""""
|
||||
self.algo_engine.cancel_order(self, vt_orderid)
|
||||
|
||||
def cancel_all(self):
|
||||
""""""
|
||||
if not self.active_orders:
|
||||
return
|
||||
|
||||
for vt_orderid in self.active_orders.keys():
|
||||
self.cancel_order(vt_orderid)
|
||||
|
||||
def get_tick(self, vt_symbol: str):
|
||||
""""""
|
||||
return self.algo_engine.get_tick(self, vt_symbol)
|
||||
|
||||
def get_contract(self, vt_symbol: str):
|
||||
""""""
|
||||
return self.algo_engine.get_contract(self, vt_symbol)
|
||||
|
||||
def write_log(self, msg: str):
|
||||
""""""
|
||||
self.algo_engine.write_log(msg, self)
|
||||
|
||||
def put_parameters_event(self):
|
||||
""""""
|
||||
parameters = {}
|
||||
for name in self.default_setting.keys():
|
||||
parameters[name] = getattr(self, name)
|
||||
|
||||
self.algo_engine.put_parameters_event(self, parameters)
|
||||
|
||||
def put_variables_event(self):
|
||||
""""""
|
||||
variables = {}
|
||||
for name in self.variables:
|
||||
variables[name] = getattr(self, name)
|
||||
|
||||
self.algo_engine.put_variables_event(self, variables)
|
||||
|
1
vnpy/app/algo_trading/ui/__init__.py
Normal file
1
vnpy/app/algo_trading/ui/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .widget import AlgoManager
|
15
vnpy/app/algo_trading/ui/display.py
Normal file
15
vnpy/app/algo_trading/ui/display.py
Normal file
@ -0,0 +1,15 @@
|
||||
NAME_DISPLAY_MAP = {
|
||||
"vt_symbol": "本地代码",
|
||||
"direction": "方向",
|
||||
"price": "价格",
|
||||
"volume": "数量",
|
||||
"time": "执行时间(秒)",
|
||||
"interval": "每轮间隔(秒)",
|
||||
"offset": "开平",
|
||||
"active": "算法状态",
|
||||
"traded": "成交数量",
|
||||
"order_volume": "单笔委托",
|
||||
"timer_count": "本轮读秒",
|
||||
"total_count": "累计读秒",
|
||||
"template_name": "算法模板"
|
||||
}
|
@ -0,0 +1,571 @@
|
||||
"""
|
||||
Widget for algo trading.
|
||||
"""
|
||||
|
||||
from functools import partial
|
||||
from datetime import datetime
|
||||
|
||||
from vnpy.event import EventEngine, Event
|
||||
from vnpy.trader.engine import MainEngine
|
||||
from vnpy.trader.ui import QtWidgets, QtCore
|
||||
|
||||
from ..engine import (
|
||||
AlgoEngine,
|
||||
AlgoTemplate,
|
||||
APP_NAME,
|
||||
EVENT_ALGO_LOG,
|
||||
EVENT_ALGO_PARAMETERS,
|
||||
EVENT_ALGO_VARIABLES,
|
||||
EVENT_ALGO_SETTING
|
||||
)
|
||||
from .display import NAME_DISPLAY_MAP
|
||||
|
||||
|
||||
class AlgoWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
Start connection of a certain gateway.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
algo_engine: AlgoEngine,
|
||||
algo_template: AlgoTemplate
|
||||
):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.algo_engine = algo_engine
|
||||
self.template_name = algo_template.__name__
|
||||
self.default_setting = algo_template.default_setting
|
||||
|
||||
self.widgets = {}
|
||||
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
"""
|
||||
Initialize line edits and form layout based on setting.
|
||||
"""
|
||||
self.setMaximumWidth(400)
|
||||
|
||||
form = QtWidgets.QFormLayout()
|
||||
|
||||
for field_name, field_value in self.default_setting.items():
|
||||
field_type = type(field_value)
|
||||
|
||||
if field_type == list:
|
||||
widget = QtWidgets.QComboBox()
|
||||
widget.addItems(field_value)
|
||||
else:
|
||||
widget = QtWidgets.QLineEdit()
|
||||
|
||||
display_name = NAME_DISPLAY_MAP.get(field_name, field_name)
|
||||
|
||||
form.addRow(display_name, widget)
|
||||
self.widgets[field_name] = (widget, field_type)
|
||||
|
||||
start_algo_button = QtWidgets.QPushButton("启动算法")
|
||||
start_algo_button.clicked.connect(self.start_algo)
|
||||
form.addRow(start_algo_button)
|
||||
|
||||
form.addRow(QtWidgets.QLabel(""))
|
||||
|
||||
self.setting_name_line = QtWidgets.QLineEdit()
|
||||
form.addRow("配置名称", self.setting_name_line)
|
||||
|
||||
save_setting_button = QtWidgets.QPushButton("保存配置")
|
||||
save_setting_button.clicked.connect(self.save_setting)
|
||||
form.addRow(save_setting_button)
|
||||
|
||||
self.setLayout(form)
|
||||
|
||||
def get_setting(self):
|
||||
"""
|
||||
Get setting value from line edits.
|
||||
"""
|
||||
setting = {"template_name": self.template_name}
|
||||
|
||||
for field_name, tp in self.widgets.items():
|
||||
widget, field_type = tp
|
||||
if field_type == list:
|
||||
field_value = str(widget.currentText())
|
||||
else:
|
||||
try:
|
||||
field_value = field_type(widget.text())
|
||||
except ValueError:
|
||||
display_name = NAME_DISPLAY_MAP.get(field_name, field_name)
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"参数错误",
|
||||
f"{display_name}参数类型应为{field_type},请检查!"
|
||||
)
|
||||
return None
|
||||
|
||||
setting[field_name] = field_value
|
||||
|
||||
return setting
|
||||
|
||||
def start_algo(self):
|
||||
"""
|
||||
Start algo trading.
|
||||
"""
|
||||
setting = self.get_setting()
|
||||
if setting:
|
||||
self.algo_engine.start_algo(setting)
|
||||
|
||||
def update_setting(self, setting_name: str, setting: dict):
|
||||
"""
|
||||
Update setting into widgets.
|
||||
"""
|
||||
self.setting_name_line.setText(setting_name)
|
||||
|
||||
for name, tp in self.widgets.items():
|
||||
widget, _ = tp
|
||||
value = setting[name]
|
||||
|
||||
if isinstance(widget, QtWidgets.QLineEdit):
|
||||
widget.setText(str(value))
|
||||
elif isinstance(widget, QtWidgets.QComboBox):
|
||||
ix = widget.findText(value)
|
||||
widget.setCurrentIndex(ix)
|
||||
|
||||
def save_setting(self):
|
||||
"""
|
||||
Save algo setting
|
||||
"""
|
||||
setting_name = self.setting_name_line.text()
|
||||
if not setting_name:
|
||||
return
|
||||
|
||||
setting = self.get_setting()
|
||||
if setting:
|
||||
self.algo_engine.update_algo_setting(setting_name, setting)
|
||||
|
||||
|
||||
class AlgoMonitor(QtWidgets.QTableWidget):
|
||||
""""""
|
||||
parameters_signal = QtCore.pyqtSignal(Event)
|
||||
variables_signal = QtCore.pyqtSignal(Event)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
algo_engine: AlgoEngine,
|
||||
event_engine: EventEngine,
|
||||
mode_active: bool
|
||||
):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.algo_engine = algo_engine
|
||||
self.event_engine = event_engine
|
||||
self.mode_active = mode_active
|
||||
|
||||
self.algo_cells = {}
|
||||
|
||||
self.init_ui()
|
||||
self.register_event()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
labels = [
|
||||
"",
|
||||
"算法",
|
||||
"参数",
|
||||
"状态"
|
||||
]
|
||||
self.setColumnCount(len(labels))
|
||||
self.setHorizontalHeaderLabels(labels)
|
||||
self.verticalHeader().setVisible(False)
|
||||
self.setEditTriggers(self.NoEditTriggers)
|
||||
|
||||
self.verticalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents
|
||||
)
|
||||
|
||||
for column in range(2, 4):
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
column,
|
||||
QtWidgets.QHeaderView.Stretch
|
||||
)
|
||||
self.setWordWrap(True)
|
||||
|
||||
if not self.mode_active:
|
||||
self.hideColumn(0)
|
||||
|
||||
def register_event(self):
|
||||
""""""
|
||||
self.parameters_signal.connect(self.process_parameters_event)
|
||||
self.variables_signal.connect(self.process_variables_event)
|
||||
|
||||
self.event_engine.register(
|
||||
EVENT_ALGO_PARAMETERS, self.parameters_signal.emit)
|
||||
self.event_engine.register(
|
||||
EVENT_ALGO_VARIABLES, self.variables_signal.emit)
|
||||
|
||||
def process_parameters_event(self, event):
|
||||
""""""
|
||||
data = event.data
|
||||
algo_name = data["algo_name"]
|
||||
parameters = data["parameters"]
|
||||
|
||||
cells = self.get_algo_cells(algo_name)
|
||||
text = to_text(parameters)
|
||||
cells["parameters"].setText(text)
|
||||
|
||||
def process_variables_event(self, event):
|
||||
""""""
|
||||
data = event.data
|
||||
algo_name = data["algo_name"]
|
||||
variables = data["variables"]
|
||||
|
||||
cells = self.get_algo_cells(algo_name)
|
||||
variables_cell = cells["variables"]
|
||||
text = to_text(variables)
|
||||
variables_cell.setText(text)
|
||||
|
||||
row = self.row(variables_cell)
|
||||
active = variables["active"]
|
||||
|
||||
if self.mode_active:
|
||||
if active:
|
||||
self.showRow(row)
|
||||
else:
|
||||
self.hideRow(row)
|
||||
else:
|
||||
if active:
|
||||
self.hideRow(row)
|
||||
else:
|
||||
self.showRow(row)
|
||||
|
||||
def stop_algo(self, algo_name: str):
|
||||
""""""
|
||||
self.algo_engine.stop_algo(algo_name)
|
||||
|
||||
def get_algo_cells(self, algo_name: str):
|
||||
""""""
|
||||
cells = self.algo_cells.get(algo_name, None)
|
||||
|
||||
if not cells:
|
||||
stop_func = partial(self.stop_algo, algo_name=algo_name)
|
||||
stop_button = QtWidgets.QPushButton("停止")
|
||||
stop_button.clicked.connect(stop_func)
|
||||
|
||||
name_cell = QtWidgets.QTableWidgetItem(algo_name)
|
||||
parameters_cell = QtWidgets.QTableWidgetItem()
|
||||
variables_cell = QtWidgets.QTableWidgetItem()
|
||||
|
||||
self.insertRow(0)
|
||||
self.setCellWidget(0, 0, stop_button)
|
||||
self.setItem(0, 1, name_cell)
|
||||
self.setItem(0, 2, parameters_cell)
|
||||
self.setItem(0, 3, variables_cell)
|
||||
|
||||
cells = {
|
||||
"name": name_cell,
|
||||
"parameters": parameters_cell,
|
||||
"variables": variables_cell
|
||||
}
|
||||
self.algo_cells[algo_name] = cells
|
||||
|
||||
return cells
|
||||
|
||||
|
||||
class ActiveAlgoMonitor(AlgoMonitor):
|
||||
"""
|
||||
Monitor for active algos.
|
||||
"""
|
||||
|
||||
def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super().__init__(algo_engine, event_engine, True)
|
||||
|
||||
|
||||
class InactiveAlgoMonitor(AlgoMonitor):
|
||||
"""
|
||||
Monitor for inactive algos.
|
||||
"""
|
||||
|
||||
def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super().__init__(algo_engine, event_engine, False)
|
||||
|
||||
|
||||
class SettingMonitor(QtWidgets.QTableWidget):
|
||||
""""""
|
||||
setting_signal = QtCore.pyqtSignal(Event)
|
||||
use_signal = QtCore.pyqtSignal(dict)
|
||||
|
||||
def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.algo_engine = algo_engine
|
||||
self.event_engine = event_engine
|
||||
|
||||
self.settings = {}
|
||||
self.setting_cells = {}
|
||||
|
||||
self.init_ui()
|
||||
self.register_event()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
labels = [
|
||||
"",
|
||||
"",
|
||||
"名称",
|
||||
"配置"
|
||||
]
|
||||
self.setColumnCount(len(labels))
|
||||
self.setHorizontalHeaderLabels(labels)
|
||||
self.verticalHeader().setVisible(False)
|
||||
self.setEditTriggers(self.NoEditTriggers)
|
||||
|
||||
self.verticalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents
|
||||
)
|
||||
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
3,
|
||||
QtWidgets.QHeaderView.Stretch
|
||||
)
|
||||
self.setWordWrap(True)
|
||||
|
||||
def register_event(self):
|
||||
""""""
|
||||
self.setting_signal.connect(self.process_setting_event)
|
||||
|
||||
self.event_engine.register(
|
||||
EVENT_ALGO_SETTING, self.setting_signal.emit)
|
||||
|
||||
def process_setting_event(self, event):
|
||||
""""""
|
||||
data = event.data
|
||||
setting_name = data["setting_name"]
|
||||
setting = data["setting"]
|
||||
cells = self.get_setting_cells(setting_name)
|
||||
|
||||
if setting:
|
||||
self.settings[setting_name] = setting
|
||||
|
||||
cells["setting"].setText(to_text(setting))
|
||||
else:
|
||||
if setting_name in self.settings:
|
||||
self.settings.pop(setting_name)
|
||||
|
||||
row = self.row(cells["setting"])
|
||||
self.removeRow(row)
|
||||
|
||||
self.setting_cells.pop(setting_name)
|
||||
|
||||
def get_setting_cells(self, setting_name: str):
|
||||
""""""
|
||||
cells = self.setting_cells.get(setting_name, None)
|
||||
|
||||
if not cells:
|
||||
use_func = partial(self.use_setting, setting_name=setting_name)
|
||||
use_button = QtWidgets.QPushButton("使用")
|
||||
use_button.clicked.connect(use_func)
|
||||
|
||||
remove_func = partial(self.remove_setting,
|
||||
setting_name=setting_name)
|
||||
remove_button = QtWidgets.QPushButton("移除")
|
||||
remove_button.clicked.connect(remove_func)
|
||||
|
||||
name_cell = QtWidgets.QTableWidgetItem(setting_name)
|
||||
setting_cell = QtWidgets.QTableWidgetItem()
|
||||
|
||||
self.insertRow(0)
|
||||
self.setCellWidget(0, 0, use_button)
|
||||
self.setCellWidget(0, 1, remove_button)
|
||||
self.setItem(0, 2, name_cell)
|
||||
self.setItem(0, 3, setting_cell)
|
||||
|
||||
cells = {
|
||||
"name": name_cell,
|
||||
"setting": setting_cell
|
||||
}
|
||||
self.setting_cells[setting_name] = cells
|
||||
|
||||
return cells
|
||||
|
||||
def use_setting(self, setting_name: str):
|
||||
""""""
|
||||
setting = self.settings[setting_name]
|
||||
setting["setting_name"] = setting_name
|
||||
self.use_signal.emit(setting)
|
||||
|
||||
def remove_setting(self, setting_name: str):
|
||||
""""""
|
||||
self.algo_engine.remove_algo_setting(setting_name)
|
||||
|
||||
|
||||
class LogMonitor(QtWidgets.QTableWidget):
|
||||
""""""
|
||||
signal = QtCore.pyqtSignal(Event)
|
||||
|
||||
def __init__(self, event_engine: EventEngine):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.event_engine = event_engine
|
||||
|
||||
self.init_ui()
|
||||
self.register_event()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
labels = [
|
||||
"时间",
|
||||
"信息"
|
||||
]
|
||||
self.setColumnCount(len(labels))
|
||||
self.setHorizontalHeaderLabels(labels)
|
||||
self.verticalHeader().setVisible(False)
|
||||
self.setEditTriggers(self.NoEditTriggers)
|
||||
|
||||
self.verticalHeader().setSectionResizeMode(
|
||||
QtWidgets.QHeaderView.ResizeToContents
|
||||
)
|
||||
|
||||
self.horizontalHeader().setSectionResizeMode(
|
||||
1,
|
||||
QtWidgets.QHeaderView.Stretch
|
||||
)
|
||||
self.setWordWrap(True)
|
||||
|
||||
def register_event(self):
|
||||
""""""
|
||||
self.signal.connect(self.process_log_event)
|
||||
|
||||
self.event_engine.register(EVENT_ALGO_LOG, self.signal.emit)
|
||||
|
||||
def process_log_event(self, event):
|
||||
""""""
|
||||
msg = event.data
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
timestamp_cell = QtWidgets.QTableWidgetItem(timestamp)
|
||||
msg_cell = QtWidgets.QTableWidgetItem(msg)
|
||||
|
||||
self.insertRow(0)
|
||||
self.setItem(0, 0, timestamp_cell)
|
||||
self.setItem(0, 1, msg_cell)
|
||||
|
||||
|
||||
class AlgoManager(QtWidgets.QWidget):
|
||||
""""""
|
||||
|
||||
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.main_engine = main_engine
|
||||
self.event_engine = event_engine
|
||||
self.algo_engine = main_engine.get_engine(APP_NAME)
|
||||
|
||||
self.algo_widgets = {}
|
||||
|
||||
self.init_ui()
|
||||
self.algo_engine.init_engine()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
self.setWindowTitle("算法交易")
|
||||
|
||||
# Left side control widgets
|
||||
self.template_combo = QtWidgets.QComboBox()
|
||||
self.template_combo.currentIndexChanged.connect(self.show_algo_widget)
|
||||
|
||||
form = QtWidgets.QFormLayout()
|
||||
form.addRow("算法", self.template_combo)
|
||||
widget = QtWidgets.QWidget()
|
||||
widget.setLayout(form)
|
||||
|
||||
vbox = QtWidgets.QVBoxLayout()
|
||||
vbox.addWidget(widget)
|
||||
|
||||
for algo_template in self.algo_engine.algo_templates.values():
|
||||
widget = AlgoWidget(self.algo_engine, algo_template)
|
||||
vbox.addWidget(widget)
|
||||
|
||||
template_name = algo_template.__name__
|
||||
display_name = algo_template.display_name
|
||||
|
||||
self.algo_widgets[template_name] = widget
|
||||
self.template_combo.addItem(display_name, template_name)
|
||||
|
||||
vbox.addStretch()
|
||||
|
||||
stop_all_button = QtWidgets.QPushButton("全部停止")
|
||||
stop_all_button.setFixedHeight(stop_all_button.sizeHint().height() * 2)
|
||||
stop_all_button.clicked.connect(self.algo_engine.stop_all)
|
||||
|
||||
vbox.addWidget(stop_all_button)
|
||||
|
||||
# Right side monitor widgets
|
||||
active_algo_monitor = ActiveAlgoMonitor(
|
||||
self.algo_engine, self.event_engine
|
||||
)
|
||||
inactive_algo_monitor = InactiveAlgoMonitor(
|
||||
self.algo_engine, self.event_engine
|
||||
)
|
||||
tab1 = QtWidgets.QTabWidget()
|
||||
tab1.addTab(active_algo_monitor, "执行中")
|
||||
tab1.addTab(inactive_algo_monitor, "已结束")
|
||||
|
||||
log_monitor = LogMonitor(self.event_engine)
|
||||
tab2 = QtWidgets.QTabWidget()
|
||||
tab2.addTab(log_monitor, "日志")
|
||||
|
||||
setting_monitor = SettingMonitor(self.algo_engine, self.event_engine)
|
||||
setting_monitor.use_signal.connect(self.use_setting)
|
||||
tab3 = QtWidgets.QTabWidget()
|
||||
tab3.addTab(setting_monitor, "配置")
|
||||
|
||||
grid = QtWidgets.QGridLayout()
|
||||
grid.addWidget(tab1, 0, 0, 1, 2)
|
||||
grid.addWidget(tab2, 1, 0)
|
||||
grid.addWidget(tab3, 1, 1)
|
||||
|
||||
hbox2 = QtWidgets.QHBoxLayout()
|
||||
hbox2.addLayout(vbox)
|
||||
hbox2.addLayout(grid)
|
||||
self.setLayout(hbox2)
|
||||
|
||||
self.show_algo_widget()
|
||||
|
||||
def show_algo_widget(self):
|
||||
""""""
|
||||
ix = self.template_combo.currentIndex()
|
||||
current_name = self.template_combo.itemData(ix)
|
||||
|
||||
for template_name, widget in self.algo_widgets.items():
|
||||
if template_name == current_name:
|
||||
widget.show()
|
||||
else:
|
||||
widget.hide()
|
||||
|
||||
def use_setting(self, setting: dict):
|
||||
""""""
|
||||
setting_name = setting["setting_name"]
|
||||
template_name = setting["template_name"]
|
||||
|
||||
widget = self.algo_widgets[template_name]
|
||||
widget.update_setting(setting_name, setting)
|
||||
|
||||
def show(self):
|
||||
""""""
|
||||
self.showMaximized()
|
||||
|
||||
|
||||
def to_text(data: dict):
|
||||
"""
|
||||
Convert dict data into string.
|
||||
"""
|
||||
buf = []
|
||||
for key, value in data.items():
|
||||
key = NAME_DISPLAY_MAP.get(key, key)
|
||||
buf.append(f"{key}:{value}")
|
||||
text = ",".join(buf)
|
||||
return text
|
@ -40,6 +40,7 @@ from vnpy.trader.database import DbTickData, DbBarData
|
||||
from vnpy.trader.setting import SETTINGS
|
||||
|
||||
from .base import (
|
||||
APP_NAME,
|
||||
EVENT_CTA_LOG,
|
||||
EVENT_CTA_STRATEGY,
|
||||
EVENT_CTA_STOPORDER,
|
||||
@ -73,7 +74,7 @@ class CtaEngine(BaseEngine):
|
||||
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super(CtaEngine, self).__init__(
|
||||
main_engine, event_engine, "CtaStrategy")
|
||||
main_engine, event_engine, APP_NAME)
|
||||
|
||||
self.strategy_setting = {} # strategy_name: dict
|
||||
self.strategy_data = {} # strategy_name: dict
|
||||
|
Loading…
Reference in New Issue
Block a user