1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-11 06:46:56 +08:00

Migrate NeuTrader to Qlib RL (#1169)

* Refine previous version RL codes

* Polish utils/__init__.py

* Draft

* Use | instead of Union

* Simulator & action interpreter

* Test passed

* Migrate to SAOEState & new qlib interpreter

* Black format

* . Revert file_storage change

* Refactor file structure & renaming functions

* Enrich test cases

* Add QlibIntradayBacktestData

* Test interpreter

* Black format

* .

.

.

* Rename receive_execute_result()

* Use indicator to simplify state update

* Format code

* Modify data path

* Adjust file structure

* Minor change

* Add copyright message

* Format code

* Rename util functions

* Add CI

* Pylint issue

* Remove useless code to pass pylint

* Pass mypy

* Mypy issue

* mypy issue

* mypy issue

* Revert "mypy issue"

This reverts commit 8eb1b0174e.

* mypy issue

* mypy issue

* Fix the numpy version incompatible bug

* Fix a minor typing issue

* Try to skip python 3.7 test for qlib simulator

* Resolve PR comments by Yuge; solve several CI issues.

* Black issue

* Fix a low-level type error

* Change data name

* Resolve PR comments. Leave TODOs in the code base.

Co-authored-by: Young <afe.young@gmail.com>
This commit is contained in:
Huoran Li
2022-08-01 09:56:07 +08:00
committed by GitHub
parent 687edd79d0
commit 2752bdc92c
35 changed files with 1305 additions and 257 deletions

View File

@@ -42,7 +42,7 @@ def get_exchange(
close_cost: float = 0.0025,
min_cost: float = 5.0,
limit_threshold: Union[Tuple[str, str], float, None] = None,
deal_price: Union[str, Tuple[str], List[str]] = None,
deal_price: Union[str, Tuple[str, str], List[str]] = None,
**kwargs: Any,
) -> Exchange:
"""get_exchange
@@ -70,10 +70,10 @@ def get_exchange(
min_cost : float
min transaction cost. It is an absolute amount of cost instead of a ratio of your order's deal amount.
e.g. You must pay at least 5 yuan of commission regardless of your order's deal amount.
deal_price: Union[str, Tuple[str], List[str]]
deal_price: Union[str, Tuple[str, str], List[str]]
The `deal_price` supports following two types of input
- <deal_price> : str
- (<buy_price>, <sell_price>): Tuple[str] or List[str]
- (<buy_price>, <sell_price>): Tuple[str, str] or List[str]
<deal_price>, <buy_price> or <sell_price> := <price>
<price> := str

View File

@@ -4,10 +4,11 @@
from __future__ import annotations
from abc import abstractmethod
from datetime import time
from enum import IntEnum
# try to fix circular imports when enabling type hints
from typing import Generic, List, TYPE_CHECKING, Any, ClassVar, Optional, Tuple, TypeVar, Union, cast
from typing import TYPE_CHECKING, Any, ClassVar, Generic, List, Optional, Tuple, TypeVar, Union, cast
from qlib.backtest.utils import TradeCalendarManager
from qlib.data.data import Cal
@@ -23,7 +24,6 @@ from dataclasses import dataclass
import numpy as np
import pandas as pd
DecisionType = TypeVar("DecisionType")
@@ -182,8 +182,8 @@ class OrderHelper:
return Order(
stock_id=code,
amount=amount,
start_time=start_time if start_time is not None else pd.Timestamp(start_time),
end_time=end_time if end_time is not None else pd.Timestamp(end_time),
start_time=None if start_time is None else pd.Timestamp(start_time),
end_time=None if end_time is None else pd.Timestamp(end_time),
direction=direction,
)
@@ -249,7 +249,7 @@ class IdxTradeRange(TradeRange):
class TradeRangeByTime(TradeRange):
"""This is a helper function for make decisions"""
def __init__(self, start_time: str, end_time: str) -> None:
def __init__(self, start_time: str | time, end_time: str | time) -> None:
"""
This is a callable class.
@@ -259,13 +259,13 @@ class TradeRangeByTime(TradeRange):
Parameters
----------
start_time : str
start_time : str | time
e.g. "9:30"
end_time : str
end_time : str | time
e.g. "14:30"
"""
self.start_time = pd.Timestamp(start_time).time()
self.end_time = pd.Timestamp(end_time).time()
self.start_time = pd.Timestamp(start_time).time() if isinstance(start_time, str) else start_time
self.end_time = pd.Timestamp(end_time).time() if isinstance(end_time, str) else end_time
assert self.start_time < self.end_time
def __call__(self, trade_calendar: TradeCalendarManager) -> Tuple[int, int]:
@@ -535,7 +535,12 @@ class TradeDecisionWO(BaseTradeDecision[Order]):
Besides, the time_range is also included.
"""
def __init__(self, order_list: List[object], strategy: BaseStrategy, trade_range: Tuple[int, int] = None) -> None:
def __init__(
self,
order_list: List[Order],
strategy: BaseStrategy,
trade_range: Union[Tuple[int, int], TradeRange] = None,
) -> None:
super().__init__(strategy, trade_range=trade_range)
self.order_list = cast(List[Order], order_list)
start, end = strategy.trade_calendar.get_step_time()

View File

@@ -32,7 +32,7 @@ class Exchange:
start_time: Union[pd.Timestamp, str] = None,
end_time: Union[pd.Timestamp, str] = None,
codes: Union[list, str] = "all",
deal_price: Union[str, Tuple[str], List[str]] = None,
deal_price: Union[str, Tuple[str, str], List[str]] = None,
subscribe_fields: list = [],
limit_threshold: Union[Tuple[str, str], float, None] = None,
volume_threshold: Union[tuple, dict] = None,
@@ -448,9 +448,9 @@ class Exchange:
start_time: pd.Timestamp,
end_time: pd.Timestamp,
method: Optional[str] = "sum",
) -> float:
) -> Union[None, int, float, bool, IndexData]:
"""get the total deal volume of stock with `stock_id` between the time interval [start_time, end_time)"""
return cast(float, self.quote.get_data(stock_id, start_time, end_time, field="$volume", method=method))
return self.quote.get_data(stock_id, start_time, end_time, field="$volume", method=method)
def get_deal_price(
self,
@@ -459,7 +459,7 @@ class Exchange:
end_time: pd.Timestamp,
direction: OrderDir,
method: Optional[str] = "ts_data_last",
) -> float:
) -> Union[None, int, float, bool, IndexData]:
if direction == OrderDir.SELL:
pstr = self.sell_price
elif direction == OrderDir.BUY:
@@ -472,7 +472,7 @@ class Exchange:
self.logger.warning(f"(stock_id:{stock_id}, trade_time:{(start_time, end_time)}, {pstr}): {deal_price}!!!")
self.logger.warning(f"setting deal_price to close price")
deal_price = self.get_close(stock_id, start_time, end_time, method)
return cast(float, deal_price)
return deal_price
def get_factor(
self,
@@ -832,8 +832,11 @@ class Exchange:
:param dealt_order_amount: the dealt order amount dict with the format of {stock_id: float}
:return: trade_price, trade_val, trade_cost
"""
trade_price = self.get_deal_price(order.stock_id, order.start_time, order.end_time, direction=order.direction)
total_trade_val = self.get_volume(order.stock_id, order.start_time, order.end_time) * trade_price
trade_price = cast(
float,
self.get_deal_price(order.stock_id, order.start_time, order.end_time, direction=order.direction),
)
total_trade_val = cast(float, self.get_volume(order.stock_id, order.start_time, order.end_time)) * trade_price
order.factor = self.get_factor(order.stock_id, order.start_time, order.end_time)
order.deal_amount = order.amount # set to full amount and clip it step by step
# Clipping amount first

View File

@@ -484,6 +484,7 @@ class NestedExecutor(BaseExecutor):
inner_exe_res :
the execution result of inner task
"""
self.inner_strategy.post_exe_step(inner_exe_res)
def get_all_executors(self) -> List[BaseExecutor]:
"""get all executors, including self and inner_executor.get_all_executors()"""