commit
2e01bef96f
@ -8,7 +8,7 @@ from vnpy.trader.ui import MainWindow, create_qapp
|
|||||||
from vnpy.gateway.bitmex import BitmexGateway
|
from vnpy.gateway.bitmex import BitmexGateway
|
||||||
# from vnpy.gateway.futu import FutuGateway
|
# from vnpy.gateway.futu import FutuGateway
|
||||||
# from vnpy.gateway.ib import IbGateway
|
# from vnpy.gateway.ib import IbGateway
|
||||||
from vnpy.gateway.ctp import CtpGateway
|
# from vnpy.gateway.ctp import CtpGateway
|
||||||
# from vnpy.gateway.ctptest import CtptestGateway
|
# from vnpy.gateway.ctptest import CtptestGateway
|
||||||
# from vnpy.gateway.mini import MiniGateway
|
# from vnpy.gateway.mini import MiniGateway
|
||||||
# from vnpy.gateway.sopt import SoptGateway
|
# from vnpy.gateway.sopt import SoptGateway
|
||||||
@ -18,20 +18,20 @@ from vnpy.gateway.ctp import CtpGateway
|
|||||||
# from vnpy.gateway.oes import OesGateway
|
# from vnpy.gateway.oes import OesGateway
|
||||||
# from vnpy.gateway.okex import OkexGateway
|
# from vnpy.gateway.okex import OkexGateway
|
||||||
# from vnpy.gateway.huobi import HuobiGateway
|
# from vnpy.gateway.huobi import HuobiGateway
|
||||||
from vnpy.gateway.bitfinex import BitfinexGateway
|
# from vnpy.gateway.bitfinex import BitfinexGateway
|
||||||
# from vnpy.gateway.onetoken import OnetokenGateway
|
# from vnpy.gateway.onetoken import OnetokenGateway
|
||||||
from vnpy.gateway.okexf import OkexfGateway
|
# from vnpy.gateway.okexf import OkexfGateway
|
||||||
from vnpy.gateway.okexs import OkexsGateway
|
# from vnpy.gateway.okexs import OkexsGateway
|
||||||
# from vnpy.gateway.xtp import XtpGateway
|
# from vnpy.gateway.xtp import XtpGateway
|
||||||
# from vnpy.gateway.hbdm import HbdmGateway
|
# from vnpy.gateway.hbdm import HbdmGateway
|
||||||
# from vnpy.gateway.tap import TapGateway
|
# from vnpy.gateway.tap import TapGateway
|
||||||
# from vnpy.gateway.tora import ToraGateway
|
# from vnpy.gateway.tora import ToraGateway
|
||||||
# from vnpy.gateway.alpaca import AlpacaGateway
|
# from vnpy.gateway.alpaca import AlpacaGateway
|
||||||
from vnpy.gateway.da import DaGateway
|
# from vnpy.gateway.da import DaGateway
|
||||||
from vnpy.gateway.coinbase import CoinbaseGateway
|
# from vnpy.gateway.coinbase import CoinbaseGateway
|
||||||
from vnpy.gateway.bitstamp import BitstampGateway
|
# from vnpy.gateway.bitstamp import BitstampGateway
|
||||||
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
|
||||||
@ -79,7 +79,7 @@ def main():
|
|||||||
# main_engine.add_gateway(CoinbaseGateway)
|
# main_engine.add_gateway(CoinbaseGateway)
|
||||||
# main_engine.add_gateway(BitstampGateway)
|
# main_engine.add_gateway(BitstampGateway)
|
||||||
# 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)
|
||||||
|
@ -48,6 +48,7 @@ public:
|
|||||||
///客户端认证响应
|
///客户端认证响应
|
||||||
virtual void OnRspAuthenticate(CThostFtdcRspAuthenticateField *pRspAuthenticateField, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {};
|
virtual void OnRspAuthenticate(CThostFtdcRspAuthenticateField *pRspAuthenticateField, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {};
|
||||||
|
|
||||||
|
|
||||||
///登录请求响应
|
///登录请求响应
|
||||||
virtual void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {};
|
virtual void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {};
|
||||||
|
|
||||||
|
@ -1976,6 +1976,11 @@ typedef char TThostFtdcCommentType[31];
|
|||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
typedef char TThostFtdcVersionType[4];
|
typedef char TThostFtdcVersionType[4];
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
/////UtpCipherVersionType是一个版本信息类型
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef char UtpCipherVersionType[65];
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
///TFtdcTradeCodeType是一个交易代码类型
|
///TFtdcTradeCodeType是一个交易代码类型
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
@ -6410,7 +6415,7 @@ typedef char TThostFtdcClientSystemInfoType[273];
|
|||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
///TThostFtdcAppIDType是App代码类型
|
///TThostFtdcAppIDType是App代码类型
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
typedef char TThostFtdcClientAppIDType[21];
|
typedef char TThostFtdcClientAppIDType[33];
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
///TThostFtdcAutoCodeType是AutoCode代码类型
|
///TThostFtdcAutoCodeType是AutoCode代码类型
|
||||||
|
@ -112,7 +112,7 @@ struct CThostFtdcReqAuthenticateField
|
|||||||
TThostFtdcProductInfoType UserProductInfo;
|
TThostFtdcProductInfoType UserProductInfo;
|
||||||
///认证码
|
///认证码
|
||||||
TThostFtdcAuthCodeType AuthCode;
|
TThostFtdcAuthCodeType AuthCode;
|
||||||
///App代码
|
// App代码
|
||||||
TThostFtdcClientAppIDType AppID;
|
TThostFtdcClientAppIDType AppID;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ struct CThostFtdcRspAuthenticateField
|
|||||||
TThostFtdcUserIDType UserID;
|
TThostFtdcUserIDType UserID;
|
||||||
///用户端产品信息
|
///用户端产品信息
|
||||||
TThostFtdcProductInfoType UserProductInfo;
|
TThostFtdcProductInfoType UserProductInfo;
|
||||||
///App代码
|
// App代码
|
||||||
TThostFtdcClientAppIDType AppID;
|
TThostFtdcClientAppIDType AppID;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -144,8 +144,10 @@ struct CThostFtdcAuthenticationInfoField
|
|||||||
TThostFtdcAuthInfoType AuthInfo;
|
TThostFtdcAuthInfoType AuthInfo;
|
||||||
///是否为认证结果
|
///是否为认证结果
|
||||||
TThostFtdcBoolType IsResult;
|
TThostFtdcBoolType IsResult;
|
||||||
///App代码
|
// App代码
|
||||||
TThostFtdcClientAppIDType AppID;
|
TThostFtdcClientAppIDType AppID;
|
||||||
|
// 版本信息
|
||||||
|
UtpCipherVersionType VerInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
///银期转帐报文头
|
///银期转帐报文头
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -552,7 +552,8 @@ class BacktestingEngine:
|
|||||||
self.pricetick,
|
self.pricetick,
|
||||||
self.capital,
|
self.capital,
|
||||||
self.end,
|
self.end,
|
||||||
self.mode
|
self.mode,
|
||||||
|
self.inverse
|
||||||
)))
|
)))
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
@ -612,6 +613,7 @@ class BacktestingEngine:
|
|||||||
global ga_capital
|
global ga_capital
|
||||||
global ga_end
|
global ga_end
|
||||||
global ga_mode
|
global ga_mode
|
||||||
|
global ga_inverse
|
||||||
|
|
||||||
ga_target_name = target_name
|
ga_target_name = target_name
|
||||||
ga_strategy_class = self.strategy_class
|
ga_strategy_class = self.strategy_class
|
||||||
@ -626,6 +628,7 @@ class BacktestingEngine:
|
|||||||
ga_capital = self.capital
|
ga_capital = self.capital
|
||||||
ga_end = self.end
|
ga_end = self.end
|
||||||
ga_mode = self.mode
|
ga_mode = self.mode
|
||||||
|
ga_inverse = self.inverse
|
||||||
|
|
||||||
# Set up genetic algorithem
|
# Set up genetic algorithem
|
||||||
toolbox = base.Toolbox()
|
toolbox = base.Toolbox()
|
||||||
@ -1217,7 +1220,8 @@ def _ga_optimize(parameter_values: tuple):
|
|||||||
ga_pricetick,
|
ga_pricetick,
|
||||||
ga_capital,
|
ga_capital,
|
||||||
ga_end,
|
ga_end,
|
||||||
ga_mode
|
ga_mode,
|
||||||
|
ga_inverse
|
||||||
)
|
)
|
||||||
return (result[1],)
|
return (result[1],)
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Sequence
|
from typing import Sequence, Type
|
||||||
|
|
||||||
from vnpy.event import EventEngine, Event
|
from vnpy.event import EventEngine, Event
|
||||||
from vnpy.trader.engine import MainEngine
|
from vnpy.trader.engine import MainEngine
|
||||||
@ -14,7 +14,7 @@ def process_log_event(event: Event):
|
|||||||
print(f"{log.time}\t{log.msg}")
|
print(f"{log.time}\t{log.msg}")
|
||||||
|
|
||||||
|
|
||||||
def init_cli_trading(gateways: Sequence[BaseGateway]):
|
def init_cli_trading(gateways: Sequence[Type[BaseGateway]]):
|
||||||
""""""
|
""""""
|
||||||
event_engine = EventEngine()
|
event_engine = EventEngine()
|
||||||
event_engine.register(EVENT_LOG, process_log_event)
|
event_engine.register(EVENT_LOG, process_log_event)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from vnpy.trader.constant import Direction
|
from vnpy.trader.constant import Direction, Offset
|
||||||
from vnpy.trader.object import (TickData, OrderData, TradeData)
|
from vnpy.trader.object import (TickData, OrderData, TradeData)
|
||||||
|
from vnpy.trader.utility import round_to
|
||||||
|
|
||||||
from .template import SpreadAlgoTemplate
|
from .template import SpreadAlgoTemplate
|
||||||
from .base import SpreadData
|
from .base import SpreadData
|
||||||
@ -17,6 +18,7 @@ class SpreadTakerAlgo(SpreadAlgoTemplate):
|
|||||||
algoid: str,
|
algoid: str,
|
||||||
spread: SpreadData,
|
spread: SpreadData,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
|
offset: Offset,
|
||||||
price: float,
|
price: float,
|
||||||
volume: float,
|
volume: float,
|
||||||
payup: int,
|
payup: int,
|
||||||
@ -25,8 +27,9 @@ class SpreadTakerAlgo(SpreadAlgoTemplate):
|
|||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
algo_engine, algoid, spread, direction,
|
algo_engine, algoid, spread,
|
||||||
price, volume, payup, interval, lock
|
direction, offset, price, volume,
|
||||||
|
payup, interval, lock
|
||||||
)
|
)
|
||||||
|
|
||||||
self.cancel_interval: int = 2
|
self.cancel_interval: int = 2
|
||||||
@ -109,6 +112,7 @@ class SpreadTakerAlgo(SpreadAlgoTemplate):
|
|||||||
# Calcualte spread volume to hedge
|
# Calcualte spread volume to hedge
|
||||||
active_leg = self.spread.active_leg
|
active_leg = self.spread.active_leg
|
||||||
active_traded = self.leg_traded[active_leg.vt_symbol]
|
active_traded = self.leg_traded[active_leg.vt_symbol]
|
||||||
|
active_traded = round_to(active_traded, self.spread.min_volume)
|
||||||
|
|
||||||
hedge_volume = self.spread.calculate_spread_volume(
|
hedge_volume = self.spread.calculate_spread_volume(
|
||||||
active_leg.vt_symbol,
|
active_leg.vt_symbol,
|
||||||
@ -118,6 +122,8 @@ class SpreadTakerAlgo(SpreadAlgoTemplate):
|
|||||||
# Calculate passive leg target volume and do hedge
|
# Calculate passive leg target volume and do hedge
|
||||||
for leg in self.spread.passive_legs:
|
for leg in self.spread.passive_legs:
|
||||||
passive_traded = self.leg_traded[leg.vt_symbol]
|
passive_traded = self.leg_traded[leg.vt_symbol]
|
||||||
|
passive_traded = round_to(passive_traded, self.spread.min_volume)
|
||||||
|
|
||||||
passive_target = self.spread.calculate_leg_volume(
|
passive_target = self.spread.calculate_leg_volume(
|
||||||
leg.vt_symbol,
|
leg.vt_symbol,
|
||||||
hedge_volume
|
hedge_volume
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
from math import floor, ceil
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from vnpy.trader.object import TickData, PositionData, TradeData
|
from vnpy.trader.object import TickData, PositionData, TradeData, ContractData
|
||||||
from vnpy.trader.constant import Direction, Offset, Exchange
|
from vnpy.trader.constant import Direction, Offset, Exchange
|
||||||
|
from vnpy.trader.utility import floor_to, ceil_to
|
||||||
|
|
||||||
|
|
||||||
EVENT_SPREAD_DATA = "eSpreadData"
|
EVENT_SPREAD_DATA = "eSpreadData"
|
||||||
@ -30,15 +30,30 @@ class LegData:
|
|||||||
self.short_pos: float = 0
|
self.short_pos: float = 0
|
||||||
self.net_pos: float = 0
|
self.net_pos: float = 0
|
||||||
|
|
||||||
|
self.last_price: float = 0
|
||||||
|
self.net_pos_price: float = 0 # Average entry price of net position
|
||||||
|
|
||||||
# Tick data buf
|
# Tick data buf
|
||||||
self.tick: TickData = None
|
self.tick: TickData = None
|
||||||
|
|
||||||
|
# Contract data
|
||||||
|
self.size: float = 0
|
||||||
|
self.net_position: bool = False
|
||||||
|
self.min_volume: float = 0
|
||||||
|
|
||||||
|
def update_contract(self, contract: ContractData):
|
||||||
|
""""""
|
||||||
|
self.size = contract.size
|
||||||
|
self.net_position = contract.net_position
|
||||||
|
self.min_volume = contract.min_volume
|
||||||
|
|
||||||
def update_tick(self, tick: TickData):
|
def update_tick(self, tick: TickData):
|
||||||
""""""
|
""""""
|
||||||
self.bid_price = tick.bid_price_1
|
self.bid_price = tick.bid_price_1
|
||||||
self.ask_price = tick.ask_price_1
|
self.ask_price = tick.ask_price_1
|
||||||
self.bid_volume = tick.bid_volume_1
|
self.bid_volume = tick.bid_volume_1
|
||||||
self.ask_volume = tick.ask_volume_1
|
self.ask_volume = tick.ask_volume_1
|
||||||
|
self.last_price = tick.last_price
|
||||||
|
|
||||||
self.tick = tick
|
self.tick = tick
|
||||||
|
|
||||||
@ -46,6 +61,7 @@ class LegData:
|
|||||||
""""""
|
""""""
|
||||||
if position.direction == Direction.NET:
|
if position.direction == Direction.NET:
|
||||||
self.net_pos = position.volume
|
self.net_pos = position.volume
|
||||||
|
self.net_pos_price = position.price
|
||||||
else:
|
else:
|
||||||
if position.direction == Direction.LONG:
|
if position.direction == Direction.LONG:
|
||||||
self.long_pos = position.volume
|
self.long_pos = position.volume
|
||||||
@ -55,6 +71,40 @@ class LegData:
|
|||||||
|
|
||||||
def update_trade(self, trade: TradeData):
|
def update_trade(self, trade: TradeData):
|
||||||
""""""
|
""""""
|
||||||
|
# Only update net pos for contract with net position mode
|
||||||
|
if self.net_position:
|
||||||
|
trade_cost = trade.volume * trade.price
|
||||||
|
old_cost = self.net_pos * self.net_pos_price
|
||||||
|
|
||||||
|
if trade.direction == Direction.LONG:
|
||||||
|
new_pos = self.net_pos + trade.volume
|
||||||
|
|
||||||
|
if self.net_pos >= 0:
|
||||||
|
new_cost = old_cost + trade_cost
|
||||||
|
self.net_pos_price = new_cost / new_pos
|
||||||
|
else:
|
||||||
|
# If all previous short position closed
|
||||||
|
if not new_pos:
|
||||||
|
self.net_pos_price = 0
|
||||||
|
# If only part short position closed
|
||||||
|
elif new_pos > 0:
|
||||||
|
self.net_pos_price = trade.price
|
||||||
|
else:
|
||||||
|
new_pos = self.net_pos - trade.volume
|
||||||
|
|
||||||
|
if self.net_pos <= 0:
|
||||||
|
new_cost = old_cost - trade_cost
|
||||||
|
self.net_pos_price = new_cost / new_pos
|
||||||
|
else:
|
||||||
|
# If all previous long position closed
|
||||||
|
if not new_pos:
|
||||||
|
self.net_pos_price = 0
|
||||||
|
# If only part long position closed
|
||||||
|
elif new_pos < 0:
|
||||||
|
self.net_pos_price = trade.price
|
||||||
|
|
||||||
|
self.net_pos = new_pos
|
||||||
|
else:
|
||||||
if trade.direction == Direction.LONG:
|
if trade.direction == Direction.LONG:
|
||||||
if trade.offset == Offset.OPEN:
|
if trade.offset == Offset.OPEN:
|
||||||
self.long_pos += trade.volume
|
self.long_pos += trade.volume
|
||||||
@ -66,7 +116,7 @@ class LegData:
|
|||||||
else:
|
else:
|
||||||
self.long_pos -= trade.volume
|
self.long_pos -= trade.volume
|
||||||
|
|
||||||
self.net_pos = self.long_pos - self.net_pos
|
self.net_pos = self.long_pos - self.short_pos
|
||||||
|
|
||||||
|
|
||||||
class SpreadData:
|
class SpreadData:
|
||||||
@ -78,7 +128,9 @@ class SpreadData:
|
|||||||
legs: List[LegData],
|
legs: List[LegData],
|
||||||
price_multipliers: Dict[str, int],
|
price_multipliers: Dict[str, int],
|
||||||
trading_multipliers: Dict[str, int],
|
trading_multipliers: Dict[str, int],
|
||||||
active_symbol: str
|
active_symbol: str,
|
||||||
|
inverse_contracts: Dict[str, bool],
|
||||||
|
min_volume: float
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
@ -87,11 +139,16 @@ class SpreadData:
|
|||||||
self.active_leg: LegData = None
|
self.active_leg: LegData = None
|
||||||
self.passive_legs: List[LegData] = []
|
self.passive_legs: List[LegData] = []
|
||||||
|
|
||||||
|
self.min_volume: float = min_volume
|
||||||
|
|
||||||
# For calculating spread price
|
# For calculating spread price
|
||||||
self.price_multipliers: Dict[str: int] = price_multipliers
|
self.price_multipliers: Dict[str, int] = price_multipliers
|
||||||
|
|
||||||
# For calculating spread pos and sending orders
|
# For calculating spread pos and sending orders
|
||||||
self.trading_multipliers: Dict[str: int] = trading_multipliers
|
self.trading_multipliers: Dict[str, int] = trading_multipliers
|
||||||
|
|
||||||
|
# For inverse derivative contracts of crypto market
|
||||||
|
self.inverse_contracts: Dict[str, bool] = inverse_contracts
|
||||||
|
|
||||||
self.price_formula: str = ""
|
self.price_formula: str = ""
|
||||||
self.trading_formula: str = ""
|
self.trading_formula: str = ""
|
||||||
@ -146,17 +203,35 @@ class SpreadData:
|
|||||||
|
|
||||||
# Calculate volume
|
# Calculate volume
|
||||||
trading_multiplier = self.trading_multipliers[leg.vt_symbol]
|
trading_multiplier = self.trading_multipliers[leg.vt_symbol]
|
||||||
|
inverse_contract = self.inverse_contracts[leg.vt_symbol]
|
||||||
|
|
||||||
|
if not inverse_contract:
|
||||||
|
leg_bid_volume = leg.bid_volume
|
||||||
|
leg_ask_volume = leg.ask_volume
|
||||||
|
else:
|
||||||
|
leg_bid_volume = calculate_inverse_volume(
|
||||||
|
leg.bid_volume, leg.bid_price, leg.size)
|
||||||
|
leg_ask_volume = calculate_inverse_volume(
|
||||||
|
leg.ask_volume, leg.ask_price, leg.size)
|
||||||
|
|
||||||
if trading_multiplier > 0:
|
if trading_multiplier > 0:
|
||||||
adjusted_bid_volume = floor(
|
adjusted_bid_volume = floor_to(
|
||||||
leg.bid_volume / trading_multiplier)
|
leg_bid_volume / trading_multiplier,
|
||||||
adjusted_ask_volume = floor(
|
self.min_volume
|
||||||
leg.ask_volume / trading_multiplier)
|
)
|
||||||
|
adjusted_ask_volume = floor_to(
|
||||||
|
leg_ask_volume / trading_multiplier,
|
||||||
|
self.min_volume
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
adjusted_bid_volume = floor(
|
adjusted_bid_volume = floor_to(
|
||||||
leg.ask_volume / abs(trading_multiplier))
|
leg_bid_volume / abs(trading_multiplier),
|
||||||
adjusted_ask_volume = floor(
|
self.min_volume
|
||||||
leg.bid_volume / abs(trading_multiplier))
|
)
|
||||||
|
adjusted_ask_volume = floor_to(
|
||||||
|
leg_ask_volume / abs(trading_multiplier),
|
||||||
|
self.min_volume
|
||||||
|
)
|
||||||
|
|
||||||
# For the first leg, just initialize
|
# For the first leg, just initialize
|
||||||
if not n:
|
if not n:
|
||||||
@ -180,13 +255,21 @@ class SpreadData:
|
|||||||
leg_short_pos = 0
|
leg_short_pos = 0
|
||||||
|
|
||||||
trading_multiplier = self.trading_multipliers[leg.vt_symbol]
|
trading_multiplier = self.trading_multipliers[leg.vt_symbol]
|
||||||
adjusted_net_pos = leg.net_pos / trading_multiplier
|
inverse_contract = self.inverse_contracts[leg.vt_symbol]
|
||||||
|
|
||||||
|
if not inverse_contract:
|
||||||
|
net_pos = leg.net_pos
|
||||||
|
else:
|
||||||
|
net_pos = calculate_inverse_volume(
|
||||||
|
leg.net_pos, leg.net_pos_price, leg.size)
|
||||||
|
|
||||||
|
adjusted_net_pos = net_pos / trading_multiplier
|
||||||
|
|
||||||
if adjusted_net_pos > 0:
|
if adjusted_net_pos > 0:
|
||||||
adjusted_net_pos = floor(adjusted_net_pos)
|
adjusted_net_pos = floor_to(adjusted_net_pos, self.min_volume)
|
||||||
leg_long_pos = adjusted_net_pos
|
leg_long_pos = adjusted_net_pos
|
||||||
else:
|
else:
|
||||||
adjusted_net_pos = ceil(adjusted_net_pos)
|
adjusted_net_pos = ceil_to(adjusted_net_pos, self.min_volume)
|
||||||
leg_short_pos = abs(adjusted_net_pos)
|
leg_short_pos = abs(adjusted_net_pos)
|
||||||
|
|
||||||
if not n:
|
if not n:
|
||||||
@ -222,9 +305,9 @@ class SpreadData:
|
|||||||
spread_volume = leg_volume / trading_multiplier
|
spread_volume = leg_volume / trading_multiplier
|
||||||
|
|
||||||
if spread_volume > 0:
|
if spread_volume > 0:
|
||||||
spread_volume = floor(spread_volume)
|
spread_volume = floor_to(spread_volume, self.min_volume)
|
||||||
else:
|
else:
|
||||||
spread_volume = ceil(spread_volume)
|
spread_volume = ceil_to(spread_volume, self.min_volume)
|
||||||
|
|
||||||
return spread_volume
|
return spread_volume
|
||||||
|
|
||||||
@ -243,3 +326,24 @@ class SpreadData:
|
|||||||
gateway_name="SPREAD"
|
gateway_name="SPREAD"
|
||||||
)
|
)
|
||||||
return tick
|
return tick
|
||||||
|
|
||||||
|
def is_inverse(self, vt_symbol: str) -> bool:
|
||||||
|
""""""
|
||||||
|
inverse_contract = self.inverse_contracts[vt_symbol]
|
||||||
|
return inverse_contract
|
||||||
|
|
||||||
|
def get_leg_size(self, vt_symbol: str) -> float:
|
||||||
|
""""""
|
||||||
|
leg = self.legs[vt_symbol]
|
||||||
|
return leg.size
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_inverse_volume(
|
||||||
|
original_volume: float,
|
||||||
|
price: float,
|
||||||
|
size: float,
|
||||||
|
) -> float:
|
||||||
|
""""""
|
||||||
|
if not price:
|
||||||
|
return 0
|
||||||
|
return original_volume * size / price
|
||||||
|
@ -116,6 +116,7 @@ class SpreadDataEngine:
|
|||||||
spread_setting["name"],
|
spread_setting["name"],
|
||||||
spread_setting["leg_settings"],
|
spread_setting["leg_settings"],
|
||||||
spread_setting["active_symbol"],
|
spread_setting["active_symbol"],
|
||||||
|
spread_setting.get("min_volume", 1),
|
||||||
save=False
|
save=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -128,18 +129,21 @@ class SpreadDataEngine:
|
|||||||
for leg in spread.legs.values():
|
for leg in spread.legs.values():
|
||||||
price_multiplier = spread.price_multipliers[leg.vt_symbol]
|
price_multiplier = spread.price_multipliers[leg.vt_symbol]
|
||||||
trading_multiplier = spread.trading_multipliers[leg.vt_symbol]
|
trading_multiplier = spread.trading_multipliers[leg.vt_symbol]
|
||||||
|
inverse_contract = spread.inverse_contracts[leg.vt_symbol]
|
||||||
|
|
||||||
leg_setting = {
|
leg_setting = {
|
||||||
"vt_symbol": leg.vt_symbol,
|
"vt_symbol": leg.vt_symbol,
|
||||||
"price_multiplier": price_multiplier,
|
"price_multiplier": price_multiplier,
|
||||||
"trading_multiplier": trading_multiplier
|
"trading_multiplier": trading_multiplier,
|
||||||
|
"inverse_contract": inverse_contract
|
||||||
}
|
}
|
||||||
leg_settings.append(leg_setting)
|
leg_settings.append(leg_setting)
|
||||||
|
|
||||||
spread_setting = {
|
spread_setting = {
|
||||||
"name": spread.name,
|
"name": spread.name,
|
||||||
"leg_settings": leg_settings,
|
"leg_settings": leg_settings,
|
||||||
"active_symbol": spread.active_leg.vt_symbol
|
"active_symbol": spread.active_leg.vt_symbol,
|
||||||
|
"min_volume": spread.min_volume
|
||||||
}
|
}
|
||||||
setting.append(spread_setting)
|
setting.append(spread_setting)
|
||||||
|
|
||||||
@ -194,8 +198,12 @@ class SpreadDataEngine:
|
|||||||
def process_contract_event(self, event: Event) -> None:
|
def process_contract_event(self, event: Event) -> None:
|
||||||
""""""
|
""""""
|
||||||
contract = event.data
|
contract = event.data
|
||||||
|
leg = self.legs.get(contract.vt_symbol, None)
|
||||||
|
|
||||||
|
if leg:
|
||||||
|
# Update contract data
|
||||||
|
leg.update_contract(contract)
|
||||||
|
|
||||||
if contract.vt_symbol in self.legs:
|
|
||||||
req = SubscribeRequest(
|
req = SubscribeRequest(
|
||||||
contract.symbol, contract.exchange
|
contract.symbol, contract.exchange
|
||||||
)
|
)
|
||||||
@ -222,6 +230,8 @@ class SpreadDataEngine:
|
|||||||
# Subscribe market data
|
# Subscribe market data
|
||||||
contract = self.main_engine.get_contract(vt_symbol)
|
contract = self.main_engine.get_contract(vt_symbol)
|
||||||
if contract:
|
if contract:
|
||||||
|
leg.update_contract(contract)
|
||||||
|
|
||||||
req = SubscribeRequest(
|
req = SubscribeRequest(
|
||||||
contract.symbol,
|
contract.symbol,
|
||||||
contract.exchange
|
contract.exchange
|
||||||
@ -243,6 +253,7 @@ class SpreadDataEngine:
|
|||||||
name: str,
|
name: str,
|
||||||
leg_settings: List[Dict],
|
leg_settings: List[Dict],
|
||||||
active_symbol: str,
|
active_symbol: str,
|
||||||
|
min_volume: float,
|
||||||
save: bool = True
|
save: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
""""""
|
""""""
|
||||||
@ -253,6 +264,7 @@ class SpreadDataEngine:
|
|||||||
legs: List[LegData] = []
|
legs: List[LegData] = []
|
||||||
price_multipliers: Dict[str, int] = {}
|
price_multipliers: Dict[str, int] = {}
|
||||||
trading_multipliers: Dict[str, int] = {}
|
trading_multipliers: Dict[str, int] = {}
|
||||||
|
inverse_contracts: Dict[str, bool] = {}
|
||||||
|
|
||||||
for leg_setting in leg_settings:
|
for leg_setting in leg_settings:
|
||||||
vt_symbol = leg_setting["vt_symbol"]
|
vt_symbol = leg_setting["vt_symbol"]
|
||||||
@ -261,13 +273,17 @@ class SpreadDataEngine:
|
|||||||
legs.append(leg)
|
legs.append(leg)
|
||||||
price_multipliers[vt_symbol] = leg_setting["price_multiplier"]
|
price_multipliers[vt_symbol] = leg_setting["price_multiplier"]
|
||||||
trading_multipliers[vt_symbol] = leg_setting["trading_multiplier"]
|
trading_multipliers[vt_symbol] = leg_setting["trading_multiplier"]
|
||||||
|
inverse_contracts[vt_symbol] = leg_setting.get(
|
||||||
|
"inverse_contract", False)
|
||||||
|
|
||||||
spread = SpreadData(
|
spread = SpreadData(
|
||||||
name,
|
name,
|
||||||
legs,
|
legs,
|
||||||
price_multipliers,
|
price_multipliers,
|
||||||
trading_multipliers,
|
trading_multipliers,
|
||||||
active_symbol
|
active_symbol,
|
||||||
|
inverse_contracts,
|
||||||
|
min_volume
|
||||||
)
|
)
|
||||||
self.spreads[name] = spread
|
self.spreads[name] = spread
|
||||||
|
|
||||||
@ -414,6 +430,7 @@ class SpreadAlgoEngine:
|
|||||||
self,
|
self,
|
||||||
spread_name: str,
|
spread_name: str,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
|
offset: Offset,
|
||||||
price: float,
|
price: float,
|
||||||
volume: float,
|
volume: float,
|
||||||
payup: int,
|
payup: int,
|
||||||
@ -437,6 +454,7 @@ class SpreadAlgoEngine:
|
|||||||
algoid,
|
algoid,
|
||||||
spread,
|
spread,
|
||||||
direction,
|
direction,
|
||||||
|
offset,
|
||||||
price,
|
price,
|
||||||
volume,
|
volume,
|
||||||
payup,
|
payup,
|
||||||
@ -711,7 +729,8 @@ class SpreadStrategyEngine:
|
|||||||
strategy = self.algo_strategy_map.get(algo.algoid, None)
|
strategy = self.algo_strategy_map.get(algo.algoid, None)
|
||||||
|
|
||||||
if strategy:
|
if strategy:
|
||||||
self.call_strategy_func(strategy, strategy.update_spread_algo, algo)
|
self.call_strategy_func(
|
||||||
|
strategy, strategy.update_spread_algo, algo)
|
||||||
|
|
||||||
def process_order_event(self, event: Event):
|
def process_order_event(self, event: Event):
|
||||||
""""""
|
""""""
|
||||||
@ -894,6 +913,7 @@ class SpreadStrategyEngine:
|
|||||||
strategy: SpreadStrategyTemplate,
|
strategy: SpreadStrategyTemplate,
|
||||||
spread_name: str,
|
spread_name: str,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
|
offset: Offset,
|
||||||
price: float,
|
price: float,
|
||||||
volume: float,
|
volume: float,
|
||||||
payup: int,
|
payup: int,
|
||||||
@ -904,6 +924,7 @@ class SpreadStrategyEngine:
|
|||||||
algoid = self.spread_engine.start_algo(
|
algoid = self.spread_engine.start_algo(
|
||||||
spread_name,
|
spread_name,
|
||||||
direction,
|
direction,
|
||||||
|
offset,
|
||||||
price,
|
price,
|
||||||
volume,
|
volume,
|
||||||
payup,
|
payup,
|
||||||
|
@ -87,6 +87,8 @@ class BasicSpreadStrategy(SpreadStrategyTemplate):
|
|||||||
|
|
||||||
# No position
|
# No position
|
||||||
if not self.spread_pos:
|
if not self.spread_pos:
|
||||||
|
self.stop_close_algos()
|
||||||
|
|
||||||
# Start open algos
|
# Start open algos
|
||||||
if not self.buy_algoid:
|
if not self.buy_algoid:
|
||||||
self.buy_algoid = self.start_long_algo(
|
self.buy_algoid = self.start_long_algo(
|
||||||
@ -98,27 +100,20 @@ class BasicSpreadStrategy(SpreadStrategyTemplate):
|
|||||||
self.short_price, self.max_pos, self.payup, self.interval
|
self.short_price, self.max_pos, self.payup, self.interval
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stop close algos
|
|
||||||
if self.sell_algoid:
|
|
||||||
self.stop_algo(self.sell_algoid)
|
|
||||||
|
|
||||||
if self.cover_algoid:
|
|
||||||
self.stop_algo(self.cover_algoid)
|
|
||||||
|
|
||||||
# Long position
|
# Long position
|
||||||
elif self.spread_pos > 0:
|
elif self.spread_pos > 0:
|
||||||
|
self.stop_open_algos()
|
||||||
|
|
||||||
# Start sell close algo
|
# Start sell close algo
|
||||||
if not self.sell_algoid:
|
if not self.sell_algoid:
|
||||||
self.sell_algoid = self.start_short_algo(
|
self.sell_algoid = self.start_short_algo(
|
||||||
self.sell_price, self.spread_pos, self.payup, self.interval
|
self.sell_price, self.spread_pos, self.payup, self.interval
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stop buy open algo
|
|
||||||
if self.buy_algoid:
|
|
||||||
self.stop_algo(self.buy_algoid)
|
|
||||||
|
|
||||||
# Short position
|
# Short position
|
||||||
elif self.spread_pos < 0:
|
elif self.spread_pos < 0:
|
||||||
|
self.stop_open_algos()
|
||||||
|
|
||||||
# Start cover close algo
|
# Start cover close algo
|
||||||
if not self.cover_algoid:
|
if not self.cover_algoid:
|
||||||
self.cover_algoid = self.start_long_algo(
|
self.cover_algoid = self.start_long_algo(
|
||||||
@ -126,10 +121,6 @@ class BasicSpreadStrategy(SpreadStrategyTemplate):
|
|||||||
self.spread_pos), self.payup, self.interval
|
self.spread_pos), self.payup, self.interval
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stop short open algo
|
|
||||||
if self.short_algoid:
|
|
||||||
self.stop_algo(self.short_algoid)
|
|
||||||
|
|
||||||
self.put_event()
|
self.put_event()
|
||||||
|
|
||||||
def on_spread_pos(self):
|
def on_spread_pos(self):
|
||||||
@ -166,3 +157,19 @@ class BasicSpreadStrategy(SpreadStrategyTemplate):
|
|||||||
Callback when new trade data is received.
|
Callback when new trade data is received.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def stop_open_algos(self):
|
||||||
|
""""""
|
||||||
|
if self.buy_algoid:
|
||||||
|
self.stop_algo(self.buy_algoid)
|
||||||
|
|
||||||
|
if self.short_algoid:
|
||||||
|
self.stop_algo(self.short_algoid)
|
||||||
|
|
||||||
|
def stop_close_algos(self):
|
||||||
|
""""""
|
||||||
|
if self.sell_algoid:
|
||||||
|
self.stop_algo(self.sell_algoid)
|
||||||
|
|
||||||
|
if self.cover_algoid:
|
||||||
|
self.stop_algo(self.cover_algoid)
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set
|
||||||
from math import floor, ceil
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
from vnpy.trader.object import TickData, TradeData, OrderData, ContractData
|
from vnpy.trader.object import TickData, TradeData, OrderData, ContractData
|
||||||
from vnpy.trader.constant import Direction, Status, Offset
|
from vnpy.trader.constant import Direction, Status, Offset
|
||||||
from vnpy.trader.utility import virtual
|
from vnpy.trader.utility import virtual, floor_to, ceil_to, round_to
|
||||||
|
|
||||||
from .base import SpreadData
|
from .base import SpreadData, calculate_inverse_volume
|
||||||
|
|
||||||
|
|
||||||
class SpreadAlgoTemplate:
|
class SpreadAlgoTemplate:
|
||||||
@ -23,11 +22,12 @@ class SpreadAlgoTemplate:
|
|||||||
algoid: str,
|
algoid: str,
|
||||||
spread: SpreadData,
|
spread: SpreadData,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
|
offset: Offset,
|
||||||
price: float,
|
price: float,
|
||||||
volume: float,
|
volume: float,
|
||||||
payup: int,
|
payup: int,
|
||||||
interval: int,
|
interval: int,
|
||||||
lock: bool
|
lock: bool,
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
self.algo_engine = algo_engine
|
self.algo_engine = algo_engine
|
||||||
@ -36,6 +36,7 @@ class SpreadAlgoTemplate:
|
|||||||
self.spread: SpreadData = spread
|
self.spread: SpreadData = spread
|
||||||
self.spread_name: str = spread.name
|
self.spread_name: str = spread.name
|
||||||
|
|
||||||
|
self.offset: Offset = offset
|
||||||
self.direction: Direction = direction
|
self.direction: Direction = direction
|
||||||
self.price: float = price
|
self.price: float = price
|
||||||
self.volume: float = volume
|
self.volume: float = volume
|
||||||
@ -117,10 +118,24 @@ class SpreadAlgoTemplate:
|
|||||||
|
|
||||||
def update_trade(self, trade: TradeData):
|
def update_trade(self, trade: TradeData):
|
||||||
""""""
|
""""""
|
||||||
if trade.direction == Direction.LONG:
|
# For inverse contract:
|
||||||
self.leg_traded[trade.vt_symbol] += trade.volume
|
# record coin trading volume as leg trading volume,
|
||||||
|
# not contract volume!
|
||||||
|
if self.spread.is_inverse(trade.vt_symbol):
|
||||||
|
size = self.spread.get_leg_size(trade.vt_symbol)
|
||||||
|
|
||||||
|
trade_volume = calculate_inverse_volume(
|
||||||
|
trade.volume,
|
||||||
|
trade.price,
|
||||||
|
size
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.leg_traded[trade.vt_symbol] -= trade.volume
|
trade_volume = trade.volume
|
||||||
|
|
||||||
|
if trade.direction == Direction.LONG:
|
||||||
|
self.leg_traded[trade.vt_symbol] += trade_volume
|
||||||
|
else:
|
||||||
|
self.leg_traded[trade.vt_symbol] -= trade_volume
|
||||||
|
|
||||||
msg = "委托成交,{},{},{}@{}".format(
|
msg = "委托成交,{},{},{}@{}".format(
|
||||||
trade.vt_symbol,
|
trade.vt_symbol,
|
||||||
@ -177,6 +192,21 @@ class SpreadAlgoTemplate:
|
|||||||
direction: Direction,
|
direction: Direction,
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
|
# For inverse contract:
|
||||||
|
# calculate contract trading volume from coin trading volume
|
||||||
|
if self.spread.is_inverse(vt_symbol):
|
||||||
|
size = self.spread.get_leg_size(vt_symbol)
|
||||||
|
|
||||||
|
if self.offset == Offset.CLOSE:
|
||||||
|
leg = self.spread.legs[vt_symbol]
|
||||||
|
volume = volume * leg.net_pos_price / size
|
||||||
|
else:
|
||||||
|
volume = volume * price / size
|
||||||
|
|
||||||
|
# Round order volume to min_volume of contract
|
||||||
|
leg = self.spread.legs[vt_symbol]
|
||||||
|
volume = round_to(volume, leg.min_volume)
|
||||||
|
|
||||||
vt_orderids = self.algo_engine.send_order(
|
vt_orderids = self.algo_engine.send_order(
|
||||||
self,
|
self,
|
||||||
vt_symbol,
|
vt_symbol,
|
||||||
@ -214,12 +244,17 @@ class SpreadAlgoTemplate:
|
|||||||
leg_traded = self.leg_traded[leg.vt_symbol]
|
leg_traded = self.leg_traded[leg.vt_symbol]
|
||||||
trading_multiplier = self.spread.trading_multipliers[
|
trading_multiplier = self.spread.trading_multipliers[
|
||||||
leg.vt_symbol]
|
leg.vt_symbol]
|
||||||
|
|
||||||
adjusted_leg_traded = leg_traded / trading_multiplier
|
adjusted_leg_traded = leg_traded / trading_multiplier
|
||||||
|
adjusted_leg_traded = round_to(
|
||||||
|
adjusted_leg_traded, self.spread.min_volume)
|
||||||
|
|
||||||
if adjusted_leg_traded > 0:
|
if adjusted_leg_traded > 0:
|
||||||
adjusted_leg_traded = floor(adjusted_leg_traded)
|
adjusted_leg_traded = floor_to(
|
||||||
|
adjusted_leg_traded, self.spread.min_volume)
|
||||||
else:
|
else:
|
||||||
adjusted_leg_traded = ceil(adjusted_leg_traded)
|
adjusted_leg_traded = ceil_to(
|
||||||
|
adjusted_leg_traded, self.spread.min_volume)
|
||||||
|
|
||||||
if not n:
|
if not n:
|
||||||
self.traded = adjusted_leg_traded
|
self.traded = adjusted_leg_traded
|
||||||
@ -434,7 +469,8 @@ class SpreadStrategyTemplate:
|
|||||||
volume: float,
|
volume: float,
|
||||||
payup: int,
|
payup: int,
|
||||||
interval: int,
|
interval: int,
|
||||||
lock: bool
|
lock: bool,
|
||||||
|
offset: Offset
|
||||||
) -> str:
|
) -> str:
|
||||||
""""""
|
""""""
|
||||||
if not self.trading:
|
if not self.trading:
|
||||||
@ -444,6 +480,7 @@ class SpreadStrategyTemplate:
|
|||||||
self,
|
self,
|
||||||
self.spread_name,
|
self.spread_name,
|
||||||
direction,
|
direction,
|
||||||
|
offset,
|
||||||
price,
|
price,
|
||||||
volume,
|
volume,
|
||||||
payup,
|
payup,
|
||||||
@ -461,10 +498,14 @@ class SpreadStrategyTemplate:
|
|||||||
volume: float,
|
volume: float,
|
||||||
payup: int,
|
payup: int,
|
||||||
interval: int,
|
interval: int,
|
||||||
lock: bool = False
|
lock: bool = False,
|
||||||
|
offset: Offset = Offset.NONE
|
||||||
) -> str:
|
) -> str:
|
||||||
""""""
|
""""""
|
||||||
return self.start_algo(Direction.LONG, price, volume, payup, interval, lock)
|
return self.start_algo(
|
||||||
|
Direction.LONG, price, volume,
|
||||||
|
payup, interval, lock, offset
|
||||||
|
)
|
||||||
|
|
||||||
def start_short_algo(
|
def start_short_algo(
|
||||||
self,
|
self,
|
||||||
@ -472,10 +513,14 @@ class SpreadStrategyTemplate:
|
|||||||
volume: float,
|
volume: float,
|
||||||
payup: int,
|
payup: int,
|
||||||
interval: int,
|
interval: int,
|
||||||
lock: bool = False
|
lock: bool = False,
|
||||||
|
offset: Offset = Offset.NONE
|
||||||
) -> str:
|
) -> str:
|
||||||
""""""
|
""""""
|
||||||
return self.start_algo(Direction.SHORT, price, volume, payup, interval, lock)
|
return self.start_algo(
|
||||||
|
Direction.SHORT, price, volume,
|
||||||
|
payup, interval, lock, offset
|
||||||
|
)
|
||||||
|
|
||||||
def stop_algo(self, algoid: str):
|
def stop_algo(self, algoid: str):
|
||||||
""""""
|
""""""
|
||||||
|
@ -4,7 +4,7 @@ Widget for spread trading.
|
|||||||
|
|
||||||
from vnpy.event import EventEngine, Event
|
from vnpy.event import EventEngine, Event
|
||||||
from vnpy.trader.engine import MainEngine
|
from vnpy.trader.engine import MainEngine
|
||||||
from vnpy.trader.constant import Direction
|
from vnpy.trader.constant import Direction, Offset
|
||||||
from vnpy.trader.ui import QtWidgets, QtCore, QtGui
|
from vnpy.trader.ui import QtWidgets, QtCore, QtGui
|
||||||
from vnpy.trader.ui.widget import (
|
from vnpy.trader.ui.widget import (
|
||||||
BaseMonitor, BaseCell,
|
BaseMonitor, BaseCell,
|
||||||
@ -169,6 +169,7 @@ class SpreadAlgoMonitor(BaseMonitor):
|
|||||||
"algoid": {"display": "算法", "cell": BaseCell, "update": False},
|
"algoid": {"display": "算法", "cell": BaseCell, "update": False},
|
||||||
"spread_name": {"display": "价差", "cell": BaseCell, "update": False},
|
"spread_name": {"display": "价差", "cell": BaseCell, "update": False},
|
||||||
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
|
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
|
||||||
|
"offset": {"display": "开平", "cell": EnumCell, "update": False},
|
||||||
"price": {"display": "价格", "cell": BaseCell, "update": False},
|
"price": {"display": "价格", "cell": BaseCell, "update": False},
|
||||||
"payup": {"display": "超价", "cell": BaseCell, "update": False},
|
"payup": {"display": "超价", "cell": BaseCell, "update": False},
|
||||||
"volume": {"display": "数量", "cell": BaseCell, "update": False},
|
"volume": {"display": "数量", "cell": BaseCell, "update": False},
|
||||||
@ -226,6 +227,11 @@ class SpreadAlgoWidget(QtWidgets.QFrame):
|
|||||||
[Direction.LONG.value, Direction.SHORT.value]
|
[Direction.LONG.value, Direction.SHORT.value]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.offset_combo = QtWidgets.QComboBox()
|
||||||
|
self.offset_combo.addItems(
|
||||||
|
[Offset.NONE.value, Offset.OPEN.value, Offset.CLOSE.value]
|
||||||
|
)
|
||||||
|
|
||||||
float_validator = QtGui.QDoubleValidator()
|
float_validator = QtGui.QDoubleValidator()
|
||||||
|
|
||||||
self.price_line = QtWidgets.QLineEdit()
|
self.price_line = QtWidgets.QLineEdit()
|
||||||
@ -273,6 +279,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame):
|
|||||||
form = QtWidgets.QFormLayout()
|
form = QtWidgets.QFormLayout()
|
||||||
form.addRow("价差", self.name_line)
|
form.addRow("价差", self.name_line)
|
||||||
form.addRow("方向", self.direction_combo)
|
form.addRow("方向", self.direction_combo)
|
||||||
|
form.addRow("开平", self.offset_combo)
|
||||||
form.addRow("价格", self.price_line)
|
form.addRow("价格", self.price_line)
|
||||||
form.addRow("数量", self.volume_line)
|
form.addRow("数量", self.volume_line)
|
||||||
form.addRow("超价", self.payup_line)
|
form.addRow("超价", self.payup_line)
|
||||||
@ -298,6 +305,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame):
|
|||||||
""""""
|
""""""
|
||||||
name = self.name_line.text()
|
name = self.name_line.text()
|
||||||
direction = Direction(self.direction_combo.currentText())
|
direction = Direction(self.direction_combo.currentText())
|
||||||
|
offset = Offset(self.offset_combo.currentText())
|
||||||
price = float(self.price_line.text())
|
price = float(self.price_line.text())
|
||||||
volume = float(self.volume_line.text())
|
volume = float(self.volume_line.text())
|
||||||
payup = int(self.payup_line.text())
|
payup = int(self.payup_line.text())
|
||||||
@ -310,7 +318,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame):
|
|||||||
lock = False
|
lock = False
|
||||||
|
|
||||||
self.spread_engine.start_algo(
|
self.spread_engine.start_algo(
|
||||||
name, direction, price, volume, payup, interval, lock
|
name, direction, offset, price, volume, payup, interval, lock
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_spread(self):
|
def add_spread(self):
|
||||||
@ -375,6 +383,17 @@ class SpreadDataDialog(QtWidgets.QDialog):
|
|||||||
self.name_line = QtWidgets.QLineEdit()
|
self.name_line = QtWidgets.QLineEdit()
|
||||||
self.active_line = QtWidgets.QLineEdit()
|
self.active_line = QtWidgets.QLineEdit()
|
||||||
|
|
||||||
|
self.min_volume_combo = QtWidgets.QComboBox()
|
||||||
|
self.min_volume_combo.addItems([
|
||||||
|
"1",
|
||||||
|
"0.1",
|
||||||
|
"0.01",
|
||||||
|
"0.001",
|
||||||
|
"0.0001",
|
||||||
|
"0.00001",
|
||||||
|
"0.000001",
|
||||||
|
])
|
||||||
|
|
||||||
self.grid = QtWidgets.QGridLayout()
|
self.grid = QtWidgets.QGridLayout()
|
||||||
|
|
||||||
button_add = QtWidgets.QPushButton("创建价差")
|
button_add = QtWidgets.QPushButton("创建价差")
|
||||||
@ -384,14 +403,17 @@ class SpreadDataDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
grid = QtWidgets.QGridLayout()
|
grid = QtWidgets.QGridLayout()
|
||||||
grid.addWidget(Label("价差名称"), 0, 0)
|
grid.addWidget(Label("价差名称"), 0, 0)
|
||||||
grid.addWidget(self.name_line, 0, 1, 1, 3)
|
grid.addWidget(self.name_line, 0, 1, 1, 4)
|
||||||
grid.addWidget(Label("主动腿代码"), 1, 0)
|
grid.addWidget(Label("主动腿代码"), 1, 0)
|
||||||
grid.addWidget(self.active_line, 1, 1, 1, 3)
|
grid.addWidget(self.active_line, 1, 1, 1, 4)
|
||||||
|
grid.addWidget(Label("最小交易量"), 2, 0)
|
||||||
|
grid.addWidget(self.min_volume_combo, 2, 1, 1, 4)
|
||||||
|
|
||||||
grid.addWidget(Label(""), 2, 0)
|
grid.addWidget(Label(""), 3, 0)
|
||||||
grid.addWidget(Label("本地代码"), 3, 1)
|
grid.addWidget(Label("本地代码"), 4, 1)
|
||||||
grid.addWidget(Label("价格乘数"), 3, 2)
|
grid.addWidget(Label("价格乘数"), 4, 2)
|
||||||
grid.addWidget(Label("交易乘数"), 3, 3)
|
grid.addWidget(Label("交易乘数"), 4, 3)
|
||||||
|
grid.addWidget(Label("合约模式"), 4, 4)
|
||||||
|
|
||||||
int_validator = QtGui.QIntValidator()
|
int_validator = QtGui.QIntValidator()
|
||||||
|
|
||||||
@ -405,20 +427,25 @@ class SpreadDataDialog(QtWidgets.QDialog):
|
|||||||
trading_line = QtWidgets.QLineEdit()
|
trading_line = QtWidgets.QLineEdit()
|
||||||
trading_line.setValidator(int_validator)
|
trading_line.setValidator(int_validator)
|
||||||
|
|
||||||
grid.addWidget(Label("腿{}".format(i + 1)), 4 + i, 0)
|
inverse_combo = QtWidgets.QComboBox()
|
||||||
grid.addWidget(symbol_line, 4 + i, 1)
|
inverse_combo.addItems(["正向", "反向"])
|
||||||
grid.addWidget(price_line, 4 + i, 2)
|
|
||||||
grid.addWidget(trading_line, 4 + i, 3)
|
grid.addWidget(Label("腿{}".format(i + 1)), 5 + i, 0)
|
||||||
|
grid.addWidget(symbol_line, 5 + i, 1)
|
||||||
|
grid.addWidget(price_line, 5 + i, 2)
|
||||||
|
grid.addWidget(trading_line, 5 + i, 3)
|
||||||
|
grid.addWidget(inverse_combo, 5 + i, 4)
|
||||||
|
|
||||||
d = {
|
d = {
|
||||||
"symbol": symbol_line,
|
"symbol": symbol_line,
|
||||||
"price": price_line,
|
"price": price_line,
|
||||||
"trading": trading_line
|
"trading": trading_line,
|
||||||
|
"inverse": inverse_combo
|
||||||
}
|
}
|
||||||
self.leg_widgets.append(d)
|
self.leg_widgets.append(d)
|
||||||
|
|
||||||
grid.addWidget(Label(""), 4 + leg_count, 0,)
|
grid.addWidget(Label(""), 5 + leg_count, 0,)
|
||||||
grid.addWidget(button_add, 5 + leg_count, 0, 1, 4)
|
grid.addWidget(button_add, 6 + leg_count, 0, 1, 5)
|
||||||
|
|
||||||
self.setLayout(grid)
|
self.setLayout(grid)
|
||||||
|
|
||||||
@ -435,6 +462,7 @@ class SpreadDataDialog(QtWidgets.QDialog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
active_symbol = self.active_line.text()
|
active_symbol = self.active_line.text()
|
||||||
|
min_volume = float(self.min_volume_combo.currentText())
|
||||||
|
|
||||||
leg_settings = {}
|
leg_settings = {}
|
||||||
for d in self.leg_widgets:
|
for d in self.leg_widgets:
|
||||||
@ -443,10 +471,16 @@ class SpreadDataDialog(QtWidgets.QDialog):
|
|||||||
price_multiplier = int(d["price"].text())
|
price_multiplier = int(d["price"].text())
|
||||||
trading_multiplier = int(d["trading"].text())
|
trading_multiplier = int(d["trading"].text())
|
||||||
|
|
||||||
|
if d["inverse"].currentText() == "正向":
|
||||||
|
inverse_contract = False
|
||||||
|
else:
|
||||||
|
inverse_contract = True
|
||||||
|
|
||||||
leg_settings[vt_symbol] = {
|
leg_settings[vt_symbol] = {
|
||||||
"vt_symbol": vt_symbol,
|
"vt_symbol": vt_symbol,
|
||||||
"price_multiplier": price_multiplier,
|
"price_multiplier": price_multiplier,
|
||||||
"trading_multiplier": trading_multiplier
|
"trading_multiplier": trading_multiplier,
|
||||||
|
"inverse_contract": inverse_contract
|
||||||
}
|
}
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
@ -472,7 +506,8 @@ class SpreadDataDialog(QtWidgets.QDialog):
|
|||||||
self.spread_engine.add_spread(
|
self.spread_engine.add_spread(
|
||||||
spread_name,
|
spread_name,
|
||||||
list(leg_settings.values()),
|
list(leg_settings.values()),
|
||||||
active_symbol
|
active_symbol,
|
||||||
|
min_volume
|
||||||
)
|
)
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
|
@ -107,8 +107,6 @@ class BinanceGateway(BaseGateway):
|
|||||||
self.market_ws_api = BinanceDataWebsocketApi(self)
|
self.market_ws_api = BinanceDataWebsocketApi(self)
|
||||||
self.rest_api = BinanceRestApi(self)
|
self.rest_api = BinanceRestApi(self)
|
||||||
|
|
||||||
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
|
|
||||||
|
|
||||||
def connect(self, setting: dict):
|
def connect(self, setting: dict):
|
||||||
""""""
|
""""""
|
||||||
key = setting["key"]
|
key = setting["key"]
|
||||||
@ -121,6 +119,8 @@ class BinanceGateway(BaseGateway):
|
|||||||
proxy_host, proxy_port)
|
proxy_host, proxy_port)
|
||||||
self.market_ws_api.connect(proxy_host, proxy_port)
|
self.market_ws_api.connect(proxy_host, proxy_port)
|
||||||
|
|
||||||
|
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
|
||||||
|
|
||||||
def subscribe(self, req: SubscribeRequest):
|
def subscribe(self, req: SubscribeRequest):
|
||||||
""""""
|
""""""
|
||||||
self.market_ws_api.subscribe(req)
|
self.market_ws_api.subscribe(req)
|
||||||
|
@ -53,6 +53,13 @@ ORDERTYPE_VT2BITFINEX = {
|
|||||||
OrderType.LIMIT: "EXCHANGE LIMIT",
|
OrderType.LIMIT: "EXCHANGE LIMIT",
|
||||||
OrderType.MARKET: "EXCHANGE MARKET",
|
OrderType.MARKET: "EXCHANGE MARKET",
|
||||||
}
|
}
|
||||||
|
ORDERTYPE_BITFINEX2VT = {
|
||||||
|
"EXCHANGE LIMIT": OrderType.LIMIT,
|
||||||
|
"EXCHANGE MARKET": OrderType.MARKET,
|
||||||
|
"LIMIT": OrderType.LIMIT,
|
||||||
|
"MARKET": OrderType.MARKET
|
||||||
|
}
|
||||||
|
|
||||||
DIRECTION_VT2BITFINEX = {
|
DIRECTION_VT2BITFINEX = {
|
||||||
Direction.LONG: "Buy",
|
Direction.LONG: "Buy",
|
||||||
Direction.SHORT: "Sell",
|
Direction.SHORT: "Sell",
|
||||||
@ -86,6 +93,7 @@ class BitfinexGateway(BaseGateway):
|
|||||||
"session": 3,
|
"session": 3,
|
||||||
"proxy_host": "127.0.0.1",
|
"proxy_host": "127.0.0.1",
|
||||||
"proxy_port": 1080,
|
"proxy_port": 1080,
|
||||||
|
"margin": ["False", "True"]
|
||||||
}
|
}
|
||||||
|
|
||||||
exchanges = [Exchange.BITFINEX]
|
exchanges = [Exchange.BITFINEX]
|
||||||
@ -108,8 +116,13 @@ class BitfinexGateway(BaseGateway):
|
|||||||
proxy_host = setting["proxy_host"]
|
proxy_host = setting["proxy_host"]
|
||||||
proxy_port = setting["proxy_port"]
|
proxy_port = setting["proxy_port"]
|
||||||
|
|
||||||
|
if setting["margin"] == "True":
|
||||||
|
margin = True
|
||||||
|
else:
|
||||||
|
margin = False
|
||||||
|
|
||||||
self.rest_api.connect(key, secret, session, proxy_host, proxy_port)
|
self.rest_api.connect(key, secret, session, proxy_host, proxy_port)
|
||||||
self.ws_api.connect(key, secret, proxy_host, proxy_port)
|
self.ws_api.connect(key, secret, proxy_host, proxy_port, margin)
|
||||||
|
|
||||||
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
|
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
|
||||||
|
|
||||||
@ -212,7 +225,7 @@ class BitfinexRestApi(RestClient):
|
|||||||
secret: str,
|
secret: str,
|
||||||
session: int,
|
session: int,
|
||||||
proxy_host: str,
|
proxy_host: str,
|
||||||
proxy_port: int,
|
proxy_port: int
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize connection to REST server.
|
Initialize connection to REST server.
|
||||||
@ -383,11 +396,17 @@ class BitfinexWebsocketApi(WebsocketClient):
|
|||||||
self.subscribed = {}
|
self.subscribed = {}
|
||||||
|
|
||||||
def connect(
|
def connect(
|
||||||
self, key: str, secret: str, proxy_host: str, proxy_port: int
|
self,
|
||||||
|
key: str,
|
||||||
|
secret: str,
|
||||||
|
proxy_host: str,
|
||||||
|
proxy_port: int,
|
||||||
|
margin: bool
|
||||||
):
|
):
|
||||||
""""""
|
""""""
|
||||||
self.key = key
|
self.key = key
|
||||||
self.secret = secret.encode()
|
self.secret = secret.encode()
|
||||||
|
self.margin = margin
|
||||||
self.init(WEBSOCKET_HOST, proxy_host, proxy_port)
|
self.init(WEBSOCKET_HOST, proxy_host, proxy_port)
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
@ -432,9 +451,13 @@ class BitfinexWebsocketApi(WebsocketClient):
|
|||||||
else:
|
else:
|
||||||
amount = -req.volume
|
amount = -req.volume
|
||||||
|
|
||||||
|
order_type = ORDERTYPE_VT2BITFINEX[req.type]
|
||||||
|
if self.margin:
|
||||||
|
order_type = order_type.replace("EXCHANGE ", "")
|
||||||
|
|
||||||
o = {
|
o = {
|
||||||
"cid": orderid,
|
"cid": orderid,
|
||||||
"type": ORDERTYPE_VT2BITFINEX[req.type],
|
"type": order_type,
|
||||||
"symbol": "t" + req.symbol,
|
"symbol": "t" + req.symbol,
|
||||||
"amount": str(amount),
|
"amount": str(amount),
|
||||||
"price": str(req.price),
|
"price": str(req.price),
|
||||||
@ -609,7 +632,14 @@ class BitfinexWebsocketApi(WebsocketClient):
|
|||||||
|
|
||||||
def on_wallet(self, data):
|
def on_wallet(self, data):
|
||||||
""""""
|
""""""
|
||||||
if str(data[0]) == "exchange":
|
print(data)
|
||||||
|
# Exchange Mode
|
||||||
|
if not self.margin and str(data[0]) != "exchange":
|
||||||
|
return
|
||||||
|
# Margin Mode
|
||||||
|
elif self.margin and str(data[0]) != "margin":
|
||||||
|
return
|
||||||
|
|
||||||
accountid = str(data[1])
|
accountid = str(data[1])
|
||||||
account = self.accounts.get(accountid, None)
|
account = self.accounts.get(accountid, None)
|
||||||
if not account:
|
if not account:
|
||||||
@ -776,6 +806,7 @@ class BitfinexWebsocketApi(WebsocketClient):
|
|||||||
order = OrderData(
|
order = OrderData(
|
||||||
symbol=str(data[3].replace("t", "")),
|
symbol=str(data[3].replace("t", "")),
|
||||||
exchange=Exchange.BITFINEX,
|
exchange=Exchange.BITFINEX,
|
||||||
|
type=ORDERTYPE_BITFINEX2VT[data[8]],
|
||||||
orderid=orderid,
|
orderid=orderid,
|
||||||
status=Status.REJECTED,
|
status=Status.REJECTED,
|
||||||
direction=direction,
|
direction=direction,
|
||||||
@ -808,6 +839,7 @@ class BitfinexWebsocketApi(WebsocketClient):
|
|||||||
symbol=str(data[3].replace("t", "")),
|
symbol=str(data[3].replace("t", "")),
|
||||||
exchange=Exchange.BITFINEX,
|
exchange=Exchange.BITFINEX,
|
||||||
orderid=orderid,
|
orderid=orderid,
|
||||||
|
type=ORDERTYPE_BITFINEX2VT[data[8]],
|
||||||
status=STATUS_BITFINEX2VT[order_status],
|
status=STATUS_BITFINEX2VT[order_status],
|
||||||
direction=direction,
|
direction=direction,
|
||||||
price=float(data[16]),
|
price=float(data[16]),
|
||||||
|
@ -533,6 +533,7 @@ class BitmexWebsocketApi(WebsocketClient):
|
|||||||
self.ticks = {}
|
self.ticks = {}
|
||||||
self.accounts = {}
|
self.accounts = {}
|
||||||
self.orders = {}
|
self.orders = {}
|
||||||
|
self.positions = {}
|
||||||
self.trades = set()
|
self.trades = set()
|
||||||
|
|
||||||
def connect(
|
def connect(
|
||||||
@ -702,9 +703,15 @@ class BitmexWebsocketApi(WebsocketClient):
|
|||||||
|
|
||||||
def on_order(self, d):
|
def on_order(self, d):
|
||||||
""""""
|
""""""
|
||||||
|
# Filter order data which cannot be processed properly
|
||||||
if "ordStatus" not in d:
|
if "ordStatus" not in d:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
side = d.get("side", "")
|
||||||
|
if not side:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update local order data
|
||||||
sysid = d["orderID"]
|
sysid = d["orderID"]
|
||||||
order = self.orders.get(sysid, None)
|
order = self.orders.get(sysid, None)
|
||||||
if not order:
|
if not order:
|
||||||
@ -713,14 +720,12 @@ class BitmexWebsocketApi(WebsocketClient):
|
|||||||
else:
|
else:
|
||||||
orderid = sysid
|
orderid = sysid
|
||||||
|
|
||||||
# time = d["timestamp"][11:19]
|
|
||||||
|
|
||||||
order = OrderData(
|
order = OrderData(
|
||||||
symbol=d["symbol"],
|
symbol=d["symbol"],
|
||||||
exchange=Exchange.BITMEX,
|
exchange=Exchange.BITMEX,
|
||||||
type=ORDERTYPE_BITMEX2VT[d["ordType"]],
|
type=ORDERTYPE_BITMEX2VT[d["ordType"]],
|
||||||
orderid=orderid,
|
orderid=orderid,
|
||||||
direction=DIRECTION_BITMEX2VT[d["side"]],
|
direction=DIRECTION_BITMEX2VT[side],
|
||||||
price=d["price"],
|
price=d["price"],
|
||||||
volume=d["orderQty"],
|
volume=d["orderQty"],
|
||||||
time=d["timestamp"][11:19],
|
time=d["timestamp"][11:19],
|
||||||
@ -735,15 +740,27 @@ class BitmexWebsocketApi(WebsocketClient):
|
|||||||
|
|
||||||
def on_position(self, d):
|
def on_position(self, d):
|
||||||
""""""
|
""""""
|
||||||
|
symbol = d["symbol"]
|
||||||
|
|
||||||
|
position = self.positions.get(symbol, None)
|
||||||
|
if not position:
|
||||||
position = PositionData(
|
position = PositionData(
|
||||||
symbol=d["symbol"],
|
symbol=d["symbol"],
|
||||||
exchange=Exchange.BITMEX,
|
exchange=Exchange.BITMEX,
|
||||||
direction=Direction.NET,
|
direction=Direction.NET,
|
||||||
volume=d.get("currentQty", 0),
|
|
||||||
gateway_name=self.gateway_name,
|
gateway_name=self.gateway_name,
|
||||||
)
|
)
|
||||||
|
self.positions[symbol] = position
|
||||||
|
|
||||||
self.gateway.on_position(position)
|
volume = d.get("currentQty", None)
|
||||||
|
if volume is not None:
|
||||||
|
position.volume = volume
|
||||||
|
|
||||||
|
price = d.get("avgEntryPrice", None)
|
||||||
|
if price is not None:
|
||||||
|
position.price = price
|
||||||
|
|
||||||
|
self.gateway.on_position(copy(position))
|
||||||
|
|
||||||
def on_account(self, d):
|
def on_account(self, d):
|
||||||
""""""
|
""""""
|
||||||
|
@ -198,6 +198,7 @@ class CoinbaseWebsocketApi(WebsocketClient):
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.orderbooks = {}
|
self.orderbooks = {}
|
||||||
|
self.subscribed = {}
|
||||||
|
|
||||||
def connect(
|
def connect(
|
||||||
self,
|
self,
|
||||||
@ -223,6 +224,10 @@ class CoinbaseWebsocketApi(WebsocketClient):
|
|||||||
|
|
||||||
def subscribe(self, req: SubscribeRequest):
|
def subscribe(self, req: SubscribeRequest):
|
||||||
""""""
|
""""""
|
||||||
|
self.subscribed[req.symbol] = req
|
||||||
|
if not self._active:
|
||||||
|
return
|
||||||
|
|
||||||
symbol = req.symbol
|
symbol = req.symbol
|
||||||
exchange = req.exchange
|
exchange = req.exchange
|
||||||
|
|
||||||
@ -259,6 +264,9 @@ class CoinbaseWebsocketApi(WebsocketClient):
|
|||||||
"""
|
"""
|
||||||
self.gateway.write_log("Websocket API连接成功")
|
self.gateway.write_log("Websocket API连接成功")
|
||||||
|
|
||||||
|
for req in self.subscribed.values():
|
||||||
|
self.subscribe(req)
|
||||||
|
|
||||||
def on_disconnected(self):
|
def on_disconnected(self):
|
||||||
""""""
|
""""""
|
||||||
self.gateway.write_log("Websocket API连接断开")
|
self.gateway.write_log("Websocket API连接断开")
|
||||||
|
@ -59,6 +59,8 @@ STATUS_HBDM2VT = {
|
|||||||
ORDERTYPE_VT2HBDM = {
|
ORDERTYPE_VT2HBDM = {
|
||||||
OrderType.MARKET: "opponent",
|
OrderType.MARKET: "opponent",
|
||||||
OrderType.LIMIT: "limit",
|
OrderType.LIMIT: "limit",
|
||||||
|
OrderType.FOK: "fok",
|
||||||
|
OrderType.FAK: "ioc"
|
||||||
}
|
}
|
||||||
ORDERTYPE_HBDM2VT = {v: k for k, v in ORDERTYPE_VT2HBDM.items()}
|
ORDERTYPE_HBDM2VT = {v: k for k, v in ORDERTYPE_VT2HBDM.items()}
|
||||||
ORDERTYPE_HBDM2VT[1] = OrderType.LIMIT
|
ORDERTYPE_HBDM2VT[1] = OrderType.LIMIT
|
||||||
@ -284,41 +286,7 @@ class HbdmRestApi(RestClient):
|
|||||||
self.add_request(
|
self.add_request(
|
||||||
method="POST",
|
method="POST",
|
||||||
path="/api/v1/contract_openorders",
|
path="/api/v1/contract_openorders",
|
||||||
callback=self.on_query_active_order,
|
callback=self.on_query_order,
|
||||||
data=data,
|
|
||||||
extra=currency
|
|
||||||
)
|
|
||||||
|
|
||||||
# History Orders
|
|
||||||
data = {
|
|
||||||
"symbol": currency,
|
|
||||||
"trade_type": 0,
|
|
||||||
"type": 2,
|
|
||||||
"status": 0,
|
|
||||||
"create_date": 7
|
|
||||||
}
|
|
||||||
|
|
||||||
self.add_request(
|
|
||||||
method="POST",
|
|
||||||
path="/api/v1/contract_hisorders",
|
|
||||||
callback=self.on_query_history_order,
|
|
||||||
data=data,
|
|
||||||
extra=currency
|
|
||||||
)
|
|
||||||
|
|
||||||
def query_trade(self):
|
|
||||||
""""""
|
|
||||||
for currency in self.currencies:
|
|
||||||
data = {
|
|
||||||
"symbol": currency,
|
|
||||||
"trade_type": 0,
|
|
||||||
"create_date": 7
|
|
||||||
}
|
|
||||||
|
|
||||||
self.add_request(
|
|
||||||
method="POST",
|
|
||||||
path="/api/v1/contract_matchresults",
|
|
||||||
callback=self.on_query_trade,
|
|
||||||
data=data,
|
data=data,
|
||||||
extra=currency
|
extra=currency
|
||||||
)
|
)
|
||||||
@ -549,7 +517,7 @@ class HbdmRestApi(RestClient):
|
|||||||
for position in self.positions.values():
|
for position in self.positions.values():
|
||||||
self.gateway.on_position(position)
|
self.gateway.on_position(position)
|
||||||
|
|
||||||
def on_query_active_order(self, data, request):
|
def on_query_order(self, data, request):
|
||||||
""""""
|
""""""
|
||||||
if self.check_error(data, "查询活动委托"):
|
if self.check_error(data, "查询活动委托"):
|
||||||
return
|
return
|
||||||
@ -582,61 +550,6 @@ class HbdmRestApi(RestClient):
|
|||||||
|
|
||||||
self.gateway.write_log(f"{request.extra}活动委托信息查询成功")
|
self.gateway.write_log(f"{request.extra}活动委托信息查询成功")
|
||||||
|
|
||||||
def on_query_history_order(self, data, request):
|
|
||||||
""""""
|
|
||||||
if self.check_error(data, "查询历史委托"):
|
|
||||||
return
|
|
||||||
|
|
||||||
for d in data["data"]["orders"]:
|
|
||||||
timestamp = d["create_date"]
|
|
||||||
dt = datetime.fromtimestamp(timestamp / 1000)
|
|
||||||
time = dt.strftime("%H:%M:%S")
|
|
||||||
|
|
||||||
orderid = d["order_id"]
|
|
||||||
|
|
||||||
order = OrderData(
|
|
||||||
orderid=orderid,
|
|
||||||
symbol=d["contract_code"],
|
|
||||||
exchange=Exchange.HUOBI,
|
|
||||||
price=d["price"],
|
|
||||||
volume=d["volume"],
|
|
||||||
type=ORDERTYPE_HBDM2VT[d["order_price_type"]],
|
|
||||||
direction=DIRECTION_HBDM2VT[d["direction"]],
|
|
||||||
offset=OFFSET_HBDM2VT[d["offset"]],
|
|
||||||
traded=d["trade_volume"],
|
|
||||||
status=STATUS_HBDM2VT[d["status"]],
|
|
||||||
time=time,
|
|
||||||
gateway_name=self.gateway_name,
|
|
||||||
)
|
|
||||||
self.gateway.on_order(order)
|
|
||||||
|
|
||||||
self.gateway.write_log(f"{request.extra}历史委托信息查询成功")
|
|
||||||
|
|
||||||
def on_query_trade(self, data, request):
|
|
||||||
""""""
|
|
||||||
if self.check_error(data, "查询成交"):
|
|
||||||
return
|
|
||||||
|
|
||||||
for d in data["data"]["trades"]:
|
|
||||||
dt = datetime.fromtimestamp(d["create_date"] / 1000)
|
|
||||||
time = dt.strftime("%H:%M:%S")
|
|
||||||
|
|
||||||
trade = TradeData(
|
|
||||||
tradeid=d["match_id"],
|
|
||||||
orderid=d["order_id"],
|
|
||||||
symbol=d["contract_code"],
|
|
||||||
exchange=Exchange.HUOBI,
|
|
||||||
price=d["trade_price"],
|
|
||||||
volume=d["trade_volume"],
|
|
||||||
direction=DIRECTION_HBDM2VT[d["direction"]],
|
|
||||||
offset=OFFSET_HBDM2VT[d["offset"]],
|
|
||||||
time=time,
|
|
||||||
gateway_name=self.gateway_name,
|
|
||||||
)
|
|
||||||
self.gateway.on_trade(trade)
|
|
||||||
|
|
||||||
self.gateway.write_log(f"{request.extra}成交信息查询成功")
|
|
||||||
|
|
||||||
def on_query_contract(self, data, request): # type: (dict, Request)->None
|
def on_query_contract(self, data, request): # type: (dict, Request)->None
|
||||||
""""""
|
""""""
|
||||||
if self.check_error(data, "查询合约"):
|
if self.check_error(data, "查询合约"):
|
||||||
@ -663,7 +576,6 @@ class HbdmRestApi(RestClient):
|
|||||||
self.gateway.write_log("合约信息查询成功")
|
self.gateway.write_log("合约信息查询成功")
|
||||||
|
|
||||||
self.query_order()
|
self.query_order()
|
||||||
self.query_trade()
|
|
||||||
|
|
||||||
def on_send_order(self, data, request):
|
def on_send_order(self, data, request):
|
||||||
""""""
|
""""""
|
||||||
@ -1041,7 +953,6 @@ class HbdmDataWebsocketApi(HbdmWebsocketApiBase):
|
|||||||
|
|
||||||
tick_data = data["tick"]
|
tick_data = data["tick"]
|
||||||
if "bids" not in tick_data or "asks" not in tick_data:
|
if "bids" not in tick_data or "asks" not in tick_data:
|
||||||
print(data)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
bids = tick_data["bids"]
|
bids = tick_data["bids"]
|
||||||
|
@ -278,6 +278,7 @@ class PositionHolding:
|
|||||||
# If no td_volume, we close opposite yd position first
|
# If no td_volume, we close opposite yd position first
|
||||||
# then open new position
|
# then open new position
|
||||||
else:
|
else:
|
||||||
|
close_volume = min(req.volume, yd_available)
|
||||||
open_volume = max(0, req.volume - yd_available)
|
open_volume = max(0, req.volume - yd_available)
|
||||||
req_list = []
|
req_list = []
|
||||||
|
|
||||||
@ -287,6 +288,7 @@ class PositionHolding:
|
|||||||
req_yd.offset = Offset.CLOSEYESTERDAY
|
req_yd.offset = Offset.CLOSEYESTERDAY
|
||||||
else:
|
else:
|
||||||
req_yd.offset = Offset.CLOSE
|
req_yd.offset = Offset.CLOSE
|
||||||
|
req_yd.volume = close_volume
|
||||||
req_list.append(req_yd)
|
req_list.append(req_yd)
|
||||||
|
|
||||||
if open_volume:
|
if open_volume:
|
||||||
|
@ -53,7 +53,7 @@ class RqdataClient:
|
|||||||
self.username,
|
self.username,
|
||||||
self.password,
|
self.password,
|
||||||
('rqdatad-pro.ricequant.com', 16011),
|
('rqdatad-pro.ricequant.com', 16011),
|
||||||
use_pool=True
|
use_pool=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -463,7 +463,7 @@ class PositionMonitor(BaseMonitor):
|
|||||||
"volume": {"display": "数量", "cell": BaseCell, "update": True},
|
"volume": {"display": "数量", "cell": BaseCell, "update": True},
|
||||||
"yd_volume": {"display": "昨仓", "cell": BaseCell, "update": True},
|
"yd_volume": {"display": "昨仓", "cell": BaseCell, "update": True},
|
||||||
"frozen": {"display": "冻结", "cell": BaseCell, "update": True},
|
"frozen": {"display": "冻结", "cell": BaseCell, "update": True},
|
||||||
"price": {"display": "均价", "cell": BaseCell, "update": False},
|
"price": {"display": "均价", "cell": BaseCell, "update": True},
|
||||||
"pnl": {"display": "盈亏", "cell": PnlCell, "update": True},
|
"pnl": {"display": "盈亏", "cell": PnlCell, "update": True},
|
||||||
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
|
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, Dict
|
from typing import Callable, Dict
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from math import floor, ceil
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import talib
|
import talib
|
||||||
@ -124,6 +125,26 @@ def round_to(value: float, target: float) -> float:
|
|||||||
return rounded
|
return rounded
|
||||||
|
|
||||||
|
|
||||||
|
def floor_to(value: float, target: float) -> float:
|
||||||
|
"""
|
||||||
|
Similar to math.floor function, but to target float number.
|
||||||
|
"""
|
||||||
|
value = Decimal(str(value))
|
||||||
|
target = Decimal(str(target))
|
||||||
|
result = float(int(floor(value / target)) * target)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def ceil_to(value: float, target: float) -> float:
|
||||||
|
"""
|
||||||
|
Similar to math.ceil function, but to target float number.
|
||||||
|
"""
|
||||||
|
value = Decimal(str(value))
|
||||||
|
target = Decimal(str(target))
|
||||||
|
result = float(int(ceil(value / target)) * target)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class BarGenerator:
|
class BarGenerator:
|
||||||
"""
|
"""
|
||||||
For:
|
For:
|
||||||
|
Loading…
Reference in New Issue
Block a user