Merge pull request #2181 from vnpy/dev-spread-crypto

Dev spread crypto
This commit is contained in:
vn.py 2019-11-07 16:08:00 +09:00 committed by GitHub
commit 2e01bef96f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 453 additions and 232 deletions

View File

@ -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)

View File

@ -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) {};

View File

@ -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代码类型

View File

@ -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.

View File

@ -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],)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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):
"""""" """"""

View File

@ -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()

View File

@ -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)

View File

@ -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]),

View File

@ -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):
"""""" """"""

View File

@ -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连接断开")

View File

@ -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"]

View File

@ -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:

View File

@ -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:

View File

@ -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},
} }

View File

@ -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: