From e48904df68b4bdda0316402af3bf37a12ba79f29 Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Thu, 18 Jul 2024 14:52:49 +0800 Subject: [PATCH 01/17] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dorder=5Ftarget=5Fportfo?= =?UTF-8?q?lio=E7=94=B1=E4=BA=8E=E4=BA=A4=E6=98=93=E8=B4=B9=E7=94=A8?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E8=B5=84=E9=87=91=E4=B8=8D=E5=A4=9F=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rqalpha/environment.py | 4 ++++ rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py | 11 ++++++++++- setup.cfg | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/rqalpha/environment.py b/rqalpha/environment.py index a2e8b8758..a27302ffb 100644 --- a/rqalpha/environment.py +++ b/rqalpha/environment.py @@ -183,6 +183,10 @@ def _get_transaction_cost_decider(self, order_book_id): def get_trade_tax(self, trade): return self._get_transaction_cost_decider(trade.order_book_id).get_trade_tax(trade) + + def get_stock_commission_and_tax(self) -> tuple[float, float]: + decider = self._transaction_cost_decider_dict[INSTRUMENT_TYPE.CS] + return decider.commission_rate, decider.tax_rate def get_trade_commission(self, trade): return self._get_transaction_cost_decider(trade.order_book_id).get_trade_commission(trade) diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py index 6c8fe0057..9c16ddbad 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -359,6 +359,15 @@ def order_target_portfolio( total_percent = sum(p for p, *__ in target.values()) if total_percent > 1 and not np.isclose(total_percent, 1): raise RQInvalidArgument(_("total percent should be lower than 1, current: {}").format(total_percent)) + + cash_buffer = 1 + if total_percent == 1: + # 在此处形成的订单不包含交易费用,需要预留一点余额以供交易费用使用 + # 1 - (股票佣金 * 佣金倍率 + 印花税 * 印花税倍率) + commission_rate, tax_rate = env.get_stock_commission_and_tax() + commission_multiplier = env.config.mod.sys_transaction_cost.stock_commission_multiplier + tax_multiplier = env.config.mod.sys_transaction_cost.tax_multiplier + cash_buffer = 1 - (commission_rate * commission_multiplier + tax_rate * tax_multiplier) account = env.portfolio.accounts[DEFAULT_ACCOUNT_TYPE.STOCK] @@ -372,7 +381,7 @@ def order_target_portfolio( order_book_id, quantity, SIDE.SELL, MarketOrder(), POSITION_EFFECT.CLOSE )) - account_value = account.total_value + account_value = account.total_value * cash_buffer close_orders, open_orders = [], [] for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): open_price = _get_order_style_price(order_book_id, open_style) diff --git a/setup.cfg b/setup.cfg index 7421f2443..817f0164b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ [metadata] name = rqalpha -version = 5.4.1 +version = 5.4.2 [versioneer] VCS = git From 7a3900db728fd19cb6eaa71fbc5315e78973be75 Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Thu, 18 Jul 2024 14:57:44 +0800 Subject: [PATCH 02/17] update --- rqalpha/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rqalpha/environment.py b/rqalpha/environment.py index a27302ffb..2d3c43563 100644 --- a/rqalpha/environment.py +++ b/rqalpha/environment.py @@ -184,7 +184,7 @@ def _get_transaction_cost_decider(self, order_book_id): def get_trade_tax(self, trade): return self._get_transaction_cost_decider(trade.order_book_id).get_trade_tax(trade) - def get_stock_commission_and_tax(self) -> tuple[float, float]: + def get_stock_commission_and_tax(self): decider = self._transaction_cost_decider_dict[INSTRUMENT_TYPE.CS] return decider.commission_rate, decider.tax_rate From ac258033eff95bdc91974754705bc5253c08986e Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Thu, 18 Jul 2024 15:57:54 +0800 Subject: [PATCH 03/17] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/outs/test_f_mean_reverting.pkl | Bin 111284 -> 110951 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/outs/test_f_mean_reverting.pkl b/tests/outs/test_f_mean_reverting.pkl index 5c6e356b5f01ad240e0c04f6b1189533d64c018d..852f2a6af71a39b8f585a338f142b6c9f907d05b 100644 GIT binary patch delta 15544 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 From bec769e36705122156978498a221f69d71a3dfeb Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Fri, 19 Jul 2024 10:24:58 +0800 Subject: [PATCH 04/17] pr update --- .../rqalpha_mod_sys_accounts/api/api_stock.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py index 9c16ddbad..3021430e7 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -359,15 +359,6 @@ def order_target_portfolio( total_percent = sum(p for p, *__ in target.values()) if total_percent > 1 and not np.isclose(total_percent, 1): raise RQInvalidArgument(_("total percent should be lower than 1, current: {}").format(total_percent)) - - cash_buffer = 1 - if total_percent == 1: - # 在此处形成的订单不包含交易费用,需要预留一点余额以供交易费用使用 - # 1 - (股票佣金 * 佣金倍率 + 印花税 * 印花税倍率) - commission_rate, tax_rate = env.get_stock_commission_and_tax() - commission_multiplier = env.config.mod.sys_transaction_cost.stock_commission_multiplier - tax_multiplier = env.config.mod.sys_transaction_cost.tax_multiplier - cash_buffer = 1 - (commission_rate * commission_multiplier + tax_rate * tax_multiplier) account = env.portfolio.accounts[DEFAULT_ACCOUNT_TYPE.STOCK] @@ -381,7 +372,20 @@ def order_target_portfolio( order_book_id, quantity, SIDE.SELL, MarketOrder(), POSITION_EFFECT.CLOSE )) - account_value = account.total_value * cash_buffer + account_value = account.total_value + if total_percent == 1: + # 在此处形成的订单不包含交易费用,需要预留一点余额以供交易费用使用 + commission_rate, tax_rate = env.get_stock_commission_and_tax() + commission_rate = env.config.mod.sys_transaction_cost.stock_commission_multiplier * commission_rate + tax_rate = env.config.mod.sys_transaction_cost.tax_multiplier * tax_rate + tax, commission = 0, 0 + for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): + current_value = current_quantities.get(order_book_id, 0) * last_price + change_value = target_percent * account_value - current_value + tax += abs(change_value) * tax_rate if change_value < 0 else 0 + commission += max(change_value * commission_rate, env.config.mod.sys_transaction_cost.cn_stock_min_commission) + account_value = account_value - tax - commission + close_orders, open_orders = [], [] for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): open_price = _get_order_style_price(order_book_id, open_style) From c4003ad2f7cc7c43f62f887cfa34eadc3dbd6004 Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Fri, 19 Jul 2024 11:57:23 +0800 Subject: [PATCH 05/17] pr update --- rqalpha/environment.py | 2 +- rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/rqalpha/environment.py b/rqalpha/environment.py index 2d3c43563..16035318d 100644 --- a/rqalpha/environment.py +++ b/rqalpha/environment.py @@ -186,7 +186,7 @@ def get_trade_tax(self, trade): def get_stock_commission_and_tax(self): decider = self._transaction_cost_decider_dict[INSTRUMENT_TYPE.CS] - return decider.commission_rate, decider.tax_rate + return decider.commission_rate * decider.commission_multiplier, decider.tax_rate * decider.tax_multiplier def get_trade_commission(self, trade): return self._get_transaction_cost_decider(trade.order_book_id).get_trade_commission(trade) diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py index 3021430e7..870462868 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -376,8 +376,6 @@ def order_target_portfolio( if total_percent == 1: # 在此处形成的订单不包含交易费用,需要预留一点余额以供交易费用使用 commission_rate, tax_rate = env.get_stock_commission_and_tax() - commission_rate = env.config.mod.sys_transaction_cost.stock_commission_multiplier * commission_rate - tax_rate = env.config.mod.sys_transaction_cost.tax_multiplier * tax_rate tax, commission = 0, 0 for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): current_value = current_quantities.get(order_book_id, 0) * last_price From e56445e89e881a5ef8f8428bb31cb2349f2f17a5 Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Fri, 19 Jul 2024 15:30:19 +0800 Subject: [PATCH 06/17] pr update --- rqalpha/environment.py | 8 +++++--- rqalpha/interface.py | 17 ++++++++++------- .../rqalpha_mod_sys_accounts/api/api_stock.py | 8 +++----- .../deciders.py | 8 ++++++++ 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/rqalpha/environment.py b/rqalpha/environment.py index 16035318d..09c0e2ed6 100644 --- a/rqalpha/environment.py +++ b/rqalpha/environment.py @@ -26,8 +26,10 @@ from rqalpha.utils.logger import system_log, user_log, user_system_log from rqalpha.core.global_var import GlobalVars from rqalpha.utils.i18n import gettext as _ +from rqalpha.const import SIDE if TYPE_CHECKING: from rqalpha.model.order import Order + class Environment(object): @@ -184,9 +186,9 @@ def _get_transaction_cost_decider(self, order_book_id): def get_trade_tax(self, trade): return self._get_transaction_cost_decider(trade.order_book_id).get_trade_tax(trade) - def get_stock_commission_and_tax(self): - decider = self._transaction_cost_decider_dict[INSTRUMENT_TYPE.CS] - return decider.commission_rate * decider.commission_multiplier, decider.tax_rate * decider.tax_multiplier + def get_transaction_cost_with_value(self, value: float) -> float: + side = SIDE.BUY if value >= 0 else SIDE.SELL + return self._transaction_cost_decider_dict[INSTRUMENT_TYPE.CS].get_transaction_cost_with_value(abs(value), side) def get_trade_commission(self, trade): return self._get_transaction_cost_decider(trade.order_book_id).get_trade_commission(trade) diff --git a/rqalpha/interface.py b/rqalpha/interface.py index 6017a3f8f..7ade86245 100644 --- a/rqalpha/interface.py +++ b/rqalpha/interface.py @@ -30,7 +30,7 @@ from rqalpha.model.order import Order from rqalpha.model.trade import Trade from rqalpha.model.instrument import Instrument -from rqalpha.const import POSITION_DIRECTION, TRADING_CALENDAR_TYPE, INSTRUMENT_TYPE +from rqalpha.const import POSITION_DIRECTION, TRADING_CALENDAR_TYPE, INSTRUMENT_TYPE, SIDE class AbstractPosition(with_metaclass(abc.ABCMeta)): @@ -675,25 +675,28 @@ class AbstractTransactionCostDecider((with_metaclass(abc.ABCMeta))): 订单税费计算接口,通过实现次接口可以定义不同市场、不同合约的个性化税费计算逻辑。 """ @abc.abstractmethod - def get_trade_tax(self, trade): - # type: (Trade) -> float + def get_trade_tax(self, trade: Trade) -> float: """ 计算指定交易应付的印花税 """ raise NotImplementedError @abc.abstractmethod - def get_trade_commission(self, trade): - # type: (Trade) -> float + def get_trade_commission(self, trade: Trade) -> float: """ 计算指定交易应付的佣金 """ raise NotImplementedError @abc.abstractmethod - def get_order_transaction_cost(self, order): - # type: (Order) -> float + def get_order_transaction_cost(self, order: Order) -> float: """ 计算指定订单应付的交易成本(税 + 费) """ raise NotImplementedError + + def get_transaction_cost_with_value(self, value: float, side: SIDE) -> float: + """ + 计算指定价格交易应付的交易成本(税 + 费) + """ + raise NotImplementedError diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py index 870462868..3384b1b27 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -375,14 +375,12 @@ def order_target_portfolio( account_value = account.total_value if total_percent == 1: # 在此处形成的订单不包含交易费用,需要预留一点余额以供交易费用使用 - commission_rate, tax_rate = env.get_stock_commission_and_tax() - tax, commission = 0, 0 + estimate_transaction_cost = 0 for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): current_value = current_quantities.get(order_book_id, 0) * last_price change_value = target_percent * account_value - current_value - tax += abs(change_value) * tax_rate if change_value < 0 else 0 - commission += max(change_value * commission_rate, env.config.mod.sys_transaction_cost.cn_stock_min_commission) - account_value = account_value - tax - commission + estimate_transaction_cost += env.get_transaction_cost_with_value(change_value) + account_value = account_value - estimate_transaction_cost close_orders, open_orders = [], [] for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): diff --git a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py index cedf75a9f..e39fd1e1d 100644 --- a/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py +++ b/rqalpha/mod/rqalpha_mod_sys_transaction_cost/deciders.py @@ -77,6 +77,9 @@ def get_order_transaction_cost(self, order): commission = self._get_order_commission(order.order_book_id, order.side, order.frozen_price, order.quantity) tax = self._get_tax(order.order_book_id, order.side, order.frozen_price * order.quantity) return tax + commission + + def get_transaction_cost_with_value(self, value: float, side: SIDE) -> float: + raise NotImplementedError class CNStockTransactionCostDecider(StockTransactionCostDecider): @@ -99,6 +102,11 @@ def set_tax_rate(self, event): else: self.tax_rate = 0.0005 + def get_transaction_cost_with_value(self, value: float, side: SIDE) -> float: + tax = value * self.tax_rate * self.tax_multiplier if side == SIDE.SELL else 0 + commission = max(value * self.commission_rate * self.commission_multiplier, self.min_commission) + return tax + commission + class CNFutureTransactionCostDecider(AbstractTransactionCostDecider): def __init__(self, commission_multiplier): From cae025a0df67bd9f7728bb57e39fd7ad53c7278a Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Tue, 30 Jul 2024 15:35:03 +0800 Subject: [PATCH 07/17] update order_target_portfolio --- .../rqalpha_mod_sys_accounts/api/api_stock.py | 61 ++++++++++++------- tests/api_tests/test_api_stock.py | 7 ++- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py index 3384b1b27..7494bccc6 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -19,6 +19,8 @@ from decimal import Decimal, getcontext from itertools import chain from typing import Dict, List, Optional, Union, Tuple +import math +from collections import defaultdict import numpy as np import pandas as pd @@ -70,14 +72,27 @@ def _get_account_position_ins(id_or_ins): return account, position, ins -def _round_order_quantity(ins, quantity) -> int: +def _round_order_quantity(ins, quantity, method: str = "round_down") -> int: + """ + 根据合约的 round_lot 对订单数量进行取整 + + :param method: 取整方式,可选择 'round_down'(向下取整), 'round_up'(向上取整), 'round'(四舍五入) + """ if ins.type == "CS" and ins.board_type == "KSH": # KSH can buy(sell) 201, 202 shares return 0 if abs(quantity) < KSH_MIN_AMOUNT else int(quantity) else: round_lot = ins.round_lot try: - return int(Decimal(quantity) / Decimal(round_lot)) * round_lot + match method: + case "round_down": + return int(Decimal(quantity) / Decimal(round_lot)) * round_lot + case "round_up": + return math.ceil(Decimal(quantity) / Decimal(round_lot)) * round_lot + case "round": + return round(Decimal(quantity) / Decimal(round_lot)) * round_lot + case _: + raise except ValueError: raise @@ -373,16 +388,8 @@ def order_target_portfolio( )) account_value = account.total_value - if total_percent == 1: - # 在此处形成的订单不包含交易费用,需要预留一点余额以供交易费用使用 - estimate_transaction_cost = 0 - for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): - current_value = current_quantities.get(order_book_id, 0) * last_price - change_value = target_percent * account_value - current_value - estimate_transaction_cost += env.get_transaction_cost_with_value(change_value) - account_value = account_value - estimate_transaction_cost - close_orders, open_orders = [], [] + waiting_to_buy = defaultdict() for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): open_price = _get_order_style_price(order_book_id, open_style) close_price = _get_order_style_price(order_book_id, close_style) @@ -392,23 +399,35 @@ def order_target_portfolio( ) env.order_creation_failed(order_book_id=order_book_id, reason=reason) continue - delta_quantity = (account_value * target_percent / close_price) - current_quantities.get(order_book_id, 0) - delta_quantity = _round_order_quantity(env.data_proxy.instrument(order_book_id), delta_quantity) + delta_quantity = _round_order_quantity(env.data_proxy.instrument(order_book_id), delta_quantity, method="round") + + # 优先生成卖单,以便计算出剩余现金,进行买单数量的计算 if delta_quantity == 0: continue elif delta_quantity > 0: - quantity, side, position_effect = delta_quantity, SIDE.BUY, POSITION_EFFECT.OPEN - order_list = open_orders - target_style = open_style + waiting_to_buy[order_book_id] = (delta_quantity, POSITION_EFFECT.OPEN, open_style, last_price) + continue else: quantity, side, position_effect = abs(delta_quantity), SIDE.SELL, POSITION_EFFECT.CLOSE - order_list = close_orders - target_style = close_style - order = Order.__from_create__(order_book_id, quantity, side, target_style, position_effect) - if isinstance(target_style, MarketOrder): + order = Order.__from_create__(order_book_id, quantity, side, close_style, position_effect) + if isinstance(close_style, MarketOrder): + order.set_frozen_price(last_price) + close_orders.append(order) + + estimate_cash = account.cash + sum([o.quantity * o.frozen_price - env.get_order_transaction_cost(o) for o in close_orders]) + for order_book_id, (delta_quantity, position_effect, open_style, last_price) in waiting_to_buy.items(): + order_price = delta_quantity * last_price + if order_price + env.get_transaction_cost_with_value(order_price) > estimate_cash: + delta_quantity = estimate_cash / last_price + delta_quantity = _round_order_quantity(env.data_proxy.instrument(order_book_id), delta_quantity) + if delta_quantity == 0: + continue + order = Order.__from_create__(order_book_id, delta_quantity, SIDE.BUY, open_style, position_effect) + if isinstance(open_style, MarketOrder): order.set_frozen_price(last_price) - order_list.append(order) + open_orders.append(order) + estimate_cash -= order.quantity * order.frozen_price + env.get_order_transaction_cost(order) return list(env.submit_order(o) for o in chain(close_orders, open_orders)) diff --git a/tests/api_tests/test_api_stock.py b/tests/api_tests/test_api_stock.py index e30f8e30f..bd79cd5e7 100644 --- a/tests/api_tests/test_api_stock.py +++ b/tests/api_tests/test_api_stock.py @@ -162,8 +162,9 @@ def handle_bar(context, bar_dict): "000001.XSHE": 0.1, "000004.XSHE": 0.2, }) - assert get_position("000001.XSHE").quantity == 6900 # (1000000 * 0.1) / 14.37 = 6958.94 - assert get_position("000004.XSHE").quantity == 10500 # (1000000 * 0.2) / 18.92 = 10570.82 + # 开仓仓位在 rqalpha == 5.4.2 之后修改为四舍五入 + assert get_position("000001.XSHE").quantity == 7000 # (1000000 * 0.1) / 14.37 = 6958.94 + assert get_position("000004.XSHE").quantity == 10600 # (1000000 * 0.2) / 18.92 = 10570.82 elif context.counter == 2: order_target_portfolio({ "000004.XSHE": 0.1, @@ -175,7 +176,7 @@ def handle_bar(context, bar_dict): "600519.XSHG": (970, 980), }) assert get_position("000001.XSHE").quantity == 0 # 清仓 - assert get_position("000004.XSHE").quantity == 5600 # (993695.7496 * 0.1) / 18 = 5520.53 + assert get_position("000004.XSHE").quantity == 5500 # (993695.7496 * 0.1) / 18 = 5520.53 assert get_position("000005.XSHE").quantity == 68000 # (993695.7496 * 0.2) / 2.92 = 68061.35 assert get_position("600519.XSHG").quantity == 0 # 970 低于 收盘价 无法买进 From b750feec84a79b14206a62837c47b6cdd45cd61a Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Tue, 30 Jul 2024 15:58:21 +0800 Subject: [PATCH 08/17] update --- .../rqalpha_mod_sys_accounts/api/api_stock.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py index 7494bccc6..c70849fd0 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -84,15 +84,14 @@ def _round_order_quantity(ins, quantity, method: str = "round_down") -> int: else: round_lot = ins.round_lot try: - match method: - case "round_down": - return int(Decimal(quantity) / Decimal(round_lot)) * round_lot - case "round_up": - return math.ceil(Decimal(quantity) / Decimal(round_lot)) * round_lot - case "round": - return round(Decimal(quantity) / Decimal(round_lot)) * round_lot - case _: - raise + if method == "round_down": + return int(Decimal(quantity) / Decimal(round_lot)) * round_lot + elif method == "round_up": + return math.ceil(Decimal(quantity) / Decimal(round_lot)) * round_lot + elif method == "round": + return round(Decimal(quantity) / Decimal(round_lot)) * round_lot + else: + raise RuntimeError("Rounding method only support 'round_down', 'round_up' and 'round'") except ValueError: raise From 6acab121f3f0c0952f5d47918f5526b4cc084300 Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Tue, 30 Jul 2024 17:13:11 +0800 Subject: [PATCH 09/17] pr update --- .../rqalpha_mod_sys_accounts/api/api_stock.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py index c70849fd0..7326f6a8a 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -18,7 +18,7 @@ import datetime from decimal import Decimal, getcontext from itertools import chain -from typing import Dict, List, Optional, Union, Tuple +from typing import Dict, List, Optional, Union, Tuple, Callable import math from collections import defaultdict @@ -72,26 +72,14 @@ def _get_account_position_ins(id_or_ins): return account, position, ins -def _round_order_quantity(ins, quantity, method: str = "round_down") -> int: - """ - 根据合约的 round_lot 对订单数量进行取整 - - :param method: 取整方式,可选择 'round_down'(向下取整), 'round_up'(向上取整), 'round'(四舍五入) - """ +def _round_order_quantity(ins, quantity, method: Callable = int) -> int: if ins.type == "CS" and ins.board_type == "KSH": # KSH can buy(sell) 201, 202 shares return 0 if abs(quantity) < KSH_MIN_AMOUNT else int(quantity) else: round_lot = ins.round_lot try: - if method == "round_down": - return int(Decimal(quantity) / Decimal(round_lot)) * round_lot - elif method == "round_up": - return math.ceil(Decimal(quantity) / Decimal(round_lot)) * round_lot - elif method == "round": - return round(Decimal(quantity) / Decimal(round_lot)) * round_lot - else: - raise RuntimeError("Rounding method only support 'round_down', 'round_up' and 'round'") + return method(Decimal(quantity) / Decimal(round_lot)) * round_lot except ValueError: raise @@ -387,6 +375,15 @@ def order_target_portfolio( )) account_value = account.total_value + if total_percent == 1: + # 在此处形成的订单不包含交易费用,需要预留一点余额以供交易费用使用 + estimate_transaction_cost = 0 + for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): + current_value = current_quantities.get(order_book_id, 0) * last_price + change_value = target_percent * account_value - current_value + estimate_transaction_cost += env.get_transaction_cost_with_value(change_value) + account_value = account_value - estimate_transaction_cost + close_orders, open_orders = [], [] waiting_to_buy = defaultdict() for order_book_id, (target_percent, open_style, close_style, last_price) in target.items(): @@ -399,7 +396,7 @@ def order_target_portfolio( env.order_creation_failed(order_book_id=order_book_id, reason=reason) continue delta_quantity = (account_value * target_percent / close_price) - current_quantities.get(order_book_id, 0) - delta_quantity = _round_order_quantity(env.data_proxy.instrument(order_book_id), delta_quantity, method="round") + delta_quantity = _round_order_quantity(env.data_proxy.instrument(order_book_id), delta_quantity, method=round) # 优先生成卖单,以便计算出剩余现金,进行买单数量的计算 if delta_quantity == 0: @@ -417,16 +414,19 @@ def order_target_portfolio( estimate_cash = account.cash + sum([o.quantity * o.frozen_price - env.get_order_transaction_cost(o) for o in close_orders]) for order_book_id, (delta_quantity, position_effect, open_style, last_price) in waiting_to_buy.items(): order_price = delta_quantity * last_price - if order_price + env.get_transaction_cost_with_value(order_price) > estimate_cash: + transaction_cost = env.get_transaction_cost_with_value(order_price) + if order_price + transaction_cost > estimate_cash: delta_quantity = estimate_cash / last_price delta_quantity = _round_order_quantity(env.data_proxy.instrument(order_book_id), delta_quantity) if delta_quantity == 0: continue + order_price = delta_quantity * last_price + transaction_cost = env.get_transaction_cost_with_value(order_price) order = Order.__from_create__(order_book_id, delta_quantity, SIDE.BUY, open_style, position_effect) if isinstance(open_style, MarketOrder): order.set_frozen_price(last_price) open_orders.append(order) - estimate_cash -= order.quantity * order.frozen_price + env.get_order_transaction_cost(order) + estimate_cash -= order_price + transaction_cost return list(env.submit_order(o) for o in chain(close_orders, open_orders)) From f88b5f60dd76b4d27964a3a7113fda2fd0b3ef2d Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Tue, 30 Jul 2024 17:46:04 +0800 Subject: [PATCH 10/17] pr update --- rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py index 7326f6a8a..840aa182b 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -413,20 +413,18 @@ def order_target_portfolio( estimate_cash = account.cash + sum([o.quantity * o.frozen_price - env.get_order_transaction_cost(o) for o in close_orders]) for order_book_id, (delta_quantity, position_effect, open_style, last_price) in waiting_to_buy.items(): - order_price = delta_quantity * last_price - transaction_cost = env.get_transaction_cost_with_value(order_price) - if order_price + transaction_cost > estimate_cash: + cost = delta_quantity * last_price + env.get_transaction_cost_with_value(delta_quantity * last_price) + if cost > estimate_cash: delta_quantity = estimate_cash / last_price delta_quantity = _round_order_quantity(env.data_proxy.instrument(order_book_id), delta_quantity) if delta_quantity == 0: continue - order_price = delta_quantity * last_price - transaction_cost = env.get_transaction_cost_with_value(order_price) + cost = delta_quantity * last_price + env.get_transaction_cost_with_value(delta_quantity * last_price) order = Order.__from_create__(order_book_id, delta_quantity, SIDE.BUY, open_style, position_effect) if isinstance(open_style, MarketOrder): order.set_frozen_price(last_price) open_orders.append(order) - estimate_cash -= order_price + transaction_cost + estimate_cash -= cost return list(env.submit_order(o) for o in chain(close_orders, open_orders)) From a7e6d6965aeda5522174c01bb59b551cf78ae715 Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Wed, 14 Aug 2024 19:57:56 +0800 Subject: [PATCH 11/17] =?UTF-8?q?=E6=9B=B4=E6=96=B0bundle=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=94=AF=E6=8C=81=E5=B0=86=E9=94=99=E8=AF=AF=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rqalpha/data/bundle.py | 63 ++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/rqalpha/data/bundle.py b/rqalpha/data/bundle.py index cc3e16d0e..64688e2ac 100644 --- a/rqalpha/data/bundle.py +++ b/rqalpha/data/bundle.py @@ -28,6 +28,7 @@ from rqalpha.utils.datetime_func import convert_date_to_date_int, convert_date_to_int from rqalpha.utils.i18n import gettext as _ from rqalpha.utils.functools import lru_cache +from rqalpha.utils.logger import init_logger, system_log from rqalpha.environment import Environment from rqalpha.model.instrument import Instrument @@ -310,24 +311,29 @@ def __call__(self, path, fields, **kwargs): class GenerateDayBarTask(DayBarTask): def __call__(self, path, fields, **kwargs): - with h5py.File(path, 'w') as h5: - i, step = 0, 300 - while True: - order_book_ids = self._order_book_ids[i:i + step] - df = rqdatac.get_price(order_book_ids, START_DATE, datetime.date.today(), '1d', - adjust_type='none', fields=fields, expect_df=True) - if not (df is None or df.empty): - df.reset_index(inplace=True) - df['datetime'] = [convert_date_to_int(d) for d in df['date']] - del df['date'] - df.set_index(['order_book_id', 'datetime'], inplace=True) - df.sort_index(inplace=True) - for order_book_id in df.index.levels[0]: - h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records(), **kwargs) - i += step - yield len(order_book_ids) - if i >= len(self._order_book_ids): - break + try: + with h5py.File(path, 'w') as h5: + i, step = 0, 300 + while True: + order_book_ids = self._order_book_ids[i:i + step] + df = rqdatac.get_price(order_book_ids, START_DATE, datetime.date.today(), '1d', + adjust_type='none', fields=fields, expect_df=True) + if not (df is None or df.empty): + df.reset_index(inplace=True) + df['datetime'] = [convert_date_to_int(d) for d in df['date']] + del df['date'] + df.set_index(['order_book_id', 'datetime'], inplace=True) + df.sort_index(inplace=True) + for order_book_id in df.index.levels[0]: + h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records(), **kwargs) + i += step + yield len(order_book_ids) + if i >= len(self._order_book_ids): + break + except OSError: + system_log.error("File {} update failed, if it is using, please update later, " + "or you can delete then update again".format(path)) + yield 1 class UpdateDayBarTask(DayBarTask): @@ -356,9 +362,10 @@ def __call__(self, path, fields, **kwargs): try: h5 = h5py.File(path, 'a') except OSError: - raise OSError("File {} update failed, if it is using, please update later, " - "or you can delete then update again".format(path)) - try: + system_log.error("File {} update failed, if it is using, please update later, " + "or you can delete then update again".format(path)) + yield 1 + else: is_futures = "futures" == os.path.basename(path).split(".")[0] for order_book_id in self._order_book_ids: # 特殊处理前复权合约,需要全量更新 @@ -367,8 +374,10 @@ def __call__(self, path, fields, **kwargs): try: last_date = int(h5[order_book_id]['datetime'][-1] // 1000000) except OSError: - raise OSError("File {} update failed, if it is using, please update later, " - "or you can delete then update again".format(path)) + system_log.error("File {} update failed, if it is using, please update later, " + "or you can delete then update again".format(path)) + yield 1 + break except ValueError: h5.pop(order_book_id) start_date = START_DATE @@ -406,12 +415,18 @@ def init_rqdatac_with_warnings_catch(): rqdatac.init() +def process_init_func(): + init_rqdatac_with_warnings_catch() + init_logger() + + def update_bundle(path, create, enable_compression=False, concurrency=1): if create: _DayBarTask = GenerateDayBarTask else: _DayBarTask = UpdateDayBarTask + init_logger() kwargs = {} if enable_compression: kwargs['compression'] = 9 @@ -431,7 +446,7 @@ def update_bundle(path, create, enable_compression=False, concurrency=1): ) with ProgressedProcessPoolExecutor( - max_workers=concurrency, initializer=init_rqdatac_with_warnings_catch + max_workers=concurrency, initializer=process_init_func ) as executor: # windows上子进程需要执行rqdatac.init, 其他os则需要执行rqdatac.reset; rqdatac.init包含了rqdatac.reset的功能 for func in gen_file_funcs: From 2c53b9c8d17b3a76efcc45619c018f901639290a Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Wed, 14 Aug 2024 20:15:34 +0800 Subject: [PATCH 12/17] update test data --- tests/outs/test_f_mean_reverting.pkl | Bin 110951 -> 111270 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/outs/test_f_mean_reverting.pkl b/tests/outs/test_f_mean_reverting.pkl index 852f2a6af71a39b8f585a338f142b6c9f907d05b..0c2fc50533a872d2099bb9571271f265c9d976d9 100644 GIT binary patch delta 15892 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 delta 15527 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 From 8c85790612c7303865c3a35685bb77d957d3a06f Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Sun, 18 Aug 2024 15:16:10 +0800 Subject: [PATCH 13/17] update --- rqalpha/data/bundle.py | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/rqalpha/data/bundle.py b/rqalpha/data/bundle.py index 64688e2ac..dd502e2ed 100644 --- a/rqalpha/data/bundle.py +++ b/rqalpha/data/bundle.py @@ -311,29 +311,24 @@ def __call__(self, path, fields, **kwargs): class GenerateDayBarTask(DayBarTask): def __call__(self, path, fields, **kwargs): - try: - with h5py.File(path, 'w') as h5: - i, step = 0, 300 - while True: - order_book_ids = self._order_book_ids[i:i + step] - df = rqdatac.get_price(order_book_ids, START_DATE, datetime.date.today(), '1d', - adjust_type='none', fields=fields, expect_df=True) - if not (df is None or df.empty): - df.reset_index(inplace=True) - df['datetime'] = [convert_date_to_int(d) for d in df['date']] - del df['date'] - df.set_index(['order_book_id', 'datetime'], inplace=True) - df.sort_index(inplace=True) - for order_book_id in df.index.levels[0]: - h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records(), **kwargs) - i += step - yield len(order_book_ids) - if i >= len(self._order_book_ids): - break - except OSError: - system_log.error("File {} update failed, if it is using, please update later, " - "or you can delete then update again".format(path)) - yield 1 + with h5py.File(path, 'w') as h5: + i, step = 0, 300 + while True: + order_book_ids = self._order_book_ids[i:i + step] + df = rqdatac.get_price(order_book_ids, START_DATE, datetime.date.today(), '1d', + adjust_type='none', fields=fields, expect_df=True) + if not (df is None or df.empty): + df.reset_index(inplace=True) + df['datetime'] = [convert_date_to_int(d) for d in df['date']] + del df['date'] + df.set_index(['order_book_id', 'datetime'], inplace=True) + df.sort_index(inplace=True) + for order_book_id in df.index.levels[0]: + h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records(), **kwargs) + i += step + yield len(order_book_ids) + if i >= len(self._order_book_ids): + break class UpdateDayBarTask(DayBarTask): From adaa3e7a479b0cf27d73a03b4ef5f06a6ba99f0e Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Thu, 22 Aug 2024 15:31:33 +0800 Subject: [PATCH 14/17] =?UTF-8?q?=E5=AD=98=E5=9C=A8=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E6=83=85=E5=86=B5=E6=97=B6=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E7=8A=B6=E6=80=81=E7=A0=81=E4=B8=8D=E4=B8=BA0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rqalpha/cmds/bundle.py | 2 +- rqalpha/data/bundle.py | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/rqalpha/cmds/bundle.py b/rqalpha/cmds/bundle.py index 89b7a3fc7..7203b33e7 100644 --- a/rqalpha/cmds/bundle.py +++ b/rqalpha/cmds/bundle.py @@ -89,7 +89,7 @@ def update_bundle(data_bundle_path, rqdatac_uri, compression, concurrency): return 1 from rqalpha.data.bundle import update_bundle as update_bundle_ - update_bundle_(os.path.join(data_bundle_path, 'bundle'), False, compression, concurrency) + return update_bundle_(os.path.join(data_bundle_path, 'bundle'), False, compression, concurrency) @cli.command(help=_("Download bundle (monthly updated)")) diff --git a/rqalpha/data/bundle.py b/rqalpha/data/bundle.py index dd502e2ed..9750b8bc4 100644 --- a/rqalpha/data/bundle.py +++ b/rqalpha/data/bundle.py @@ -31,6 +31,8 @@ from rqalpha.utils.logger import init_logger, system_log from rqalpha.environment import Environment from rqalpha.model.instrument import Instrument +import multiprocessing +from multiprocessing.sharedctypes import Synchronized START_DATE = 20050104 END_DATE = 29991231 @@ -354,11 +356,13 @@ def __call__(self, path, fields, **kwargs): if need_recreate_h5: yield from GenerateDayBarTask(self._order_book_ids)(path, fields, **kwargs) else: + h5 = None try: h5 = h5py.File(path, 'a') except OSError: system_log.error("File {} update failed, if it is using, please update later, " "or you can delete then update again".format(path)) + sval.value = 1 yield 1 else: is_futures = "futures" == os.path.basename(path).split(".")[0] @@ -371,6 +375,7 @@ def __call__(self, path, fields, **kwargs): except OSError: system_log.error("File {} update failed, if it is using, please update later, " "or you can delete then update again".format(path)) + sval.value = 1 yield 1 break except ValueError: @@ -400,19 +405,20 @@ def __call__(self, path, fields, **kwargs): h5.create_dataset(order_book_id, data=df.to_records(), **kwargs) yield 1 finally: - h5.close() + if h5: + h5.close() -def init_rqdatac_with_warnings_catch(): +def process_init(args: Optional[Synchronized] = None): import warnings with warnings.catch_warnings(record=True): # catch warning: rqdatac is already inited. Settings will be changed rqdatac.init() - - -def process_init_func(): - init_rqdatac_with_warnings_catch() init_logger() + # Initialize process shared variables + if args: + global sval + sval = args def update_bundle(path, create, enable_compression=False, concurrency=1): @@ -440,14 +446,16 @@ def update_bundle(path, create, enable_compression=False, concurrency=1): gen_suspended_days, gen_yield_curve, gen_share_transformation, gen_future_info ) + status_code = multiprocessing.Value("i", 0) with ProgressedProcessPoolExecutor( - max_workers=concurrency, initializer=process_init_func + max_workers=concurrency, initializer=process_init, initargs=(status_code, ) ) as executor: # windows上子进程需要执行rqdatac.init, 其他os则需要执行rqdatac.reset; rqdatac.init包含了rqdatac.reset的功能 for func in gen_file_funcs: executor.submit(GenerateFileTask(func), path) for file, order_book_id, field in day_bar_args: executor.submit(_DayBarTask(order_book_id), os.path.join(path, file), field, **kwargs) + return status_code.value class AutomaticUpdateBundle(object): From a7da46f697759f25f143346c3ba60a34582df98f Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Thu, 22 Aug 2024 15:51:59 +0800 Subject: [PATCH 15/17] update --- rqalpha/cmds/bundle.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rqalpha/cmds/bundle.py b/rqalpha/cmds/bundle.py index 7203b33e7..ed2fbdee4 100644 --- a/rqalpha/cmds/bundle.py +++ b/rqalpha/cmds/bundle.py @@ -19,6 +19,7 @@ import time import datetime import dateutil +import sys import click import requests @@ -89,7 +90,9 @@ def update_bundle(data_bundle_path, rqdatac_uri, compression, concurrency): return 1 from rqalpha.data.bundle import update_bundle as update_bundle_ - return update_bundle_(os.path.join(data_bundle_path, 'bundle'), False, compression, concurrency) + status = update_bundle_(os.path.join(data_bundle_path, 'bundle'), False, compression, concurrency) + if status != 0: + sys.exit(status) @cli.command(help=_("Download bundle (monthly updated)")) From 158402fe5a63ca9bdf56e8316f6dc74d6d11e93b Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Fri, 23 Aug 2024 11:40:43 +0800 Subject: [PATCH 16/17] update --- rqalpha/cmds/bundle.py | 6 ++--- rqalpha/data/bundle.py | 60 ++++++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/rqalpha/cmds/bundle.py b/rqalpha/cmds/bundle.py index ed2fbdee4..d93f0f248 100644 --- a/rqalpha/cmds/bundle.py +++ b/rqalpha/cmds/bundle.py @@ -90,9 +90,9 @@ def update_bundle(data_bundle_path, rqdatac_uri, compression, concurrency): return 1 from rqalpha.data.bundle import update_bundle as update_bundle_ - status = update_bundle_(os.path.join(data_bundle_path, 'bundle'), False, compression, concurrency) - if status != 0: - sys.exit(status) + succeed = update_bundle_(os.path.join(data_bundle_path, 'bundle'), False, compression, concurrency) + if not succeed: + sys.exit(1) @cli.command(help=_("Download bundle (monthly updated)")) diff --git a/rqalpha/data/bundle.py b/rqalpha/data/bundle.py index 9750b8bc4..96fdb7cb5 100644 --- a/rqalpha/data/bundle.py +++ b/rqalpha/data/bundle.py @@ -20,6 +20,9 @@ from itertools import chain from typing import Callable, Optional, Union, List from filelock import FileLock, Timeout +import multiprocessing +from multiprocessing.sharedctypes import Synchronized +from ctypes import c_bool import h5py import numpy as np @@ -31,8 +34,7 @@ from rqalpha.utils.logger import init_logger, system_log from rqalpha.environment import Environment from rqalpha.model.instrument import Instrument -import multiprocessing -from multiprocessing.sharedctypes import Synchronized + START_DATE = 20050104 END_DATE = 29991231 @@ -313,24 +315,32 @@ def __call__(self, path, fields, **kwargs): class GenerateDayBarTask(DayBarTask): def __call__(self, path, fields, **kwargs): - with h5py.File(path, 'w') as h5: - i, step = 0, 300 - while True: - order_book_ids = self._order_book_ids[i:i + step] - df = rqdatac.get_price(order_book_ids, START_DATE, datetime.date.today(), '1d', - adjust_type='none', fields=fields, expect_df=True) - if not (df is None or df.empty): - df.reset_index(inplace=True) - df['datetime'] = [convert_date_to_int(d) for d in df['date']] - del df['date'] - df.set_index(['order_book_id', 'datetime'], inplace=True) - df.sort_index(inplace=True) - for order_book_id in df.index.levels[0]: - h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records(), **kwargs) - i += step - yield len(order_book_ids) - if i >= len(self._order_book_ids): - break + try: + h5 = h5py.File(path, "w") + except OSError: + system_log.error("File {} update failed, if it is using, please update later, " + "or you can delete then update again".format(path)) + sval.value = False + yield 1 + else: + with h5: + i, step = 0, 300 + while True: + order_book_ids = self._order_book_ids[i:i + step] + df = rqdatac.get_price(order_book_ids, START_DATE, datetime.date.today(), '1d', + adjust_type='none', fields=fields, expect_df=True) + if not (df is None or df.empty): + df.reset_index(inplace=True) + df['datetime'] = [convert_date_to_int(d) for d in df['date']] + del df['date'] + df.set_index(['order_book_id', 'datetime'], inplace=True) + df.sort_index(inplace=True) + for order_book_id in df.index.levels[0]: + h5.create_dataset(order_book_id, data=df.loc[order_book_id].to_records(), **kwargs) + i += step + yield len(order_book_ids) + if i >= len(self._order_book_ids): + break class UpdateDayBarTask(DayBarTask): @@ -362,7 +372,7 @@ def __call__(self, path, fields, **kwargs): except OSError: system_log.error("File {} update failed, if it is using, please update later, " "or you can delete then update again".format(path)) - sval.value = 1 + sval.value = False yield 1 else: is_futures = "futures" == os.path.basename(path).split(".")[0] @@ -375,7 +385,7 @@ def __call__(self, path, fields, **kwargs): except OSError: system_log.error("File {} update failed, if it is using, please update later, " "or you can delete then update again".format(path)) - sval.value = 1 + sval.value = False yield 1 break except ValueError: @@ -446,16 +456,16 @@ def update_bundle(path, create, enable_compression=False, concurrency=1): gen_suspended_days, gen_yield_curve, gen_share_transformation, gen_future_info ) - status_code = multiprocessing.Value("i", 0) + succeed = multiprocessing.Value(c_bool, True) with ProgressedProcessPoolExecutor( - max_workers=concurrency, initializer=process_init, initargs=(status_code, ) + max_workers=concurrency, initializer=process_init, initargs=(succeed, ) ) as executor: # windows上子进程需要执行rqdatac.init, 其他os则需要执行rqdatac.reset; rqdatac.init包含了rqdatac.reset的功能 for func in gen_file_funcs: executor.submit(GenerateFileTask(func), path) for file, order_book_id, field in day_bar_args: executor.submit(_DayBarTask(order_book_id), os.path.join(path, file), field, **kwargs) - return status_code.value + return succeed.value class AutomaticUpdateBundle(object): From b6058edfc6bcbcfb13b68c18190be709d180453f Mon Sep 17 00:00:00 2001 From: "lin.dongzhao" <542698096@qq.com> Date: Tue, 10 Sep 2024 17:06:28 +0800 Subject: [PATCH 17/17] revise matcher validate --- rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py b/rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py index 9af13dd34..9852db31b 100644 --- a/rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py +++ b/rqalpha/mod/rqalpha_mod_sys_simulation/matcher.py @@ -210,7 +210,7 @@ def match(self, account, order, open_auction): trade._commission = self._env.get_trade_commission(trade) trade._tax = self._env.get_trade_tax(trade) - if order.side == SIDE.BUY and self._slippage_decider.decider.rate != 0: + if order.position_effect == POSITION_EFFECT.OPEN and self._slippage_decider.decider.rate != 0: # 标的价格经过滑点处理后,账户资金可能不够买入,需要进行验证 cost_money = instrument.calc_cash_occupation(price, order.quantity, order.position_direction, order.trading_datetime.date()) cost_money += trade.transaction_cost @@ -467,7 +467,7 @@ def match(self, account, order, open_auction): # type: (Account, Order, bool) - trade._commission = self._env.get_trade_commission(trade) trade._tax = self._env.get_trade_tax(trade) - if order.side == SIDE.BUY and self._slippage_decider.decider.rate != 0: + if order.position_effect == POSITION_EFFECT.OPEN and self._slippage_decider.decider.rate != 0: cost_money = instrument.calc_cash_occupation(price, order.quantity, order.position_direction, order.trading_datetime.date()) cost_money += trade.transaction_cost if cost_money > account.cash + order.init_frozen_cash: