-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Support Highfreq Backtest with the Model/Rule/RL Strategy #408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
qlib/contrib/backtest/__init__.py
Outdated
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT License. | ||
|
|
||
| from .order import Order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could move the core framework outside of contrib folder now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can move them later
| @@ -0,0 +1,145 @@ | |||
| # Copyright (c) Microsoft Corporation. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our framework is a Multi-level Trading system instead of a single high frequency trading system.
The folder name could have a better name than highfreq.
| }, | ||
| }, | ||
| "backtest": { | ||
| "start_time": trade_start_time, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Part of the config looks like belonging to env.
qlib/utils/__init__.py
Outdated
|
|
||
|
|
||
| def parse_freq(freq): | ||
| freq = freq.lower() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add docs
qlib/workflow/record_temp.py
Outdated
| ret_freq.extend(self._get_report_freq(env_config["kwargs"]["sub_env"])) | ||
| return ret_freq | ||
|
|
||
| def _cal_risk_analysis_scaler(self, freq): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can combine these into risk_analysis and make it more powerful
qlib/utils/__init__.py
Outdated
| raise ValueError("sample freq must be xmin, xd, xw, xm") | ||
|
|
||
|
|
||
| def get_sample_freq_calendar(start_time=None, end_time=None, freq="day", **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Give some docs about the **kwargs.
What could it be?
qlib/utils/__init__.py
Outdated
| try: | ||
| _calendar = Cal.calendar(start_time=start_time, end_time=end_time, freq=freq, **kwargs) | ||
| freq, freq_sam = freq, None | ||
| except ValueError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part looks not intuitive.
Let's have some discussions later
| self.instruments = D.instruments(instruments) | ||
| self.freq = freq | ||
|
|
||
| def _convert_index_format(self, df): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function appears multiple times.
It will be better move it into utils.
qlib/contrib/backtest/account.py
Outdated
|
|
||
| for k, v in kwargs.items(): | ||
| if hasattr(self, k): | ||
| setattr(self, k, v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Give warning in else branch
|
I add a notebook file |
| "n_drop": 5, | ||
| }, | ||
| }, | ||
| "env": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"env" -> "executor"?
| "class": "SimulatorExecutor", | ||
| "module_path": "qlib.contrib.backtest.executor", | ||
| "kwargs": { | ||
| "step_bar": "day", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's figure out a better name for step_bar and freq
qlib/contrib/backtest/executor.py
Outdated
| self._init_sub_trading(order_list) | ||
| sub_execute_state = self.sub_env.get_init_state() | ||
| while not self.sub_env.finished(): | ||
| _order_list = self.sub_strategy.generate_order_list(sub_execute_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why current can't be global?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't we pass an account instead of sub_execute_state? account should contain everything that strategy needs other than information in exchange.
qlib/strategy/base.py
Outdated
| from ..rl.interpreter import ActionInterpreter, StateInterpreter | ||
|
|
||
|
|
||
| class BaseStrategy(BaseTradeCalendar): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strategy should not stateless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reading the implementation of TWAP, I agree that the strategy is hard to be stateless.
| _interpret_state = self.state_interpretor.interpret( | ||
| execute_result=execute_state, **self.action_interpret_kwargs | ||
| ) | ||
| _policy_action = self.policy.step(_interpret_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.policy(_interpret_state)
qlib/contrib/backtest/faculty.py
Outdated
| # Licensed under the MIT License. | ||
|
|
||
|
|
||
| class Faculty: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use this name?
qlib/contrib/backtest/faculty.py
Outdated
| self.__dict__["_faculty"].update(*args, **kwargs) | ||
|
|
||
|
|
||
| common_faculty = Faculty() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Singleton is not enough for our scenario.
| # amount of successfully completed orders | ||
| self.deal_amount = 0 | ||
| self.trade_date = trade_date | ||
| self.start_time = start_time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
define the start_time & end_time (e.g. include or exclude)
|
|
||
| def update_stock_count(self, stock_id, count): | ||
| self.position[stock_id]["count"] = count | ||
| def update_stock_count(self, stock_id, bar, count): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will it better to unify the name bar and freq?
| del p["cash"] | ||
| del p["today_account_value"] | ||
| del p["now_account_value"] | ||
| positions = pd.DataFrame.from_dict(p, orient="index") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please import pandas and numpy
qlib/utils/sample.py
Outdated
| else: | ||
| if raw_count > sam_count: | ||
| raise ValueError("raw freq must be higher than sampling freq") | ||
| _calendar_minute = np.unique( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have to implement such a complicated version?
Will the following logic simpler?
div = freq_targert / freq_orig
cal_target = cal_orig[::div]There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add docstring
qlib/utils/sample.py
Outdated
| start sampling time, by default None | ||
| end_time : Union[str, pd.Timestamp], optional | ||
| end sampling time, by default None | ||
| fields : Union[str, List[str]], optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What scenario do we have to resample part of the field?
| else feature.loc[(slice(None), selector_datetime), fields] | ||
| ) | ||
| if feature.empty: | ||
| return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning the empty feature will be more reasonable
qlib/utils/sample.py
Outdated
| from ..data.dataset.utils import get_level_index | ||
|
|
||
| datetime_level = get_level_index(feature, level="datetime") == 0 | ||
| if isinstance(feature, pd.Series): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't filter fields in this function.
It will be unnecessary to use different logic between pd.Series and pd.DataFrame
|
|
||
| from ..data.dataset.utils import get_level_index | ||
|
|
||
| datetime_level = get_level_index(feature, level="datetime") == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you make sure the datetime is sorted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lasy_sort_index
qlib.utils
index.is_lexsorted()
| "class": "SimulatorExecutor", | ||
| "module_path": "qlib.contrib.backtest.executor", | ||
| "kwargs": { | ||
| "step_bar": "day", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please send all the new names to the group for discussion
qlib/contrib/backtest/account.py
Outdated
| self.current = Position(cash=init_cash) | ||
| self._reset_report() | ||
|
|
||
| def _cal_benchmark(self, benchmark_config, freq): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move it to report.py
| self.update_state_from_order(order, trade_val, cost, trade_price) | ||
|
|
||
| def update_daily_end(self, today, trader): | ||
| def update_bar_count(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Giving Account an interface is doable
qlib/contrib/backtest/backtest.py
Outdated
|
|
||
| _execute_state = trade_env.get_init_state() | ||
| while not trade_env.finished(): | ||
| _order_list = trade_strategy.generate_order_list(_execute_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for example, decision
qlib/contrib/backtest/backtest.py
Outdated
|
|
||
| _execute_state = trade_env.get_init_state() | ||
| while not trade_env.finished(): | ||
| _order_list = trade_strategy.generate_order_list(_execute_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
list sharing granularity and send it to group for discussion
| """ | ||
| self.freq = freq | ||
| self.start_time = start_time | ||
| self.end_time = end_time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 2 3 4 5
1 2 3 4
[1, 4]
[1, 4.5]
| def __init__( | ||
| self, | ||
| trade_dates=None, | ||
| freq="day", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
turnover limit threshing
qlib/utils/sample.py
Outdated
| else: | ||
| if raw_count > sam_count: | ||
| raise ValueError("raw freq must be higher than sampling freq") | ||
| _calendar_minute = np.unique( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add docstring
|
|
||
| from ..data.dataset.utils import get_level_index | ||
|
|
||
| datetime_level = get_level_index(feature, level="datetime") == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lasy_sort_index
qlib.utils
index.is_lexsorted()
…dd-qlib_highfreq_backtest
| Return the proportion of your total value you will used in investment. | ||
| Dynamically risk_degree will result in Market timing. | ||
| """ | ||
| # It will use 95% amoutn of your total value by default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
amount
| if rely_trade_decision is not None: | ||
| self.rely_trade_decision = rely_trade_decision | ||
|
|
||
| def generate_trade_decision(self, execute_state): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the format/interface of execute state? What is expected to get from execute_state when I write a new strategy.
What is the interface of return value of generate_trade_decision?
| if "trade_account" in common_infra: | ||
| self.trade_position = common_infra.get("trade_account").current | ||
|
|
||
| def reset(self, level_infra: dict = None, common_infra: dict = None, rely_trade_decision=None, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the interface of:
- level_infra
- common_infra
- rely_trade_decision
When is reset expected to be called?
|
This pr is closed, see #438 |
| "freq format is not supported, the freq should be like (n)month/mon, (n)week/w, (n)day/d, (n)minute/min" | ||
| ) | ||
| _count = int(match_obj.group(1) if match_obj.group(1) else "1") | ||
| _count = int(match_obj.group(1)) if match_obj.group(1) is None else 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be int(match_obj.group(1)) if match_obj.group(1) else 1?
Example: If call parse_freq("min"), match_obj.group(1) == ' ' rather than None
…microsoft#409) * Fixes on kaggle output * feat: add kaggle s3e14 template (microsoft#394) * add s3e14 template * fix CI * Initialisation of a template of competition * add kaggle s3e16 template (microsoft#396) * get kaggle competition scores (microsoft#397) * Adding a new competition s4e6 * feat: s4e5 (microsoft#400) * init for s4e5 * edit s4e5 * ci issue * feat: S4e3 (microsoft#402) * Initialisation of a template of competition * Adding a new competition s4e6 * Competition Initialised * Fixed to make sure that now it runs * Fixing for CI * correct evaluation (microsoft#403) * find rank in leaderboard (microsoft#405) * fix: model templates for KG scenario (microsoft#408) * fix feature selection for some models * feat select template * Updating the prompts for a more powerful model tuning * refine the prompt * fix: template error in s4e6 * feat: show simple execution time in demo (microsoft#410) * show time in kaggle demo * change color * fix a small bug * edit loop.py and proposal * delete useless files * CI issues * ci issue --------- Co-authored-by: XianBW <36835909+XianBW@users.noreply.github.com> Co-authored-by: Haoran Pan <167847254+TPLin22@users.noreply.github.com> Co-authored-by: Way2Learn <118058822+Xisen-Wang@users.noreply.github.com> Co-authored-by: WinstonLiyt <1957922024@qq.com> Co-authored-by: TPLin22 <tplin2@163.com>
Description
Support Highfreq Backtest with the Model/Rule/RL Strategy
Motivation and Context
How Has This Been Tested?
pytest qlib/tests/test_all_pipeline.pyunder upper directory ofqlib.Screenshots of Test Results (if appropriate):
Types of changes