From 12a44cdfe23d15787f8703fc584cbddb895d2f18 Mon Sep 17 00:00:00 2001 From: msincenselee Date: Sun, 16 Aug 2020 19:51:36 +0800 Subject: [PATCH] =?UTF-8?q?[update]=20=E6=9C=9F=E6=9D=83api,gateway,=20vnp?= =?UTF-8?q?y=202.15=20sync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vnpy/api/sopt/vnsopt/vnsopttd/vnsopttd.cpp | 4 +- vnpy/api/sopt/vnsopttd.pyd | Bin 2129920 -> 2129920 bytes vnpy/app/algo_trading/engine.py | 56 ++- vnpy/app/algo_trading/template.py | 1 + vnpy/app/cta_strategy_pro/back_testing.py | 3 +- vnpy/app/cta_strategy_pro/template.py | 4 +- vnpy/app/option_master/algo.py | 2 +- vnpy/app/option_master/base.py | 126 ++++-- vnpy/app/option_master/engine.py | 14 +- .../option_master/pricing/binomial_tree.py | 32 +- .../pricing/binomial_tree_cython.pyd | Bin 91136 -> 93184 bytes vnpy/app/option_master/pricing/black_76.py | 4 +- .../option_master/pricing/black_76_cython.pyd | Bin 71680 -> 71680 bytes .../option_master/pricing/black_scholes.py | 2 +- .../pricing/black_scholes_cython.pyd | Bin 71168 -> 71168 bytes .../binomial_tree_cython.pyx | 40 +- .../black_76_cython/black_76_cython.pyx | 4 +- .../black_scholes_cython.pyx | 4 +- vnpy/app/option_master/ui/manager.py | 127 ++++-- vnpy/app/option_master/ui/monitor.py | 30 +- vnpy/app/option_master/ui/widget.py | 76 +++- vnpy/gateway/ctp/ctp_gateway.py | 3 +- vnpy/gateway/rohon/rohon_gateway.py | 4 +- vnpy/gateway/sopt/sopt_gateway.py | 387 +++++++++++++++++- vnpy/trader/utility.py | 19 + 25 files changed, 792 insertions(+), 150 deletions(-) diff --git a/vnpy/api/sopt/vnsopt/vnsopttd/vnsopttd.cpp b/vnpy/api/sopt/vnsopt/vnsopttd/vnsopttd.cpp index bdc7fd1d..e84ae639 100644 --- a/vnpy/api/sopt/vnsopt/vnsopttd/vnsopttd.cpp +++ b/vnpy/api/sopt/vnsopt/vnsopttd/vnsopttd.cpp @@ -8678,12 +8678,12 @@ int TdApi::exit() void TdApi::subscribePrivateTopic(int x) { - this->api->SubscribePrivateTopic(THOST_TERT_RESTART); + this->api->SubscribePrivateTopic((THOST_TE_RESUME_TYPE) x); } void TdApi::subscribePublicTopic(int x) { - this->api->SubscribePublicTopic(THOST_TERT_RESTART); + this->api->SubscribePublicTopic((THOST_TE_RESUME_TYPE) x); } string TdApi::getTradingDay() diff --git a/vnpy/api/sopt/vnsopttd.pyd b/vnpy/api/sopt/vnsopttd.pyd index 0cf3939bf46385c439c067f84598b8ebd238ba94..a6284a0aee438a9b0b6ff1a7638eda97ddc628f2 100644 GIT binary patch delta 277 zcmZo@Xku&t;td9jEL-I@-A*fLIWSg@9NXh(&-{6o|!u zSR9BYwy))u{Ql7f501gya A3jhEB delta 277 zcmZo@Xku&t;td9jEZhCQ#5S8Swwo}5FcT0n12GE_vjQ<25VHd@2M}`tF&7YXZ#QA& zN#K$+zU0x(= None: """""" - if not self.tick: + if not self.tick or not self.underlying: return underlying_price = self.underlying.mid_price @@ -153,8 +168,16 @@ class OptionData(InstrumentData): return underlying_price += self.underlying_adjustment + # Adjustment for crypto inverse option contract + if self.inverse: + ask_price = self.tick.ask_price_1 * underlying_price + bid_price = self.tick.bid_price_1 * underlying_price + else: + ask_price = self.tick.ask_price_1 + bid_price = self.tick.bid_price_1 + self.ask_impv = self.calculate_impv( - self.tick.ask_price_1, + ask_price, underlying_price, self.strike_price, self.interest_rate, @@ -163,7 +186,7 @@ class OptionData(InstrumentData): ) self.bid_impv = self.calculate_impv( - self.tick.bid_price_1, + bid_price, underlying_price, self.strike_price, self.interest_rate, @@ -173,7 +196,7 @@ class OptionData(InstrumentData): self.mid_impv = (self.ask_impv + self.bid_impv) / 2 - def calculate_theo_greeks(self) -> None: + def calculate_cash_greeks(self) -> None: """""" if not self.underlying: return @@ -192,20 +215,27 @@ class OptionData(InstrumentData): self.option_type ) - self.theo_delta = delta * self.size - self.theo_gamma = gamma * self.size - self.theo_theta = theta * self.size - self.theo_vega = vega * self.size + self.cash_delta = delta * self.size + self.cash_gamma = gamma * self.size + self.cash_theta = theta * self.size + self.cash_vega = vega * self.size + + # Adjustment for crypto inverse option contract + if self.inverse: + self.cash_delta /= underlying_price + self.cash_gamma /= underlying_price + self.cash_theta /= underlying_price + self.cash_vega /= underlying_price def calculate_pos_greeks(self) -> None: """""" if self.tick: self.pos_value = self.tick.last_price * self.size * self.net_pos - self.pos_delta = self.theo_delta * self.net_pos - self.pos_gamma = self.theo_gamma * self.net_pos - self.pos_theta = self.theo_theta * self.net_pos - self.pos_vega = self.theo_vega * self.net_pos + self.pos_delta = self.cash_delta * self.net_pos + self.pos_gamma = self.cash_gamma * self.net_pos + self.pos_theta = self.cash_theta * self.net_pos + self.pos_vega = self.cash_vega * self.net_pos def calculate_ref_price(self) -> float: """""" @@ -221,11 +251,21 @@ class OptionData(InstrumentData): self.option_type ) + # Adjustment for crypto inverse option contract + if self.inverse: + ref_price /= underlying_price + return ref_price def update_tick(self, tick: TickData) -> None: """""" super().update_tick(tick) + + if self.inverse: + current_dt = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + self.days_to_expiry = self.option_expiry - current_dt + self.time_to_expiry = self.days_to_expiry / timedelta(365) + self.calculate_option_impv() def update_trade(self, trade: TradeData) -> None: @@ -238,7 +278,7 @@ class OptionData(InstrumentData): self.underlying_adjustment = underlying_adjustment self.calculate_option_impv() - self.calculate_theo_greeks() + self.calculate_cash_greeks() self.calculate_pos_greeks() def set_chain(self, chain: "ChainData") -> None: @@ -253,6 +293,10 @@ class OptionData(InstrumentData): """""" self.interest_rate = interest_rate + def set_inverse(self, inverse: bool) -> None: + """""" + self.inverse = inverse + def set_pricing_model(self, pricing_model: ModuleType) -> None: """""" self.calculate_greeks = pricing_model.calculate_greeks @@ -267,7 +311,7 @@ class UnderlyingData(InstrumentData): """""" super().__init__(contract) - self.theo_delta: float = 0 + self.cash_delta: float = 0 self.pos_delta: float = 0 self.chains: Dict[str: ChainData] = {} @@ -279,7 +323,7 @@ class UnderlyingData(InstrumentData): """""" super().update_tick(tick) - self.theo_delta = self.size * self.mid_price / 100 + self.cash_delta = self.size * self.mid_price / 100 for chain in self.chains.values(): chain.update_underlying_tick() @@ -293,7 +337,7 @@ class UnderlyingData(InstrumentData): def calculate_pos_greeks(self) -> None: """""" - self.pos_delta = self.theo_delta * self.net_pos + self.pos_delta = self.cash_delta * self.net_pos class ChainData: @@ -326,6 +370,7 @@ class ChainData: self.atm_index: str = "" self.underlying_adjustment: float = 0 self.days_to_expiry: int = 0 + self.inverse: bool = False def add_option(self, option: OptionData) -> None: """""" @@ -340,7 +385,13 @@ class ChainData: if option.chain_index not in self.indexes: self.indexes.append(option.chain_index) - self.indexes.sort() + + # Sort index by number if possible, otherwise by string + try: + float(option.chain_index) + self.indexes.sort(key=float) + except ValueError: + self.indexes.sort() self.days_to_expiry = option.days_to_expiry @@ -428,6 +479,13 @@ class ChainData: for option in self.options.values(): option.set_pricing_model(pricing_model) + def set_inverse(self, inverse: bool) -> None: + """""" + self.inverse = inverse + + for option in self.options.values(): + option.set_inverse(inverse) + def set_portfolio(self, portfolio: "PortfolioData") -> None: """""" for option in self.options: @@ -460,7 +518,15 @@ class ChainData: atm_call = self.calls[self.atm_index] atm_put = self.puts[self.atm_index] - synthetic_price = atm_call.mid_price - atm_put.mid_price + self.atm_price + # Adjustment for crypto inverse option contract + if self.inverse: + call_price = atm_call.mid_price * self.underlying.mid_price + put_price = atm_put.mid_price * self.underlying.mid_price + else: + call_price = atm_call.mid_price + put_price = atm_put.mid_price + + synthetic_price = call_price - put_price + self.atm_price self.underlying_adjustment = synthetic_price - self.underlying.mid_price @@ -488,6 +554,9 @@ class PortfolioData: self.chains: Dict[str, ChainData] = {} self.underlyings: Dict[str, UnderlyingData] = {} + # Greeks decimals precision + self.precision: int = 0 + def calculate_pos_greeks(self) -> None: """""" self.long_pos = 0 @@ -548,6 +617,15 @@ class PortfolioData: for chain in self.chains.values(): chain.set_pricing_model(pricing_model) + def set_inverse(self, inverse: bool) -> None: + """""" + for chain in self.chains.values(): + chain.set_inverse(inverse) + + def set_precision(self, precision: int) -> None: + """""" + self.precision = precision + def set_chain_underlying(self, chain_symbol: str, contract: ContractData) -> None: """""" underlying = self.underlyings.get(contract.vt_symbol, None) diff --git a/vnpy/app/option_master/engine.py b/vnpy/app/option_master/engine.py index a018a153..07759e71 100644 --- a/vnpy/app/option_master/engine.py +++ b/vnpy/app/option_master/engine.py @@ -244,6 +244,8 @@ class OptionEngine(BaseEngine): model_name: str, interest_rate: float, chain_underlying_map: Dict[str, str], + inverse: bool = False, + precision: int = 0 ) -> None: """""" portfolio = self.get_portfolio(portfolio_name) @@ -256,12 +258,16 @@ class OptionEngine(BaseEngine): pricing_model = PRICING_MODELS[model_name] portfolio.set_pricing_model(pricing_model) + portfolio.set_inverse(inverse) + portfolio.set_precision(precision) portfolio_settings = self.setting.setdefault("portfolio_settings", {}) portfolio_settings[portfolio_name] = { "model_name": model_name, "interest_rate": interest_rate, - "chain_underlying_map": chain_underlying_map + "chain_underlying_map": chain_underlying_map, + "inverse": inverse, + "precision": precision } self.save_setting() @@ -434,13 +440,17 @@ class OptionHedgeEngine: # Calculate volume of contract to hedge delta_to_hedge = self.delta_target - portfolio.pos_delta instrument = self.option_engine.get_instrument(self.vt_symbol) - hedge_volume = delta_to_hedge / instrument.theo_delta + hedge_volume = delta_to_hedge / instrument.cash_delta # Send hedge orders tick = self.main_engine.get_tick(self.vt_symbol) contract = self.main_engine.get_contract(self.vt_symbol) holding = self.option_engine.get_position_holding(self.vt_symbol) + # Check if hedge volume meets contract minimum trading volume + if abs(hedge_volume) < contract.min_volume: + return + if hedge_volume > 0: price = tick.ask_price_1 + contract.pricetick * self.hedge_payup direction = Direction.LONG diff --git a/vnpy/app/option_master/pricing/binomial_tree.py b/vnpy/app/option_master/pricing/binomial_tree.py index d6697f12..91d09adf 100644 --- a/vnpy/app/option_master/pricing/binomial_tree.py +++ b/vnpy/app/option_master/pricing/binomial_tree.py @@ -77,9 +77,13 @@ def calculate_delta( ) -> float: """Calculate option delta""" option_tree, underlying_tree = generate_tree(f, k, r, t, v, cp, n) - option_price_change = option_tree[0, 1] - option_tree[1, 1] - underlying_price_change = underlying_tree[0, 1] - underlying_tree[1, 1] - return option_price_change / underlying_price_change + + option_price_change: float = option_tree[0, 1] - option_tree[1, 1] + underlying_price_change: float = underlying_tree[0, 1] - underlying_tree[1, 1] + + _delta: float = option_price_change / underlying_price_change + delta: float = _delta * f * 0.01 + return delta def calculate_gamma( @@ -94,12 +98,14 @@ def calculate_gamma( """Calculate option gamma""" option_tree, underlying_tree = generate_tree(f, k, r, t, v, cp, n) - gamma_delta_1 = (option_tree[0, 2] - option_tree[1, 2]) / \ + gamma_delta_1: float = (option_tree[0, 2] - option_tree[1, 2]) / \ (underlying_tree[0, 2] - underlying_tree[1, 2]) - gamma_delta_2 = (option_tree[1, 2] - option_tree[2, 2]) / \ + gamma_delta_2: float = (option_tree[1, 2] - option_tree[2, 2]) / \ (underlying_tree[1, 2] - underlying_tree[2, 2]) - gamma = (gamma_delta_1 - gamma_delta_2) / \ + + _gamma: float = (gamma_delta_1 - gamma_delta_2) / \ (0.5 * (underlying_tree[0, 2] - underlying_tree[2, 2])) + gamma: float = _gamma * pow(f, 2) * 0.0001 return gamma @@ -174,15 +180,17 @@ def calculate_greeks( # Delta option_price_change = option_tree[0, 1] - option_tree[1, 1] underlying_price_change = underlying_tree[0, 1] - underlying_tree[1, 1] - delta = option_price_change / underlying_price_change + _delta: float = option_price_change / underlying_price_change + delta: float = _delta * f * 0.01 # Gamma gamma_delta_1 = (option_tree[0, 2] - option_tree[1, 2]) / \ (underlying_tree[0, 2] - underlying_tree[1, 2]) gamma_delta_2 = (option_tree[1, 2] - option_tree[2, 2]) / \ (underlying_tree[1, 2] - underlying_tree[2, 2]) - gamma = (gamma_delta_1 - gamma_delta_2) / \ + _gamma: float = (gamma_delta_1 - gamma_delta_2) / \ (0.5 * (underlying_tree[0, 2] - underlying_tree[2, 2])) + gamma: float = _gamma * pow(f, 2) * 0.0001 # Theta theta = (option_tree[1, 2] - option_tree[0, 0]) / (2 * dt * annual_days) @@ -220,7 +228,7 @@ def calculate_impv( return 0 # Calculate implied volatility with Newton's method - v = 0.3 # Initial guess of volatility + v: float = 0.01 # Initial guess of volatility for i in range(50): # Caculate option price and vega with current guess @@ -241,9 +249,9 @@ def calculate_impv( # Calculate guessed implied volatility of next round v += dx - # Check end result to be non-negative - if v <= 0: - return 0 + # Check new volatility to be non-negative + if v <= 0: + return 0 # Round to 4 decimal places v = round(v, 4) diff --git a/vnpy/app/option_master/pricing/binomial_tree_cython.pyd b/vnpy/app/option_master/pricing/binomial_tree_cython.pyd index dc3682f70826aa65e02f174815e79f39037c9e0f..49457a44076c12e5e58469b1e2483459ebe28345 100644 GIT binary patch literal 93184 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 LucLMU_x}DLUiN{S diff --git a/vnpy/app/option_master/pricing/black_76.py b/vnpy/app/option_master/pricing/black_76.py index 3ed021c3..4a2bda47 100644 --- a/vnpy/app/option_master/pricing/black_76.py +++ b/vnpy/app/option_master/pricing/black_76.py @@ -33,7 +33,7 @@ def calculate_price( return max(0, cp * (s - k)) if not d1: - d1: float = calculate_d1(s, k, r, r, v) + d1: float = calculate_d1(s, k, r, t, v) d2: float = d1 - v * sqrt(t) price: float = cp * (s * cdf(cp * d1) - k * cdf(cp * d2)) * exp(-r * t) @@ -186,7 +186,7 @@ def calculate_impv( return 0 # Calculate implied volatility with Newton's method - v: float = 0.3 # Initial guess of volatility + v: float = 0.01 # Initial guess of volatility for i in range(50): # Caculate option price and vega with current guess diff --git a/vnpy/app/option_master/pricing/black_76_cython.pyd b/vnpy/app/option_master/pricing/black_76_cython.pyd index 85b0aad7ff536ada0464588941747f6d59432839..bc26ec3b1ca897640414a10739c4e412eadc974d 100644 GIT binary patch delta 7444 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 diff --git a/vnpy/app/option_master/pricing/black_scholes.py b/vnpy/app/option_master/pricing/black_scholes.py index c3b6c979..36d9c347 100644 --- a/vnpy/app/option_master/pricing/black_scholes.py +++ b/vnpy/app/option_master/pricing/black_scholes.py @@ -185,7 +185,7 @@ def calculate_impv( return 0 # Calculate implied volatility with Newton's method - v: float = 0.3 # Initial guess of volatility + v: float = 0.01 # Initial guess of volatility for i in range(50): # Caculate option price and vega with current guess diff --git a/vnpy/app/option_master/pricing/black_scholes_cython.pyd b/vnpy/app/option_master/pricing/black_scholes_cython.pyd index a636dbb1785a1b047e49205ab5b09fa407c81d99..7d40cded3c1fa9449241bd0e472581ce3ceb8989 100644 GIT binary patch delta 5049 zcmeH~{Zmxe8ONWyykr-Hcac|{s8BCv@dZ&7#b63b;+j~q8X}TGfk=oUrc#5c({ZwE z#~2jMKrXGMu?<#gH<2VX8;LJ;W|6HDQL65#vjY^n17u49y?VpWK;e zKi}s$=REhh=bpRgtgY7HR_kA9l>FC>G3BUX(nf>v2GIs0DpB8)9+RTgMroXss$P(C zB)20?*YU9aixaQ1ovKo`?d@75;wMhSNE`(&fCbbc_0UF?Y?(HFzyk} zO&F5h-20@Q)hwpO`}GKsEi?97_`owjYgUam3Y{^)2n1O5XG1>6brtyl~jD^D+%UJE;|~U9E~n}fK;j$9!phQRv3aC2TC`! ziCH_ZI*%ns6d1dM&84mV;p?;}k7&BA$}N>qON`yJxpa}TxonrDclxThyEvo*+Ea|YCdLkkscHh8U z`^0>HBj)CYm@&ti6R%1?3r7tD&%lY|y>oQ@P_pLY^=Lgheg>-WzKHdL7sm`O7NAkY z=83lt&JPCl>{qc;k1DxshE)bF_?%;#`wht&(T|+?1c^0;kHVbI%wVo#ZejkBS^jm#ewT>|nZ>^~@S(uQsov=ySe+jwia>QStQxo$5JlQjKj&xY%|@ zMfg>vRJ_`$mtl^DZ6kES7C%hJy8OK2JcQJ|_Q*Kf6(T)4#;+@0r7TQK69Y^BC-%Y% zB37>MY7bpeHM}K5YpF)8$bbwRw7i3xef&$BUxLj)9~t6b5xz2AWa~i;`_N!4QN&0M zIU9;CIreO#E-s;)C`Rv3`eJ{g8G+=BCR!LnG!RGMAz$6pnG;egdLx=Jfv5&nt6uL+ zoYE(vk%1K1=T9TMMo@q&h$>7(@587^8!_aoOU{Mr`nuJEbJIiexzRn5D0~vCS3f-0 zlALl4LyZ1p>{3Xd5l5Za+A~v#EFzAKKlN>B7qJxe_ z`;^EoVyNP$ek$5UtWq^x{7p#8b*)$30nGhDQCr2uS3_JJwYZ2*!(3``S99saH!qk=UW~qpF8uN0<&M4pr*AMqlr4ThJw&!)v3kU8h}nn5ig6t~ zVr|1>m58-@DqfiQ@v}aClvV5$GmJ!=VbNDL z{wV}M*@#zz@i?}7p%cJWp+qM1$p?4AmP6NoZLp2df**$ASwh!>WAGoL2s#=(4%-YZ z_#I4K>(L0D3};1X&TR14tcyWAYr%i8c7k5kfJf-U;`{4+6|tE6^pUpEd@nG zQ!LSV?6y_VLpv|D*n!tShVjsXTVSW4t>q9NSQi{0n1_wH2f7Fp8?o51hc;yF!D6Gn z9fv!B7E~r;{Xh%OgQY`Tz$Vyo=%M`@TI}0(uno|H-IMVA%iz#tTnM%kI$;Vn$9OCt z=xT5!{sy|Bi@-au4roe10f{IS+5#Sd5w>wRm^>4QC+H%u1ExUxz@8 zpEW1~Iu%^?8eZM+H2&fptRZL%XkGpXyq9ot!L>WETA^)Vt64FMM$DEF}1v8~G6zY;@!K7sGMG zS=5dTK<9#gJBvGjt^@xA^FsT;ogE;w-~pKUk!b)UJ8`iam=$oI(0FSEvtXVp8rQQH n+zYc{>|O&z6Rhs4D*2UsF>;g?s0*mtQ@3aT7o)?Z%hG=VbRE(o delta 5132 zcmeH~{Zmxu701tA76cS`5kWM*Kv{M11&N>$M-3?ITA7Fr5wWPKNQqJ<78A#23U)|> z0Uhw#h>59Wq=qUrG^-6p+F6C5Q8XJhl@Za!*s|hFR>X*@)_%|4!w$_~&>!5H!{>X> z^M3Ag@AFVltuLt7*BBN3`inD`s{@s{{K6g(Z6=~}b&oPyiB?Z2Ba~#dOfS z{e0g%Ph{CXJy#uRi1nSXIf>SlhRir6Rc*AWLBA;_N;E{4m`W9*&P0PTpv;;xugF&k zwLV;UyRS0T+O`ayYF(T4O1P3}J@#&dS8(V8BIk@bM21xFSm;hens=9a|GiM zgDLvR0b_~K`3Zyhyz|AMxyK)k5RczJ1|0<=#?A$6-%tPOqa<1Xa&)b~(q^Z(XD?YfcAx^0!<{Da^a?!GPti@CMTj?lL6v_|K+04UV>5^lP^bG9Mv z8#FrUZ0YMx-!QKbclNpRUac7_ccDtX^LjsbVqDy^kz1ZX*!C>Adw_c?m^#LDFT?C! z)iScYZQC@_QXJ48gjG-V;S2;ET7hg#zhZJx7+hamZ%}P-+wv9`*Jl~l zpL$^UmkrrU?@|voy<)tAl?SG~pTPwzy-D>w_mbMD!Kf}c*C!GM;*dz z!k&&wf7Dc^uvU0M=n%SuI({|r8z3|brwHc=Gng2k@+7cB_y^%(VWaSputTVS!ov`a z62=PW2vdZ)!Xn{bVJ*nxQ|JEEVPnvxF(a#BTTciDD%%MCc>@;gL4L z4dHUhNRn`hFihw#{81)&M|ertC_Ez6g-R+KWPkyZ`WkE5w{vxUV%)!r>gCIkzS>NP zb~c-W`JiSWUt@~L_aM0)W{qp^s|(2RB8u9WxiW7hLh8aRgCa|B5pnCNjd`n0^AZxc zVfp{WmM-O3g<5tcXjxV7k`%3^2C-Z}^srg$x955gwMO&Hab3tn4^gkNZ<@%xJwXg7 zS5GX4V@6eJ2~ujT!7VYm$oSht;l6tE&C}xn_>e1%XkIwh!*Nvh`0DGeD+22z8a;}r z5>}_SwZ@Fs-$C6$J~XIHPyKK9CGQ4*Dl-#(2s3gS`tkT`!nGis{*Jo%+Qh&-DYTCv z3LcAhP~CIwY+TY^G%@PQc+*6_DUsBQ%RuThq706stDpJ>DCby``qA~VN{#x}^%43S z)KzU)gH^?`T8UNHI3|Q=-qV`@HHB!_t4NjjM)(|yRgXGiQ1_N&w=%`LwQZI%snUfe zeme3i@}u2{i57o~Bama5#ZUcslyj_7eSG81z@+3K+yd=@&2BOQb zOX}0_65~hv)97X|8h%<&A+_GrKiiM|m0$`F_MzY^A2O8p!32ECb)M+mud$svwXQwB zy7lIV;fwH~6-=wgEbx=SR0;DS5Jf}hrhZGqS9Wm zM#KsbtL+sl#5h%mIeWz_5o>mog@-&p&7PlT&rh@e%hT-E3jfc#d_(j3D2X)^6~dxB zG=3CFbOiAVFbe+-bVEmjtAmK9piUP0iV&!1076qAL5sWJ_t)d&i{x1;SMBE4_!MdQCMWRcd42RXgDsg=dfjF!z_q1%VFuz%sN;ev>m(-E9AD|BNHYK zO(TgSa2!`bdyZykKB6~`M*5+d@4#+C=a)h_U|n!r;7Xj-PoQ%_KB@Uc_ng?!e3ExZ zVg=Al(-{0?2hE%bOMy-X>tWf@o&z145B3^Z5j6AmSUmr|aA+JR0IPlWfEn*W zTZp#K!{ne#!PZ6C0{L)U5Pm60DYWMb0^R$1!q=4FW@2)PGv9$#K{K0Rwb0DFu+z}A z2Cr%kCIKA}uKpGL?=`->1A7QM8O&euKHf_>8Q_N9NFj6qxN9Fa2y_K_va zcNlL8wC9oo-TMN>7o)*7_!c0}JPx}9&AbeI0_^~+juG{{kLT~X7vV<4_oMA~NEtM< z6;=rC0;e@#QqZ0o6ExqPQXBD2fM)(4MxB^6=s1sTLVGSp(0plHYe)ak%)i_5{0rgO z;Urzb0MHrWCoNb3bPad|)(!0f_g=*S4>TTw=8sGrIH(naAs!FTWQ{k7nFe#+(zr=9 n^AIcpZ4dcD)Wd8Ys^V4jdH(^5kIuX5fbGEP&j$}w+LiwT<@?$_ diff --git a/vnpy/app/option_master/pricing/cython_model/binomial_tree_cython/binomial_tree_cython.pyx b/vnpy/app/option_master/pricing/cython_model/binomial_tree_cython/binomial_tree_cython.pyx index 9390d302..e0ee0185 100644 --- a/vnpy/app/option_master/pricing/cython_model/binomial_tree_cython/binomial_tree_cython.pyx +++ b/vnpy/app/option_master/pricing/cython_model/binomial_tree_cython/binomial_tree_cython.pyx @@ -88,11 +88,17 @@ def calculate_delta( int n = DEFAULT_STEP ) -> float: """Calculate option delta""" + cdef double option_price_change, underlying_price_change + cdef _delta, delta + option_tree, underlying_tree = generate_tree(f, k, r, t, v, cp, n) + option_price_change = option_tree[0, 1] - option_tree[1, 1] underlying_price_change = underlying_tree[0, 1] - underlying_tree[1, 1] - return option_price_change / underlying_price_change + _delta = option_price_change / underlying_price_change + delta = _delta * f * 0.01 + return delta def calculate_gamma( double f, @@ -104,14 +110,19 @@ def calculate_gamma( int n = DEFAULT_STEP ) -> float: """Calculate option gamma""" + cdef double gamma_delta_1, gamma_delta_2 + cdef double _gamma, gamma + option_tree, underlying_tree = generate_tree(f, k, r, t, v, cp, n) gamma_delta_1 = (option_tree[0, 2] - option_tree[1, 2]) / \ (underlying_tree[0, 2] - underlying_tree[1, 2]) gamma_delta_2 = (option_tree[1, 2] - option_tree[2, 2]) / \ (underlying_tree[1, 2] - underlying_tree[2, 2]) - gamma = (gamma_delta_1 - gamma_delta_2) / \ + + _gamma = (gamma_delta_1 - gamma_delta_2) / \ (0.5 * (underlying_tree[0, 2] - underlying_tree[2, 2])) + gamma = _gamma * pow(f, 2) * 0.0001 return gamma @@ -127,6 +138,8 @@ def calculate_theta( int annual_days = 240 ) -> float: """Calcualte option theta""" + cdef double dt, theta + option_tree, underlying_tree = generate_tree(f, k, r, t, v, cp, n) dt = t / n @@ -177,9 +190,10 @@ def calculate_greeks( ) -> Tuple[float, float, float, float, float]: """Calculate option price and greeks""" cdef double dt = t / n - cdef price, delta, gamma, vega, theta - cdef option_price_change, underlying_price_change - cdef gamma_delta_1, gamma_delta_2 + cdef double price, delta, gamma, vega, theta + cdef double _delta, _gamma + cdef double option_price_change, underlying_price_change + cdef double gamma_delta_1, gamma_delta_2 option_tree, underlying_tree = generate_tree(f, k, r, t, v, cp, n) option_tree_vega, underlying_tree_vega = generate_tree(f, k, r, t, v * 1.001, cp, n) @@ -190,15 +204,19 @@ def calculate_greeks( # Delta option_price_change = option_tree[0, 1] - option_tree[1, 1] underlying_price_change = underlying_tree[0, 1] - underlying_tree[1, 1] - delta = option_price_change / underlying_price_change + + _delta = option_price_change / underlying_price_change + delta = _delta * f * 0.01 # Gamma gamma_delta_1 = (option_tree[0, 2] - option_tree[1, 2]) / \ (underlying_tree[0, 2] - underlying_tree[1, 2]) gamma_delta_2 = (option_tree[1, 2] - option_tree[2, 2]) / \ (underlying_tree[1, 2] - underlying_tree[2, 2]) - gamma = (gamma_delta_1 - gamma_delta_2) / \ + + _gamma = (gamma_delta_1 - gamma_delta_2) / \ (0.5 * (underlying_tree[0, 2] - underlying_tree[2, 2])) + gamma = _gamma * pow(f, 2) * 0.0001 # Theta theta = (option_tree[1, 2] - option_tree[0, 0]) / (2 * dt * annual_days) @@ -238,7 +256,7 @@ def calculate_impv( return 0 # Calculate implied volatility with Newton's method - v = 0.3 # Initial guess of volatility + v = 0.01 # Initial guess of volatility for i in range(50): # Caculate option price and vega with current guess @@ -259,9 +277,9 @@ def calculate_impv( # Calculate guessed implied volatility of next round v += dx - # Check end result to be non-negative - if v <= 0: - return 0 + # Check new volatility to be non-negative + if v <= 0: + return 0 # Round to 4 decimal places v = round(v, 4) diff --git a/vnpy/app/option_master/pricing/cython_model/black_76_cython/black_76_cython.pyx b/vnpy/app/option_master/pricing/cython_model/black_76_cython/black_76_cython.pyx index cd290cbb..7cd37a6e 100644 --- a/vnpy/app/option_master/pricing/cython_model/black_76_cython/black_76_cython.pyx +++ b/vnpy/app/option_master/pricing/cython_model/black_76_cython/black_76_cython.pyx @@ -40,7 +40,7 @@ def calculate_price( return max(0, cp * (s - k)) if not d1: - d1 = calculate_d1(s, k, r, r, v) + d1 = calculate_d1(s, k, r, t, v) d2 = d1 - v * sqrt(t) price = cp * (s * cdf(cp * d1) - k * cdf(cp * d2)) * exp(-r * t) @@ -208,7 +208,7 @@ def calculate_impv( return 0 # Calculate implied volatility with Newton's method - v = 0.3 # Initial guess of volatility + v = 0.01 # Initial guess of volatility for i in range(50): # Caculate option price and vega with current guess diff --git a/vnpy/app/option_master/pricing/cython_model/black_scholes_cython/black_scholes_cython.pyx b/vnpy/app/option_master/pricing/cython_model/black_scholes_cython/black_scholes_cython.pyx index 6af59d7f..66977ba3 100644 --- a/vnpy/app/option_master/pricing/cython_model/black_scholes_cython/black_scholes_cython.pyx +++ b/vnpy/app/option_master/pricing/cython_model/black_scholes_cython/black_scholes_cython.pyx @@ -40,7 +40,7 @@ def calculate_price( return max(0, cp * (s - k)) if not d1: - d1 = calculate_d1(s, k, r, r, v) + d1 = calculate_d1(s, k, r, t, v) d2 = d1 - v * sqrt(t) price = cp * (s * cdf(cp * d1) - k * cdf(cp * d2) * exp(-r * t)) @@ -207,7 +207,7 @@ def calculate_impv( return 0 # Calculate implied volatility with Newton's method - v = 0.3 # Initial guess of volatility + v = 0.01 # Initial guess of volatility for i in range(50): # Caculate option price and vega with current guess diff --git a/vnpy/app/option_master/ui/manager.py b/vnpy/app/option_master/ui/manager.py index a4b59da2..cd9503b9 100644 --- a/vnpy/app/option_master/ui/manager.py +++ b/vnpy/app/option_master/ui/manager.py @@ -5,13 +5,14 @@ from functools import partial from scipy import interpolate from vnpy.event import Event -from vnpy.trader.ui import QtWidgets, QtCore -from vnpy.trader.event import EVENT_TICK, EVENT_TIMER +from vnpy.trader.ui import QtWidgets, QtCore, QtGui +from vnpy.trader.event import EVENT_TICK, EVENT_TIMER, EVENT_TRADE +from vnpy.trader.object import TickData, TradeData +from vnpy.trader.utility import save_json, load_json from ..engine import OptionEngine from ..base import ( EVENT_OPTION_ALGO_PRICING, - EVENT_OPTION_ALGO_TRADING, EVENT_OPTION_ALGO_STATUS, EVENT_OPTION_ALGO_LOG ) @@ -36,6 +37,10 @@ class AlgoSpinBox(QtWidgets.QSpinBox): """""" return self.value() + def set_value(self, value: int) -> None: + """""" + self.setValue(value) + def update_status(self, active: bool) -> None: """""" self.setEnabled(not active) @@ -67,6 +72,10 @@ class AlgoDoubleSpinBox(QtWidgets.QDoubleSpinBox): """""" return self.value() + def set_value(self, value: float) -> None: + """""" + self.setValue(value) + def update_status(self, active: bool) -> None: """""" self.setEnabled(not active) @@ -105,6 +114,15 @@ class AlgoDirectionCombo(QtWidgets.QComboBox): return value + def set_value(self, value: dict) -> None: + """""" + if value["long_allowed"] and value["short_allowed"]: + self.setCurrentIndex(0) + elif value["long_allowed"]: + self.setCurrentIndex(1) + else: + self.setCurrentIndex(2) + def update_status(self, active: bool) -> None: """""" self.setEnabled(not active) @@ -178,7 +196,7 @@ class ElectronicEyeMonitor(QtWidgets.QTableWidget): signal_tick = QtCore.pyqtSignal(Event) signal_pricing = QtCore.pyqtSignal(Event) signal_status = QtCore.pyqtSignal(Event) - signal_trading = QtCore.pyqtSignal(Event) + signal_trade = QtCore.pyqtSignal(Event) headers: List[Dict] = [ {"name": "bid_volume", "display": "买量", "cell": BidCell}, @@ -194,7 +212,7 @@ class ElectronicEyeMonitor(QtWidgets.QTableWidget): {"name": "price_spread", "display": "价格\n价差", "cell": AlgoDoubleSpinBox}, {"name": "volatility_spread", "display": "隐波\n价差", "cell": AlgoDoubleSpinBox}, - {"name": "max_pos", "display": "持仓\n上限", "cell": AlgoPositiveSpinBox}, + {"name": "max_pos", "display": "持仓\n范围", "cell": AlgoPositiveSpinBox}, {"name": "target_pos", "display": "目标\n持仓", "cell": AlgoSpinBox}, {"name": "max_order_size", "display": "最大\n委托", "cell": AlgoPositiveSpinBox}, {"name": "direction", "display": "方向", "cell": AlgoDirectionCombo}, @@ -209,13 +227,16 @@ class ElectronicEyeMonitor(QtWidgets.QTableWidget): self.option_engine = option_engine self.event_engine = option_engine.event_engine + self.main_engine = option_engine.main_engine self.algo_engine = option_engine.algo_engine self.portfolio_name = portfolio_name + self.setting_filename = f"{portfolio_name}_electronic_eye.json" self.cells: Dict[str, Dict] = {} self.init_ui() self.register_event() + self.load_setting() def init_ui(self) -> None: """""" @@ -315,21 +336,64 @@ class ElectronicEyeMonitor(QtWidgets.QTableWidget): self.resizeColumnsToContents() + # Update all net pos and tick cells + for vt_symbol in self.cells.keys(): + self.update_net_pos(vt_symbol) + + tick = self.main_engine.get_tick(vt_symbol) + if tick: + self.update_tick(tick) + + def load_setting(self) -> None: + """""" + fields = [ + "price_spread", + "volatility_spread", + "max_pos", + "target_pos", + "max_order_size", + "direction" + ] + + setting = load_json(self.setting_filename) + + for vt_symbol, cells in self.cells.items(): + buf = setting.get(vt_symbol, None) + if buf: + for field in fields: + cells[field].set_value(buf[field]) + + def save_setting(self) -> None: + """""" + fields = [ + "price_spread", + "volatility_spread", + "max_pos", + "target_pos", + "max_order_size", + "direction" + ] + + setting = {} + for vt_symbol, cells in self.cells.items(): + buf = {} + for field in fields: + buf[field] = cells[field].get_value() + setting[vt_symbol] = buf + + save_json(self.setting_filename, setting) + def register_event(self) -> None: """""" self.signal_pricing.connect(self.process_pricing_event) - self.signal_trading.connect(self.process_trading_event) self.signal_status.connect(self.process_status_event) self.signal_tick.connect(self.process_tick_event) + self.signal_trade.connect(self.process_trade_event) self.event_engine.register( EVENT_OPTION_ALGO_PRICING, self.signal_pricing.emit ) - self.event_engine.register( - EVENT_OPTION_ALGO_TRADING, - self.signal_trading.emit - ) self.event_engine.register( EVENT_OPTION_ALGO_STATUS, self.signal_status.emit @@ -338,10 +402,18 @@ class ElectronicEyeMonitor(QtWidgets.QTableWidget): EVENT_TICK, self.signal_tick.emit ) + self.event_engine.register( + EVENT_TRADE, + self.signal_trade.emit + ) def process_tick_event(self, event: Event) -> None: """""" - tick = event.data + tick: TickData = event.data + self.update_tick(tick) + + def update_tick(self, tick: TickData) -> None: + """""" cells = self.cells.get(tick.vt_symbol, None) if not cells: return @@ -384,22 +456,19 @@ class ElectronicEyeMonitor(QtWidgets.QTableWidget): cells["ref_price"].setText("") cells["pricing_impv"].setText("") - def process_trading_event(self, event: Event) -> None: + def process_trade_event(self, event: Event) -> None: """""" - algo = event.data - cells = self.cells[algo.vt_symbol] + trade: TradeData = event.data + self.update_net_pos(trade.vt_symbol) - if algo.trading_active: - cells["net_pos"].setText(str(algo.option.net_pos)) - else: - cells["net_pos"].setText("") - - def process_position_event(self, event: Event) -> None: + def update_net_pos(self, vt_symbol: str) -> None: """""" - algo = event.data + cells = self.cells.get(vt_symbol, None) + if not cells: + return - cells = self.cells[algo.vt_symbol] - cells["net_pos"].setText(str(algo.option.net_pos)) + option = self.option_engine.get_instrument(vt_symbol) + cells["net_pos"].setText(str(option.net_pos)) def start_algo_pricing(self, vt_symbol: str) -> None: """""" @@ -605,6 +674,11 @@ class ElectronicEyeManager(QtWidgets.QWidget): for vt_symbol in self.algo_monitor.cells.keys(): self.algo_monitor.stop_algo_trading(vt_symbol) + def closeEvent(self, event: QtGui.QCloseEvent) -> None: + """""" + self.algo_monitor.save_setting() + event.accept() + class VolatilityDoubleSpinBox(QtWidgets.QDoubleSpinBox): """""" @@ -859,8 +933,11 @@ class PricingVolatilityManager(QtWidgets.QWidget): otm = chain.puts[index] value = round(otm.pricing_impv * 100, 1) - cells = self.cells[(chain_symbol, index)] - cells["pricing_impv"].setValue(value) + + key = (chain_symbol, index) + cells = self.cells.get(key, None) + if cells: + cells["pricing_impv"].setValue(value) def update_mid_impv(self, chain_symbol: str) -> None: """""" diff --git a/vnpy/app/option_master/ui/monitor.py b/vnpy/app/option_master/ui/monitor.py index 69c8153f..e1aacc55 100644 --- a/vnpy/app/option_master/ui/monitor.py +++ b/vnpy/app/option_master/ui/monitor.py @@ -115,10 +115,10 @@ class OptionMarketMonitor(MonitorTable): headers: List[Dict] = [ {"name": "symbol", "display": "代码", "cell": MonitorCell}, - {"name": "theo_vega", "display": "Vega", "cell": GreeksCell}, - {"name": "theo_theta", "display": "Theta", "cell": GreeksCell}, - {"name": "theo_gamma", "display": "Gamma", "cell": GreeksCell}, - {"name": "theo_delta", "display": "Delta", "cell": GreeksCell}, + {"name": "cash_vega", "display": "Vega", "cell": GreeksCell}, + {"name": "cash_theta", "display": "Theta", "cell": GreeksCell}, + {"name": "cash_gamma", "display": "Gamma", "cell": GreeksCell}, + {"name": "cash_delta", "display": "Delta", "cell": GreeksCell}, {"name": "open_interest", "display": "持仓量", "cell": MonitorCell}, {"name": "volume", "display": "成交量", "cell": MonitorCell}, {"name": "bid_impv", "display": "买隐波", "cell": BidCell}, @@ -158,6 +158,9 @@ class OptionMarketMonitor(MonitorTable): self.option_symbols.add(option.vt_symbol) self.underlying_option_map[option.underlying.vt_symbol].append(option.vt_symbol) + # Get greeks decimals precision + self.greeks_precision = f"{portfolio.precision}f" + # Set table row and column numbers row_count = 0 for chain in portfolio.chains.values(): @@ -310,10 +313,10 @@ class OptionMarketMonitor(MonitorTable): option = self.option_engine.get_instrument(vt_symbol) - option_cells["theo_delta"].setText(f"{option.theo_delta:.0f}") - option_cells["theo_gamma"].setText(f"{option.theo_gamma:.0f}") - option_cells["theo_theta"].setText(f"{option.theo_theta:.0f}") - option_cells["theo_vega"].setText(f"{option.theo_vega:.0f}") + option_cells["cash_delta"].setText(f"{option.cash_delta:.{self.greeks_precision}}") + option_cells["cash_gamma"].setText(f"{option.cash_gamma:.{self.greeks_precision}}") + option_cells["cash_theta"].setText(f"{option.cash_theta:.{self.greeks_precision}}") + option_cells["cash_vega"].setText(f"{option.cash_vega:.{self.greeks_precision}}") class OptionGreeksMonitor(MonitorTable): @@ -362,6 +365,9 @@ class OptionGreeksMonitor(MonitorTable): self.option_symbols.add(option.vt_symbol) self.underlying_option_map[option.underlying.vt_symbol].append(option.vt_symbol) + # Get greeks decimals precision + self.greeks_precision = f"{portfolio.precision}f" + # Set table row and column numbers row_count = 1 for chain in portfolio.chains.values(): @@ -506,12 +512,12 @@ class OptionGreeksMonitor(MonitorTable): row_cells["long_pos"].setText(f"{row_data.long_pos}") row_cells["short_pos"].setText(f"{row_data.short_pos}") row_cells["net_pos"].setText(f"{row_data.net_pos}") - row_cells["pos_delta"].setText(f"{row_data.pos_delta:.0f}") + row_cells["pos_delta"].setText(f"{row_data.pos_delta:.{self.greeks_precision}}") if not isinstance(row_data, UnderlyingData): - row_cells["pos_gamma"].setText(f"{row_data.pos_gamma:.0f}") - row_cells["pos_theta"].setText(f"{row_data.pos_theta:.0f}") - row_cells["pos_vega"].setText(f"{row_data.pos_vega:.0f}") + row_cells["pos_gamma"].setText(f"{row_data.pos_gamma:.{self.greeks_precision}}") + row_cells["pos_theta"].setText(f"{row_data.pos_theta:.{self.greeks_precision}}") + row_cells["pos_vega"].setText(f"{row_data.pos_vega:.{self.greeks_precision}}") class OptionChainMonitor(MonitorTable): diff --git a/vnpy/app/option_master/ui/widget.py b/vnpy/app/option_master/ui/widget.py index c8ad9c6d..2ad56ef9 100644 --- a/vnpy/app/option_master/ui/widget.py +++ b/vnpy/app/option_master/ui/widget.py @@ -6,6 +6,7 @@ from vnpy.trader.ui import QtWidgets, QtCore, QtGui from vnpy.trader.constant import Direction, Offset, OrderType from vnpy.trader.object import OrderRequest, ContractData, TickData from vnpy.trader.event import EVENT_TICK +from vnpy.trader.utility import get_digits from ..base import APP_NAME, EVENT_OPTION_NEW_PORTFOLIO from ..engine import OptionEngine, PRICING_MODELS @@ -169,7 +170,7 @@ class OptionManager(QtWidgets.QWidget): def closeEvent(self, event: QtGui.QCloseEvent) -> None: """""" - if self.portfolio_name: + if self.market_monitor: self.market_monitor.close() self.greeks_monitor.close() self.volatility_chart.close() @@ -215,7 +216,7 @@ class PortfolioDialog(QtWidgets.QDialog): self.model_name_combo.findText(model_name) ) - form.addRow("模型", self.model_name_combo) + form.addRow("定价模型", self.model_name_combo) # Interest rate spin self.interest_rate_spin = QtWidgets.QDoubleSpinBox() @@ -227,7 +228,29 @@ class PortfolioDialog(QtWidgets.QDialog): interest_rate = portfolio_setting.get("interest_rate", 0.02) self.interest_rate_spin.setValue(interest_rate * 100) - form.addRow("利率", self.interest_rate_spin) + form.addRow("年化利率", self.interest_rate_spin) + + # Inverse combo + self.inverse_combo = QtWidgets.QComboBox() + self.inverse_combo.addItems(["正向", "反向"]) + + inverse = portfolio_setting.get("inverse", False) + if inverse: + self.inverse_combo.setCurrentIndex(1) + else: + self.inverse_combo.setCurrentIndex(0) + + form.addRow("合约模式", self.inverse_combo) + + # Greeks decimals precision + self.precision_spin = QtWidgets.QSpinBox() + self.precision_spin.setMinimum(0) + self.precision_spin.setMaximum(10) + + precision = portfolio_setting.get("precision", 0) + self.precision_spin.setValue(precision) + + form.addRow("Greeks小数位", self.precision_spin) # Underlying for each chain self.combos: Dict[str, QtWidgets.QComboBox] = {} @@ -266,6 +289,13 @@ class PortfolioDialog(QtWidgets.QDialog): model_name = self.model_name_combo.currentText() interest_rate = self.interest_rate_spin.value() / 100 + if self.inverse_combo.currentIndex() == 0: + inverse = False + else: + inverse = True + + precision = self.precision_spin.value() + chain_underlying_map = {} for chain_symbol, combo in self.combos.items(): underlying_symbol = combo.currentText() @@ -277,7 +307,9 @@ class PortfolioDialog(QtWidgets.QDialog): self.portfolio_name, model_name, interest_rate, - chain_underlying_map + chain_underlying_map, + inverse, + precision ) result = self.option_engine.init_portfolio(self.portfolio_name) @@ -301,10 +333,12 @@ class OptionManualTrader(QtWidgets.QWidget): self.event_engine: EventEngine = option_engine.event_engine self.contracts: Dict[str, ContractData] = {} - self.vt_symbol = "" + self.vt_symbol: str = "" + self.price_digits: int = 0 self.init_ui() self.init_contracts() + self.connect_signal() def init_ui(self) -> None: """""" @@ -489,12 +523,14 @@ class OptionManualTrader(QtWidgets.QWidget): vt_symbol = contract.vt_symbol self.vt_symbol = vt_symbol + self.price_digits = get_digits(contract.pricetick) tick = self.main_engine.get_tick(vt_symbol) if tick: self.update_tick(tick) - self.event_engine.unregister(EVENT_TICK + vt_symbol, self.process_tick_event) + print(EVENT_TICK + vt_symbol) + self.event_engine.register(EVENT_TICK + vt_symbol, self.process_tick_event) def create_label( self, @@ -513,18 +549,18 @@ class OptionManualTrader(QtWidgets.QWidget): def process_tick_event(self, event: Event) -> None: """""" tick = event.data - if tick.vt_symbol != self.vt_symbol: return - self.signal_tick.emit(tick) def update_tick(self, tick: TickData) -> None: """""" - self.lp_label.setText(str(tick.last_price)) - self.bp1_label.setText(str(tick.bid_price_1)) + price_digits = self.price_digits + + self.lp_label.setText(f"{tick.last_price:.{price_digits}f}") + self.bp1_label.setText(f"{tick.bid_price_1:.{price_digits}f}") self.bv1_label.setText(str(tick.bid_volume_1)) - self.ap1_label.setText(str(tick.ask_price_1)) + self.ap1_label.setText(f"{tick.ask_price_1:.{price_digits}f}") self.av1_label.setText(str(tick.ask_volume_1)) if tick.pre_close: @@ -532,24 +568,24 @@ class OptionManualTrader(QtWidgets.QWidget): self.return_label.setText(f"{r:.2f}%") if tick.bid_price_2: - self.bp2_label.setText(str(tick.bid_price_2)) + self.bp2_label.setText(f"{tick.bid_price_2:.{price_digits}f}") self.bv2_label.setText(str(tick.bid_volume_2)) - self.ap2_label.setText(str(tick.ask_price_2)) + self.ap2_label.setText(f"{tick.ask_price_2:.{price_digits}f}") self.av2_label.setText(str(tick.ask_volume_2)) - self.bp3_label.setText(str(tick.bid_price_3)) + self.bp3_label.setText(f"{tick.bid_price_3:.{price_digits}f}") self.bv3_label.setText(str(tick.bid_volume_3)) - self.ap3_label.setText(str(tick.ask_price_3)) + self.ap3_label.setText(f"{tick.ask_price_3:.{price_digits}f}") self.av3_label.setText(str(tick.ask_volume_3)) - self.bp4_label.setText(str(tick.bid_price_4)) + self.bp4_label.setText(f"{tick.bid_price_4:.{price_digits}f}") self.bv4_label.setText(str(tick.bid_volume_4)) - self.ap4_label.setText(str(tick.ask_price_4)) + self.ap4_label.setText(f"{tick.ask_price_4:.{price_digits}f}") self.av4_label.setText(str(tick.ask_volume_4)) - self.bp5_label.setText(str(tick.bid_price_5)) + self.bp5_label.setText(f"{tick.bid_price_5:.{price_digits}f}") self.bv5_label.setText(str(tick.bid_volume_5)) - self.ap5_label.setText(str(tick.ask_price_5)) + self.ap5_label.setText(f"{tick.ask_price_5:.{price_digits}f}") self.av5_label.setText(str(tick.ask_volume_5)) def clear_data(self) -> None: @@ -664,7 +700,7 @@ class OptionHedgeWidget(QtWidgets.QWidget): # Check delta of underlying underlying = self.option_engine.get_instrument(vt_symbol) - min_range = int(underlying.theo_delta * 0.6) + min_range = int(underlying.cash_delta * 0.6) if delta_range < min_range: msg = f"Delta对冲阈值({delta_range})低于对冲合约"\ f"Delta值的60%({min_range}),可能导致来回频繁对冲!" diff --git a/vnpy/gateway/ctp/ctp_gateway.py b/vnpy/gateway/ctp/ctp_gateway.py index 75c9ee5a..2caa0ea6 100644 --- a/vnpy/gateway/ctp/ctp_gateway.py +++ b/vnpy/gateway/ctp/ctp_gateway.py @@ -842,7 +842,7 @@ class CtpTdApi(TdApi): ) self.gateway.on_order(order) - self.gateway.write_error("交易委托失败", error) + #self.gateway.write_error("交易委托失败", error) def onRspOrderAction(self, data: dict, error: dict, reqid: int, last: bool): """""" @@ -1076,6 +1076,7 @@ class CtpTdApi(TdApi): traded=data["VolumeTraded"], status=STATUS_CTP2VT[data["OrderStatus"]], time=data["InsertTime"], + cancel_time=data["CancelTime"], gateway_name=self.gateway_name ) self.gateway.on_order(order) diff --git a/vnpy/gateway/rohon/rohon_gateway.py b/vnpy/gateway/rohon/rohon_gateway.py index b0952edc..864930d8 100644 --- a/vnpy/gateway/rohon/rohon_gateway.py +++ b/vnpy/gateway/rohon/rohon_gateway.py @@ -1042,8 +1042,8 @@ class RohonTdApi(TdApi): """ Callback of order status update. """ - #if self.gateway.debug: - # print(f'onRtnOrder') + if self.gateway.debug: + print(f'onRtnOrder') symbol = data["InstrumentID"] exchange = symbol_exchange_map.get(symbol, "") diff --git a/vnpy/gateway/sopt/sopt_gateway.py b/vnpy/gateway/sopt/sopt_gateway.py index aa21b500..8bcd3593 100644 --- a/vnpy/gateway/sopt/sopt_gateway.py +++ b/vnpy/gateway/sopt/sopt_gateway.py @@ -1,9 +1,10 @@ """ """ +import pytz from datetime import datetime from time import sleep - +from copy import copy from vnpy.api.sopt import ( MdApi, TdApi, @@ -59,7 +60,15 @@ from vnpy.trader.object import ( CancelRequest, SubscribeRequest, ) -from vnpy.trader.utility import get_folder_path +from vnpy.trader.utility import ( + extract_vt_symbol, + get_folder_path, + get_trading_date, + get_underlying_symbol, + round_to, + BarGenerator, + print_dict +) from vnpy.trader.event import EVENT_TIMER @@ -111,6 +120,7 @@ OPTIONTYPE_SOPT2VT = { THOST_FTDC_CP_PutOptions: OptionType.PUT } +CHINA_TZ = pytz.timezone("Asia/Shanghai") symbol_exchange_map = {} symbol_name_map = {} @@ -135,13 +145,19 @@ class SoptGateway(BaseGateway): exchanges = list(EXCHANGE_SOPT2VT.values()) - def __init__(self, event_engine): + def __init__(self, event_engine, gateway_name="SOPT"): """Constructor""" - super().__init__(event_engine, "SOPT") + super().__init__(event_engine, gateway_name) self.td_api = SoptTdApi(self) self.md_api = SoptMdApi(self) + self.subscribed_symbols = set() # 已订阅合约代码 + + # 自定义价差/加比的tick合成器 + self.combiners = {} + self.tick_combiner_map = {} + def connect(self, setting: dict): """""" userid = setting["用户名"] @@ -161,10 +177,88 @@ class SoptGateway(BaseGateway): self.td_api.connect(td_address, userid, password, brokerid, auth_code, appid, product_info) self.md_api.connect(md_address, userid, password, brokerid) + # 获取自定义价差/价比合约的配置 + try: + from vnpy.trader.engine import CustomContract + c = CustomContract() + self.combiner_conf_dict = c.get_config() + if len(self.combiner_conf_dict) > 0: + self.write_log(u'加载的自定义价差/价比配置:{}'.format(self.combiner_conf_dict)) + + contract_dict = c.get_contracts() + for vt_symbol, contract in contract_dict.items(): + contract.gateway_name = self.gateway_name + symbol_exchange_map[contract.symbol] = contract.exchange + self.on_contract(contract) + + except Exception as ex: # noqa + pass + self.init_query() + # 从新发出委托 + for (vt_symbol, is_bar) in list(self.subscribed_symbols): + symbol, exchange = extract_vt_symbol(vt_symbol) + req = SubscribeRequest( + symbol=symbol, + exchange=exchange, + is_bar=is_bar + ) + self.subscribe(req) + def subscribe(self, req: SubscribeRequest): """""" + # 如果是自定义的套利合约符号 + if req.symbol in self.combiner_conf_dict: + self.write_log(u'订阅自定义套利合约:{}'.format(req.symbol)) + # 创建合成器 + if req.symbol not in self.combiners: + setting = self.combiner_conf_dict.get(req.symbol) + setting.update({"symbol": req.symbol}) + combiner = TickCombiner(self, setting) + # 更新合成器 + self.write_log(u'添加{}与合成器映射'.format(req.symbol)) + self.combiners.update({setting.get('symbol'): combiner}) + + # 增加映射( leg1 对应的合成器列表映射) + leg1_symbol = setting.get('leg1_symbol') + leg1_exchange = Exchange(setting.get('leg1_exchange')) + combiner_list = self.tick_combiner_map.get(leg1_symbol, []) + if combiner not in combiner_list: + self.write_log(u'添加Leg1:{}与合成器得映射'.format(leg1_symbol)) + combiner_list.append(combiner) + self.tick_combiner_map.update({leg1_symbol: combiner_list}) + + # 增加映射( leg2 对应的合成器列表映射) + leg2_symbol = setting.get('leg2_symbol') + leg2_exchange = Exchange(setting.get('leg2_exchange')) + combiner_list = self.tick_combiner_map.get(leg2_symbol, []) + if combiner not in combiner_list: + self.write_log(u'添加Leg2:{}与合成器得映射'.format(leg2_symbol)) + combiner_list.append(combiner) + self.tick_combiner_map.update({leg2_symbol: combiner_list}) + + self.write_log(u'订阅leg1:{}'.format(leg1_symbol)) + leg1_req = SubscribeRequest( + symbol=leg1_symbol, + exchange=leg1_exchange + ) + self.subscribe(leg1_req) + + self.write_log(u'订阅leg2:{}'.format(leg2_symbol)) + leg2_req = SubscribeRequest( + symbol=leg2_symbol, + exchange=leg2_exchange + ) + self.subscribe(leg2_req) + + self.subscribed_symbols.add((req.vt_symbol, req.is_bar)) + else: + self.write_log(u'{}合成器已经在存在'.format(req.symbol)) + return + elif req.exchange == Exchange.SPD: + self.write_error(u'自定义合约{}不在CTP设置中'.format(req.symbol)) + self.md_api.subscribe(req) def send_order(self, req: OrderRequest): @@ -213,6 +307,15 @@ class SoptGateway(BaseGateway): self.event_engine.register(EVENT_TIMER, self.process_timer_event) + def on_custom_tick(self, tick): + """推送自定义合约行情""" + # 自定义合约行情 + + for combiner in self.tick_combiner_map.get(tick.symbol, []): + tick = copy(tick) + combiner.on_tick(tick) + + class SoptMdApi(MdApi): """""" @@ -239,6 +342,7 @@ class SoptMdApi(MdApi): """ self.gateway.write_log("行情服务器连接成功") self.login() + self.gateway.status.update({'md_con': True, 'md_con_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}) def onFrontDisconnected(self, reason: int): """ @@ -246,6 +350,7 @@ class SoptMdApi(MdApi): """ self.login_status = False self.gateway.write_log(f"行情服务器连接断开,原因{reason}") + self.gateway.status.update({'md_con': False, 'md_dis_con_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}) def onRspUserLogin(self, data: dict, error: dict, reqid: int, last: bool): """ @@ -282,11 +387,13 @@ class SoptMdApi(MdApi): if not exchange: return timestamp = f"{data['TradingDay']} {data['UpdateTime']}.{int(data['UpdateMillisec']/100)}" + dt = datetime.strptime(timestamp, "%Y%m%d %H:%M:%S.%f") + dt = CHINA_TZ.localize(dt) tick = TickData( symbol=symbol, exchange=exchange, - datetime=datetime.strptime(timestamp, "%Y%m%d %H:%M:%S.%f"), + datetime=dt, name=symbol_name_map[symbol], volume=data["Volume"], open_interest=data["OpenInterest"], @@ -325,6 +432,7 @@ class SoptMdApi(MdApi): tick.ask_volume_5 = data["AskVolume5"] self.gateway.on_tick(tick) + self.gateway.on_custom_tick(tick) def connect(self, address: str, userid: str, password: str, brokerid: int): """ @@ -370,6 +478,7 @@ class SoptMdApi(MdApi): Subscribe to tick data update. """ if self.login_status: + self.gateway.write_log(f'订阅:{req.exchange} {req.symbol}') self.subscribeMarketData(req.symbol) self.subscribed.add(req.symbol) @@ -422,11 +531,13 @@ class SoptTdApi(TdApi): self.authenticate() else: self.login() + self.gateway.status.update({'td_con': True, 'td_con_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}) def onFrontDisconnected(self, reason: int): """""" self.login_status = False self.gateway.write_log(f"交易服务器连接断开,原因{reason}") + self.gateway.status.update({'td_con': True, 'td_dis_con_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}) def onRspAuthenticate(self, data: dict, error: dict, reqid: int, last: bool): """""" @@ -464,14 +575,19 @@ class SoptTdApi(TdApi): symbol = data["InstrumentID"] exchange = symbol_exchange_map[symbol] - + order_type = OrderType.LIMIT + if data["OrderPriceType"] == THOST_FTDC_OPT_AnyPrice: + order_type = OrderType.MARKET order = OrderData( + accountid=self.userid, symbol=symbol, exchange=exchange, orderid=orderid, + sys_orderid=orderid, direction=DIRECTION_SOPT2VT[data["Direction"]], offset=OFFSET_SOPT2VT[data["CombOffsetFlag"]], price=data["LimitPrice"], + type=order_type, volume=data["VolumeTotalOriginal"], status=Status.REJECTED, gateway_name=self.gateway_name @@ -558,6 +674,20 @@ class SoptTdApi(TdApi): gateway_name=self.gateway_name ) account.available = data["Available"] + account.commission = round(float(data['Commission']), 7) + account.margin = round(float(data['CurrMargin']), 7) + account.close_profit = round(float(data['CloseProfit']), 7) + account.holding_profit = round(float(data['PositionProfit']), 7) + + account.trading_day = str(data.get('TradingDay',datetime.now().strftime('%Y-%m-%d'))) + if '-' not in account.trading_day and len(account.trading_day) == 8: + account.trading_day = '-'.join( + [ + account.trading_day[0:4], + account.trading_day[4:6], + account.trading_day[6:8] + ] + ) self.gateway.on_account(account) @@ -589,7 +719,7 @@ class SoptTdApi(TdApi): ) contract.option_type = OPTIONTYPE_SOPT2VT.get(data["OptionsType"], None) contract.option_strike = data["StrikePrice"] - contract.option_index = str(data["StrikePrice"]) + #contract.option_index = str(data["StrikePrice"]) contract.option_expiry = datetime.strptime(data["ExpireDate"], "%Y%m%d") contract.option_index = get_option_index( contract.option_strike, data["InstrumentCode"] @@ -627,10 +757,16 @@ class SoptTdApi(TdApi): order_ref = data["OrderRef"] orderid = f"{frontid}_{sessionid}_{order_ref}" + timestamp = f"{data['InsertDate']} {data['InsertTime']}" + dt = datetime.strptime(timestamp, "%Y%m%d %H:%M:%S") + dt = CHINA_TZ.localize(dt) + order = OrderData( + accountid=self.userid, symbol=symbol, exchange=exchange, orderid=orderid, + sys_orderid=orderid, type=ORDERTYPE_SOPT2VT[data["OrderPriceType"]], direction=DIRECTION_SOPT2VT[data["Direction"]], offset=OFFSET_SOPT2VT[data["CombOffsetFlag"]], @@ -638,7 +774,8 @@ class SoptTdApi(TdApi): volume=data["VolumeTotalOriginal"], traded=data["VolumeTraded"], status=STATUS_SOPT2VT[data["OrderStatus"]], - time=data["InsertTime"], + datetime=dt, + cancel_time=data["CancelTime"], gateway_name=self.gateway_name ) self.gateway.on_order(order) @@ -657,16 +794,22 @@ class SoptTdApi(TdApi): orderid = self.sysid_orderid_map[data["OrderSysID"]] + timestamp = f"{data['TradeDate']} {data['TradeTime']}" + dt = datetime.strptime(timestamp, "%Y%m%d %H:%M:%S") + dt = CHINA_TZ.localize(dt) + trade = TradeData( + accountid=self.userid, symbol=symbol, exchange=exchange, orderid=orderid, + sys_orderid=orderid, tradeid=data["TradeID"], direction=DIRECTION_SOPT2VT[data["Direction"]], offset=OFFSET_SOPT2VT[data["OffsetFlag"]], price=data["Price"], volume=data["Volume"], - time=data["TradeTime"], + datetime=dt, gateway_name=self.gateway_name ) self.gateway.on_trade(trade) @@ -783,6 +926,8 @@ class SoptTdApi(TdApi): orderid = f"{self.frontid}_{self.sessionid}_{self.order_ref}" order = req.create_order_data(orderid, self.gateway_name) + order.accountid = self.userid + order.vt_accountid = f"{self.gateway_name}.{self.userid}" self.gateway.on_order(order) return order.vt_orderid @@ -852,3 +997,227 @@ def get_option_index(strike_price: float, exchange_instrument_id: str) -> str: option_index = f"{strike_price:.3f}-{index}" return option_index + + +class TickCombiner(object): + """ + Tick合成类 + """ + + def __init__(self, gateway, setting): + self.gateway = gateway + self.gateway_name = self.gateway.gateway_name + self.gateway.write_log(u'创建tick合成类:{}'.format(setting)) + + self.symbol = setting.get('symbol', None) + self.leg1_symbol = setting.get('leg1_symbol', None) + self.leg2_symbol = setting.get('leg2_symbol', None) + self.leg1_ratio = setting.get('leg1_ratio', 1) # 腿1的数量配比 + self.leg2_ratio = setting.get('leg2_ratio', 1) # 腿2的数量配比 + self.price_tick = setting.get('price_tick', 1) # 合成价差加比后的最小跳动 + # 价差 + self.is_spread = setting.get('is_spread', False) + # 价比 + self.is_ratio = setting.get('is_ratio', False) + + self.last_leg1_tick = None + self.last_leg2_tick = None + + # 价差日内最高/最低价 + self.spread_high = None + self.spread_low = None + + # 价比日内最高/最低价 + self.ratio_high = None + self.ratio_low = None + + # 当前交易日 + self.trading_day = None + + if self.is_ratio and self.is_spread: + self.gateway.write_error(u'{}参数有误,不能同时做价差/加比.setting:{}'.format(self.symbol, setting)) + return + + self.gateway.write_log(u'初始化{}合成器成功'.format(self.symbol)) + if self.is_spread: + self.gateway.write_log( + u'leg1:{} * {} - leg2:{} * {}'.format(self.leg1_symbol, self.leg1_ratio, self.leg2_symbol, + self.leg2_ratio)) + if self.is_ratio: + self.gateway.write_log( + u'leg1:{} * {} / leg2:{} * {}'.format(self.leg1_symbol, self.leg1_ratio, self.leg2_symbol, + self.leg2_ratio)) + + def on_tick(self, tick): + """OnTick处理""" + combinable = False + + if tick.symbol == self.leg1_symbol: + # leg1合约 + self.last_leg1_tick = tick + if self.last_leg2_tick is not None: + if self.last_leg1_tick.datetime.replace(microsecond=0) == self.last_leg2_tick.datetime.replace( + microsecond=0): + combinable = True + + elif tick.symbol == self.leg2_symbol: + # leg2合约 + self.last_leg2_tick = tick + if self.last_leg1_tick is not None: + if self.last_leg2_tick.datetime.replace(microsecond=0) == self.last_leg1_tick.datetime.replace( + microsecond=0): + combinable = True + + # 不能合并 + if not combinable: + return + + if not self.is_ratio and not self.is_spread: + return + + # 以下情况,基本为单腿涨跌停,不合成价差/价格比 Tick + if (self.last_leg1_tick.ask_price_1 == 0 or self.last_leg1_tick.bid_price_1 == self.last_leg1_tick.limit_up) \ + and self.last_leg1_tick.ask_volume_1 == 0: + self.gateway.write_log( + u'leg1:{0}涨停{1},不合成价差Tick'.format(self.last_leg1_tick.vt_symbol, self.last_leg1_tick.bid_price_1)) + return + if (self.last_leg1_tick.bid_price_1 == 0 or self.last_leg1_tick.ask_price_1 == self.last_leg1_tick.limit_down) \ + and self.last_leg1_tick.bid_volume_1 == 0: + self.gateway.write_log( + u'leg1:{0}跌停{1},不合成价差Tick'.format(self.last_leg1_tick.vt_symbol, self.last_leg1_tick.ask_price_1)) + return + if (self.last_leg2_tick.ask_price_1 == 0 or self.last_leg2_tick.bid_price_1 == self.last_leg2_tick.limit_up) \ + and self.last_leg2_tick.ask_volume_1 == 0: + self.gateway.write_log( + u'leg2:{0}涨停{1},不合成价差Tick'.format(self.last_leg2_tick.vt_symbol, self.last_leg2_tick.bid_price_1)) + return + if (self.last_leg2_tick.bid_price_1 == 0 or self.last_leg2_tick.ask_price_1 == self.last_leg2_tick.limit_down) \ + and self.last_leg2_tick.bid_volume_1 == 0: + self.gateway.write_log( + u'leg2:{0}跌停{1},不合成价差Tick'.format(self.last_leg2_tick.vt_symbol, self.last_leg2_tick.ask_price_1)) + return + + if self.trading_day != tick.trading_day: + self.trading_day = tick.trading_day + self.spread_high = None + self.spread_low = None + self.ratio_high = None + self.ratio_low = None + + if self.is_spread: + spread_tick = TickData(gateway_name=self.gateway_name, + symbol=self.symbol, + exchange=Exchange.SPD, + datetime=tick.datetime) + + spread_tick.trading_day = tick.trading_day + spread_tick.date = tick.date + spread_tick.time = tick.time + + # 叫卖价差=leg1.ask_price_1 * 配比 - leg2.bid_price_1 * 配比,volume为两者最小 + spread_tick.ask_price_1 = round_to(target=self.price_tick, + value=self.last_leg1_tick.ask_price_1 * self.leg1_ratio - self.last_leg2_tick.bid_price_1 * self.leg2_ratio) + spread_tick.ask_volume_1 = min(self.last_leg1_tick.ask_volume_1, self.last_leg2_tick.bid_volume_1) + + # 叫买价差=leg1.bid_price_1 * 配比 - leg2.ask_price_1 * 配比,volume为两者最小 + spread_tick.bid_price_1 = round_to(target=self.price_tick, + value=self.last_leg1_tick.bid_price_1 * self.leg1_ratio - self.last_leg2_tick.ask_price_1 * self.leg2_ratio) + spread_tick.bid_volume_1 = min(self.last_leg1_tick.bid_volume_1, self.last_leg2_tick.ask_volume_1) + + # 最新价 + spread_tick.last_price = round_to(target=self.price_tick, + value=(spread_tick.ask_price_1 + spread_tick.bid_price_1) / 2) + # 昨收盘价 + if self.last_leg2_tick.pre_close > 0 and self.last_leg1_tick.pre_close > 0: + spread_tick.pre_close = round_to(target=self.price_tick, + value=self.last_leg1_tick.pre_close * self.leg1_ratio - self.last_leg2_tick.pre_close * self.leg2_ratio) + # 开盘价 + if self.last_leg2_tick.open_price > 0 and self.last_leg1_tick.open_price > 0: + spread_tick.open_price = round_to(target=self.price_tick, + value=self.last_leg1_tick.open_price * self.leg1_ratio - self.last_leg2_tick.open_price * self.leg2_ratio) + # 最高价 + if self.spread_high: + self.spread_high = max(self.spread_high, spread_tick.ask_price_1) + else: + self.spread_high = spread_tick.ask_price_1 + spread_tick.high_price = self.spread_high + + # 最低价 + if self.spread_low: + self.spread_low = min(self.spread_low, spread_tick.bid_price_1) + else: + self.spread_low = spread_tick.bid_price_1 + + spread_tick.low_price = self.spread_low + + self.gateway.on_tick(spread_tick) + + if self.is_ratio: + ratio_tick = TickData( + gateway_name=self.gateway_name, + symbol=self.symbol, + exchange=Exchange.SPD, + datetime=tick.datetime + ) + + ratio_tick.trading_day = tick.trading_day + ratio_tick.date = tick.date + ratio_tick.time = tick.time + + # 比率tick = (腿1 * 腿1 手数 / 腿2价格 * 腿2手数) 百分比 + ratio_tick.ask_price_1 = 100 * self.last_leg1_tick.ask_price_1 * self.leg1_ratio \ + / (self.last_leg2_tick.bid_price_1 * self.leg2_ratio) # noqa + ratio_tick.ask_price_1 = round_to( + target=self.price_tick, + value=ratio_tick.ask_price_1 + ) + + ratio_tick.ask_volume_1 = min(self.last_leg1_tick.ask_volume_1, self.last_leg2_tick.bid_volume_1) + ratio_tick.bid_price_1 = 100 * self.last_leg1_tick.bid_price_1 * self.leg1_ratio \ + / (self.last_leg2_tick.ask_price_1 * self.leg2_ratio) # noqa + ratio_tick.bid_price_1 = round_to( + target=self.price_tick, + value=ratio_tick.bid_price_1 + ) + + ratio_tick.bid_volume_1 = min(self.last_leg1_tick.bid_volume_1, self.last_leg2_tick.ask_volume_1) + ratio_tick.last_price = (ratio_tick.ask_price_1 + ratio_tick.bid_price_1) / 2 + ratio_tick.last_price = round_to( + target=self.price_tick, + value=ratio_tick.last_price + ) + + # 昨收盘价 + if self.last_leg2_tick.pre_close > 0 and self.last_leg1_tick.pre_close > 0: + ratio_tick.pre_close = 100 * self.last_leg1_tick.pre_close * self.leg1_ratio / ( + self.last_leg2_tick.pre_close * self.leg2_ratio) # noqa + ratio_tick.pre_close = round_to( + target=self.price_tick, + value=ratio_tick.pre_close + ) + + # 开盘价 + if self.last_leg2_tick.open_price > 0 and self.last_leg1_tick.open_price > 0: + ratio_tick.open_price = 100 * self.last_leg1_tick.open_price * self.leg1_ratio / ( + self.last_leg2_tick.open_price * self.leg2_ratio) # noqa + ratio_tick.open_price = round_to( + target=self.price_tick, + value=ratio_tick.open_price + ) + + # 最高价 + if self.ratio_high: + self.ratio_high = max(self.ratio_high, ratio_tick.ask_price_1) + else: + self.ratio_high = ratio_tick.ask_price_1 + ratio_tick.high_price = self.spread_high + + # 最低价 + if self.ratio_low: + self.ratio_low = min(self.ratio_low, ratio_tick.bid_price_1) + else: + self.ratio_low = ratio_tick.bid_price_1 + + ratio_tick.low_price = self.spread_low + + self.gateway.on_tick(ratio_tick) diff --git a/vnpy/trader/utility.py b/vnpy/trader/utility.py index fef74bca..f40ad200 100644 --- a/vnpy/trader/utility.py +++ b/vnpy/trader/utility.py @@ -316,6 +316,21 @@ def ceil_to(value: float, target: float) -> float: return result +def get_digits(value: float) -> int: + """ + Get number of digits after decimal point. + """ + value_str = str(value) + + if "e-" in value_str: + _, buf = value_str.split("e-") + return int(buf) + elif "." in value_str: + _, buf = value_str.split(".") + return len(buf) + else: + return 0 + def print_dict(d: dict): """返回dict的字符串类型""" return '\n'.join([f'{key}:{d[key]}' for key in sorted(d.keys())]) @@ -705,6 +720,10 @@ class BarGenerator: if not tick.last_price: return + # Filter tick data with older timestamp + if self.last_tick and tick.datetime < self.last_tick.datetime: + return + if not self.bar: new_minute = True elif self.bar.datetime.minute != tick.datetime.minute: