From 1e05ada22222581f075980ad7debfe009eb52d04 Mon Sep 17 00:00:00 2001 From: Mingyang Liu Date: Mon, 1 Jul 2024 14:00:05 -0400 Subject: [PATCH 1/4] bug/fix_setup.py: fix error in setup.py --- requirements_dev.txt | 11 ++++++----- setup.py | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 5a728014e..04a27afd1 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,12 +5,13 @@ python-dateutil >=2.5.3 six >=1.10.0 logbook >=1.0.0 click >=7.0 -jsonpickle ==0.9.4 simplejson >=3.10.0 -dill ==0.2.5 PyYAML >=3.12 tabulate -rqrisk >=0.0.9 h5py -matplotlib >=1.5.1 ; python_version >= '3.6' -matplotlib >=1.5.1,<=3.0.3 ; python_version == '3.5' + +# pip install jsonpickle==0.9.4 +# pip install dill==0.2.5 +# pip install rqrisk +# mamba install matplotlib +# pip install . -e diff --git a/setup.py b/setup.py index 10c9debcf..254251d4c 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,8 @@ setup( name='rqalpha', - version=versioneer.get_version(), + # remove .dirty in version + version=versioneer.get_version() if ".dirty" not in versioneer.get_version() else versioneer.get_version().split('.dirty')[0], cmdclass=versioneer.get_cmdclass(), description='Ricequant Algorithm Trading System', packages=find_packages(exclude=[]), From 30178560fcba244e38815dc9ac35d87176a66f91 Mon Sep 17 00:00:00 2001 From: Mingyang Liu Date: Wed, 3 Jul 2024 13:47:43 -0400 Subject: [PATCH 2/4] feature/allow_fraction_lot: allow_fraction_lot --- rqalpha/apis/api_abstract.py | 2 +- .../rqalpha_mod_sys_accounts/api/api_stock.py | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/rqalpha/apis/api_abstract.py b/rqalpha/apis/api_abstract.py index 1237f8e91..ae7c2e75d 100644 --- a/rqalpha/apis/api_abstract.py +++ b/rqalpha/apis/api_abstract.py @@ -175,7 +175,7 @@ def order_percent(id_or_ins, percent, price_or_style=None, price=None, style=Non *common_rules ) @instype_singledispatch -def order_target_value(id_or_ins, cash_amount, price_or_style=None, price=None, style=None): +def order_target_value(id_or_ins, cash_amount, price_or_style=None, price=None, style=None, allow_fraction_lot=False): # type: (Union[str, Instrument], float, TUPLE_PRICE_OR_STYLE_TYPE, Optional[float], Optional[OrderStyle]) -> Optional[Order] """ 买入/卖出并且自动调整该证券的仓位到一个目标价值。 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..0f144e93a 100644 --- a/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py +++ b/rqalpha/mod/rqalpha_mod_sys_accounts/api/api_stock.py @@ -56,6 +56,8 @@ export_as_api(sector_code, name='sector_code') KSH_MIN_AMOUNT = 200 +ROUND_LOT_PRECISION = 1e-10 + def _get_account_position_ins(id_or_ins): @@ -70,12 +72,14 @@ 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, allow_fraction_lot = False) -> 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 + if allow_fraction_lot: + round_lot = ROUND_LOT_PRECISION try: return int(Decimal(quantity) / Decimal(round_lot)) * round_lot except ValueError: @@ -94,7 +98,7 @@ def _get_order_style_price(order_book_id, style): raise RuntimeError(f"no support {style} order style") -def _submit_order(ins, amount, side, position_effect, style, current_quantity, auto_switch_order_value, zero_amount_as_exception=True): +def _submit_order(ins, amount, side, position_effect, style, current_quantity, auto_switch_order_value, zero_amount_as_exception=True, allow_fraction_lot = False): env = Environment.get_instance() if isinstance(style, LimitOrder) and np.isnan(style.get_limit_price()): raise RQInvalidArgument(_(u"Limit order price should not be nan.")) @@ -106,7 +110,7 @@ def _submit_order(ins, amount, side, position_effect, style, current_quantity, a if (side == SIDE.BUY and current_quantity != -amount) or (side == SIDE.SELL and current_quantity != abs(amount)): # 在融券回测中,需要用买单作为平空,对于此种情况下出现的碎股,亦允许一次性申报卖出 - amount = _round_order_quantity(ins, amount) + amount = _round_order_quantity(ins, amount, allow_fraction_lot) if amount == 0: if zero_amount_as_exception: @@ -124,12 +128,12 @@ def _submit_order(ins, amount, side, position_effect, style, current_quantity, a return env.submit_order(order) -def _order_shares(ins, amount, style, quantity, auto_switch_order_value, zero_amount_as_exception=True): +def _order_shares(ins, amount, style, quantity, auto_switch_order_value, zero_amount_as_exception=True, allow_fraction_lot = False): side, position_effect = (SIDE.BUY, POSITION_EFFECT.OPEN) if amount > 0 else (SIDE.SELL, POSITION_EFFECT.CLOSE) - return _submit_order(ins, amount, side, position_effect, style, quantity, auto_switch_order_value, zero_amount_as_exception) + return _submit_order(ins, amount, side, position_effect, style, quantity, auto_switch_order_value, zero_amount_as_exception, allow_fraction_lot) -def _order_value(account, position, ins, cash_amount, style, zero_amount_as_exception=True): +def _order_value(account, position, ins, cash_amount, style, zero_amount_as_exception=True, allow_fraction_lot = False): env = Environment.get_instance() if cash_amount > 0: cash_amount = min(cash_amount, account.cash) @@ -144,8 +148,11 @@ def _order_value(account, position, ins, cash_amount, style, zero_amount_as_exce amount = int(Decimal(cash_amount) / Decimal(price)) round_lot = int(ins.round_lot) + if allow_fraction_lot: + amount = float(Decimal(cash_amount) / Decimal(price)) + round_lot = ROUND_LOT_PRECISION if cash_amount > 0: - amount = _round_order_quantity(ins, amount) + amount = _round_order_quantity(ins, amount, allow_fraction_lot = allow_fraction_lot) while amount > 0: expected_transaction_cost = env.get_order_transaction_cost(Order.__from_create__( ins.order_book_id, amount, SIDE.BUY, LimitOrder(price), POSITION_EFFECT.OPEN @@ -158,11 +165,10 @@ def _order_value(account, position, ins, cash_amount, style, zero_amount_as_exce reason = _(u"Order Creation Failed: 0 order quantity, order_book_id={order_book_id}").format(order_book_id=ins.order_book_id) env.order_creation_failed(order_book_id=ins.order_book_id, reason=reason) return - if amount < 0: amount = max(amount, -position.closable) - return _order_shares(ins, amount, style, position.quantity, auto_switch_order_value=False, zero_amount_as_exception=zero_amount_as_exception) + return _order_shares(ins, amount, style, position.quantity, auto_switch_order_value=False, zero_amount_as_exception=zero_amount_as_exception, allow_fraction_lot = allow_fraction_lot) @order_shares.register(INST_TYPE_IN_STOCK_ACCOUNT) @@ -188,7 +194,7 @@ def stock_order_percent(id_or_ins, percent, price_or_style=None, price=None, sty @order_target_value.register(INST_TYPE_IN_STOCK_ACCOUNT) -def stock_order_target_value(id_or_ins, cash_amount, price_or_style=None, price=None, style=None): +def stock_order_target_value(id_or_ins, cash_amount, price_or_style=None, price=None, style=None, allow_fraction_lot=False): account, position, ins = _get_account_position_ins(id_or_ins) open_style, close_style = calc_open_close_style(price, style, price_or_style) if cash_amount == 0: @@ -197,7 +203,7 @@ def stock_order_target_value(id_or_ins, cash_amount, price_or_style=None, price= ) _delta = cash_amount - position.market_value _style = open_style if _delta > 0 else close_style - return _order_value(account, position, ins, _delta, _style, zero_amount_as_exception=False) + return _order_value(account, position, ins, _delta, _style, zero_amount_as_exception=False, allow_fraction_lot = allow_fraction_lot) @order_target_percent.register(INST_TYPE_IN_STOCK_ACCOUNT) From 4eb96147b6e27ddaa97e1c0c516edca9094b3294 Mon Sep 17 00:00:00 2001 From: Mingyang Liu Date: Wed, 3 Jul 2024 16:40:50 -0400 Subject: [PATCH 3/4] feature/log: allow log_to_console and log_to_file --- rqalpha/main.py | 9 +------ rqalpha/utils/logger.py | 55 ++++++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/rqalpha/main.py b/rqalpha/main.py index 7ff577247..36229b822 100644 --- a/rqalpha/main.py +++ b/rqalpha/main.py @@ -301,14 +301,7 @@ def set_loggers(config): from rqalpha.utils.logger import init_logger from rqalpha.utils import logger extra_config = config.extra - - init_logger() - - for log in [system_log, user_system_log]: - log.level = getattr(logbook, config.extra.log_level.upper(), logbook.NOTSET) - - user_log.level = logbook.DEBUG - + init_logger(level=extra_config.log_level, log_to_file = extra_config.log_to_file, log_to_console=extra_config.log_to_console, file_name = extra_config.log_file) for logger_name, level in extra_config.logger: getattr(logger, logger_name).level = getattr(logbook, level.upper()) diff --git a/rqalpha/utils/logger.py b/rqalpha/utils/logger.py index 2f9ee7bbf..753730005 100644 --- a/rqalpha/utils/logger.py +++ b/rqalpha/utils/logger.py @@ -14,7 +14,7 @@ # 详细的授权流程,请联系 public@ricequant.com 获取。 import logbook from logbook import Logger, StderrHandler - +import logging logbook.set_datetime_format("local") @@ -32,6 +32,35 @@ DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f" +def configure_logger( + logger, + level=logging.DEBUG, + log_to_file=False, + log_to_console=True, + file_name='app_log.log' +): + # Create a logger + logger.setLevel(level) + # Avoid adding handlers multiple times + if not logger.handlers: + # Create formatter + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + + # Create handlers based on configuration + if log_to_file: + file_handler = logging.FileHandler(file_name) + file_handler.setLevel(level) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + if log_to_console: + console_handler = logging.StreamHandler() + console_handler.setLevel(level) + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + + return logger + def user_log_processor(record): from rqalpha.environment import Environment @@ -39,30 +68,22 @@ def user_log_processor(record): if time is not None: record.time = time - -user_log_group = logbook.LoggerGroup(processor=user_log_processor) - - # loggers # 用户代码logger日志 -user_log = Logger("user_log") +user_log = logging.getLogger("user_log") # 给用户看的系统日志 -user_system_log = Logger("user_system_log") - -user_log_group.add_logger(user_log) -user_log_group.add_logger(user_system_log) - +user_system_log = logging.getLogger("user_system_log") # 系统日志 -system_log = Logger("system_log") +system_log = logging.getLogger("system_log") original_print = print -def init_logger(): - system_log.handlers = [StderrHandler(bubble=True)] - user_log.handlers = [StderrHandler(bubble=True)] - user_system_log.handlers = [StderrHandler(bubble=True)] - +def init_logger(level, log_to_file=False, log_to_console=True, file_name='app_log.log'): + _level = logging._nameToLevel[level.upper()] + configure_logger(logger=system_log, level = _level, log_to_file=log_to_file, log_to_console = log_to_console, file_name = file_name) + configure_logger(logger=user_log, level = _level, log_to_file=log_to_file, log_to_console = log_to_console, file_name = file_name) + configure_logger(logger=user_system_log, level = _level, log_to_file=log_to_file, log_to_console = log_to_console, file_name = file_name) def user_print(*args, **kwargs): sep = kwargs.get("sep", " ") From 61f3ea7d51ee5a4a7b115e532cd9869faeefc71b Mon Sep 17 00:00:00 2001 From: Mingyang Liu Date: Wed, 3 Jul 2024 17:04:46 -0400 Subject: [PATCH 4/4] dev/plot: fix plot in config so that it does not always show fig in the notebook --- rqalpha/mod/rqalpha_mod_sys_analyser/plot/plot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rqalpha/mod/rqalpha_mod_sys_analyser/plot/plot.py b/rqalpha/mod/rqalpha_mod_sys_analyser/plot/plot.py index f6c375dc6..3b089079c 100644 --- a/rqalpha/mod/rqalpha_mod_sys_analyser/plot/plot.py +++ b/rqalpha/mod/rqalpha_mod_sys_analyser/plot/plot.py @@ -170,14 +170,13 @@ def _plot(title: str, sub_plots: List[SubPlot], strategy_name): img_height = sum(s.height for s in sub_plots) water_mark = WaterMark(IMG_WIDTH, img_height, strategy_name) fig = pyplot.figure(title, figsize=(IMG_WIDTH, img_height), dpi=water_mark.dpi, clear=True) - water_mark.plot(fig) gs = gridspec.GridSpec(img_height, 8, figure=fig) last_height = 0 for p in sub_plots: p.plot(pyplot.subplot(gs[last_height:last_height + p.height, :p.right_pad])) last_height += p.height - + pyplot.tight_layout() return fig @@ -253,3 +252,5 @@ def plot_result( if show: pyplot.show() + else: + pyplot.close()