1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-06-06 05:51:17 +08:00
Files
qlib/tests/rl/test_qlib_simulator.py
Huoran Li 35794846ff Refine RL todos (#1332)
* Refine several todos

* CI issues

* Remove Dropna limitation of `quote_df` in Exchange  (#1334)

* Remove Dropna limitation of `quote_df` of Exchange

* Impreove docstring

* Fix type error when expression is specified (#1335)

* Refine fill_missing_data()

* Remove several TODO comments

* Add back env for interpreters

* Change Literal import

* Resolve PR comments

* Move  to SAOEState

* Add Trainer.get_policy_state_dict()

* Mypy issue

Co-authored-by: you-n-g <you-n-g@users.noreply.github.com>
2022-11-10 21:10:11 +08:00

196 lines
7.2 KiB
Python

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import sys
from pathlib import Path
from typing import Tuple
import pandas as pd
import pytest
from qlib.backtest.decision import Order, OrderDir
from qlib.backtest.executor import SimulatorExecutor
from qlib.rl.order_execution import CategoricalActionInterpreter
from qlib.rl.order_execution.simulator_qlib import SingleAssetOrderExecution
TOTAL_POSITION = 2100.0
python_version_request = pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher")
def is_close(a: float, b: float, epsilon: float = 1e-4) -> bool:
return abs(a - b) <= epsilon
def get_order() -> Order:
return Order(
stock_id="SH600000",
amount=TOTAL_POSITION,
direction=OrderDir.BUY,
start_time=pd.Timestamp("2019-03-04 09:30:00"),
end_time=pd.Timestamp("2019-03-04 14:29:00"),
)
def get_configs(order: Order) -> Tuple[dict, dict]:
executor_config = {
"class": "NestedExecutor",
"module_path": "qlib.backtest.executor",
"kwargs": {
"time_per_step": "1day",
"inner_strategy": {"class": "ProxySAOEStrategy", "module_path": "qlib.rl.order_execution.strategy"},
"track_data": True,
"inner_executor": {
"class": "NestedExecutor",
"module_path": "qlib.backtest.executor",
"kwargs": {
"time_per_step": "30min",
"inner_strategy": {
"class": "TWAPStrategy",
"module_path": "qlib.contrib.strategy.rule_strategy",
},
"inner_executor": {
"class": "SimulatorExecutor",
"module_path": "qlib.backtest.executor",
"kwargs": {
"time_per_step": "1min",
"verbose": False,
"trade_type": SimulatorExecutor.TT_SERIAL,
"generate_report": False,
"track_data": True,
},
},
"track_data": True,
},
},
"start_time": pd.Timestamp(order.start_time.date()),
"end_time": pd.Timestamp(order.start_time.date()),
},
}
exchange_config = {
"freq": "1min",
"codes": [order.stock_id],
"limit_threshold": ("$ask == 0", "$bid == 0"),
"deal_price": ("If($ask == 0, $bid, $ask)", "If($bid == 0, $ask, $bid)"),
"volume_threshold": {
"all": ("cum", "0.2 * DayCumsum($volume, '9:30', '14:29')"),
"buy": ("current", "$askV1"),
"sell": ("current", "$bidV1"),
},
"open_cost": 0.0005,
"close_cost": 0.0015,
"min_cost": 5.0,
"trade_unit": None,
}
return executor_config, exchange_config
def get_simulator(order: Order) -> SingleAssetOrderExecution:
DATA_ROOT_DIR = Path(__file__).parent.parent / ".data" / "rl" / "qlib_simulator"
# fmt: off
qlib_config = {
"provider_uri_day": DATA_ROOT_DIR / "qlib_1d",
"provider_uri_1min": DATA_ROOT_DIR / "qlib_1min",
"feature_root_dir": DATA_ROOT_DIR / "qlib_handler_stock",
"feature_columns_today": [
"$open", "$high", "$low", "$close", "$vwap", "$bid", "$ask", "$volume",
"$bidV", "$bidV1", "$bidV3", "$bidV5", "$askV", "$askV1", "$askV3", "$askV5",
],
"feature_columns_yesterday": [
"$open_1", "$high_1", "$low_1", "$close_1", "$vwap_1", "$bid_1", "$ask_1", "$volume_1",
"$bidV_1", "$bidV1_1", "$bidV3_1", "$bidV5_1", "$askV_1", "$askV1_1", "$askV3_1", "$askV5_1",
],
}
# fmt: on
executor_config, exchange_config = get_configs(order)
return SingleAssetOrderExecution(
order=order,
qlib_config=qlib_config,
executor_config=executor_config,
exchange_config=exchange_config,
)
@python_version_request
def test_simulator_first_step():
order = get_order()
simulator = get_simulator(order)
state = simulator.get_state()
assert state.cur_time == pd.Timestamp("2019-03-04 09:30:00")
assert state.position == TOTAL_POSITION
AMOUNT = 300.0
simulator.step(AMOUNT)
state = simulator.get_state()
assert state.cur_time == pd.Timestamp("2019-03-04 10:00:00")
assert state.position == TOTAL_POSITION - AMOUNT
assert len(state.history_exec) == 30
assert state.history_exec.index[0] == pd.Timestamp("2019-03-04 09:30:00")
assert is_close(state.history_exec["market_volume"].iloc[0], 109382.382812)
assert is_close(state.history_exec["market_price"].iloc[0], 149.566483)
assert (state.history_exec["amount"] == AMOUNT / 30).all()
assert (state.history_exec["deal_amount"] == AMOUNT / 30).all()
assert is_close(state.history_exec["trade_price"].iloc[0], 149.566483)
assert is_close(state.history_exec["trade_value"].iloc[0], 1495.664825)
assert is_close(state.history_exec["position"].iloc[0], TOTAL_POSITION - AMOUNT / 30)
assert is_close(state.history_exec["ffr"].iloc[0], AMOUNT / TOTAL_POSITION / 30)
assert is_close(state.history_steps["market_volume"].iloc[0], 1254848.5756835938)
assert state.history_steps["amount"].iloc[0] == AMOUNT
assert state.history_steps["deal_amount"].iloc[0] == AMOUNT
assert state.history_steps["ffr"].iloc[0] == AMOUNT / TOTAL_POSITION
assert is_close(
state.history_steps["pa"].iloc[0] * (1.0 if order.direction == OrderDir.SELL else -1.0),
(state.history_steps["trade_price"].iloc[0] / simulator.twap_price - 1) * 10000,
)
@python_version_request
def test_simulator_stop_twap() -> None:
order = get_order()
simulator = get_simulator(order)
NUM_STEPS = 7
for i in range(NUM_STEPS):
simulator.step(TOTAL_POSITION / NUM_STEPS)
HISTORY_STEP_LENGTH = 30 * NUM_STEPS
state = simulator.get_state()
assert len(state.history_exec) == HISTORY_STEP_LENGTH
assert (state.history_exec["deal_amount"] == TOTAL_POSITION / HISTORY_STEP_LENGTH).all()
assert is_close(state.history_steps["position"].iloc[0], TOTAL_POSITION * (NUM_STEPS - 1) / NUM_STEPS)
assert is_close(state.history_steps["position"].iloc[-1], 0.0)
assert is_close(state.position, 0.0)
assert is_close(state.metrics["ffr"], 1.0)
assert is_close(state.metrics["market_price"], state.backtest_data.get_deal_price().mean())
assert is_close(state.metrics["market_volume"], state.backtest_data.get_volume().sum())
assert is_close(state.metrics["trade_price"], state.metrics["market_price"])
assert is_close(state.metrics["pa"], 0.0)
assert simulator.done()
@python_version_request
def test_interpreter() -> None:
NUM_EXECUTION = 3
order = get_order()
simulator = get_simulator(order)
interpreter_action = CategoricalActionInterpreter(values=NUM_EXECUTION)
NUM_STEPS = 7
state = simulator.get_state()
position_history = []
for i in range(NUM_STEPS):
simulator.step(interpreter_action(state, 1))
state = simulator.get_state()
position_history.append(state.position)
assert position_history[-1] == max(TOTAL_POSITION - TOTAL_POSITION / NUM_EXECUTION * (i + 1), 0.0)