[Add] spread price and pos calculation
This commit is contained in:
parent
5a9dfa747d
commit
98912462f6
156
vnpy/app/spread_trading/base.py
Normal file
156
vnpy/app/spread_trading/base.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
from typing import Dict, List
|
||||||
|
from math import floor, ceil
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from vnpy.trader.object import TickData, PositionData
|
||||||
|
from vnpy.trader.constant import Direction
|
||||||
|
|
||||||
|
|
||||||
|
class LegData:
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
vt_symbol: str,
|
||||||
|
price_multiplier: float,
|
||||||
|
trading_multiplier: float
|
||||||
|
):
|
||||||
|
""""""
|
||||||
|
self.vt_symbol: str = vt_symbol
|
||||||
|
|
||||||
|
# For calculating spread price
|
||||||
|
self.price_multiplier: float = price_multiplier
|
||||||
|
|
||||||
|
# For calculating spread pos and sending orders
|
||||||
|
self.trading_multiplier: float = trading_multiplier
|
||||||
|
|
||||||
|
# Price and position data
|
||||||
|
self.bid_price: float = 0
|
||||||
|
self.ask_price: float = 0
|
||||||
|
self.bid_volume: float = 0
|
||||||
|
self.ask_volume: float = 0
|
||||||
|
|
||||||
|
self.long_pos: float = 0
|
||||||
|
self.short_pos: float = 0
|
||||||
|
self.net_pos: float = 0
|
||||||
|
|
||||||
|
def update_tick(self, tick: TickData):
|
||||||
|
""""""
|
||||||
|
self.bid_price = tick.bid_price_1
|
||||||
|
self.ask_price = tick.ask_price_1
|
||||||
|
self.bid_volume = tick.bid_volume_1
|
||||||
|
self.ask_volume = tick.ask_volume_1
|
||||||
|
|
||||||
|
def update_position(self, position: PositionData):
|
||||||
|
""""""
|
||||||
|
if position.direction == Direction.NET:
|
||||||
|
self.net_pos = position.volume
|
||||||
|
else:
|
||||||
|
if position.direction == Direction.LONG:
|
||||||
|
self.long_pos = position.volume
|
||||||
|
else:
|
||||||
|
self.short_pos = position.volume
|
||||||
|
self.net_pos = self.long_pos - self.short_pos
|
||||||
|
|
||||||
|
|
||||||
|
class SpreadData:
|
||||||
|
""""""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
legs: List[LegData],
|
||||||
|
active_symbol: str
|
||||||
|
):
|
||||||
|
""""""
|
||||||
|
self.name: str = name
|
||||||
|
|
||||||
|
self.legs: Dict[str, LegData] = {}
|
||||||
|
self.active_leg: LegData = None
|
||||||
|
self.passive_legs: List[LegData] = []
|
||||||
|
|
||||||
|
for leg in legs:
|
||||||
|
self.legs[leg.vt_symbol] = leg
|
||||||
|
if leg.vt_symbol == active_symbol:
|
||||||
|
self.active_leg = leg
|
||||||
|
else:
|
||||||
|
self.passive_legs.append(leg)
|
||||||
|
|
||||||
|
# Spread data
|
||||||
|
self.bid_price: float = 0
|
||||||
|
self.ask_price: float = 0
|
||||||
|
self.bid_volume: float = 0
|
||||||
|
self.ask_volume: float = 0
|
||||||
|
|
||||||
|
self.net_pos: float = 0
|
||||||
|
self.datetime: datetime = None
|
||||||
|
|
||||||
|
def calculate_price(self):
|
||||||
|
""""""
|
||||||
|
self.clear_price()
|
||||||
|
|
||||||
|
# Go through all legs to calculate price
|
||||||
|
for n, leg in enumerate(self.legs.values()):
|
||||||
|
# Filter not all leg price data has been received
|
||||||
|
if not leg.bid_volume or not leg.ask_volume:
|
||||||
|
self.clear_price()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate price
|
||||||
|
if leg.price_multiplier > 0:
|
||||||
|
self.bid_price += leg.bid_price * leg.price_multiplier
|
||||||
|
self.ask_price += leg.ask_price * leg.price_multiplier
|
||||||
|
else:
|
||||||
|
self.bid_price += leg.ask_price * leg.price_multiplier
|
||||||
|
self.ask_price += leg.bid_price * leg.price_multiplier
|
||||||
|
|
||||||
|
# Calculate volume
|
||||||
|
if leg.trading_multiplier > 0:
|
||||||
|
adjusted_bid_volume = floor(
|
||||||
|
leg.bid_volume / leg.trading_multiplier)
|
||||||
|
adjusted_ask_volume = floor(
|
||||||
|
leg.ask_volume / leg.trading_multiplier)
|
||||||
|
else:
|
||||||
|
adjusted_bid_volume = floor(
|
||||||
|
leg.ask_volume / abs(leg.trading_multiplier))
|
||||||
|
adjusted_ask_volume = floor(
|
||||||
|
leg.bid_volume / abs(leg.trading_multiplier))
|
||||||
|
|
||||||
|
# For the first leg, just initialize
|
||||||
|
if not n:
|
||||||
|
self.bid_volume = adjusted_bid_volume
|
||||||
|
self.ask_volume = adjusted_ask_volume
|
||||||
|
# For following legs, use min value of each leg quoting volume
|
||||||
|
else:
|
||||||
|
self.bid_volume = min(self.bid_volume, adjusted_bid_volume)
|
||||||
|
self.ask_volume = min(self.ask_volume, adjusted_ask_volume)
|
||||||
|
|
||||||
|
# Update calculate time
|
||||||
|
self.datetime = datetime.now()
|
||||||
|
|
||||||
|
def calculate_pos(self):
|
||||||
|
""""""
|
||||||
|
self.net_pos = 0
|
||||||
|
|
||||||
|
for n, leg in enumerate(self.legs.values()):
|
||||||
|
adjusted_net_pos = leg.net_pos / leg.trading_multiplier
|
||||||
|
|
||||||
|
if adjusted_net_pos > 0:
|
||||||
|
adjusted_net_pos = floor(adjusted_net_pos)
|
||||||
|
else:
|
||||||
|
adjusted_net_pos = ceil(adjusted_net_pos)
|
||||||
|
|
||||||
|
if not n:
|
||||||
|
self.net_pos = adjusted_net_pos
|
||||||
|
else:
|
||||||
|
if adjusted_net_pos > 0:
|
||||||
|
self.net_pos = min(self.net_pos, adjusted_net_pos)
|
||||||
|
else:
|
||||||
|
self.net_pos = max(self.net_pos, adjusted_net_pos)
|
||||||
|
|
||||||
|
def clear_price(self):
|
||||||
|
""""""
|
||||||
|
self.bid_price = 0
|
||||||
|
self.ask_price = 0
|
||||||
|
self.bid_volume = 0
|
||||||
|
self.ask_volume = 0
|
@ -1,6 +1,13 @@
|
|||||||
|
from typing import List, Dict
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from vnpy.event import EventEngine, Event
|
from vnpy.event import EventEngine, Event
|
||||||
from vnpy.trader.engine import BaseEngine, MainEngine
|
from vnpy.trader.engine import BaseEngine, MainEngine
|
||||||
|
from vnpy.trader.event import EVENT_TICK, EVENT_POSITION
|
||||||
|
from vnpy.trader.utility import load_json, save_json
|
||||||
|
|
||||||
|
from .base import LegData, SpreadData
|
||||||
|
|
||||||
|
|
||||||
APP_NAME = "SpreadTrading"
|
APP_NAME = "SpreadTrading"
|
||||||
|
|
||||||
@ -11,3 +18,141 @@ class SpreadEngine(BaseEngine):
|
|||||||
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
|
||||||
"""Constructor"""
|
"""Constructor"""
|
||||||
super().__init__(main_engine, event_engine, APP_NAME)
|
super().__init__(main_engine, event_engine, APP_NAME)
|
||||||
|
|
||||||
|
def write_log(self, msg: str):
|
||||||
|
""""""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SpreadDataEngine:
|
||||||
|
""""""
|
||||||
|
setting_filename = "spread_trading_setting.json"
|
||||||
|
|
||||||
|
def __init__(self, spread_engine: SpreadEngine):
|
||||||
|
""""""
|
||||||
|
self.spread_engine: SpreadEngine = spread_engine
|
||||||
|
self.main_engine: MainEngine = spread_engine.main_engine
|
||||||
|
self.event_engine: EventEngine = spread_engine.event_engine
|
||||||
|
|
||||||
|
self.write_log = spread_engine.write_log
|
||||||
|
|
||||||
|
self.legs: Dict[str, LegData] = {} # vt_symbol: leg
|
||||||
|
self.spreads: Dict[str, SpreadData] = {} # name: spread
|
||||||
|
self.symbol_spread_map: Dict[str, List[SpreadData]] = defaultdict(list)
|
||||||
|
|
||||||
|
self.load_setting()
|
||||||
|
self.register_event()
|
||||||
|
|
||||||
|
def load_setting(self):
|
||||||
|
""""""
|
||||||
|
setting = load_json(self.setting_filename)
|
||||||
|
|
||||||
|
for spread_setting in setting:
|
||||||
|
self.add_spread(
|
||||||
|
spread_setting["name"],
|
||||||
|
spread_setting["leg_settings"],
|
||||||
|
spread_setting["active_symbol"],
|
||||||
|
save=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def save_setting(self):
|
||||||
|
""""""
|
||||||
|
setting = []
|
||||||
|
|
||||||
|
for spread in self.spreads.values():
|
||||||
|
leg_settings = []
|
||||||
|
for leg in spread.legs:
|
||||||
|
leg_setting = {
|
||||||
|
"vt_symbol": leg.vt_symbol,
|
||||||
|
"price_multiplier": leg.price_multiplier,
|
||||||
|
"trading_multiplier": leg.trading_multiplier
|
||||||
|
}
|
||||||
|
leg_settings.append(leg_setting)
|
||||||
|
|
||||||
|
spread_setting = {
|
||||||
|
"name": spread.name,
|
||||||
|
"leg_settings": leg_settings,
|
||||||
|
"active_symbol": spread.active_leg.vt_symbol
|
||||||
|
}
|
||||||
|
setting.append(spread_setting)
|
||||||
|
|
||||||
|
save_json(self.setting_filename, setting)
|
||||||
|
|
||||||
|
def register_event(self):
|
||||||
|
""""""
|
||||||
|
self.event_engine.register(EVENT_TICK, self.process_tick_event)
|
||||||
|
self.event_engine.register(EVENT_POSITION, self.process_position_event)
|
||||||
|
|
||||||
|
def process_tick_event(self, event: Event):
|
||||||
|
""""""
|
||||||
|
tick = event.data
|
||||||
|
|
||||||
|
leg = self.legs.get(tick.vt_symbol, None)
|
||||||
|
if not leg:
|
||||||
|
return
|
||||||
|
leg.update_tick(tick)
|
||||||
|
|
||||||
|
for spread in self.symbol_spread_map[tick.vt_symbol]:
|
||||||
|
spread.calculate_price()
|
||||||
|
|
||||||
|
def process_position_event(self, event: Event):
|
||||||
|
""""""
|
||||||
|
position = event.data
|
||||||
|
|
||||||
|
leg = self.legs.get(position.vt_symbol, None)
|
||||||
|
if not leg:
|
||||||
|
return
|
||||||
|
leg.update_position(position)
|
||||||
|
|
||||||
|
for spread in self.symbol_spread_map[position.vt_symbol]:
|
||||||
|
spread.calculate_pos()
|
||||||
|
|
||||||
|
def add_spread(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
leg_settings: List[Dict],
|
||||||
|
active_symbol: str,
|
||||||
|
save: bool = True
|
||||||
|
):
|
||||||
|
""""""
|
||||||
|
if name in self.spreads:
|
||||||
|
self.write_log("价差创建失败,名称重复:{}".format(name))
|
||||||
|
return
|
||||||
|
|
||||||
|
legs: List[LegData] = []
|
||||||
|
for leg_setting in leg_settings:
|
||||||
|
vt_symbol = leg_setting["vt_symbol"]
|
||||||
|
|
||||||
|
leg = self.legs.get(vt_symbol, None)
|
||||||
|
if not leg:
|
||||||
|
leg = LegData(
|
||||||
|
vt_symbol,
|
||||||
|
leg_setting["price_multiplier"],
|
||||||
|
leg_setting["trading_multiplier"]
|
||||||
|
)
|
||||||
|
self.legs[vt_symbol] = leg
|
||||||
|
|
||||||
|
legs.append(leg)
|
||||||
|
|
||||||
|
spread = SpreadData(name, legs, active_symbol)
|
||||||
|
self.spreads[name] = spread
|
||||||
|
|
||||||
|
for leg in spread.legs:
|
||||||
|
self.symbol_spread_map[leg.vt_symbol].append(spread)
|
||||||
|
|
||||||
|
if save:
|
||||||
|
self.save_setting()
|
||||||
|
|
||||||
|
self.write_log("价差创建成功:{}".format(name))
|
||||||
|
|
||||||
|
def remove_spread(self, name: str):
|
||||||
|
""""""
|
||||||
|
if name not in self.spreads:
|
||||||
|
return
|
||||||
|
|
||||||
|
spread = self.spreads.pop(name)
|
||||||
|
|
||||||
|
for leg in spread.legs:
|
||||||
|
self.symbol_spread_map[leg.vt_symbol].remove(spread)
|
||||||
|
|
||||||
|
self.write_log("价差删除成功:{}".format(name))
|
||||||
|
Loading…
Reference in New Issue
Block a user