Merge pull request #2193 from vnpy/dev-portfolio-manager
Dev portfolio manager
This commit is contained in:
commit
1351496280
@ -33,15 +33,16 @@ from vnpy.gateway.bitmex import BitmexGateway
|
||||
# from vnpy.gateway.gateios import GateiosGateway
|
||||
from vnpy.gateway.bybit import BybitGateway
|
||||
|
||||
from vnpy.app.cta_strategy import CtaStrategyApp
|
||||
# from vnpy.app.cta_strategy import CtaStrategyApp
|
||||
# from vnpy.app.csv_loader import CsvLoaderApp
|
||||
# from vnpy.app.algo_trading import AlgoTradingApp
|
||||
from vnpy.app.cta_backtester import CtaBacktesterApp
|
||||
from vnpy.app.data_recorder import DataRecorderApp
|
||||
# from vnpy.app.cta_backtester import CtaBacktesterApp
|
||||
# from vnpy.app.data_recorder import DataRecorderApp
|
||||
# from vnpy.app.risk_manager import RiskManagerApp
|
||||
from vnpy.app.script_trader import ScriptTraderApp
|
||||
from vnpy.app.rpc_service import RpcServiceApp
|
||||
from vnpy.app.spread_trading import SpreadTradingApp
|
||||
# from vnpy.app.script_trader import ScriptTraderApp
|
||||
# from vnpy.app.rpc_service import RpcServiceApp
|
||||
# from vnpy.app.spread_trading import SpreadTradingApp
|
||||
from vnpy.app.portfolio_manager import PortfolioManagerApp
|
||||
|
||||
|
||||
def main():
|
||||
@ -81,15 +82,16 @@ def main():
|
||||
# main_engine.add_gateway(GateiosGateway)
|
||||
main_engine.add_gateway(BybitGateway)
|
||||
|
||||
main_engine.add_app(CtaStrategyApp)
|
||||
main_engine.add_app(CtaBacktesterApp)
|
||||
# main_engine.add_app(CtaStrategyApp)
|
||||
# main_engine.add_app(CtaBacktesterApp)
|
||||
# main_engine.add_app(CsvLoaderApp)
|
||||
# main_engine.add_app(AlgoTradingApp)
|
||||
main_engine.add_app(DataRecorderApp)
|
||||
# main_engine.add_app(DataRecorderApp)
|
||||
# main_engine.add_app(RiskManagerApp)
|
||||
# main_engine.add_app(ScriptTraderApp)
|
||||
# main_engine.add_app(RpcServiceApp)
|
||||
main_engine.add_app(SpreadTradingApp)
|
||||
# main_engine.add_app(SpreadTradingApp)
|
||||
main_engine.add_app(PortfolioManagerApp)
|
||||
|
||||
main_window = MainWindow(main_engine, event_engine)
|
||||
main_window.showMaximized()
|
||||
|
16
vnpy/app/portfolio_manager/__init__.py
Normal file
16
vnpy/app/portfolio_manager/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
from pathlib import Path
|
||||
|
||||
from vnpy.trader.app import BaseApp
|
||||
|
||||
from .engine import PortfolioEngine, APP_NAME
|
||||
|
||||
|
||||
class PortfolioManagerApp(BaseApp):
|
||||
""""""
|
||||
app_name = APP_NAME
|
||||
app_module = __module__
|
||||
app_path = Path(__file__).parent
|
||||
display_name = "投资组合"
|
||||
engine_class = PortfolioEngine
|
||||
widget_name = "PortfolioManager"
|
||||
icon_name = "portfolio.ico"
|
381
vnpy/app/portfolio_manager/engine.py
Normal file
381
vnpy/app/portfolio_manager/engine.py
Normal file
@ -0,0 +1,381 @@
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Set
|
||||
from collections import defaultdict
|
||||
from copy import copy
|
||||
|
||||
from vnpy.event import Event, EventEngine
|
||||
from vnpy.trader.engine import BaseEngine, MainEngine
|
||||
from vnpy.trader.event import (
|
||||
EVENT_TRADE, EVENT_ORDER, EVENT_TICK, EVENT_CONTRACT, EVENT_TIMER
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset, OrderType
|
||||
from vnpy.trader.object import (
|
||||
OrderRequest, CancelRequest, SubscribeRequest,
|
||||
OrderData, TradeData, TickData, ContractData
|
||||
)
|
||||
from vnpy.trader.utility import load_json, save_json
|
||||
|
||||
|
||||
APP_NAME = "PortfolioManager"
|
||||
|
||||
EVENT_PORTFOLIO_UPDATE = "ePortfioUpdate"
|
||||
EVENT_PORTFOLIO_ORDER = "ePortfioOrder"
|
||||
EVENT_PORTFOLIO_TRADE = "ePortfioTrade"
|
||||
|
||||
|
||||
class PortfolioEngine(BaseEngine):
|
||||
""""""
|
||||
setting_filename = "portfolio_manager_setting.json"
|
||||
|
||||
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super().__init__(main_engine, event_engine, APP_NAME)
|
||||
|
||||
self.inited = False
|
||||
|
||||
self.strategies: Dict[str, PortfolioStrategy] = {}
|
||||
self.symbol_strategy_map: Dict[str, List] = defaultdict(list)
|
||||
self.order_strategy_map: Dict[str, PortfolioStrategy] = {}
|
||||
self.active_orders: Set[str] = set()
|
||||
|
||||
self.register_event()
|
||||
|
||||
def init_engine(self):
|
||||
""""""
|
||||
if self.inited:
|
||||
return
|
||||
self.inited = True
|
||||
|
||||
self.load_setting()
|
||||
|
||||
def load_setting(self):
|
||||
""""""
|
||||
setting: dict = load_json(self.setting_filename)
|
||||
|
||||
for d in setting.values():
|
||||
self.add_strategy(
|
||||
d["name"],
|
||||
d["vt_symbol"],
|
||||
d["size"],
|
||||
d["net_pos"],
|
||||
d["open_price"],
|
||||
d["last_price"],
|
||||
d["create_time"],
|
||||
d["note_text"],
|
||||
)
|
||||
|
||||
def save_setting(self):
|
||||
""""""
|
||||
setting: dict = {}
|
||||
|
||||
for strategy in self.strategies.values():
|
||||
setting[strategy.name] = {
|
||||
"name": strategy.name,
|
||||
"vt_symbol": strategy.vt_symbol,
|
||||
"size": strategy.size,
|
||||
"net_pos": strategy.net_pos,
|
||||
"open_price": strategy.open_price,
|
||||
"last_price": strategy.last_price,
|
||||
"create_time": strategy.create_time,
|
||||
"note_text": strategy.note_text,
|
||||
}
|
||||
|
||||
save_json(self.setting_filename, setting)
|
||||
|
||||
def register_event(self):
|
||||
""""""
|
||||
self.event_engine.register(EVENT_ORDER, self.process_order_event)
|
||||
self.event_engine.register(EVENT_TRADE, self.process_trade_event)
|
||||
self.event_engine.register(EVENT_TICK, self.process_tick_event)
|
||||
self.event_engine.register(EVENT_CONTRACT, self.process_contract_event)
|
||||
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
|
||||
|
||||
def process_timer_event(self, event: Event):
|
||||
""""""
|
||||
if self.inited:
|
||||
self.save_setting()
|
||||
|
||||
def process_contract_event(self, event: Event):
|
||||
""""""
|
||||
contract: ContractData = event.data
|
||||
|
||||
if contract.vt_symbol in self.symbol_strategy_map:
|
||||
self.subscribe_data(contract.vt_symbol)
|
||||
|
||||
def process_order_event(self, event: Event):
|
||||
""""""
|
||||
order: OrderData = event.data
|
||||
|
||||
if order.vt_orderid not in self.active_orders:
|
||||
return
|
||||
|
||||
if not order.is_active():
|
||||
self.active_orders.remove(order.vt_orderid)
|
||||
|
||||
strategy: PortfolioStrategy = self.order_strategy_map[order.vt_orderid]
|
||||
strategy_order = copy(order)
|
||||
strategy_order.gateway_name = strategy.name
|
||||
|
||||
event = Event(EVENT_PORTFOLIO_ORDER, strategy_order)
|
||||
self.event_engine.put(event)
|
||||
|
||||
def process_trade_event(self, event: Event):
|
||||
""""""
|
||||
trade: TradeData = event.data
|
||||
|
||||
strategy: PortfolioStrategy = self.order_strategy_map.get(
|
||||
trade.vt_orderid)
|
||||
if strategy:
|
||||
strategy.update_trade(
|
||||
trade.direction,
|
||||
trade.volume,
|
||||
trade.price
|
||||
)
|
||||
|
||||
self.put_strategy_event(strategy.name)
|
||||
|
||||
strategy_trade = copy(trade)
|
||||
strategy_trade.gateway_name = strategy.name
|
||||
event = Event(EVENT_PORTFOLIO_TRADE, strategy_trade)
|
||||
self.event_engine.put(event)
|
||||
|
||||
self.save_setting()
|
||||
|
||||
def process_tick_event(self, event: Event):
|
||||
""""""
|
||||
tick: TickData = event.data
|
||||
|
||||
strategies: List = self.symbol_strategy_map[tick.vt_symbol]
|
||||
for strategy in strategies:
|
||||
strategy.update_price(tick.last_price)
|
||||
|
||||
self.put_strategy_event(strategy.name)
|
||||
|
||||
def add_strategy(
|
||||
self,
|
||||
name: str,
|
||||
vt_symbol: str,
|
||||
size: int = 0,
|
||||
net_pos: int = 0,
|
||||
open_price: float = 0,
|
||||
last_price: float = 0,
|
||||
create_time: str = "",
|
||||
note_text: str = ""
|
||||
):
|
||||
""""""
|
||||
if name in self.strategies:
|
||||
return False
|
||||
|
||||
if not size:
|
||||
contract = self.main_engine.get_contract(vt_symbol)
|
||||
if not contract:
|
||||
return False
|
||||
size = contract.size
|
||||
|
||||
strategy = PortfolioStrategy(
|
||||
name,
|
||||
vt_symbol,
|
||||
size,
|
||||
net_pos,
|
||||
open_price,
|
||||
last_price,
|
||||
create_time,
|
||||
note_text
|
||||
)
|
||||
|
||||
self.strategies[strategy.name] = strategy
|
||||
self.symbol_strategy_map[strategy.vt_symbol].append(strategy)
|
||||
self.save_setting()
|
||||
|
||||
self.subscribe_data(vt_symbol)
|
||||
self.put_strategy_event(name)
|
||||
|
||||
return True
|
||||
|
||||
def remove_strategy(self, name: str):
|
||||
""""""
|
||||
if name not in self.strategies:
|
||||
return False
|
||||
|
||||
strategy = self.strategies.pop(name)
|
||||
self.symbol_strategy_map[strategy.vt_symbol].remove(strategy)
|
||||
|
||||
return True
|
||||
|
||||
def subscribe_data(self, vt_symbol: str):
|
||||
""""""
|
||||
contract = self.main_engine.get_contract(vt_symbol)
|
||||
if not contract:
|
||||
return
|
||||
|
||||
req = SubscribeRequest(
|
||||
symbol=contract.symbol,
|
||||
exchange=contract.exchange
|
||||
)
|
||||
self.main_engine.subscribe(req, contract.gateway_name)
|
||||
|
||||
def send_order(
|
||||
self,
|
||||
name: str,
|
||||
price: float,
|
||||
volume: int,
|
||||
direction: Direction,
|
||||
offset: Offset = Offset.NONE
|
||||
):
|
||||
""""""
|
||||
strategy = self.strategies[name]
|
||||
vt_symbol = strategy.vt_symbol
|
||||
|
||||
contract = self.main_engine.get_contract(vt_symbol)
|
||||
if not contract:
|
||||
return False
|
||||
|
||||
req = OrderRequest(
|
||||
symbol=contract.symbol,
|
||||
exchange=contract.exchange,
|
||||
direction=direction,
|
||||
type=OrderType.LIMIT,
|
||||
volume=volume,
|
||||
price=price,
|
||||
offset=offset
|
||||
)
|
||||
vt_orderid = self.main_engine.send_order(req, contract.gateway_name)
|
||||
|
||||
self.order_strategy_map[vt_orderid] = strategy
|
||||
self.active_orders.add(vt_orderid)
|
||||
|
||||
return True
|
||||
|
||||
def cancel_order(self, vt_orderid: str):
|
||||
""""""
|
||||
if vt_orderid not in self.order_strategy_map:
|
||||
return False
|
||||
|
||||
order = self.main_engine.get_order(vt_orderid)
|
||||
|
||||
req = CancelRequest(
|
||||
orderid=order.orderid,
|
||||
symbol=order.symbol,
|
||||
exchange=order.exchange
|
||||
)
|
||||
self.main_engine.cancel_order(req, order.gateway_name)
|
||||
|
||||
return True
|
||||
|
||||
def cancel_all(self, name: str):
|
||||
""""""
|
||||
for vt_orderid in self.active_orders:
|
||||
strategy = self.order_strategy_map[vt_orderid]
|
||||
if strategy.name == name:
|
||||
self.cancel_order(vt_orderid)
|
||||
|
||||
def put_strategy_event(self, name: str):
|
||||
""""""
|
||||
strategy = self.strategies[name]
|
||||
event = Event(EVENT_PORTFOLIO_UPDATE, strategy)
|
||||
self.event_engine.put(event)
|
||||
|
||||
def stop(self):
|
||||
""""""
|
||||
self.save_setting()
|
||||
|
||||
|
||||
class PortfolioStrategy:
|
||||
""""""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
vt_symbol: str,
|
||||
size: int,
|
||||
net_pos: int,
|
||||
open_price: float,
|
||||
last_price: float,
|
||||
create_time: str,
|
||||
note_text: str
|
||||
):
|
||||
""""""
|
||||
self.name: str = name
|
||||
self.vt_symbol: str = vt_symbol
|
||||
self.size: int = size
|
||||
|
||||
self.net_pos: int = net_pos
|
||||
self.open_price: float = open_price
|
||||
self.last_price: float = last_price
|
||||
|
||||
self.pos_pnl: float = 0
|
||||
self.realized_pnl: float = 0
|
||||
|
||||
self.create_time: str = ""
|
||||
if create_time:
|
||||
self.create_time = create_time
|
||||
else:
|
||||
self.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
self.note_text: str = note_text
|
||||
|
||||
self.calculate_pnl()
|
||||
|
||||
def calculate_pnl(self):
|
||||
""""""
|
||||
self.pos_pnl = (self.last_price - self.open_price) * \
|
||||
self.net_pos * self.size
|
||||
|
||||
def update_trade(
|
||||
self,
|
||||
trade_direction: Direction,
|
||||
trade_volume: int,
|
||||
trade_price: float
|
||||
):
|
||||
""""""
|
||||
old_cost = self.net_pos * self.open_price
|
||||
|
||||
if trade_direction == Direction.LONG:
|
||||
new_pos = self.net_pos + trade_volume
|
||||
|
||||
# Open new long position
|
||||
if self.net_pos >= 0:
|
||||
new_cost = old_cost + trade_volume * trade_price
|
||||
self.open_price = new_cost / new_pos
|
||||
# Close short position
|
||||
else:
|
||||
close_volume = min(trade_volume, abs(self.net_pos))
|
||||
realized_pnl = (trade_price - self.open_price) * \
|
||||
close_volume * (-1)
|
||||
self.realized_pnl += realized_pnl
|
||||
|
||||
if new_pos > 0:
|
||||
self.open_price = trade_price
|
||||
|
||||
# Update net pos
|
||||
self.net_pos = new_pos
|
||||
|
||||
else:
|
||||
new_pos = self.net_pos - trade_volume
|
||||
|
||||
# Open new short position
|
||||
if self.net_pos <= 0:
|
||||
new_cost = old_cost - trade_volume * trade_price
|
||||
self.open_price = new_cost / new_pos
|
||||
# Close long position
|
||||
else:
|
||||
close_volume = min(trade_volume, abs(self.net_pos))
|
||||
realized_pnl = (trade_price - self.open_price) * close_volume
|
||||
self.realized_pnl += realized_pnl
|
||||
|
||||
if new_pos < 0:
|
||||
self.open_price = trade_price
|
||||
|
||||
# Update net pos
|
||||
self.net_pos = new_pos
|
||||
|
||||
self.calculate_pnl()
|
||||
|
||||
def update_price(self, last_price: float):
|
||||
""""""
|
||||
self.last_price = last_price
|
||||
self.calculate_pnl()
|
||||
|
||||
def update_note(self, note_text: str):
|
||||
""""""
|
||||
self.note_text = note_text
|
1
vnpy/app/portfolio_manager/ui/__init__.py
Normal file
1
vnpy/app/portfolio_manager/ui/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .widget import PortfolioManager
|
BIN
vnpy/app/portfolio_manager/ui/portfolio.ico
Normal file
BIN
vnpy/app/portfolio_manager/ui/portfolio.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
393
vnpy/app/portfolio_manager/ui/widget.py
Normal file
393
vnpy/app/portfolio_manager/ui/widget.py
Normal file
@ -0,0 +1,393 @@
|
||||
from vnpy.event import EventEngine
|
||||
from vnpy.trader.engine import MainEngine
|
||||
from vnpy.trader.constant import Direction, Offset
|
||||
from vnpy.trader.ui import QtWidgets, QtGui
|
||||
from vnpy.trader.ui.widget import (
|
||||
BaseMonitor, BaseCell,
|
||||
PnlCell, DirectionCell, EnumCell,
|
||||
)
|
||||
|
||||
from ..engine import (
|
||||
PortfolioEngine,
|
||||
APP_NAME,
|
||||
EVENT_PORTFOLIO_UPDATE,
|
||||
EVENT_PORTFOLIO_TRADE,
|
||||
EVENT_PORTFOLIO_ORDER
|
||||
)
|
||||
|
||||
|
||||
class PortfolioManager(QtWidgets.QWidget):
|
||||
""""""
|
||||
|
||||
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.main_engine = main_engine
|
||||
self.event_engine = event_engine
|
||||
|
||||
self.portfolio_engine = main_engine.get_engine(APP_NAME)
|
||||
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
self.setWindowTitle("投资组合")
|
||||
|
||||
strategy_monitor = PortfolioStrategyMonitor(
|
||||
self.main_engine, self.event_engine)
|
||||
order_monitor = PortfolioOrderMonitor(
|
||||
self.main_engine, self.event_engine)
|
||||
trade_monitor = PortfolioTradeMonitor(
|
||||
self.main_engine, self.event_engine)
|
||||
|
||||
self.trading_widget = StrategyTradingWidget(self.portfolio_engine)
|
||||
self.management_widget = StrategyManagementWidget(
|
||||
self.portfolio_engine,
|
||||
self.trading_widget,
|
||||
strategy_monitor
|
||||
)
|
||||
|
||||
vbox = QtWidgets.QVBoxLayout()
|
||||
vbox.addWidget(self.management_widget)
|
||||
vbox.addWidget(self.create_group("策略", strategy_monitor))
|
||||
vbox.addWidget(self.trading_widget)
|
||||
vbox.addWidget(self.create_group("委托", order_monitor))
|
||||
vbox.addWidget(self.create_group("成交", trade_monitor))
|
||||
|
||||
self.setLayout(vbox)
|
||||
|
||||
def show(self):
|
||||
""""""
|
||||
self.portfolio_engine.init_engine()
|
||||
self.management_widget.update_combo()
|
||||
|
||||
self.showMaximized()
|
||||
|
||||
def create_group(self, title: str, widget: QtWidgets.QWidget):
|
||||
""""""
|
||||
group = QtWidgets.QGroupBox()
|
||||
|
||||
vbox = QtWidgets.QVBoxLayout()
|
||||
vbox.addWidget(widget)
|
||||
|
||||
group.setLayout(vbox)
|
||||
group.setTitle(title)
|
||||
|
||||
return group
|
||||
|
||||
|
||||
class PortfolioStrategyMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for portfolio strategy.
|
||||
"""
|
||||
|
||||
event_type = EVENT_PORTFOLIO_UPDATE
|
||||
data_key = "name"
|
||||
sorting = False
|
||||
|
||||
headers = {
|
||||
"name": {"display": "策略名称", "cell": BaseCell, "update": False},
|
||||
"vt_symbol": {"display": "交易合约", "cell": BaseCell, "update": False},
|
||||
"size": {"display": "合约乘数", "cell": BaseCell, "update": False},
|
||||
"net_pos": {"display": "策略持仓", "cell": BaseCell, "update": True},
|
||||
"open_price": {"display": "持仓价格", "cell": BaseCell, "update": True},
|
||||
"last_price": {"display": "最新价格", "cell": BaseCell, "update": True},
|
||||
"pos_pnl": {"display": "持仓盈亏", "cell": PnlCell, "update": True},
|
||||
"realized_pnl": {"display": "平仓盈亏", "cell": PnlCell, "update": True},
|
||||
"create_time": {"display": "创建时间", "cell": BaseCell, "update": False},
|
||||
}
|
||||
|
||||
def remove_strategy(self, name: str):
|
||||
""""""
|
||||
if name not in self.cells:
|
||||
return
|
||||
|
||||
row_cells = self.cells.pop(name)
|
||||
row = self.row(row_cells["net_pos"])
|
||||
self.removeRow(row)
|
||||
|
||||
|
||||
class PortfolioTradeMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for trade data.
|
||||
"""
|
||||
|
||||
event_type = EVENT_PORTFOLIO_TRADE
|
||||
data_key = ""
|
||||
|
||||
headers = {
|
||||
"gateway_name": {"display": "策略名称", "cell": BaseCell, "update": False},
|
||||
"tradeid": {"display": "成交号 ", "cell": BaseCell, "update": False},
|
||||
"orderid": {"display": "委托号", "cell": BaseCell, "update": False},
|
||||
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
|
||||
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
|
||||
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
|
||||
"offset": {"display": "开平", "cell": EnumCell, "update": False},
|
||||
"price": {"display": "价格", "cell": BaseCell, "update": False},
|
||||
"volume": {"display": "数量", "cell": BaseCell, "update": False},
|
||||
"time": {"display": "时间", "cell": BaseCell, "update": False},
|
||||
}
|
||||
|
||||
|
||||
class PortfolioOrderMonitor(BaseMonitor):
|
||||
"""
|
||||
Monitor for order data.
|
||||
"""
|
||||
|
||||
event_type = EVENT_PORTFOLIO_ORDER
|
||||
data_key = "vt_orderid"
|
||||
sorting = True
|
||||
|
||||
headers = {
|
||||
"gateway_name": {"display": "策略名称", "cell": BaseCell, "update": False},
|
||||
"orderid": {"display": "委托号", "cell": BaseCell, "update": False},
|
||||
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
|
||||
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
|
||||
"type": {"display": "类型", "cell": EnumCell, "update": False},
|
||||
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
|
||||
"offset": {"display": "开平", "cell": EnumCell, "update": False},
|
||||
"price": {"display": "价格", "cell": BaseCell, "update": False},
|
||||
"volume": {"display": "总数量", "cell": BaseCell, "update": True},
|
||||
"traded": {"display": "已成交", "cell": BaseCell, "update": True},
|
||||
"status": {"display": "状态", "cell": EnumCell, "update": True},
|
||||
"time": {"display": "时间", "cell": BaseCell, "update": True},
|
||||
}
|
||||
|
||||
def init_ui(self):
|
||||
"""
|
||||
Connect signal.
|
||||
"""
|
||||
super(PortfolioOrderMonitor, self).init_ui()
|
||||
|
||||
self.setToolTip("双击单元格撤单")
|
||||
self.itemDoubleClicked.connect(self.cancel_order)
|
||||
|
||||
def cancel_order(self, cell):
|
||||
"""
|
||||
Cancel order if cell double clicked.
|
||||
"""
|
||||
order = cell.get_data()
|
||||
req = order.create_cancel_request()
|
||||
self.main_engine.cancel_order(req, order.gateway_name)
|
||||
|
||||
|
||||
class StrategyTradingWidget(QtWidgets.QWidget):
|
||||
""""""
|
||||
|
||||
def __init__(self, portfolio_engine: PortfolioEngine):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.portfolio_engine = portfolio_engine
|
||||
self.init_ui()
|
||||
self.update_combo()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
self.name_combo = QtWidgets.QComboBox()
|
||||
|
||||
self.direction_combo = QtWidgets.QComboBox()
|
||||
self.direction_combo.addItems(
|
||||
[Direction.LONG.value, Direction.SHORT.value])
|
||||
|
||||
self.offset_combo = QtWidgets.QComboBox()
|
||||
self.offset_combo.addItems([offset.value for offset in Offset])
|
||||
|
||||
double_validator = QtGui.QDoubleValidator()
|
||||
double_validator.setBottom(0)
|
||||
|
||||
self.price_line = QtWidgets.QLineEdit()
|
||||
self.price_line.setValidator(double_validator)
|
||||
|
||||
self.volume_line = QtWidgets.QLineEdit()
|
||||
self.volume_line.setValidator(double_validator)
|
||||
|
||||
for w in [
|
||||
self.name_combo,
|
||||
self.price_line,
|
||||
self.volume_line,
|
||||
self.direction_combo,
|
||||
self.offset_combo
|
||||
]:
|
||||
w.setFixedWidth(150)
|
||||
|
||||
send_button = QtWidgets.QPushButton("委托")
|
||||
send_button.clicked.connect(self.send_order)
|
||||
send_button.setFixedWidth(70)
|
||||
|
||||
cancel_button = QtWidgets.QPushButton("全撤")
|
||||
cancel_button.clicked.connect(self.cancel_all)
|
||||
cancel_button.setFixedWidth(70)
|
||||
|
||||
hbox = QtWidgets.QHBoxLayout()
|
||||
hbox.addWidget(QtWidgets.QLabel("策略名称"))
|
||||
hbox.addWidget(self.name_combo)
|
||||
hbox.addWidget(QtWidgets.QLabel("方向"))
|
||||
hbox.addWidget(self.direction_combo)
|
||||
hbox.addWidget(QtWidgets.QLabel("开平"))
|
||||
hbox.addWidget(self.offset_combo)
|
||||
hbox.addWidget(QtWidgets.QLabel("价格"))
|
||||
hbox.addWidget(self.price_line)
|
||||
hbox.addWidget(QtWidgets.QLabel("数量"))
|
||||
hbox.addWidget(self.volume_line)
|
||||
hbox.addWidget(send_button)
|
||||
hbox.addWidget(cancel_button)
|
||||
hbox.addStretch()
|
||||
|
||||
self.setLayout(hbox)
|
||||
|
||||
def send_order(self):
|
||||
""""""
|
||||
name = self.name_combo.currentText()
|
||||
|
||||
price_text = self.price_line.text()
|
||||
volume_text = self.volume_line.text()
|
||||
|
||||
if not price_text or not volume_text:
|
||||
return
|
||||
|
||||
price = float(price_text)
|
||||
volume = float(volume_text)
|
||||
direction = Direction(self.direction_combo.currentText())
|
||||
offset = Offset(self.offset_combo.currentText())
|
||||
|
||||
self.portfolio_engine.send_order(
|
||||
name,
|
||||
price,
|
||||
volume,
|
||||
direction,
|
||||
offset
|
||||
)
|
||||
|
||||
def cancel_all(self):
|
||||
""""""
|
||||
name = self.name_combo.currentText()
|
||||
self.portfolio_engine.cancel_all(name)
|
||||
|
||||
def update_combo(self):
|
||||
""""""
|
||||
strategy_names = list(self.portfolio_engine.strategies.keys())
|
||||
|
||||
self.name_combo.clear()
|
||||
self.name_combo.addItems(strategy_names)
|
||||
|
||||
|
||||
class StrategyManagementWidget(QtWidgets.QWidget):
|
||||
""""""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
portfolio_engine: PortfolioEngine,
|
||||
trading_widget: StrategyTradingWidget,
|
||||
strategy_monitor: PortfolioStrategyMonitor
|
||||
):
|
||||
""""""
|
||||
super().__init__()
|
||||
|
||||
self.portfolio_engine = portfolio_engine
|
||||
self.trading_widget = trading_widget
|
||||
self.strategy_monitor = strategy_monitor
|
||||
|
||||
self.init_ui()
|
||||
self.update_combo()
|
||||
|
||||
def init_ui(self):
|
||||
""""""
|
||||
self.name_line = QtWidgets.QLineEdit()
|
||||
self.symbol_line = QtWidgets.QLineEdit()
|
||||
self.remove_combo = QtWidgets.QComboBox()
|
||||
|
||||
for w in [
|
||||
self.name_line,
|
||||
self.symbol_line,
|
||||
self.remove_combo
|
||||
]:
|
||||
w.setFixedWidth(150)
|
||||
|
||||
add_button = QtWidgets.QPushButton("创建策略")
|
||||
add_button.clicked.connect(self.add_strategy)
|
||||
|
||||
remove_button = QtWidgets.QPushButton("移除策略")
|
||||
remove_button.clicked.connect(self.remove_strategy)
|
||||
|
||||
hbox = QtWidgets.QHBoxLayout()
|
||||
hbox.addWidget(QtWidgets.QLabel("策略名称"))
|
||||
hbox.addWidget(self.name_line)
|
||||
hbox.addWidget(QtWidgets.QLabel("交易合约"))
|
||||
hbox.addWidget(self.symbol_line)
|
||||
hbox.addWidget(add_button)
|
||||
hbox.addStretch()
|
||||
hbox.addWidget(self.remove_combo)
|
||||
hbox.addWidget(remove_button)
|
||||
|
||||
self.setLayout(hbox)
|
||||
|
||||
def add_strategy(self):
|
||||
""""""
|
||||
name = self.name_line.text()
|
||||
vt_symbol = self.symbol_line.text()
|
||||
|
||||
if not name or not vt_symbol:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
"提示",
|
||||
"请输入策略名称和交易合约",
|
||||
QtWidgets.QMessageBox.Ok
|
||||
)
|
||||
|
||||
result = self.portfolio_engine.add_strategy(name, vt_symbol)
|
||||
|
||||
if result:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
"提示",
|
||||
"策略创建成功",
|
||||
QtWidgets.QMessageBox.Ok
|
||||
)
|
||||
|
||||
self.update_combo()
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"提示",
|
||||
"策略创建失败,存在重名或找不到合约",
|
||||
QtWidgets.QMessageBox.Ok
|
||||
)
|
||||
|
||||
def remove_strategy(self):
|
||||
""""""
|
||||
name = self.remove_combo.currentText()
|
||||
|
||||
if not name:
|
||||
return
|
||||
|
||||
result = self.portfolio_engine.remove_strategy(name)
|
||||
|
||||
if result:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
"提示",
|
||||
"策略移除成功",
|
||||
QtWidgets.QMessageBox.Ok
|
||||
)
|
||||
|
||||
self.update_combo()
|
||||
|
||||
self.strategy_monitor.remove_strategy(name)
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"提示",
|
||||
"策略移除失败,不存在该策略",
|
||||
QtWidgets.QMessageBox.Ok
|
||||
)
|
||||
|
||||
def update_combo(self):
|
||||
""""""
|
||||
strategy_names = list(self.portfolio_engine.strategies.keys())
|
||||
|
||||
self.remove_combo.clear()
|
||||
self.remove_combo.addItems(strategy_names)
|
||||
|
||||
self.trading_widget.update_combo()
|
Loading…
Reference in New Issue
Block a user