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.gateios import GateiosGateway
|
||||||
from vnpy.gateway.bybit import BybitGateway
|
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.csv_loader import CsvLoaderApp
|
||||||
# from vnpy.app.algo_trading import AlgoTradingApp
|
# from vnpy.app.algo_trading import AlgoTradingApp
|
||||||
from vnpy.app.cta_backtester import CtaBacktesterApp
|
# from vnpy.app.cta_backtester import CtaBacktesterApp
|
||||||
from vnpy.app.data_recorder import DataRecorderApp
|
# from vnpy.app.data_recorder import DataRecorderApp
|
||||||
# from vnpy.app.risk_manager import RiskManagerApp
|
# from vnpy.app.risk_manager import RiskManagerApp
|
||||||
from vnpy.app.script_trader import ScriptTraderApp
|
# from vnpy.app.script_trader import ScriptTraderApp
|
||||||
from vnpy.app.rpc_service import RpcServiceApp
|
# from vnpy.app.rpc_service import RpcServiceApp
|
||||||
from vnpy.app.spread_trading import SpreadTradingApp
|
# from vnpy.app.spread_trading import SpreadTradingApp
|
||||||
|
from vnpy.app.portfolio_manager import PortfolioManagerApp
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -81,15 +82,16 @@ def main():
|
|||||||
# main_engine.add_gateway(GateiosGateway)
|
# main_engine.add_gateway(GateiosGateway)
|
||||||
main_engine.add_gateway(BybitGateway)
|
main_engine.add_gateway(BybitGateway)
|
||||||
|
|
||||||
main_engine.add_app(CtaStrategyApp)
|
# main_engine.add_app(CtaStrategyApp)
|
||||||
main_engine.add_app(CtaBacktesterApp)
|
# main_engine.add_app(CtaBacktesterApp)
|
||||||
# main_engine.add_app(CsvLoaderApp)
|
# main_engine.add_app(CsvLoaderApp)
|
||||||
# main_engine.add_app(AlgoTradingApp)
|
# main_engine.add_app(AlgoTradingApp)
|
||||||
main_engine.add_app(DataRecorderApp)
|
# main_engine.add_app(DataRecorderApp)
|
||||||
# main_engine.add_app(RiskManagerApp)
|
# main_engine.add_app(RiskManagerApp)
|
||||||
# main_engine.add_app(ScriptTraderApp)
|
# main_engine.add_app(ScriptTraderApp)
|
||||||
# main_engine.add_app(RpcServiceApp)
|
# 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 = MainWindow(main_engine, event_engine)
|
||||||
main_window.showMaximized()
|
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