diff --git a/examples/vn_trader/run.py b/examples/vn_trader/run.py index bc0a304d..55ca8949 100644 --- a/examples/vn_trader/run.py +++ b/examples/vn_trader/run.py @@ -8,7 +8,7 @@ from vnpy.trader.ui import MainWindow, create_qapp from vnpy.gateway.bitmex import BitmexGateway # from vnpy.gateway.futu import FutuGateway # 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.mini import MiniGateway # 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.okex import OkexGateway # 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.okexf import OkexfGateway -from vnpy.gateway.okexs import OkexsGateway +# from vnpy.gateway.okexf import OkexfGateway +# from vnpy.gateway.okexs import OkexsGateway # from vnpy.gateway.xtp import XtpGateway # from vnpy.gateway.hbdm import HbdmGateway # from vnpy.gateway.tap import TapGateway # from vnpy.gateway.tora import ToraGateway # from vnpy.gateway.alpaca import AlpacaGateway -from vnpy.gateway.da import DaGateway -from vnpy.gateway.coinbase import CoinbaseGateway -from vnpy.gateway.bitstamp import BitstampGateway -from vnpy.gateway.gateios import GateiosGateway -from vnpy.gateway.bybit import BybitGateway +# from vnpy.gateway.da import DaGateway +# from vnpy.gateway.coinbase import CoinbaseGateway +# from vnpy.gateway.bitstamp import BitstampGateway +# from vnpy.gateway.gateios import GateiosGateway +# from vnpy.gateway.bybit import BybitGateway from vnpy.app.cta_strategy import CtaStrategyApp # from vnpy.app.csv_loader import CsvLoaderApp @@ -79,7 +79,7 @@ def main(): # main_engine.add_gateway(CoinbaseGateway) # main_engine.add_gateway(BitstampGateway) # main_engine.add_gateway(GateiosGateway) - main_engine.add_gateway(BybitGateway) + # main_engine.add_gateway(BybitGateway) main_engine.add_app(CtaStrategyApp) main_engine.add_app(CtaBacktesterApp) diff --git a/vnpy/api/mini/include/mini/ThostFtdcTraderApi.h b/vnpy/api/mini/include/mini/ThostFtdcTraderApi.h index 6c054b27..433ffc8a 100644 --- a/vnpy/api/mini/include/mini/ThostFtdcTraderApi.h +++ b/vnpy/api/mini/include/mini/ThostFtdcTraderApi.h @@ -48,6 +48,7 @@ public: ///客户端认证响应 virtual void OnRspAuthenticate(CThostFtdcRspAuthenticateField *pRspAuthenticateField, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {}; + ///登录请求响应 virtual void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {}; diff --git a/vnpy/api/mini/include/mini/ThostFtdcUserApiDataType.h b/vnpy/api/mini/include/mini/ThostFtdcUserApiDataType.h index c9726243..d9efe26e 100644 --- a/vnpy/api/mini/include/mini/ThostFtdcUserApiDataType.h +++ b/vnpy/api/mini/include/mini/ThostFtdcUserApiDataType.h @@ -1976,6 +1976,11 @@ typedef char TThostFtdcCommentType[31]; ///////////////////////////////////////////////////////////////////////// typedef char TThostFtdcVersionType[4]; +///////////////////////////////////////////////////////////////////////// +/////UtpCipherVersionType是一个版本信息类型 +/////////////////////////////////////////////////////////////////////////// +typedef char UtpCipherVersionType[65]; + ///////////////////////////////////////////////////////////////////////// ///TFtdcTradeCodeType是一个交易代码类型 ///////////////////////////////////////////////////////////////////////// @@ -6410,11 +6415,11 @@ typedef char TThostFtdcClientSystemInfoType[273]; ///////////////////////////////////////////////////////////////////////// ///TThostFtdcAppIDType是App代码类型 ///////////////////////////////////////////////////////////////////////// -typedef char TThostFtdcClientAppIDType[21]; +typedef char TThostFtdcClientAppIDType[33]; ///////////////////////////////////////////////////////////////////////// ///TThostFtdcAutoCodeType是AutoCode代码类型 ///////////////////////////////////////////////////////////////////////// -typedef char TThostFtdcAutoCodeType[17]; +typedef char TThostFtdcAutoCodeType[17]; #endif diff --git a/vnpy/api/mini/include/mini/ThostFtdcUserApiStruct.h b/vnpy/api/mini/include/mini/ThostFtdcUserApiStruct.h index 9ffe23bc..39fc8848 100644 --- a/vnpy/api/mini/include/mini/ThostFtdcUserApiStruct.h +++ b/vnpy/api/mini/include/mini/ThostFtdcUserApiStruct.h @@ -112,8 +112,8 @@ struct CThostFtdcReqAuthenticateField TThostFtdcProductInfoType UserProductInfo; ///认证码 TThostFtdcAuthCodeType AuthCode; - ///App代码 - TThostFtdcClientAppIDType AppID; + // App代码 + TThostFtdcClientAppIDType AppID; }; ///客户端认证响应 @@ -125,8 +125,8 @@ struct CThostFtdcRspAuthenticateField TThostFtdcUserIDType UserID; ///用户端产品信息 TThostFtdcProductInfoType UserProductInfo; - ///App代码 - TThostFtdcClientAppIDType AppID; + // App代码 + TThostFtdcClientAppIDType AppID; }; ///客户端认证信息 @@ -139,13 +139,15 @@ struct CThostFtdcAuthenticationInfoField ///用户端产品信息 TThostFtdcProductInfoType UserProductInfo; ///时间戳 - TThostFtdcAuthInfoType TimeStamp; + TThostFtdcAuthInfoType TimeStamp; ///认证信息 TThostFtdcAuthInfoType AuthInfo; ///是否为认证结果 TThostFtdcBoolType IsResult; - ///App代码 - TThostFtdcClientAppIDType AppID; + // App代码 + TThostFtdcClientAppIDType AppID; + // 版本信息 + UtpCipherVersionType VerInfo; }; ///银期转帐报文头 @@ -1926,9 +1928,9 @@ struct CThostFtdcTransFundField ///投资者帐号 TThostFtdcAccountIDType AccountID; ///出金的核心地址 - TThostFtdcAddressAndPortType DepositKernel; + TThostFtdcAddressAndPortType DepositKernel; ///入金的核心地址 - TThostFtdcAddressAndPortType IncomingKernel; + TThostFtdcAddressAndPortType IncomingKernel; ///币种代码 TThostFtdcCurrencyIDType CurrencyID; ///转账金额 diff --git a/vnpy/api/mini/libs/thostmduserapi.lib b/vnpy/api/mini/libs/thostmduserapi.lib index 94a9ae3e..29f5418b 100644 Binary files a/vnpy/api/mini/libs/thostmduserapi.lib and b/vnpy/api/mini/libs/thostmduserapi.lib differ diff --git a/vnpy/api/mini/libs/thosttraderapi.lib b/vnpy/api/mini/libs/thosttraderapi.lib index 233630c3..08bc6bb8 100644 Binary files a/vnpy/api/mini/libs/thosttraderapi.lib and b/vnpy/api/mini/libs/thosttraderapi.lib differ diff --git a/vnpy/api/mini/thostmduserapi.dll b/vnpy/api/mini/thostmduserapi.dll index 2194b52b..6c482613 100644 Binary files a/vnpy/api/mini/thostmduserapi.dll and b/vnpy/api/mini/thostmduserapi.dll differ diff --git a/vnpy/api/mini/thosttraderapi.dll b/vnpy/api/mini/thosttraderapi.dll index 243b4e4c..ef667416 100644 Binary files a/vnpy/api/mini/thosttraderapi.dll and b/vnpy/api/mini/thosttraderapi.dll differ diff --git a/vnpy/app/cta_strategy/backtesting.py b/vnpy/app/cta_strategy/backtesting.py index 600742a0..6b3f21d5 100644 --- a/vnpy/app/cta_strategy/backtesting.py +++ b/vnpy/app/cta_strategy/backtesting.py @@ -552,7 +552,8 @@ class BacktestingEngine: self.pricetick, self.capital, self.end, - self.mode + self.mode, + self.inverse ))) results.append(result) @@ -612,6 +613,7 @@ class BacktestingEngine: global ga_capital global ga_end global ga_mode + global ga_inverse ga_target_name = target_name ga_strategy_class = self.strategy_class @@ -626,6 +628,7 @@ class BacktestingEngine: ga_capital = self.capital ga_end = self.end ga_mode = self.mode + ga_inverse = self.inverse # Set up genetic algorithem toolbox = base.Toolbox() @@ -1217,7 +1220,8 @@ def _ga_optimize(parameter_values: tuple): ga_pricetick, ga_capital, ga_end, - ga_mode + ga_mode, + ga_inverse ) return (result[1],) diff --git a/vnpy/app/script_trader/cli.py b/vnpy/app/script_trader/cli.py index a68a76cb..b6935abf 100644 --- a/vnpy/app/script_trader/cli.py +++ b/vnpy/app/script_trader/cli.py @@ -1,4 +1,4 @@ -from typing import Sequence +from typing import Sequence, Type from vnpy.event import EventEngine, Event from vnpy.trader.engine import MainEngine @@ -14,7 +14,7 @@ def process_log_event(event: Event): 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.register(EVENT_LOG, process_log_event) diff --git a/vnpy/app/spread_trading/algo.py b/vnpy/app/spread_trading/algo.py index bda5d317..e8c0c52b 100644 --- a/vnpy/app/spread_trading/algo.py +++ b/vnpy/app/spread_trading/algo.py @@ -1,7 +1,8 @@ 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.utility import round_to from .template import SpreadAlgoTemplate from .base import SpreadData @@ -17,6 +18,7 @@ class SpreadTakerAlgo(SpreadAlgoTemplate): algoid: str, spread: SpreadData, direction: Direction, + offset: Offset, price: float, volume: float, payup: int, @@ -25,8 +27,9 @@ class SpreadTakerAlgo(SpreadAlgoTemplate): ): """""" super().__init__( - algo_engine, algoid, spread, direction, - price, volume, payup, interval, lock + algo_engine, algoid, spread, + direction, offset, price, volume, + payup, interval, lock ) self.cancel_interval: int = 2 @@ -109,6 +112,7 @@ class SpreadTakerAlgo(SpreadAlgoTemplate): # Calcualte spread volume to hedge active_leg = self.spread.active_leg 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( active_leg.vt_symbol, @@ -118,6 +122,8 @@ class SpreadTakerAlgo(SpreadAlgoTemplate): # Calculate passive leg target volume and do hedge for leg in self.spread.passive_legs: 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( leg.vt_symbol, hedge_volume diff --git a/vnpy/app/spread_trading/base.py b/vnpy/app/spread_trading/base.py index e6777d30..efbe185f 100644 --- a/vnpy/app/spread_trading/base.py +++ b/vnpy/app/spread_trading/base.py @@ -1,9 +1,9 @@ from typing import Dict, List -from math import floor, ceil 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.utility import floor_to, ceil_to EVENT_SPREAD_DATA = "eSpreadData" @@ -30,15 +30,30 @@ class LegData: self.short_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 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): """""" self.bid_price = tick.bid_price_1 self.ask_price = tick.ask_price_1 self.bid_volume = tick.bid_volume_1 self.ask_volume = tick.ask_volume_1 + self.last_price = tick.last_price self.tick = tick @@ -46,6 +61,7 @@ class LegData: """""" if position.direction == Direction.NET: self.net_pos = position.volume + self.net_pos_price = position.price else: if position.direction == Direction.LONG: self.long_pos = position.volume @@ -55,18 +71,52 @@ class LegData: def update_trade(self, trade: TradeData): """""" - if trade.direction == Direction.LONG: - if trade.offset == Offset.OPEN: - self.long_pos += trade.volume - else: - self.short_pos -= trade.volume - else: - if trade.offset == Offset.OPEN: - self.short_pos += trade.volume - else: - self.long_pos -= trade.volume + # 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 - self.net_pos = self.long_pos - self.net_pos + 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.offset == Offset.OPEN: + self.long_pos += trade.volume + else: + self.short_pos -= trade.volume + else: + if trade.offset == Offset.OPEN: + self.short_pos += trade.volume + else: + self.long_pos -= trade.volume + + self.net_pos = self.long_pos - self.short_pos class SpreadData: @@ -78,7 +128,9 @@ class SpreadData: legs: List[LegData], price_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 @@ -87,11 +139,16 @@ class SpreadData: self.active_leg: LegData = None self.passive_legs: List[LegData] = [] + self.min_volume: float = min_volume + # 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 - 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.trading_formula: str = "" @@ -146,17 +203,35 @@ class SpreadData: # Calculate volume 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: - adjusted_bid_volume = floor( - leg.bid_volume / trading_multiplier) - adjusted_ask_volume = floor( - leg.ask_volume / trading_multiplier) + adjusted_bid_volume = floor_to( + leg_bid_volume / trading_multiplier, + self.min_volume + ) + adjusted_ask_volume = floor_to( + leg_ask_volume / trading_multiplier, + self.min_volume + ) else: - adjusted_bid_volume = floor( - leg.ask_volume / abs(trading_multiplier)) - adjusted_ask_volume = floor( - leg.bid_volume / abs(trading_multiplier)) + adjusted_bid_volume = floor_to( + leg_bid_volume / abs(trading_multiplier), + self.min_volume + ) + adjusted_ask_volume = floor_to( + leg_ask_volume / abs(trading_multiplier), + self.min_volume + ) # For the first leg, just initialize if not n: @@ -180,13 +255,21 @@ class SpreadData: leg_short_pos = 0 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: - adjusted_net_pos = floor(adjusted_net_pos) + adjusted_net_pos = floor_to(adjusted_net_pos, self.min_volume) leg_long_pos = adjusted_net_pos 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) if not n: @@ -222,9 +305,9 @@ class SpreadData: spread_volume = leg_volume / trading_multiplier if spread_volume > 0: - spread_volume = floor(spread_volume) + spread_volume = floor_to(spread_volume, self.min_volume) else: - spread_volume = ceil(spread_volume) + spread_volume = ceil_to(spread_volume, self.min_volume) return spread_volume @@ -243,3 +326,24 @@ class SpreadData: gateway_name="SPREAD" ) 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 diff --git a/vnpy/app/spread_trading/engine.py b/vnpy/app/spread_trading/engine.py index 433eb19d..8550e0de 100644 --- a/vnpy/app/spread_trading/engine.py +++ b/vnpy/app/spread_trading/engine.py @@ -116,6 +116,7 @@ class SpreadDataEngine: spread_setting["name"], spread_setting["leg_settings"], spread_setting["active_symbol"], + spread_setting.get("min_volume", 1), save=False ) @@ -128,18 +129,21 @@ class SpreadDataEngine: for leg in spread.legs.values(): price_multiplier = spread.price_multipliers[leg.vt_symbol] trading_multiplier = spread.trading_multipliers[leg.vt_symbol] + inverse_contract = spread.inverse_contracts[leg.vt_symbol] leg_setting = { "vt_symbol": leg.vt_symbol, "price_multiplier": price_multiplier, - "trading_multiplier": trading_multiplier + "trading_multiplier": trading_multiplier, + "inverse_contract": inverse_contract } leg_settings.append(leg_setting) spread_setting = { "name": spread.name, "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) @@ -194,8 +198,12 @@ class SpreadDataEngine: def process_contract_event(self, event: Event) -> None: """""" 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( contract.symbol, contract.exchange ) @@ -222,6 +230,8 @@ class SpreadDataEngine: # Subscribe market data contract = self.main_engine.get_contract(vt_symbol) if contract: + leg.update_contract(contract) + req = SubscribeRequest( contract.symbol, contract.exchange @@ -243,6 +253,7 @@ class SpreadDataEngine: name: str, leg_settings: List[Dict], active_symbol: str, + min_volume: float, save: bool = True ) -> None: """""" @@ -253,6 +264,7 @@ class SpreadDataEngine: legs: List[LegData] = [] price_multipliers: Dict[str, int] = {} trading_multipliers: Dict[str, int] = {} + inverse_contracts: Dict[str, bool] = {} for leg_setting in leg_settings: vt_symbol = leg_setting["vt_symbol"] @@ -261,13 +273,17 @@ class SpreadDataEngine: legs.append(leg) price_multipliers[vt_symbol] = leg_setting["price_multiplier"] trading_multipliers[vt_symbol] = leg_setting["trading_multiplier"] + inverse_contracts[vt_symbol] = leg_setting.get( + "inverse_contract", False) spread = SpreadData( name, legs, price_multipliers, trading_multipliers, - active_symbol + active_symbol, + inverse_contracts, + min_volume ) self.spreads[name] = spread @@ -414,6 +430,7 @@ class SpreadAlgoEngine: self, spread_name: str, direction: Direction, + offset: Offset, price: float, volume: float, payup: int, @@ -437,6 +454,7 @@ class SpreadAlgoEngine: algoid, spread, direction, + offset, price, volume, payup, @@ -711,7 +729,8 @@ class SpreadStrategyEngine: strategy = self.algo_strategy_map.get(algo.algoid, None) 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): """""" @@ -894,6 +913,7 @@ class SpreadStrategyEngine: strategy: SpreadStrategyTemplate, spread_name: str, direction: Direction, + offset: Offset, price: float, volume: float, payup: int, @@ -904,6 +924,7 @@ class SpreadStrategyEngine: algoid = self.spread_engine.start_algo( spread_name, direction, + offset, price, volume, payup, diff --git a/vnpy/app/spread_trading/strategies/basic_spread_strategy.py b/vnpy/app/spread_trading/strategies/basic_spread_strategy.py index b1213531..a72504e3 100644 --- a/vnpy/app/spread_trading/strategies/basic_spread_strategy.py +++ b/vnpy/app/spread_trading/strategies/basic_spread_strategy.py @@ -87,6 +87,8 @@ class BasicSpreadStrategy(SpreadStrategyTemplate): # No position if not self.spread_pos: + self.stop_close_algos() + # Start open algos if not self.buy_algoid: self.buy_algoid = self.start_long_algo( @@ -98,27 +100,20 @@ class BasicSpreadStrategy(SpreadStrategyTemplate): 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 elif self.spread_pos > 0: + self.stop_open_algos() + # Start sell close algo if not self.sell_algoid: self.sell_algoid = self.start_short_algo( 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 elif self.spread_pos < 0: + self.stop_open_algos() + # Start cover close algo if not self.cover_algoid: self.cover_algoid = self.start_long_algo( @@ -126,10 +121,6 @@ class BasicSpreadStrategy(SpreadStrategyTemplate): self.spread_pos), self.payup, self.interval ) - # Stop short open algo - if self.short_algoid: - self.stop_algo(self.short_algoid) - self.put_event() def on_spread_pos(self): @@ -166,3 +157,19 @@ class BasicSpreadStrategy(SpreadStrategyTemplate): Callback when new trade data is received. """ 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) diff --git a/vnpy/app/spread_trading/template.py b/vnpy/app/spread_trading/template.py index de4ca343..0d8838f3 100644 --- a/vnpy/app/spread_trading/template.py +++ b/vnpy/app/spread_trading/template.py @@ -1,14 +1,13 @@ from collections import defaultdict from typing import Dict, List, Set -from math import floor, ceil from copy import copy from vnpy.trader.object import TickData, TradeData, OrderData, ContractData 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: @@ -23,11 +22,12 @@ class SpreadAlgoTemplate: algoid: str, spread: SpreadData, direction: Direction, + offset: Offset, price: float, volume: float, payup: int, interval: int, - lock: bool + lock: bool, ): """""" self.algo_engine = algo_engine @@ -36,6 +36,7 @@ class SpreadAlgoTemplate: self.spread: SpreadData = spread self.spread_name: str = spread.name + self.offset: Offset = offset self.direction: Direction = direction self.price: float = price self.volume: float = volume @@ -117,10 +118,24 @@ class SpreadAlgoTemplate: def update_trade(self, trade: TradeData): """""" - if trade.direction == Direction.LONG: - self.leg_traded[trade.vt_symbol] += trade.volume + # For inverse contract: + # 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: - 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( trade.vt_symbol, @@ -177,6 +192,21 @@ class SpreadAlgoTemplate: 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( self, vt_symbol, @@ -214,12 +244,17 @@ class SpreadAlgoTemplate: leg_traded = self.leg_traded[leg.vt_symbol] trading_multiplier = self.spread.trading_multipliers[ leg.vt_symbol] + adjusted_leg_traded = leg_traded / trading_multiplier + adjusted_leg_traded = round_to( + adjusted_leg_traded, self.spread.min_volume) 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: - adjusted_leg_traded = ceil(adjusted_leg_traded) + adjusted_leg_traded = ceil_to( + adjusted_leg_traded, self.spread.min_volume) if not n: self.traded = adjusted_leg_traded @@ -434,7 +469,8 @@ class SpreadStrategyTemplate: volume: float, payup: int, interval: int, - lock: bool + lock: bool, + offset: Offset ) -> str: """""" if not self.trading: @@ -444,6 +480,7 @@ class SpreadStrategyTemplate: self, self.spread_name, direction, + offset, price, volume, payup, @@ -461,10 +498,14 @@ class SpreadStrategyTemplate: volume: float, payup: int, interval: int, - lock: bool = False + lock: bool = False, + offset: Offset = Offset.NONE ) -> 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( self, @@ -472,10 +513,14 @@ class SpreadStrategyTemplate: volume: float, payup: int, interval: int, - lock: bool = False + lock: bool = False, + offset: Offset = Offset.NONE ) -> 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): """""" diff --git a/vnpy/app/spread_trading/ui/widget.py b/vnpy/app/spread_trading/ui/widget.py index 00ef63a5..44dda6fe 100644 --- a/vnpy/app/spread_trading/ui/widget.py +++ b/vnpy/app/spread_trading/ui/widget.py @@ -4,7 +4,7 @@ Widget for spread trading. from vnpy.event import EventEngine, Event 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.widget import ( BaseMonitor, BaseCell, @@ -169,6 +169,7 @@ class SpreadAlgoMonitor(BaseMonitor): "algoid": {"display": "绠楁硶", "cell": BaseCell, "update": False}, "spread_name": {"display": "浠峰樊", "cell": BaseCell, "update": False}, "direction": {"display": "鏂瑰悜", "cell": DirectionCell, "update": False}, + "offset": {"display": "寮骞", "cell": EnumCell, "update": False}, "price": {"display": "浠锋牸", "cell": BaseCell, "update": False}, "payup": {"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] ) + self.offset_combo = QtWidgets.QComboBox() + self.offset_combo.addItems( + [Offset.NONE.value, Offset.OPEN.value, Offset.CLOSE.value] + ) + float_validator = QtGui.QDoubleValidator() self.price_line = QtWidgets.QLineEdit() @@ -273,6 +279,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame): form = QtWidgets.QFormLayout() form.addRow("浠峰樊", self.name_line) form.addRow("鏂瑰悜", self.direction_combo) + form.addRow("寮骞", self.offset_combo) form.addRow("浠锋牸", self.price_line) form.addRow("鏁伴噺", self.volume_line) form.addRow("瓒呬环", self.payup_line) @@ -298,6 +305,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame): """""" name = self.name_line.text() direction = Direction(self.direction_combo.currentText()) + offset = Offset(self.offset_combo.currentText()) price = float(self.price_line.text()) volume = float(self.volume_line.text()) payup = int(self.payup_line.text()) @@ -310,7 +318,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame): lock = False self.spread_engine.start_algo( - name, direction, price, volume, payup, interval, lock + name, direction, offset, price, volume, payup, interval, lock ) def add_spread(self): @@ -375,6 +383,17 @@ class SpreadDataDialog(QtWidgets.QDialog): self.name_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() button_add = QtWidgets.QPushButton("鍒涘缓浠峰樊") @@ -384,14 +403,17 @@ class SpreadDataDialog(QtWidgets.QDialog): grid = QtWidgets.QGridLayout() 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(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("鏈湴浠g爜"), 3, 1) - grid.addWidget(Label("浠锋牸涔樻暟"), 3, 2) - grid.addWidget(Label("浜ゆ槗涔樻暟"), 3, 3) + grid.addWidget(Label(""), 3, 0) + grid.addWidget(Label("鏈湴浠g爜"), 4, 1) + grid.addWidget(Label("浠锋牸涔樻暟"), 4, 2) + grid.addWidget(Label("浜ゆ槗涔樻暟"), 4, 3) + grid.addWidget(Label("鍚堢害妯″紡"), 4, 4) int_validator = QtGui.QIntValidator() @@ -405,20 +427,25 @@ class SpreadDataDialog(QtWidgets.QDialog): trading_line = QtWidgets.QLineEdit() trading_line.setValidator(int_validator) - grid.addWidget(Label("鑵縶}".format(i + 1)), 4 + i, 0) - grid.addWidget(symbol_line, 4 + i, 1) - grid.addWidget(price_line, 4 + i, 2) - grid.addWidget(trading_line, 4 + i, 3) + inverse_combo = QtWidgets.QComboBox() + inverse_combo.addItems(["姝e悜", "鍙嶅悜"]) + + 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 = { "symbol": symbol_line, "price": price_line, - "trading": trading_line + "trading": trading_line, + "inverse": inverse_combo } self.leg_widgets.append(d) - grid.addWidget(Label(""), 4 + leg_count, 0,) - grid.addWidget(button_add, 5 + leg_count, 0, 1, 4) + grid.addWidget(Label(""), 5 + leg_count, 0,) + grid.addWidget(button_add, 6 + leg_count, 0, 1, 5) self.setLayout(grid) @@ -435,6 +462,7 @@ class SpreadDataDialog(QtWidgets.QDialog): return active_symbol = self.active_line.text() + min_volume = float(self.min_volume_combo.currentText()) leg_settings = {} for d in self.leg_widgets: @@ -443,10 +471,16 @@ class SpreadDataDialog(QtWidgets.QDialog): price_multiplier = int(d["price"].text()) trading_multiplier = int(d["trading"].text()) + if d["inverse"].currentText() == "姝e悜": + inverse_contract = False + else: + inverse_contract = True + leg_settings[vt_symbol] = { "vt_symbol": vt_symbol, "price_multiplier": price_multiplier, - "trading_multiplier": trading_multiplier + "trading_multiplier": trading_multiplier, + "inverse_contract": inverse_contract } except ValueError: pass @@ -472,7 +506,8 @@ class SpreadDataDialog(QtWidgets.QDialog): self.spread_engine.add_spread( spread_name, list(leg_settings.values()), - active_symbol + active_symbol, + min_volume ) self.accept() diff --git a/vnpy/gateway/binance/binance_gateway.py b/vnpy/gateway/binance/binance_gateway.py index b2936948..b60b3f7b 100644 --- a/vnpy/gateway/binance/binance_gateway.py +++ b/vnpy/gateway/binance/binance_gateway.py @@ -107,8 +107,6 @@ class BinanceGateway(BaseGateway): self.market_ws_api = BinanceDataWebsocketApi(self) self.rest_api = BinanceRestApi(self) - self.event_engine.register(EVENT_TIMER, self.process_timer_event) - def connect(self, setting: dict): """""" key = setting["key"] @@ -121,6 +119,8 @@ class BinanceGateway(BaseGateway): 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): """""" self.market_ws_api.subscribe(req) diff --git a/vnpy/gateway/bitfinex/bitfinex_gateway.py b/vnpy/gateway/bitfinex/bitfinex_gateway.py index a37dd995..c61bd83d 100644 --- a/vnpy/gateway/bitfinex/bitfinex_gateway.py +++ b/vnpy/gateway/bitfinex/bitfinex_gateway.py @@ -53,6 +53,13 @@ ORDERTYPE_VT2BITFINEX = { OrderType.LIMIT: "EXCHANGE LIMIT", OrderType.MARKET: "EXCHANGE MARKET", } +ORDERTYPE_BITFINEX2VT = { + "EXCHANGE LIMIT": OrderType.LIMIT, + "EXCHANGE MARKET": OrderType.MARKET, + "LIMIT": OrderType.LIMIT, + "MARKET": OrderType.MARKET +} + DIRECTION_VT2BITFINEX = { Direction.LONG: "Buy", Direction.SHORT: "Sell", @@ -86,6 +93,7 @@ class BitfinexGateway(BaseGateway): "session": 3, "proxy_host": "127.0.0.1", "proxy_port": 1080, + "margin": ["False", "True"] } exchanges = [Exchange.BITFINEX] @@ -108,8 +116,13 @@ class BitfinexGateway(BaseGateway): proxy_host = setting["proxy_host"] 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.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) @@ -212,7 +225,7 @@ class BitfinexRestApi(RestClient): secret: str, session: int, proxy_host: str, - proxy_port: int, + proxy_port: int ): """ Initialize connection to REST server. @@ -383,11 +396,17 @@ class BitfinexWebsocketApi(WebsocketClient): self.subscribed = {} 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.secret = secret.encode() + self.margin = margin self.init(WEBSOCKET_HOST, proxy_host, proxy_port) self.start() @@ -432,9 +451,13 @@ class BitfinexWebsocketApi(WebsocketClient): else: amount = -req.volume + order_type = ORDERTYPE_VT2BITFINEX[req.type] + if self.margin: + order_type = order_type.replace("EXCHANGE ", "") + o = { "cid": orderid, - "type": ORDERTYPE_VT2BITFINEX[req.type], + "type": order_type, "symbol": "t" + req.symbol, "amount": str(amount), "price": str(req.price), @@ -609,19 +632,26 @@ class BitfinexWebsocketApi(WebsocketClient): def on_wallet(self, data): """""" - if str(data[0]) == "exchange": - accountid = str(data[1]) - account = self.accounts.get(accountid, None) - if not account: - account = AccountData( - accountid=accountid, - gateway_name=self.gateway_name, - ) + 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 - account.balance = float(data[2]) - account.available = 0.0 - account.frozen = 0.0 - self.gateway.on_account(copy(account)) + accountid = str(data[1]) + account = self.accounts.get(accountid, None) + if not account: + account = AccountData( + accountid=accountid, + gateway_name=self.gateway_name, + ) + + account.balance = float(data[2]) + account.available = 0.0 + account.frozen = 0.0 + self.gateway.on_account(copy(account)) def on_trade_update(self, data): """""" @@ -776,6 +806,7 @@ class BitfinexWebsocketApi(WebsocketClient): order = OrderData( symbol=str(data[3].replace("t", "")), exchange=Exchange.BITFINEX, + type=ORDERTYPE_BITFINEX2VT[data[8]], orderid=orderid, status=Status.REJECTED, direction=direction, @@ -808,6 +839,7 @@ class BitfinexWebsocketApi(WebsocketClient): symbol=str(data[3].replace("t", "")), exchange=Exchange.BITFINEX, orderid=orderid, + type=ORDERTYPE_BITFINEX2VT[data[8]], status=STATUS_BITFINEX2VT[order_status], direction=direction, price=float(data[16]), diff --git a/vnpy/gateway/bitmex/bitmex_gateway.py b/vnpy/gateway/bitmex/bitmex_gateway.py index 02781cc4..b089e380 100644 --- a/vnpy/gateway/bitmex/bitmex_gateway.py +++ b/vnpy/gateway/bitmex/bitmex_gateway.py @@ -533,6 +533,7 @@ class BitmexWebsocketApi(WebsocketClient): self.ticks = {} self.accounts = {} self.orders = {} + self.positions = {} self.trades = set() def connect( @@ -702,9 +703,15 @@ class BitmexWebsocketApi(WebsocketClient): def on_order(self, d): """""" + # Filter order data which cannot be processed properly if "ordStatus" not in d: return + side = d.get("side", "") + if not side: + return + + # Update local order data sysid = d["orderID"] order = self.orders.get(sysid, None) if not order: @@ -713,14 +720,12 @@ class BitmexWebsocketApi(WebsocketClient): else: orderid = sysid - # time = d["timestamp"][11:19] - order = OrderData( symbol=d["symbol"], exchange=Exchange.BITMEX, type=ORDERTYPE_BITMEX2VT[d["ordType"]], orderid=orderid, - direction=DIRECTION_BITMEX2VT[d["side"]], + direction=DIRECTION_BITMEX2VT[side], price=d["price"], volume=d["orderQty"], time=d["timestamp"][11:19], @@ -735,15 +740,27 @@ class BitmexWebsocketApi(WebsocketClient): def on_position(self, d): """""" - position = PositionData( - symbol=d["symbol"], - exchange=Exchange.BITMEX, - direction=Direction.NET, - volume=d.get("currentQty", 0), - gateway_name=self.gateway_name, - ) + symbol = d["symbol"] - self.gateway.on_position(position) + position = self.positions.get(symbol, None) + if not position: + position = PositionData( + symbol=d["symbol"], + exchange=Exchange.BITMEX, + direction=Direction.NET, + gateway_name=self.gateway_name, + ) + self.positions[symbol] = 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): """""" diff --git a/vnpy/gateway/coinbase/coinbase_gateway.py b/vnpy/gateway/coinbase/coinbase_gateway.py index 1730eb72..9ed871cd 100644 --- a/vnpy/gateway/coinbase/coinbase_gateway.py +++ b/vnpy/gateway/coinbase/coinbase_gateway.py @@ -198,6 +198,7 @@ class CoinbaseWebsocketApi(WebsocketClient): } self.orderbooks = {} + self.subscribed = {} def connect( self, @@ -223,6 +224,10 @@ class CoinbaseWebsocketApi(WebsocketClient): def subscribe(self, req: SubscribeRequest): """""" + self.subscribed[req.symbol] = req + if not self._active: + return + symbol = req.symbol exchange = req.exchange @@ -259,6 +264,9 @@ class CoinbaseWebsocketApi(WebsocketClient): """ self.gateway.write_log("Websocket API杩炴帴鎴愬姛") + for req in self.subscribed.values(): + self.subscribe(req) + def on_disconnected(self): """""" self.gateway.write_log("Websocket API杩炴帴鏂紑") diff --git a/vnpy/gateway/hbdm/hbdm_gateway.py b/vnpy/gateway/hbdm/hbdm_gateway.py index b8de5b76..b055f32b 100644 --- a/vnpy/gateway/hbdm/hbdm_gateway.py +++ b/vnpy/gateway/hbdm/hbdm_gateway.py @@ -59,6 +59,8 @@ STATUS_HBDM2VT = { ORDERTYPE_VT2HBDM = { OrderType.MARKET: "opponent", OrderType.LIMIT: "limit", + OrderType.FOK: "fok", + OrderType.FAK: "ioc" } ORDERTYPE_HBDM2VT = {v: k for k, v in ORDERTYPE_VT2HBDM.items()} ORDERTYPE_HBDM2VT[1] = OrderType.LIMIT @@ -284,41 +286,7 @@ class HbdmRestApi(RestClient): self.add_request( method="POST", path="/api/v1/contract_openorders", - callback=self.on_query_active_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, + callback=self.on_query_order, data=data, extra=currency ) @@ -549,7 +517,7 @@ class HbdmRestApi(RestClient): for position in self.positions.values(): 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, "鏌ヨ娲诲姩濮旀墭"): return @@ -582,61 +550,6 @@ class HbdmRestApi(RestClient): 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 """""" if self.check_error(data, "鏌ヨ鍚堢害"): @@ -663,7 +576,6 @@ class HbdmRestApi(RestClient): self.gateway.write_log("鍚堢害淇℃伅鏌ヨ鎴愬姛") self.query_order() - self.query_trade() def on_send_order(self, data, request): """""" @@ -1041,7 +953,6 @@ class HbdmDataWebsocketApi(HbdmWebsocketApiBase): tick_data = data["tick"] if "bids" not in tick_data or "asks" not in tick_data: - print(data) return bids = tick_data["bids"] diff --git a/vnpy/trader/converter.py b/vnpy/trader/converter.py index d42a39eb..b181bdbe 100644 --- a/vnpy/trader/converter.py +++ b/vnpy/trader/converter.py @@ -278,6 +278,7 @@ class PositionHolding: # If no td_volume, we close opposite yd position first # then open new position else: + close_volume = min(req.volume, yd_available) open_volume = max(0, req.volume - yd_available) req_list = [] @@ -287,6 +288,7 @@ class PositionHolding: req_yd.offset = Offset.CLOSEYESTERDAY else: req_yd.offset = Offset.CLOSE + req_yd.volume = close_volume req_list.append(req_yd) if open_volume: diff --git a/vnpy/trader/rqdata.py b/vnpy/trader/rqdata.py index 04fd17fe..539ecf87 100644 --- a/vnpy/trader/rqdata.py +++ b/vnpy/trader/rqdata.py @@ -53,7 +53,7 @@ class RqdataClient: self.username, self.password, ('rqdatad-pro.ricequant.com', 16011), - use_pool=True + use_pool=True, ) try: diff --git a/vnpy/trader/ui/widget.py b/vnpy/trader/ui/widget.py index 591b0b00..c5322c4e 100644 --- a/vnpy/trader/ui/widget.py +++ b/vnpy/trader/ui/widget.py @@ -463,7 +463,7 @@ class PositionMonitor(BaseMonitor): "volume": {"display": "鏁伴噺", "cell": BaseCell, "update": True}, "yd_volume": {"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}, "gateway_name": {"display": "鎺ュ彛", "cell": BaseCell, "update": False}, } diff --git a/vnpy/trader/utility.py b/vnpy/trader/utility.py index 51471b70..256a26f8 100644 --- a/vnpy/trader/utility.py +++ b/vnpy/trader/utility.py @@ -7,6 +7,7 @@ import logging from pathlib import Path from typing import Callable, Dict from decimal import Decimal +from math import floor, ceil import numpy as np import talib @@ -124,6 +125,26 @@ def round_to(value: float, target: float) -> float: 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: """ For: