diff --git a/CHANGELOG.md b/CHANGELOG.md index e19d998..f8e241a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 1.0.3版本 + +1. 调整模块设计(模板、引擎),只支持单合约算法执行交易 +2. 调整算法模板,增加默认参数和变量字段,以及对应函数传参 +3. 优化算法状态控制,增加状态枚举值,算法支持暂停和恢复运行 +4. 移除DMA算法 +5. 移除算法配置缓存和加载功能 +6. 调整图形界面,优化算法状态信息显示 + # 1.0.2版本 1. 将模块的图标文件信息,改为完整路径字符串 diff --git a/README.md b/README.md index 3abdfca..323e290 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

- + diff --git a/setup.cfg b/setup.cfg index 4b7eca4..6a335ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = vnpy_algotrading -version = 1.0.2 +version = 1.0.3 url = https://www.vnpy.com license = MIT author = Xiaoyou Chen diff --git a/vnpy_algotrading/__init__.py b/vnpy_algotrading/__init__.py index c7da03a..83803b1 100644 --- a/vnpy_algotrading/__init__.py +++ b/vnpy_algotrading/__init__.py @@ -27,7 +27,6 @@ from vnpy.trader.app import BaseApp from .engine import AlgoEngine, APP_NAME -from .template import AlgoTemplate try: diff --git a/vnpy_algotrading/algos/best_limit_algo.py b/vnpy_algotrading/algos/best_limit_algo.py index 52fd90f..d72b59f 100644 --- a/vnpy_algotrading/algos/best_limit_algo.py +++ b/vnpy_algotrading/algos/best_limit_algo.py @@ -1,163 +1,121 @@ from random import uniform -from vnpy.trader.constant import Offset, Direction +from vnpy.trader.constant import Direction from vnpy.trader.object import TradeData, OrderData, TickData from vnpy.trader.engine import BaseEngine -from vnpy.trader.utility import round_to from ..template import AlgoTemplate class BestLimitAlgo(AlgoTemplate): - """""" - - display_name = "BestLimit 最优限价" - - default_setting = { - "vt_symbol": "", - "direction": [Direction.LONG.value, Direction.SHORT.value], - "volume": 0.0, - "min_volume": 0.0, - "max_volume": 0.0, - "volume_change": [ - "1", - "0.1", - "0.01", - "0.001", - "0.0001", - "0.00001" - ], - "offset": [ - Offset.NONE.value, - Offset.OPEN.value, - Offset.CLOSE.value, - Offset.CLOSETODAY.value, - Offset.CLOSEYESTERDAY.value - ] + """最优限价算法类""" + + display_name: str = "BestLimit 最优限价" + + default_setting: dict = { + "min_volume": 0, + "max_volume": 0, } - variables = [ - "traded", + variables: list = [ "vt_orderid", - "order_price", - "last_tick", + "order_price" ] def __init__( self, algo_engine: BaseEngine, algo_name: str, + vt_symbol: str, + direction: str, + offset: str, + price: float, + volume: float, setting: dict - ): - """""" - super().__init__(algo_engine, algo_name, setting) + ) -> None: + """构造函数""" + super().__init__(algo_engine, algo_name, vt_symbol, direction, offset, price, volume, setting) # 参数 - self.vt_symbol = setting["vt_symbol"] - self.direction = Direction(setting["direction"]) - self.volume = setting["volume"] - self.offset = Offset(setting["offset"]) - - self.min_volume = setting["min_volume"] - self.max_volume = setting["max_volume"] - - if "." in setting["volume_change"]: - self.volume_change = float(setting["volume_change"]) - else: - self.volume_change = int(setting["volume_change"]) + self.min_volume: float = setting["min_volume"] + self.max_volume: float = setting["max_volume"] # 变量 - self.vt_orderid = "" - self.traded = 0 - self.last_tick = None - self.order_price = 0 + self.vt_orderid: str = "" + self.order_price: float = 0 - self.put_parameters_event() - self.put_variables_event() + self.put_event() # 检查最大/最小挂单量 if self.min_volume <= 0: self.write_log("最小挂单量必须大于0,算法启动失败") - self.stop() + self.finish() return if self.max_volume < self.min_volume: self.write_log("最大挂单量必须不小于最小委托量,算法启动失败") - self.stop() + self.finish() return - self.subscribe(self.vt_symbol) - - def on_tick(self, tick: TickData): - """""" - self.last_tick = tick - + def on_tick(self, tick: TickData) -> None: + """Tick行情回调""" if self.direction == Direction.LONG: if not self.vt_orderid: - self.buy_best_limit() - elif self.order_price != self.last_tick.bid_price_1: + self.buy_best_limit(tick.bid_price_1) + elif self.order_price != tick.bid_price_1: self.cancel_all() else: if not self.vt_orderid: - self.sell_best_limit() - elif self.order_price != self.last_tick.ask_price_1: + self.sell_best_limit(tick.ask_price_1) + elif self.order_price != tick.ask_price_1: self.cancel_all() - self.put_variables_event() - - def on_trade(self, trade: TradeData): - """""" - self.traded += trade.volume + self.put_event() + def on_trade(self, trade: TradeData) -> None: + """成交回调""" if self.traded >= self.volume: self.write_log(f"已交易数量:{self.traded},总数量:{self.volume}") - self.stop() + self.finish() else: - self.put_variables_event() + self.put_event() - def on_order(self, order: OrderData): - """""" + def on_order(self, order: OrderData) -> None: + """委托回调""" if not order.is_active(): self.vt_orderid = "" self.order_price = 0 - self.put_variables_event() + self.put_event() - def buy_best_limit(self): - """""" - volume_left = self.volume - self.traded + def buy_best_limit(self, bid_price_1: float) -> None: + """最优限价买入""" + volume_left: float = self.volume - self.traded - rand_volume = self.generate_rand_volume() - order_volume = min(rand_volume, volume_left) + rand_volume: int = self.generate_rand_volume() + order_volume: float = min(rand_volume, volume_left) - self.order_price = self.last_tick.bid_price_1 + self.order_price = bid_price_1 self.vt_orderid = self.buy( - self.vt_symbol, self.order_price, order_volume, offset=self.offset ) - def sell_best_limit(self): - """""" - volume_left = self.volume - self.traded + def sell_best_limit(self, ask_price_1: float) -> None: + """最优限价卖出""" + volume_left: float = self.volume - self.traded - rand_volume = self.generate_rand_volume() - order_volume = min(rand_volume, volume_left) + rand_volume: int = self.generate_rand_volume() + order_volume: float = min(rand_volume, volume_left) - self.order_price = self.last_tick.ask_price_1 + self.order_price = ask_price_1 self.vt_orderid = self.sell( - self.vt_symbol, self.order_price, order_volume, offset=self.offset ) - def generate_rand_volume(self): - """""" - rand_volume = uniform(self.min_volume, self.max_volume) - rand_volume = round_to(rand_volume, self.volume_change) - - if self.volume_change == 1: - rand_volume = int(rand_volume) - - return rand_volume + def generate_rand_volume(self) -> int: + """随机生成委托数量""" + rand_volume: float = uniform(self.min_volume, self.max_volume) + return int(rand_volume) diff --git a/vnpy_algotrading/algos/dma_algo.py b/vnpy_algotrading/algos/dma_algo.py deleted file mode 100644 index 79aca34..0000000 --- a/vnpy_algotrading/algos/dma_algo.py +++ /dev/null @@ -1,99 +0,0 @@ -from vnpy.trader.constant import Offset, Direction, OrderType -from vnpy.trader.object import TradeData, OrderData, TickData -from vnpy.trader.engine import BaseEngine - -from ..template import AlgoTemplate - - -class DmaAlgo(AlgoTemplate): - """""" - - display_name = "DMA 直接委托" - - default_setting = { - "vt_symbol": "", - "direction": [Direction.LONG.value, Direction.SHORT.value], - "order_type": [ - OrderType.MARKET.value, - OrderType.LIMIT.value, - OrderType.STOP.value, - OrderType.FAK.value, - OrderType.FOK.value - ], - "price": 0.0, - "volume": 0.0, - "offset": [ - Offset.NONE.value, - Offset.OPEN.value, - Offset.CLOSE.value, - Offset.CLOSETODAY.value, - Offset.CLOSEYESTERDAY.value - ] - } - - variables = [ - "traded", - "vt_orderid", - "order_status", - ] - - def __init__( - self, - algo_engine: BaseEngine, - algo_name: str, - setting: dict - ): - """""" - super().__init__(algo_engine, algo_name, setting) - - # 参数 - self.vt_symbol = setting["vt_symbol"] - self.direction = Direction(setting["direction"]) - self.order_type = OrderType(setting["order_type"]) - self.price = setting["price"] - self.volume = setting["volume"] - self.offset = Offset(setting["offset"]) - - # 变量 - self.vt_orderid = "" - self.traded = 0 - self.order_status = "" - - self.subscribe(self.vt_symbol) - self.put_parameters_event() - self.put_variables_event() - - def on_tick(self, tick: TickData): - """""" - if not self.vt_orderid: - if self.direction == Direction.LONG: - self.vt_orderid = self.buy( - self.vt_symbol, - self.price, - self.volume, - self.order_type, - self.offset - ) - - else: - self.vt_orderid = self.sell( - self.vt_symbol, - self.price, - self.volume, - self.order_type, - self.offset - ) - self.put_variables_event() - - def on_order(self, order: OrderData): - """""" - self.traded = order.traded - self.order_status = order.status - - if not order.is_active(): - self.stop() - self.put_variables_event() - - def on_trade(self, trade: TradeData): - """""" - pass diff --git a/vnpy_algotrading/algos/iceberg_algo.py b/vnpy_algotrading/algos/iceberg_algo.py index 482aeaa..aab54ae 100644 --- a/vnpy_algotrading/algos/iceberg_algo.py +++ b/vnpy_algotrading/algos/iceberg_algo.py @@ -1,4 +1,4 @@ -from vnpy.trader.constant import Offset, Direction +from vnpy.trader.constant import Direction from vnpy.trader.object import TradeData, OrderData, TickData from vnpy.trader.engine import BaseEngine @@ -6,28 +6,16 @@ class IcebergAlgo(AlgoTemplate): - """""" + """冰山算法类""" - display_name = "Iceberg 冰山" + display_name: str = "Iceberg 冰山" - default_setting = { - "vt_symbol": "", - "direction": [Direction.LONG.value, Direction.SHORT.value], - "price": 0.0, - "volume": 0.0, + default_setting: dict = { "display_volume": 0.0, - "interval": 0, - "offset": [ - Offset.NONE.value, - Offset.OPEN.value, - Offset.CLOSE.value, - Offset.CLOSETODAY.value, - Offset.CLOSEYESTERDAY.value - ] + "interval": 0 } - variables = [ - "traded", + variables: list = [ "timer_count", "vt_orderid" ] @@ -36,87 +24,70 @@ def __init__( self, algo_engine: BaseEngine, algo_name: str, + vt_symbol: str, + direction: str, + offset: str, + price: float, + volume: float, setting: dict - ): - """""" - super().__init__(algo_engine, algo_name, setting) + ) -> None: + """构造函数""" + super().__init__(algo_engine, algo_name, vt_symbol, direction, offset, price, volume, setting) # 参数 - self.vt_symbol = setting["vt_symbol"] - self.direction = Direction(setting["direction"]) - self.price = setting["price"] - self.volume = setting["volume"] - self.display_volume = setting["display_volume"] - self.interval = setting["interval"] - self.offset = Offset(setting["offset"]) + self.display_volume: float = setting["display_volume"] + self.interval: int = setting["interval"] # 变量 - self.timer_count = 0 - self.vt_orderid = "" - self.traded = 0 - - self.last_tick = None - - self.subscribe(self.vt_symbol) - self.put_parameters_event() - self.put_variables_event() + self.timer_count: int = 0 + self.vt_orderid: str = "" - def on_stop(self): - """""" - self.write_log("停止算法") + self.put_event() - def on_tick(self, tick: TickData): - """""" - self.last_tick = tick - - def on_order(self, order: OrderData): - """""" - msg = f"委托号:{order.vt_orderid},委托状态:{order.status.value}" + def on_order(self, order: OrderData) -> None: + """委托回调""" + msg: str = f"委托号:{order.vt_orderid},委托状态:{order.status.value}" self.write_log(msg) if not order.is_active(): self.vt_orderid = "" - self.put_variables_event() - - def on_trade(self, trade: TradeData): - """""" - self.traded += trade.volume + self.put_event() + def on_trade(self, trade: TradeData) -> None: + """成交回调""" if self.traded >= self.volume: self.write_log(f"已交易数量:{self.traded},总数量:{self.volume}") - self.stop() + self.finish() else: - self.put_variables_event() + self.put_event() - def on_timer(self): - """""" + def on_timer(self) -> None: + """定时回调""" self.timer_count += 1 if self.timer_count < self.interval: - self.put_variables_event() + self.put_event() return self.timer_count = 0 - contract = self.get_contract(self.vt_symbol) - if not contract: + tick: TickData = self.get_tick() + if not tick: return # 当委托完成后,发起新的委托 if not self.vt_orderid: - order_volume = self.volume - self.traded + order_volume: float = self.volume - self.traded order_volume = min(order_volume, self.display_volume) if self.direction == Direction.LONG: self.vt_orderid = self.buy( - self.vt_symbol, self.price, order_volume, offset=self.offset ) else: self.vt_orderid = self.sell( - self.vt_symbol, self.price, order_volume, offset=self.offset @@ -124,14 +95,14 @@ def on_timer(self): # 否则检查撤单 else: if self.direction == Direction.LONG: - if self.last_tick.ask_price_1 <= self.price: + if tick.ask_price_1 <= self.price: self.cancel_order(self.vt_orderid) self.vt_orderid = "" self.write_log(u"最新Tick卖一价,低于买入委托价格,之前委托可能丢失,强制撤单") else: - if self.last_tick.bid_price_1 >= self.price: + if tick.bid_price_1 >= self.price: self.cancel_order(self.vt_orderid) self.vt_orderid = "" self.write_log(u"最新Tick买一价,高于卖出委托价格,之前委托可能丢失,强制撤单") - self.put_variables_event() + self.put_event() diff --git a/vnpy_algotrading/algos/sniper_algo.py b/vnpy_algotrading/algos/sniper_algo.py index 3feed9a..19ea052 100644 --- a/vnpy_algotrading/algos/sniper_algo.py +++ b/vnpy_algotrading/algos/sniper_algo.py @@ -1,4 +1,4 @@ -from vnpy.trader.constant import Offset, Direction +from vnpy.trader.constant import Direction from vnpy.trader.object import TradeData, OrderData, TickData from vnpy.trader.engine import BaseEngine @@ -6,96 +6,72 @@ class SniperAlgo(AlgoTemplate): - """""" - - display_name = "Sniper 狙击手" - - default_setting = { - "vt_symbol": "", - "direction": [Direction.LONG.value, Direction.SHORT.value], - "price": 0.0, - "volume": 0.0, - "offset": [ - Offset.NONE.value, - Offset.OPEN.value, - Offset.CLOSE.value, - Offset.CLOSETODAY.value, - Offset.CLOSEYESTERDAY.value - ] - } - - variables = [ - "traded", - "vt_orderid" - ] + """狙击手算法类""" + + display_name: str = "Sniper 狙击手" + + default_setting: dict = {} + + variables: list = ["vt_orderid"] def __init__( self, algo_engine: BaseEngine, algo_name: str, + vt_symbol: str, + direction: str, + offset: str, + price: float, + volume: float, setting: dict - ): - """""" - super().__init__(algo_engine, algo_name, setting) - - # 参数 - self.vt_symbol = setting["vt_symbol"] - self.direction = Direction(setting["direction"]) - self.price = setting["price"] - self.volume = setting["volume"] - self.offset = Offset(setting["offset"]) + ) -> None: + """构造函数""" + super().__init__(algo_engine, algo_name, vt_symbol, direction, offset, price, volume, setting) # 变量 self.vt_orderid = "" - self.traded = 0 - self.subscribe(self.vt_symbol) - self.put_parameters_event() - self.put_variables_event() + self.put_event() - def on_tick(self, tick: TickData): - """""" + def on_tick(self, tick: TickData) -> None: + """Tick行情回调""" if self.vt_orderid: self.cancel_all() return if self.direction == Direction.LONG: if tick.ask_price_1 <= self.price: - order_volume = self.volume - self.traded + order_volume: float = self.volume - self.traded order_volume = min(order_volume, tick.ask_volume_1) self.vt_orderid = self.buy( - self.vt_symbol, self.price, order_volume, offset=self.offset ) else: if tick.bid_price_1 >= self.price: - order_volume = self.volume - self.traded + order_volume: float = self.volume - self.traded order_volume = min(order_volume, tick.bid_volume_1) self.vt_orderid = self.sell( - self.vt_symbol, self.price, order_volume, offset=self.offset ) - self.put_variables_event() + self.put_event() - def on_order(self, order: OrderData): - """""" + def on_order(self, order: OrderData) -> None: + """委托回调""" if not order.is_active(): self.vt_orderid = "" - self.put_variables_event() - - def on_trade(self, trade: TradeData): - """""" - self.traded += trade.volume + self.put_event() + def on_trade(self, trade: TradeData) -> None: + """成交回调""" if self.traded >= self.volume: self.write_log(f"已交易数量:{self.traded},总数量:{self.volume}") - self.stop() + self.finish() else: - self.put_variables_event() + self.put_event() diff --git a/vnpy_algotrading/algos/stop_algo.py b/vnpy_algotrading/algos/stop_algo.py index 3cca78b..e8c1245 100644 --- a/vnpy_algotrading/algos/stop_algo.py +++ b/vnpy_algotrading/algos/stop_algo.py @@ -1,32 +1,20 @@ -from vnpy.trader.constant import Offset, Direction -from vnpy.trader.object import TradeData, OrderData, TickData +from vnpy.trader.constant import Direction +from vnpy.trader.object import OrderData, TickData from vnpy.trader.engine import BaseEngine from ..template import AlgoTemplate class StopAlgo(AlgoTemplate): - """""" - - display_name = "Stop 条件委托" - - default_setting = { - "vt_symbol": "", - "direction": [Direction.LONG.value, Direction.SHORT.value], - "stop_price": 0.0, - "volume": 0.0, - "price_add": 0.0, - "offset": [ - Offset.NONE.value, - Offset.OPEN.value, - Offset.CLOSE.value, - Offset.CLOSETODAY.value, - Offset.CLOSEYESTERDAY.value - ] + """条件委托算法类""" + + display_name: str = "Stop 条件委托" + + default_setting: dict = { + "price_add": 0.0 } - variables = [ - "traded", + variables: list = [ "vt_orderid", "order_status", ] @@ -35,76 +23,66 @@ def __init__( self, algo_engine: BaseEngine, algo_name: str, + vt_symbol: str, + direction: str, + offset: str, + price: float, + volume: float, setting: dict - ): - """""" - super().__init__(algo_engine, algo_name, setting) + ) -> None: + """构造函数""" + super().__init__(algo_engine, algo_name, vt_symbol, direction, offset, price, volume, setting) # 参数 - self.vt_symbol = setting["vt_symbol"] - self.direction = Direction(setting["direction"]) - self.stop_price = setting["stop_price"] - self.volume = setting["volume"] - self.price_add = setting["price_add"] - self.offset = Offset(setting["offset"]) + self.price_add: float = setting["price_add"] # 变量 - self.vt_orderid = "" - self.traded = 0 - self.order_status = "" + self.vt_orderid: str = "" + self.order_status: str = "" - self.subscribe(self.vt_symbol) - self.put_parameters_event() - self.put_variables_event() + self.put_event() - def on_tick(self, tick: TickData): - """""" + def on_tick(self, tick: TickData) -> None: + """Tick行情回调""" if self.vt_orderid: return if self.direction == Direction.LONG: - if tick.last_price >= self.stop_price: - price = self.stop_price + self.price_add + if tick.last_price >= self.price: + price: float = self.price + self.price_add if tick.limit_up: price = min(price, tick.limit_up) self.vt_orderid = self.buy( - self.vt_symbol, price, self.volume, offset=self.offset ) self.write_log( - f"停止单已触发,代码:{self.vt_symbol},方向:{self.direction}, 价格:{self.stop_price},数量:{self.volume},开平:{self.offset}") + f"停止单已触发,代码:{self.vt_symbol},方向:{self.direction}, 价格:{self.price},数量:{self.volume},开平:{self.offset}") else: - if tick.last_price <= self.stop_price: - price = self.stop_price - self.price_add + if tick.last_price <= self.price: + price: float = self.price - self.price_add if tick.limit_down: price = max(price, tick.limit_down) self.vt_orderid = self.sell( - self.vt_symbol, price, self.volume, offset=self.offset ) self.write_log( - f"停止单已触发,代码:{self.vt_symbol},方向:{self.direction}, 价格:{self.stop_price},数量:{self.volume},开平:{self.offset}") + f"停止单已触发,代码:{self.vt_symbol},方向:{self.direction}, 价格:{self.price},数量:{self.volume},开平:{self.offset}") - self.put_variables_event() + self.put_event() - def on_order(self, order: OrderData): - """""" - self.traded = order.traded + def on_order(self, order: OrderData) -> None: + """委托回调""" self.order_status = order.status if not order.is_active(): - self.stop() - self.put_variables_event() - - def on_trade(self, trade: TradeData): - """""" - pass + self.finish() + self.put_event() diff --git a/vnpy_algotrading/algos/twap_algo.py b/vnpy_algotrading/algos/twap_algo.py index a1a8ce1..37a9303 100644 --- a/vnpy_algotrading/algos/twap_algo.py +++ b/vnpy_algotrading/algos/twap_algo.py @@ -1,34 +1,22 @@ from vnpy.trader.utility import round_to -from vnpy.trader.constant import Offset, Direction -from vnpy.trader.object import TradeData, TickData +from vnpy.trader.constant import Direction +from vnpy.trader.object import TradeData, TickData, ContractData from vnpy.trader.engine import BaseEngine from ..template import AlgoTemplate class TwapAlgo(AlgoTemplate): - """""" + """TWAP算法类""" - display_name = "TWAP 时间加权平均" + display_name: str = "TWAP 时间加权平均" - default_setting = { - "vt_symbol": "", - "direction": [Direction.LONG.value, Direction.SHORT.value], - "price": 0.0, - "volume": 0.0, + default_setting: dict = { "time": 600, - "interval": 60, - "offset": [ - Offset.NONE.value, - Offset.OPEN.value, - Offset.CLOSE.value, - Offset.CLOSETODAY.value, - Offset.CLOSEYESTERDAY.value - ] + "interval": 60 } - variables = [ - "traded", + variables: list = [ "order_volume", "timer_count", "total_count" @@ -38,80 +26,66 @@ def __init__( self, algo_engine: BaseEngine, algo_name: str, + vt_symbol: str, + direction: str, + offset: str, + price: float, + volume: float, setting: dict - ): - """""" - super().__init__(algo_engine, algo_name, setting) + ) -> None: + """构造函数""" + super().__init__(algo_engine, algo_name, vt_symbol, direction, offset, price, volume, setting) # 参数 - 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"]) + self.time: int = setting["time"] + self.interval: int = setting["interval"] # 变量 - self.order_volume = self.volume / (self.time / self.interval) - contract = self.get_contract(self.vt_symbol) + self.order_volume: int = self.volume / (self.time / self.interval) + contract: ContractData = self.get_contract() if contract: self.order_volume = round_to(self.order_volume, contract.min_volume) - self.timer_count = 0 - self.total_count = 0 - self.traded = 0 - - self.last_tick = None - - self.subscribe(self.vt_symbol) - self.put_parameters_event() - self.put_variables_event() - - def on_tick(self, tick: TickData): - """""" - self.last_tick = tick + self.timer_count: int = 0 + self.total_count: int = 0 - def on_trade(self, trade: TradeData): - """""" - self.traded += trade.volume + self.put_event() + def on_trade(self, trade: TradeData) -> None: + """成交回调""" if self.traded >= self.volume: self.write_log(f"已交易数量:{self.traded},总数量:{self.volume}") - self.stop() + self.finish() else: - self.put_variables_event() + self.put_event() - def on_timer(self): - """""" + def on_timer(self) -> None: + """定时回调""" self.timer_count += 1 self.total_count += 1 - self.put_variables_event() + self.put_event() if self.total_count >= self.time: self.write_log("执行时间已结束,停止算法") - self.stop() + self.finish() return if self.timer_count < self.interval: return self.timer_count = 0 - if not self.last_tick: + tick: TickData = self.get_tick() + if not tick: return - tick = self.last_tick - self.last_tick = None self.cancel_all() - left_volume = self.volume - self.traded + left_volume: int = 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.buy(self.price, order_volume, offset=self.offset) else: if tick.bid_price_1 >= self.price: - self.sell(self.vt_symbol, self.price, - order_volume, offset=self.offset) + self.sell(self.price, order_volume, offset=self.offset) diff --git a/vnpy_algotrading/base.py b/vnpy_algotrading/base.py index c08ba37..3220a30 100644 --- a/vnpy_algotrading/base.py +++ b/vnpy_algotrading/base.py @@ -1,6 +1,17 @@ +from enum import Enum + + EVENT_ALGO_LOG = "eAlgoLog" -EVENT_ALGO_SETTING = "eAlgoSetting" -EVENT_ALGO_VARIABLES = "eAlgoVariables" -EVENT_ALGO_PARAMETERS = "eAlgoParameters" +EVENT_ALGO_UPDATE = "eAlgoUpdate" + APP_NAME = "AlgoTrading" + + +class AlgoStatus(Enum): + """算法状态""" + + RUNNING = "运行" + PAUSED = "暂停" + STOPPED = "停止" + FINISHED = "结束" diff --git a/vnpy_algotrading/engine.py b/vnpy_algotrading/engine.py index 5d47ae2..db735d2 100644 --- a/vnpy_algotrading/engine.py +++ b/vnpy_algotrading/engine.py @@ -1,5 +1,5 @@ from collections import defaultdict -from typing import Dict, List, Optional, Set +from typing import Dict, List, Optional, Set, Type from vnpy.event import EventEngine, Event from vnpy.trader.engine import BaseEngine, MainEngine @@ -9,7 +9,7 @@ EVENT_ORDER, EVENT_TRADE ) -from vnpy.trader.constant import Direction, Offset, OrderType +from vnpy.trader.constant import Direction, Offset, OrderType, Exchange from vnpy.trader.object import ( SubscribeRequest, OrderRequest, @@ -20,99 +20,80 @@ TradeData, CancelRequest ) -from vnpy.trader.utility import load_json, save_json, round_to +from vnpy.trader.utility import round_to from .template import AlgoTemplate from .base import ( EVENT_ALGO_LOG, - EVENT_ALGO_PARAMETERS, - EVENT_ALGO_SETTING, - EVENT_ALGO_VARIABLES, - APP_NAME + EVENT_ALGO_UPDATE, + APP_NAME, + AlgoStatus ) class AlgoEngine(BaseEngine): - """""" - setting_filename: str = "algo_trading_setting.json" + """算法引擎""" def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None: """构造函数""" super().__init__(main_engine, event_engine, APP_NAME) + self.algo_templates: Dict[str, Type[AlgoTemplate]] = {} + self.algos: Dict[str, AlgoTemplate] = {} self.symbol_algo_map: Dict[str, Set[AlgoTemplate]] = defaultdict(set) - self.orderid_algo_map: dict = {} - - self.algo_templates: dict = {} - self.algo_settings: dict = {} + self.orderid_algo_map: Dict[str, AlgoTemplate] = {} self.load_algo_template() self.register_event() def init_engine(self) -> None: - """""" + """初始化引擎""" self.write_log("算法交易引擎启动") - self.load_algo_setting() def close(self) -> None: - """""" - pass + """关闭引擎""" + self.stop_all() def load_algo_template(self) -> None: - """""" + """载入算法类""" from .algos.twap_algo import TwapAlgo from .algos.iceberg_algo import IcebergAlgo from .algos.sniper_algo import SniperAlgo from .algos.stop_algo import StopAlgo from .algos.best_limit_algo import BestLimitAlgo - from .algos.dma_algo import DmaAlgo self.add_algo_template(TwapAlgo) self.add_algo_template(IcebergAlgo) self.add_algo_template(SniperAlgo) self.add_algo_template(StopAlgo) self.add_algo_template(BestLimitAlgo) - self.add_algo_template(DmaAlgo) def add_algo_template(self, template: AlgoTemplate) -> None: - """""" + """添加算法类""" self.algo_templates[template.__name__] = template def get_algo_template(self) -> dict: - """""" + """获取算法类""" return self.algo_templates - def load_algo_setting(self) -> None: - """""" - self.algo_settings: dict = 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) -> None: - """""" - save_json(self.setting_filename, self.algo_settings) - def register_event(self) -> None: - """""" + """注册事件监听""" 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, event: Event) -> None: - """""" + """处理行情事件""" tick: TickData = event.data - algos: Optional[Set[AlgoTemplate]] = self.symbol_algo_map[tick.vt_symbol] + algos: Set[AlgoTemplate] = self.symbol_algo_map[tick.vt_symbol] for algo in algos: algo.update_tick(tick) def process_timer_event(self, event: Event) -> None: - """""" + """处理定时事件""" # 生成列表避免字典改变 algos: List[AlgoTemplate] = list(self.algos.values()) @@ -120,7 +101,7 @@ def process_timer_event(self, event: Event) -> None: algo.update_timer() def process_trade_event(self, event: Event) -> None: - """""" + """处理成交事件""" trade: TradeData = event.data algo: Optional[AlgoTemplate] = self.orderid_algo_map.get(trade.vt_orderid, None) @@ -128,69 +109,99 @@ def process_trade_event(self, event: Event) -> None: algo.update_trade(trade) def process_order_event(self, event: Event) -> None: - """""" + """处理委托事件""" order: OrderData = event.data algo: Optional[AlgoTemplate] = self.orderid_algo_map.get(order.vt_orderid, None) if algo: algo.update_order(order) - def start_algo(self, setting: dict) -> str: - """""" - template_name: str = setting["template_name"] + def start_algo( + self, + template_name: str, + vt_symbol: str, + direction: Direction, + offset: Offset, + price: float, + volume: int, + setting: dict + ) -> str: + """启动算法""" + contract: Optional[ContractData] = self.main_engine.get_contract(vt_symbol) + if not contract: + self.write_log(f'算法启动失败,找不到合约:{vt_symbol}') + return "" + algo_template: AlgoTemplate = self.algo_templates[template_name] - algo: AlgoTemplate = algo_template.new(self, setting) + # 创建算法实例 + algo_template._count += 1 + algo_name: str = f"{algo_template.__name__}_{algo_template._count}" + algo: AlgoTemplate = algo_template( + self, + algo_name, + vt_symbol, + direction, + offset, + price, + volume, + setting + ) + + # 订阅行情 + algos: set = self.symbol_algo_map[algo.vt_symbol] + if not algos: + self.subscribe(contract.symbol, contract.exchange, contract.gateway_name) + algos.add(algo) + + # 启动算法 algo.start() + self.algos[algo_name] = algo - self.algos[algo.algo_name] = algo - return algo.algo_name + return algo_name + + def pause_algo(self, algo_name: str) -> None: + """暂停算法""" + algo: Optional[AlgoTemplate] = self.algos.get(algo_name, None) + if algo: + algo.pause() + + def resume_algo(self, algo_name: str) -> None: + """恢复算法""" + algo: Optional[AlgoTemplate] = self.algos.get(algo_name, None) + if algo: + algo.resume() def stop_algo(self, algo_name: str) -> None: - """""" + """停止算法""" algo: Optional[AlgoTemplate] = self.algos.get(algo_name, None) if algo: algo.stop() def stop_all(self) -> None: - """""" + """停止全部算法""" for algo_name in list(self.algos.keys()): self.stop_algo(algo_name) - def subscribe(self, algo: AlgoTemplate, vt_symbol: str) -> None: - """""" - contract: Optional[ContractData] = self.main_engine.get_contract(vt_symbol) - if not contract: - self.write_log(f'订阅行情失败,找不到合约:{vt_symbol}', algo) - return - - algos: set = self.symbol_algo_map[vt_symbol] - - if not algos: - req: SubscribeRequest = SubscribeRequest( - symbol=contract.symbol, - exchange=contract.exchange - ) - self.main_engine.subscribe(req, contract.gateway_name) - - algos.add(algo) + def subscribe(self, symbol: str, exchange: Exchange, gateway_name: str) -> None: + """订阅行情""" + req: SubscribeRequest = SubscribeRequest( + symbol=symbol, + exchange=exchange + ) + self.main_engine.subscribe(req, gateway_name) def send_order( self, algo: AlgoTemplate, - vt_symbol: str, direction: Direction, price: float, volume: float, order_type: OrderType, offset: Offset ) -> str: - """""" - contract: Optional[ContractData] = self.main_engine.get_contract(vt_symbol) - if not contract: - self.write_log(f'委托下单失败,找不到合约:{vt_symbol}', algo) - return - + """委托下单""" + contract: Optional[ContractData] = self.main_engine.get_contract(algo.vt_symbol) volume: float = round_to(volume, contract.min_volume) if not volume: return "" @@ -211,7 +222,7 @@ def send_order( return vt_orderid def cancel_order(self, algo: AlgoTemplate, vt_orderid: str) -> None: - """""" + """委托撤单""" order: Optional[OrderData] = self.main_engine.get_order(vt_orderid) if not order: @@ -221,26 +232,26 @@ def cancel_order(self, algo: AlgoTemplate, vt_orderid: str) -> None: req: CancelRequest = order.create_cancel_request() self.main_engine.cancel_order(req, order.gateway_name) - def get_tick(self, algo: AlgoTemplate, vt_symbol: str) -> Optional[TickData]: - """""" - tick: Optional[TickData] = self.main_engine.get_tick(vt_symbol) + def get_tick(self, algo: AlgoTemplate) -> Optional[TickData]: + """查询行情""" + tick: Optional[TickData] = self.main_engine.get_tick(algo.vt_symbol) if not tick: - self.write_log(f"查询行情失败,找不到行情:{vt_symbol}", algo) + self.write_log(f"查询行情失败,找不到行情:{algo.vt_symbol}", algo) return tick - def get_contract(self, algo: AlgoTemplate, vt_symbol: str) -> Optional[ContractData]: - """""" - contract: Optional[ContractData] = self.main_engine.get_contract(vt_symbol) + def get_contract(self, algo: AlgoTemplate) -> Optional[ContractData]: + """查询合约""" + contract: Optional[ContractData] = self.main_engine.get_contract(algo.vt_symbol) if not contract: - self.write_log(f"查询合约失败,找不到合约:{vt_symbol}", algo) + self.write_log(f"查询合约失败,找不到合约:{algo.vt_symbol}", algo) return contract def write_log(self, msg: str, algo: AlgoTemplate = None) -> None: - """""" + """输出日志""" if algo: msg: str = f"{algo.algo_name}:{msg}" @@ -248,61 +259,18 @@ def write_log(self, msg: str, algo: AlgoTemplate = None) -> None: event: Event = Event(EVENT_ALGO_LOG, data=log) self.event_engine.put(event) - def put_setting_event(self, setting_name: str, setting: dict) -> None: - """""" - event: 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) -> None: - """""" - 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) -> None: - """""" - if setting_name not in self.algo_settings: - return - self.algo_settings.pop(setting_name) - - event: 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) -> None: - """""" - event: 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) -> None: - """""" - # 检查算法是否运行结束 - if not variables["active"] and algo in self.algos.values(): + def put_algo_event(self, algo: AlgoTemplate, data: dict) -> None: + """推送更新""" + # 移除运行结束的算法实例 + if ( + algo in self.algos.values() + and algo.status in [AlgoStatus.STOPPED, AlgoStatus.FINISHED] + ): self.algos.pop(algo.algo_name) for algos in self.symbol_algo_map.values(): if algo in algos: algos.remove(algo) - # 推送事件 - event: Event = Event(EVENT_ALGO_VARIABLES) - event.data = { - "algo_name": algo.algo_name, - "variables": variables - } + event: Event = Event(EVENT_ALGO_UPDATE, data) self.event_engine.put(event) diff --git a/vnpy_algotrading/template.py b/vnpy_algotrading/template.py index 6efc445..1497096 100644 --- a/vnpy_algotrading/template.py +++ b/vnpy_algotrading/template.py @@ -1,48 +1,59 @@ -from typing import Dict, Optional +from typing import Dict, Optional, TYPE_CHECKING from vnpy.trader.engine import BaseEngine from vnpy.trader.object import TickData, OrderData, TradeData, ContractData from vnpy.trader.constant import OrderType, Offset, Direction from vnpy.trader.utility import virtual +from .base import AlgoStatus + +if TYPE_CHECKING: + from .engine import AlgoEngine + class AlgoTemplate: - """""" - _count: int = 0 - display_name: str = "" - default_setting: dict = {} - variables: list = [] + """算法模板""" + + _count: int = 0 # 实例计数 + + display_name: str = "" # 显示名称 + default_setting: dict = {} # 默认参数 + variables: list = [] # 变量名称 def __init__( self, - algo_engine: BaseEngine, + algo_engine: "AlgoEngine", algo_name: str, + vt_symbol: str, + direction: Direction, + offset: Offset, + price: float, + volume: int, setting: dict ) -> None: """构造函数""" self.algo_engine: BaseEngine = algo_engine self.algo_name: str = algo_name - self.active: bool = False - self.active_orders: Dict[str, OrderData] = {} # vt_orderid:order + self.vt_symbol: str = vt_symbol + self.direction: Direction = direction + self.offset: Offset = offset + self.price: float = price + self.volume: int = volume - self.variables.insert(0, "active") + self.status: AlgoStatus = AlgoStatus.PAUSED + self.traded: float = 0 + self.traded_price: float = 0 - @classmethod - def new(cls, algo_engine: BaseEngine, setting: dict) -> "AlgoTemplate": - """创建一个新的算法实例""" - cls._count += 1 - algo_name: str = f"{cls.__name__}_{cls._count}" - algo: AlgoTemplate = cls(algo_engine, algo_name, setting) - return algo + self.active_orders: Dict[str, OrderData] = {} # vt_orderid:order def update_tick(self, tick: TickData) -> None: - """""" - if self.active: + """行情数据更新""" + if self.status == AlgoStatus.RUNNING: self.on_tick(tick) def update_order(self, order: OrderData) -> None: - """""" + """委托数据更新""" if order.is_active(): self.active_orders[order.vt_orderid] = order elif order.vt_orderid in self.active_orders: @@ -51,80 +62,91 @@ def update_order(self, order: OrderData) -> None: self.on_order(order) def update_trade(self, trade: TradeData) -> None: - """""" + """成交数据更新""" + cost: float = self.traded_price * self.traded + trade.price * trade.volume + self.traded += trade.volume + self.traded_price = cost / self.traded + self.on_trade(trade) def update_timer(self) -> None: - """""" - if self.active: + """每秒定时更新""" + if self.status == AlgoStatus.RUNNING: self.on_timer() - def on_start(self) -> None: - """""" - pass - - @virtual - def on_stop(self) -> None: - """""" - pass - @virtual def on_tick(self, tick: TickData) -> None: - """""" + """行情回调""" pass @virtual def on_order(self, order: OrderData) -> None: - """""" + """委托回调""" pass @virtual def on_trade(self, trade: TradeData) -> None: - """""" + """成交回调""" pass @virtual def on_timer(self) -> None: - """""" + """定时回调""" pass def start(self) -> None: - """""" - self.active = True - self.on_start() - self.put_variables_event() + """启动""" + self.status = AlgoStatus.RUNNING + self.put_event() + + self.write_log("算法启动") def stop(self) -> None: - """""" - self.active = False + """停止""" + self.status = AlgoStatus.STOPPED self.cancel_all() - self.on_stop() - self.put_variables_event() + self.put_event() - self.write_log("停止算法") + self.write_log("算法停止") - def subscribe(self, vt_symbol: str) -> None: - """""" - self.algo_engine.subscribe(self, vt_symbol) + def finish(self) -> None: + """结束""" + self.status = AlgoStatus.FINISHED + self.cancel_all() + self.put_event() + + self.write_log("算法结束") + + def pause(self) -> None: + """暂停""" + self.status = AlgoStatus.PAUSED + self.put_event() + + self.write_log("算法暂停") + + def resume(self) -> None: + """恢复""" + self.status = AlgoStatus.RUNNING + self.put_event() + + self.write_log("算法恢复") def buy( self, - vt_symbol: str, price: float, volume: float, order_type: OrderType = OrderType.LIMIT, offset: Offset = Offset.NONE ) -> None: - """""" - if not self.active: + """买入""" + if self.status != AlgoStatus.RUNNING: return - msg: str = f"委托买入{vt_symbol}:{volume}@{price}" + msg: str = f"{self.vt_symbol},委托买入{order_type.value},{volume}@{price}" self.write_log(msg) return self.algo_engine.send_order( self, - vt_symbol, Direction.LONG, price, volume, @@ -134,22 +156,20 @@ def buy( def sell( self, - vt_symbol: str, price: float, volume: float, order_type: OrderType = OrderType.LIMIT, offset: Offset = Offset.NONE ) -> None: - """""" - if not self.active: + """卖出""" + if self.status != AlgoStatus.RUNNING: return - msg: str = f"委托卖出{vt_symbol}:{volume}@{price}" + msg: str = f"{self.vt_symbol}委托卖出{order_type.value},{volume}@{price}" self.write_log(msg) return self.algo_engine.send_order( self, - vt_symbol, Direction.SHORT, price, volume, @@ -158,41 +178,62 @@ def sell( ) def cancel_order(self, vt_orderid: str) -> None: - """""" + """撤销委托""" self.algo_engine.cancel_order(self, vt_orderid) def cancel_all(self) -> None: - """""" + """全撤委托""" 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) -> Optional[TickData]: - """""" - return self.algo_engine.get_tick(self, vt_symbol) + def get_tick(self) -> Optional[TickData]: + """查询行情""" + return self.algo_engine.get_tick(self) - def get_contract(self, vt_symbol: str) -> Optional[ContractData]: - """""" - return self.algo_engine.get_contract(self, vt_symbol) + def get_contract(self) -> Optional[ContractData]: + """查询合约""" + return self.algo_engine.get_contract(self) - def write_log(self, msg: str) -> None: - """""" - self.algo_engine.write_log(msg, self) - - def put_parameters_event(self) -> None: - """""" - parameters: dict = {} + def get_parameters(self) -> dict: + """获取算法参数""" + strategy_parameters: dict = {} for name in self.default_setting.keys(): - parameters[name] = getattr(self, name) - - self.algo_engine.put_parameters_event(self, parameters) + strategy_parameters[name] = getattr(self, name) + return strategy_parameters - def put_variables_event(self) -> None: - """""" - variables: dict = {} + def get_variables(self) -> dict: + """获取算法变量""" + strategy_variables: dict = {} for name in self.variables: - variables[name] = getattr(self, name) + strategy_variables[name] = getattr(self, name) + return strategy_variables + + def get_data(self) -> dict: + """获取算法信息""" + algo_data: dict = { + "algo_name": self.algo_name, + "vt_symbol": self.vt_symbol, + "direction": self.direction, + "offset": self.offset, + "price": self.price, + "volume": self.volume, + "status": self.status, + "traded": self.traded, + "left": self.volume - self.traded, + "traded_price": self.traded_price, + "parameters": self.get_parameters(), + "variables": self.get_variables() + } + return algo_data + + def write_log(self, msg: str) -> None: + """输出日志""" + self.algo_engine.write_log(msg, self) - self.algo_engine.put_variables_event(self, variables) + def put_event(self) -> None: + """推送更新""" + data: dict = self.get_data() + self.algo_engine.put_algo_event(self, data) diff --git a/vnpy_algotrading/ui/display.py b/vnpy_algotrading/ui/display.py index dce3822..de36bea 100644 --- a/vnpy_algotrading/ui/display.py +++ b/vnpy_algotrading/ui/display.py @@ -2,18 +2,19 @@ "vt_symbol": "本地代码", "direction": "方向", "price": "价格", - "volume": "数量", + "volume": "委托数量", "time": "执行时间(秒)", "interval": "每轮间隔(秒)", "offset": "开平", - "active": "算法状态", + "status": "算法状态", + "traded_price": "成交均价", "traded": "成交数量", + "left": "剩余数量", "order_volume": "单笔委托", "timer_count": "本轮读秒", "total_count": "累计读秒", "template_name": "算法模板", "display_volume": "挂出数量", - "stop_price": "触发价格", "price_add": "委托超价", "step_price": "网格交易间距", "step_volume": "网格交易数量", @@ -23,10 +24,8 @@ "spread_up": "价差上限", "spread_down": "价差下限", "max_pos": "最大持仓", - "start_time": "开始时间", "end_time": "结束时间", - "min_volume": "最小委托量", "max_volume": "最大委托量", "volume_change": "委托量变化" diff --git a/vnpy_algotrading/ui/widget.py b/vnpy_algotrading/ui/widget.py index cd7fe01..760824c 100644 --- a/vnpy_algotrading/ui/widget.py +++ b/vnpy_algotrading/ui/widget.py @@ -1,7 +1,7 @@ import csv from functools import partial from datetime import datetime -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, List, Tuple from vnpy.event import EventEngine, Event from vnpy.trader.engine import MainEngine, LogData @@ -12,29 +12,47 @@ AlgoTemplate, APP_NAME, EVENT_ALGO_LOG, - EVENT_ALGO_PARAMETERS, - EVENT_ALGO_VARIABLES, - EVENT_ALGO_SETTING + EVENT_ALGO_UPDATE, + AlgoStatus, + Direction, + Offset ) from .display import NAME_DISPLAY_MAP class AlgoWidget(QtWidgets.QWidget): - """算法交易控件""" + """算法启动控件""" def __init__( self, algo_engine: AlgoEngine, algo_template: AlgoTemplate ) -> None: - """""" + """构造函数""" super().__init__() self.algo_engine: AlgoEngine = algo_engine self.template_name: str = algo_template.__name__ - self.default_setting: dict = algo_template.default_setting - self.widgets: dict = {} + self.default_setting: dict = { + "vt_symbol": "", + "direction": [ + Direction.LONG.value, + Direction.SHORT.value + ], + "offset": [ + Offset.NONE.value, + Offset.OPEN.value, + Offset.CLOSE.value, + Offset.CLOSETODAY.value, + Offset.CLOSEYESTERDAY.value + ], + "price": 0.0, + "volume": 0, + } + self.default_setting.update(algo_template.default_setting) + + self.widgets: Dict[str, QtWidgets.QWidget] = {} self.init_ui() @@ -66,28 +84,16 @@ def init_ui(self) -> None: load_csv_button.clicked.connect(self.load_csv) form.addRow(load_csv_button) - form.addRow(QtWidgets.QLabel("")) - form.addRow(QtWidgets.QLabel("")) - form.addRow(QtWidgets.QLabel("")) - - self.setting_name_line: str = QtWidgets.QLineEdit() - form.addRow("配置名称", self.setting_name_line) - - save_setting_button: QtWidgets.QPushButton = QtWidgets.QPushButton("保存配置") - save_setting_button.clicked.connect(self.save_setting) - form.addRow(save_setting_button) - for button in [ start_algo_button, - load_csv_button, - save_setting_button + load_csv_button ]: button.setFixedHeight(button.sizeHint().height() * 2) self.setLayout(form) def load_csv(self) -> None: - """""" + """加载CSV文件中的算法配置""" # 从对话框获取csv地址 path, type_ = QtWidgets.QFileDialog.getOpenFileName( self, @@ -118,9 +124,7 @@ def load_csv(self) -> None: for d in reader: # 用模版名初始化算法配置 - setting: dict = { - "template_name": self.template_name - } + setting: dict = {} # 读取csv文件每行中各个字段内容 for field_name, tp in self.widgets.items(): @@ -147,11 +151,19 @@ def load_csv(self) -> None: # 当没有错误发生时启动算法 for setting in settings: - self.algo_engine.start_algo(setting) + self.algo_engine.start_algo( + template_name=self.template_name, + vt_symbol=setting.pop("vt_symbol"), + direction=Direction(setting.pop("direction")), + offset=Offset(setting.pop("offset")), + price=setting.pop("price"), + volume=setting.pop("volume"), + setting=setting + ) def get_setting(self) -> dict: - """获取配置""" - setting: dict = {"template_name": self.template_name} + """获取当前配置""" + setting: dict = {} for field_name, tp in self.widgets.items(): widget, field_type = tp @@ -176,38 +188,24 @@ def get_setting(self) -> dict: def start_algo(self) -> None: """启动交易算法""" setting: dict = self.get_setting() - if setting: - self.algo_engine.start_algo(setting) - - def update_setting(self, setting_name: str, setting: dict) -> None: - """更新控件配置""" - 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) -> None: - """保存算法配置""" - setting_name: str = self.setting_name_line.text() - if not setting_name: + if not setting: return - setting: dict = self.get_setting() - if setting: - self.algo_engine.update_algo_setting(setting_name, setting) + self.algo_engine.start_algo( + template_name=self.template_name, + vt_symbol=setting.pop("vt_symbol"), + direction=Direction(setting.pop("direction")), + offset=Offset(setting.pop("offset")), + price=setting.pop("price"), + volume=setting.pop("volume"), + setting=setting + ) class AlgoMonitor(QtWidgets.QTableWidget): - """""" - parameters_signal: QtCore.pyqtSignal = QtCore.pyqtSignal(Event) - variables_signal: QtCore.pyqtSignal = QtCore.pyqtSignal(Event) + """算法监控组件""" + + algo_signal: QtCore.pyqtSignal = QtCore.pyqtSignal(Event) def __init__( self, @@ -215,7 +213,7 @@ def __init__( event_engine: EventEngine, mode_active: bool ): - """""" + """构造函数""" super().__init__() self.algo_engine: AlgoEngine = algo_engine @@ -228,12 +226,22 @@ def __init__( self.register_event() def init_ui(self) -> None: - """""" + """初始化界面""" labels: list = [ + "", "", "算法", + "本地代码", + "方向", + "开平", + "价格", + "总数量", + "成交量", + "剩余量", + "成交均价", + "状态", "参数", - "状态" + "变量" ] self.setColumnCount(len(labels)) self.setHorizontalHeaderLabels(labels) @@ -244,7 +252,7 @@ def init_ui(self) -> None: QtWidgets.QHeaderView.ResizeToContents ) - for column in range(2, 4): + for column in range(12, 14): self.horizontalHeader().setSectionResizeMode( column, QtWidgets.QHeaderView.Stretch @@ -253,40 +261,48 @@ def init_ui(self) -> None: if not self.mode_active: self.hideColumn(0) + self.hideColumn(1) def register_event(self) -> None: - """""" - self.parameters_signal.connect(self.process_parameters_event) - self.variables_signal.connect(self.process_variables_event) + """注册事件监听""" + self.algo_signal.connect(self.process_algo_event) + self.event_engine.register(EVENT_ALGO_UPDATE, self.algo_signal.emit) - self.event_engine.register( - EVENT_ALGO_PARAMETERS, self.parameters_signal.emit) - self.event_engine.register( - EVENT_ALGO_VARIABLES, self.variables_signal.emit) + def process_algo_event(self, event: Event) -> None: + """处理算法更新事件""" + data: dict = event.data - def process_parameters_event(self, event: Event) -> None: - """""" - data: Any = event.data + # 读取算法的标准参数,并获取内容单元格字典 algo_name: str = data["algo_name"] + vt_symbol: str = data["vt_symbol"] + direction: Direction = data["direction"] + offset: Offset = data["offset"] + price: float = data["price"] + volume: float = data["volume"] + + cells: dict = self.get_algo_cells(algo_name, vt_symbol, direction, offset, price, volume) + + # 读取算法的标准变量,并更新到内容单元格 + traded_price: float = data["traded_price"] + traded: float = data["traded"] + left: float = data["left"] + status: AlgoStatus = data["status"] + + cells["status"].setText(status.value) + cells["traded_price"].setText(str(traded_price)) + cells["traded"].setText(str(traded)) + cells["left"].setText(str(left)) + + # 读取算法的自定义参数和变量,并显示到单元格 parameters: dict = data["parameters"] + cells["parameters"].setText(to_text(parameters)) - cells: dict = self.get_algo_cells(algo_name) - text: str = to_text(parameters) - cells["parameters"].setText(text) - - def process_variables_event(self, event: Event) -> None: - """""" - data: Any = event.data - algo_name: str = data["algo_name"] variables: dict = data["variables"] + cells["variables"].setText(to_text(variables)) - cells: dict = self.get_algo_cells(algo_name) - variables_cell: Optional[QtWidgets.QTableWidgetItem] = cells["variables"] - text: str = to_text(variables) - variables_cell.setText(text) - - row: int = self.row(variables_cell) - active: bool = variables["active"] + # 基于显示模式决定是否隐藏 + row: int = self.row(cells["variables"]) + active: bool = status not in [AlgoStatus.STOPPED, AlgoStatus.FINISHED] if self.mode_active: if active: @@ -300,11 +316,32 @@ def process_variables_event(self, event: Event) -> None: self.showRow(row) def stop_algo(self, algo_name: str) -> None: - """""" + """停止算法""" self.algo_engine.stop_algo(algo_name) - def get_algo_cells(self, algo_name: str) -> dict: - """""" + def switch(self, algo_name: str) -> None: + """算法开关调整""" + button: QtWidgets.QPushButton = self.algo_cells[algo_name]["button"] + + if button.text() == "暂停": + self.algo_engine.pause_algo(algo_name) + button.setText("恢复") + else: + self.algo_engine.resume_algo(algo_name) + button.setText("暂停") + + self.algo_cells[algo_name]["button"] = button + + def get_algo_cells( + self, + algo_name: str, + vt_symbol: str, + direction: Direction, + offset: Offset, + price: float, + volume: float + ) -> Dict[str, QtWidgets.QTableWidgetItem]: + """获取算法对应的单元格字典""" cells: Optional[dict] = self.algo_cells.get(algo_name, None) if not cells: @@ -312,28 +349,53 @@ def get_algo_cells(self, algo_name: str) -> dict: stop_button: QtWidgets.QPushButton = QtWidgets.QPushButton("停止") stop_button.clicked.connect(stop_func) - name_cell: QtWidgets.QTableWidgetItem = QtWidgets.QTableWidgetItem(algo_name) + # 初始化时先设置暂停按钮 + switch_func = partial(self.switch, algo_name=algo_name) + switch_button: QtWidgets.QPushButton = QtWidgets.QPushButton("暂停") + switch_button.clicked.connect(switch_func) + parameters_cell: QtWidgets.QTableWidgetItem = QtWidgets.QTableWidgetItem() variables_cell: QtWidgets.QTableWidgetItem = 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) + self.setCellWidget(0, 1, switch_button) + self.setItem(0, 12, parameters_cell) + self.setItem(0, 13, variables_cell) - cells: dict = { - "name": name_cell, + cells: Dict[str, QtWidgets.QTableWidgetItem] = { "parameters": parameters_cell, - "variables": variables_cell + "variables": variables_cell, + "button": switch_button # 缓存对应algo_name的button进字典便于更新按钮状态 } + + items: List[Tuple[int, str, str]] = [ + (2, "name", algo_name), + (3, "vt_symbol", vt_symbol), + (4, "direction", direction.value), + (5, "offset", offset.value), + (6, "price", str(price)), + (7, "volume", str(volume)), + (8, "traded", ""), + (9, "left", ""), + (10, "traded_price", ""), + (11, "status", ""), + ] + + for column, name, content in items: + cell: QtWidgets.QTableWidgetItem = QtWidgets.QTableWidgetItem(content) + cell.setTextAlignment(QtCore.Qt.AlignCenter) + + self.setItem(0, column, cell) + cells[name] = cell + self.algo_cells[algo_name] = cells return cells class ActiveAlgoMonitor(AlgoMonitor): - """监控激活算法""" + """活动算法监控组件""" def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine) -> None: """""" @@ -341,129 +403,20 @@ def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine) -> None: class InactiveAlgoMonitor(AlgoMonitor): - """监控未激活算法""" + """结束算法监控组件""" def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine) -> None: """""" super().__init__(algo_engine, event_engine, False) -class SettingMonitor(QtWidgets.QTableWidget): - """""" - setting_signal: QtCore.pyqtSignal = QtCore.pyqtSignal(Event) - use_signal: QtCore.pyqtSignal = QtCore.pyqtSignal(dict) - - def __init__(self, algo_engine: AlgoEngine, event_engine: EventEngine) -> None: - """""" - super().__init__() - - self.algo_engine: AlgoEngine = algo_engine - self.event_engine: EventEngine = event_engine - - self.settings: dict = {} - self.setting_cells: dict = {} - - self.init_ui() - self.register_event() - - def init_ui(self) -> None: - """""" - labels: list = [ - "", - "", - "名称", - "配置" - ] - 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) -> None: - """""" - 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: Event) -> None: - """""" - data: Any = event.data - setting_name: str = data["setting_name"] - setting: dict = data["setting"] - cells: dict = 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: int = self.row(cells["setting"]) - self.removeRow(row) - - self.setting_cells.pop(setting_name) - - def get_setting_cells(self, setting_name: str) -> dict: - """""" - cells: Optional[dict] = self.setting_cells.get(setting_name, None) - - if not cells: - use_func = partial(self.use_setting, setting_name=setting_name) - use_button: QtWidgets.QPushButton = QtWidgets.QPushButton("使用") - use_button.clicked.connect(use_func) - - remove_func = partial(self.remove_setting, - setting_name=setting_name) - remove_button: QtWidgets.QPushButton = QtWidgets.QPushButton("移除") - remove_button.clicked.connect(remove_func) - - name_cell: QtWidgets.QTableWidgetItem = QtWidgets.QTableWidgetItem(setting_name) - setting_cell: QtWidgets.QTableWidgetItem = 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: dict = { - "name": name_cell, - "setting": setting_cell - } - self.setting_cells[setting_name] = cells - - return cells - - def use_setting(self, setting_name: str) -> None: - """""" - setting: dict = self.settings[setting_name] - setting["setting_name"] = setting_name - self.use_signal.emit(setting) - - def remove_setting(self, setting_name: str) -> None: - """""" - self.algo_engine.remove_algo_setting(setting_name) - - class LogMonitor(QtWidgets.QTableWidget): - """""" + """日志组件""" + signal: QtCore.pyqtSignal = QtCore.pyqtSignal(Event) def __init__(self, event_engine: EventEngine) -> None: - """""" + """构造函数""" super().__init__() self.event_engine: EventEngine = event_engine @@ -472,34 +425,27 @@ def __init__(self, event_engine: EventEngine) -> None: self.register_event() def init_ui(self) -> None: - """""" + """初始化界面""" labels: list = [ "时间", "信息" ] 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.verticalHeader().setVisible(False) + self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + self.horizontalHeader().setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) self.setWordWrap(True) def register_event(self) -> None: - """""" + """注册事件监听""" self.signal.connect(self.process_log_event) self.event_engine.register(EVENT_ALGO_LOG, self.signal.emit) def process_log_event(self, event: Event) -> None: - """""" + """处理日志事件""" log: LogData = event.data msg: str = log.msg timestamp: str = datetime.now().strftime("%H:%M:%S") @@ -513,7 +459,7 @@ def process_log_event(self, event: Event) -> None: class AlgoManager(QtWidgets.QWidget): - """""" + """算法交易管理控件""" def __init__(self, main_engine: MainEngine, event_engine: EventEngine) -> None: """""" @@ -578,19 +524,13 @@ def init_ui(self) -> None: tab2: QtWidgets.QTabWidget = QtWidgets.QTabWidget() tab2.addTab(log_monitor, "日志") - setting_monitor: SettingMonitor = SettingMonitor(self.algo_engine, self.event_engine) - setting_monitor.use_signal.connect(self.use_setting) - tab3: QtWidgets.QTabWidget = QtWidgets.QTabWidget() - tab3.addTab(setting_monitor, "配置") - - grid: QtWidgets.QGridLayout = QtWidgets.QGridLayout() - grid.addWidget(tab1, 0, 0, 1, 2) - grid.addWidget(tab2, 1, 0) - grid.addWidget(tab3, 1, 1) + vbox2: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout() + vbox2.addWidget(tab1) + vbox2.addWidget(tab2) hbox2: QtWidgets.QHBoxLayout = QtWidgets.QHBoxLayout() hbox2.addLayout(vbox) - hbox2.addLayout(grid) + hbox2.addLayout(vbox2) self.setLayout(hbox2) self.show_algo_widget() @@ -606,18 +546,6 @@ def show_algo_widget(self) -> None: else: widget.hide() - def use_setting(self, setting: dict) -> None: - """""" - setting_name: str = setting["setting_name"] - template_name: str = setting["template_name"] - - widget: AlgoWidget = self.algo_widgets[template_name] - widget.update_setting(setting_name, setting) - - ix: int = self.template_combo.findData(template_name) - self.template_combo.setCurrentIndex(ix) - self.show_algo_widget() - def show(self) -> None: """""" self.showMaximized() @@ -628,6 +556,6 @@ def to_text(data: dict) -> str: buf: list = [] for key, value in data.items(): key: str = NAME_DISPLAY_MAP.get(key, key) - buf.append(f"{key}:{value}") - text: str = ",".join(buf) + buf.append(f"{key}:{value}") + text: str = ";".join(buf) return text