mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-13 07:46:53 +08:00
fix some comments and add docstring
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
from .order import Order
|
||||
from .position import Position
|
||||
|
||||
from .exchange import Exchange
|
||||
from .report import Report
|
||||
from .executor import BaseExecutor
|
||||
from .backtest import backtest as backtest_func
|
||||
|
||||
import copy
|
||||
import numpy as np
|
||||
import inspect
|
||||
from ...strategy.base import BaseStrategy
|
||||
from ...utils import init_instance_by_config
|
||||
from ...log import get_module_logger
|
||||
from ...config import C
|
||||
@@ -90,21 +88,6 @@ def get_exchange(
|
||||
return init_instance_by_config(exchange, accept_types=Exchange)
|
||||
|
||||
|
||||
def init_env_instance_by_config(env):
|
||||
if isinstance(env, dict):
|
||||
env_config = copy.copy(env)
|
||||
if "kwargs" in env_config:
|
||||
env_kwargs = copy.copy(env_config["kwargs"])
|
||||
if "sub_env" in env_kwargs:
|
||||
env_kwargs["sub_env"] = init_env_instance_by_config(env_kwargs["sub_env"])
|
||||
if "sub_strategy" in env_kwargs:
|
||||
env_kwargs["sub_strategy"] = init_instance_by_config(env_kwargs["sub_strategy"])
|
||||
env_config["kwargs"] = env_kwargs
|
||||
return init_instance_by_config(env_config)
|
||||
else:
|
||||
return env
|
||||
|
||||
|
||||
def setup_exchange(root_instance, trade_exchange=None, force=False):
|
||||
if "trade_exchange" in inspect.getfullargspec(root_instance.__class__).args:
|
||||
if force:
|
||||
@@ -118,13 +101,11 @@ def setup_exchange(root_instance, trade_exchange=None, force=False):
|
||||
setup_exchange(root_instance.sub_strategy, trade_exchange)
|
||||
|
||||
|
||||
def backtest(start_time, end_time, strategy, env, benchmark="SH000905", account=1e9, **kwargs):
|
||||
trade_strategy = init_instance_by_config(strategy)
|
||||
trade_env = init_env_instance_by_config(env)
|
||||
def backtest(start_time, end_time, strategy, env, benchmark="SH000905", account=1e9, exchange_kwargs={}):
|
||||
trade_strategy = init_instance_by_config(strategy, accept_types=BaseStrategy)
|
||||
trade_env = init_instance_by_config(env, accept_types=BaseExecutor)
|
||||
|
||||
spec = inspect.getfullargspec(get_exchange)
|
||||
exchange_args = {k: v for k, v in kwargs.items() if k in spec.args}
|
||||
trade_exchange = get_exchange(**exchange_args)
|
||||
trade_exchange = get_exchange(**exchange_kwargs)
|
||||
|
||||
setup_exchange(trade_env, trade_exchange)
|
||||
setup_exchange(trade_strategy, trade_exchange)
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
|
||||
|
||||
import copy
|
||||
import warnings
|
||||
import pandas as pd
|
||||
|
||||
from .position import Position
|
||||
from .report import Report
|
||||
from .order import Order
|
||||
from ...data import D
|
||||
from ...utils import parse_freq, sample_feature
|
||||
from ...utils.sample import parse_freq, sample_feature
|
||||
|
||||
|
||||
"""
|
||||
@@ -110,6 +111,8 @@ class Account:
|
||||
for k, v in kwargs.items():
|
||||
if hasattr(self, k):
|
||||
setattr(self, k, v)
|
||||
else:
|
||||
warnings.warn(f"reser error, attribute {k} is not found!")
|
||||
|
||||
def get_positions(self):
|
||||
return self.positions
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from .account import Account
|
||||
|
||||
|
||||
@@ -14,9 +10,9 @@ def backtest(start_time, end_time, trade_strategy, trade_env, benchmark, account
|
||||
trade_env.reset(start_time=start_time, end_time=end_time, trade_account=trade_account)
|
||||
trade_strategy.reset(start_time=start_time, end_time=end_time)
|
||||
|
||||
trade_state = trade_env.get_init_state()
|
||||
_execute_state = trade_env.get_init_state()
|
||||
while not trade_env.finished():
|
||||
_order_list = trade_strategy.generate_order_list(**trade_state)
|
||||
trade_state, trade_info = trade_env.execute(_order_list)
|
||||
_order_list = trade_strategy.generate_order_list(_execute_state)
|
||||
_execute_state = trade_env.execute(_order_list)
|
||||
|
||||
return trade_env.get_report()
|
||||
|
||||
@@ -11,7 +11,7 @@ import pandas as pd
|
||||
from ...data.data import D
|
||||
from ...data.dataset.utils import get_level_index
|
||||
from ...config import C, REG_CN
|
||||
from ...utils import sample_feature
|
||||
from ...utils.sample import sample_feature
|
||||
from ...log import get_module_logger
|
||||
from .order import Order
|
||||
|
||||
|
||||
@@ -1,19 +1,34 @@
|
||||
import re
|
||||
import json
|
||||
import copy
|
||||
import warnings
|
||||
import pathlib
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from typing import Tuple, List, Union, Optional, Callable
|
||||
from ...data.data import Cal
|
||||
from ...utils import get_sample_freq_calendar, parse_freq
|
||||
from .position import Position
|
||||
from ...strategy.base import BaseStrategy
|
||||
from ...utils import init_instance_by_config
|
||||
from ...utils.sample import get_sample_freq_calendar, parse_freq
|
||||
from .report import Report
|
||||
from .order import Order
|
||||
from .account import Account
|
||||
from .exchange import Exchange
|
||||
|
||||
|
||||
class BaseTradeCalendar:
|
||||
def __init__(self, step_bar, start_time=None, end_time=None, **kwargs):
|
||||
def __init__(
|
||||
self, step_bar: str, start_time: Union[str, pd.Timestamp] = None, end_time: Union[str, pd.Timestamp] = None
|
||||
):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
step_bar : str
|
||||
frequency of each trading step bar
|
||||
start_time : Union[str, pd.Timestamp], optional
|
||||
start time of trading, by default None
|
||||
If `start_time` is None, it must be reset before trading.
|
||||
end_time : Union[str, pd.Timestamp], optional
|
||||
end time of trading, by default None
|
||||
If `end_time` is None, it must be reset before trading.
|
||||
"""
|
||||
|
||||
self.step_bar = step_bar
|
||||
self.reset(start_time=start_time, end_time=end_time)
|
||||
|
||||
@@ -27,10 +42,9 @@ class BaseTradeCalendar:
|
||||
if self.start_time and self.end_time:
|
||||
_calendar, freq, freq_sam = get_sample_freq_calendar(freq=self.step_bar)
|
||||
self.calendar = _calendar
|
||||
_start_time, _end_time, _start_index, _end_index = Cal.locate_index(
|
||||
_, _, _start_index, _end_index = Cal.locate_index(
|
||||
self.start_time, self.end_time, freq=freq, freq_sam=freq_sam
|
||||
)
|
||||
_trade_calendar = self.calendar[_start_index : _end_index + 1]
|
||||
self.start_index = _start_index
|
||||
self.end_index = _end_index
|
||||
self.trade_len = _end_index - _start_index + 1
|
||||
@@ -45,6 +59,8 @@ class BaseTradeCalendar:
|
||||
for k, v in kwargs.items():
|
||||
if hasattr(self, k):
|
||||
setattr(self, k, v)
|
||||
else:
|
||||
warnings.warn(f"reser error, attribute {k} is not found!")
|
||||
|
||||
def _get_calendar_time(self, trade_index=1, shift=0):
|
||||
trade_index = trade_index - shift
|
||||
@@ -55,34 +71,43 @@ class BaseTradeCalendar:
|
||||
return self.trade_index >= self.trade_len - 1
|
||||
|
||||
def step(self):
|
||||
if self.finished():
|
||||
raise RuntimeError(f"this env has completed its task, please reset it if you want to call it!")
|
||||
self.trade_index = self.trade_index + 1
|
||||
|
||||
|
||||
class BaseEnv(BaseTradeCalendar):
|
||||
"""
|
||||
# Strategy framework document
|
||||
|
||||
class Env(BaseEnv):
|
||||
"""
|
||||
class BaseExecutor(BaseTradeCalendar):
|
||||
"""Base executor for trading"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
step_bar,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
trade_account=None,
|
||||
generate_report=False,
|
||||
verbose=False,
|
||||
step_bar: str,
|
||||
start_time: Union[str, pd.Timestamp] = None,
|
||||
end_time: Union[str, pd.Timestamp] = None,
|
||||
trade_account: Account = None,
|
||||
generate_report: bool = False,
|
||||
verbose: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
self.generate_report = generate_report
|
||||
self.verbose = verbose
|
||||
super(BaseEnv, self).__init__(
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
trade_account : Account, optional
|
||||
trade account for trading, by default None
|
||||
If `trade_account` is None, it must be reset before trading
|
||||
generate_report : bool, optional
|
||||
whether to generate report, by default False
|
||||
verbose : bool, optional
|
||||
whether to print log, by default False
|
||||
"""
|
||||
super(BaseExecutor, self).__init__(
|
||||
step_bar=step_bar, start_time=start_time, end_time=end_time, trade_account=trade_account, **kwargs
|
||||
)
|
||||
self.generate_report = generate_report
|
||||
self.verbose = verbose
|
||||
|
||||
def reset(self, trade_account=None, **kwargs):
|
||||
super(BaseEnv, self).reset(**kwargs)
|
||||
super(BaseExecutor, self).reset(**kwargs)
|
||||
if trade_account:
|
||||
self.trade_account = trade_account
|
||||
self.trade_account.reset(freq=self.step_bar, report=Report(), positions={})
|
||||
@@ -101,23 +126,31 @@ class BaseEnv(BaseTradeCalendar):
|
||||
raise NotImplementedError("get_report is not implemented!")
|
||||
|
||||
|
||||
class SplitEnv(BaseEnv):
|
||||
class SplitExecutor(BaseExecutor):
|
||||
def __init__(
|
||||
self,
|
||||
step_bar,
|
||||
sub_env,
|
||||
sub_strategy,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
trade_account=None,
|
||||
trade_exchange=None,
|
||||
generate_report=False,
|
||||
verbose=False,
|
||||
step_bar: str,
|
||||
sub_env: Union[BaseExecutor, dict],
|
||||
sub_strategy: Union[BaseStrategy, dict],
|
||||
start_time: Union[str, pd.Timestamp] = None,
|
||||
end_time: Union[str, pd.Timestamp] = None,
|
||||
trade_account: Account = None,
|
||||
trade_exchange: Exchange = None,
|
||||
generate_report: bool = False,
|
||||
verbose: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
self.sub_env = sub_env
|
||||
self.sub_strategy = sub_strategy
|
||||
super(SplitEnv, self).__init__(
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
sub_env : BaseExecutor
|
||||
trading env in each trading bar.
|
||||
sub_strategy : BaseStrategy
|
||||
trading strategy in each trading bar
|
||||
trade_exchange : Exchange
|
||||
exchange that provides market info
|
||||
"""
|
||||
super(SplitExecutor, self).__init__(
|
||||
step_bar=step_bar,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
@@ -127,28 +160,26 @@ class SplitEnv(BaseEnv):
|
||||
verbose=verbose,
|
||||
**kwargs,
|
||||
)
|
||||
self.sub_env = init_instance_by_config(sub_env, accept_types=BaseExecutor)
|
||||
self.sub_strategy = init_instance_by_config(sub_strategy, accept_types=BaseStrategy)
|
||||
|
||||
def reset(self, trade_account=None, trade_exchange=None, **kwargs):
|
||||
super(SplitEnv, self).reset(trade_account=trade_account, **kwargs)
|
||||
|
||||
super(SplitExecutor, self).reset(trade_account=trade_account, **kwargs)
|
||||
if trade_account:
|
||||
self.sub_env.reset(trade_account=copy.copy(trade_account))
|
||||
if trade_exchange:
|
||||
self.trade_exchange = trade_exchange
|
||||
|
||||
def execute(self, order_list, **kwargs):
|
||||
if self.finished():
|
||||
raise StopIteration(f"this env has completed its task, please reset it if you want to call it!")
|
||||
# if self.track:
|
||||
# yield action
|
||||
# episode_reward = 0
|
||||
super(SplitEnv, self).step()
|
||||
def execute(self, order_list):
|
||||
super(SplitExecutor, self).step()
|
||||
trade_start_time, trade_end_time = self._get_calendar_time(self.trade_index)
|
||||
self.sub_env.reset(start_time=trade_start_time, end_time=trade_end_time)
|
||||
self.sub_strategy.reset(start_time=trade_start_time, end_time=trade_end_time, trade_order_list=order_list)
|
||||
trade_state = self.sub_env.get_init_state()
|
||||
_execute_state = self.sub_env.get_init_state()
|
||||
while not self.sub_env.finished():
|
||||
_order_list = self.sub_strategy.generate_order_list(**trade_state)
|
||||
trade_state, trade_info = self.sub_env.execute(order_list=_order_list)
|
||||
_order_list = self.sub_strategy.generate_order_list(_execute_state)
|
||||
_execute_state = self.sub_env.execute(order_list=_order_list)
|
||||
|
||||
self.trade_account.update_bar_end(
|
||||
trade_start_time=trade_start_time,
|
||||
@@ -156,9 +187,8 @@ class SplitEnv(BaseEnv):
|
||||
trade_exchange=self.trade_exchange,
|
||||
update_report=self.generate_report,
|
||||
)
|
||||
_obs = {"current": self.trade_account.current}
|
||||
_info = {}
|
||||
return _obs, _info
|
||||
_execute_state = {"current": self.trade_account.current}
|
||||
return _execute_state
|
||||
|
||||
def get_report(self):
|
||||
sub_env_report_dict = self.sub_env.get_report()
|
||||
@@ -167,12 +197,10 @@ class SplitEnv(BaseEnv):
|
||||
_positions = self.trade_account.get_positions()
|
||||
_count, _freq = parse_freq(self.step_bar)
|
||||
sub_env_report_dict.update({f"{_count}{_freq}": (_report, _positions)})
|
||||
return sub_env_report_dict
|
||||
else:
|
||||
return sub_env_report_dict
|
||||
return sub_env_report_dict
|
||||
|
||||
|
||||
class SimulatorEnv(BaseEnv):
|
||||
class SimulatorExecutor(BaseExecutor):
|
||||
def __init__(
|
||||
self,
|
||||
step_bar,
|
||||
@@ -184,7 +212,13 @@ class SimulatorEnv(BaseEnv):
|
||||
verbose=False,
|
||||
**kwargs,
|
||||
):
|
||||
super(SimulatorEnv, self).__init__(
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
trade_exchange : Exchange
|
||||
exchange that provides market info
|
||||
"""
|
||||
super(SimulatorExecutor, self).__init__(
|
||||
step_bar=step_bar,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
@@ -196,17 +230,12 @@ class SimulatorEnv(BaseEnv):
|
||||
)
|
||||
|
||||
def reset(self, trade_exchange=None, **kwargs):
|
||||
super(SimulatorEnv, self).reset(**kwargs)
|
||||
super(SimulatorExecutor, self).reset(**kwargs)
|
||||
if trade_exchange:
|
||||
self.trade_exchange = trade_exchange
|
||||
|
||||
def execute(self, order_list, **kwargs):
|
||||
"""
|
||||
Return: obs, done, info
|
||||
"""
|
||||
if self.finished():
|
||||
raise StopIteration(f"this env has completed its task, please reset it if you want to call it!")
|
||||
super(SimulatorEnv, self).step()
|
||||
def execute(self, order_list):
|
||||
super(SimulatorExecutor, self).step()
|
||||
trade_start_time, trade_end_time = self._get_calendar_time(self.trade_index)
|
||||
trade_info = []
|
||||
for order in order_list:
|
||||
@@ -219,21 +248,25 @@ class SimulatorEnv(BaseEnv):
|
||||
if self.verbose:
|
||||
if order.direction == Order.SELL: # sell
|
||||
print(
|
||||
"[I {:%Y-%m-%d}]: sell {}, price {:.2f}, amount {}, value {:.2f}.".format(
|
||||
"[I {:%Y-%m-%d}]: sell {}, price {:.2f}, amount {}, deal_amount {}, factor {}, value {:.2f}.".format(
|
||||
trade_start_time,
|
||||
order.stock_id,
|
||||
trade_price,
|
||||
order.amount,
|
||||
order.deal_amount,
|
||||
order.factor,
|
||||
trade_val,
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"[I {:%Y-%m-%d}]: buy {}, price {:.2f}, amount {}, value {:.2f}.".format(
|
||||
"[I {:%Y-%m-%d}]: buy {}, price {:.2f}, amount {}, deal_amount {}, factor {}, value {:.2f}.".format(
|
||||
trade_start_time,
|
||||
order.stock_id,
|
||||
trade_price,
|
||||
order.amount,
|
||||
order.deal_amount,
|
||||
order.factor,
|
||||
trade_val,
|
||||
)
|
||||
)
|
||||
@@ -249,9 +282,8 @@ class SimulatorEnv(BaseEnv):
|
||||
trade_exchange=self.trade_exchange,
|
||||
update_report=self.generate_report,
|
||||
)
|
||||
_obs = {"current": self.trade_account.current}
|
||||
_info = {"trade_info": trade_info}
|
||||
return _obs, _info
|
||||
_execute_state = {"current": self.trade_account.current, "trade_info": trade_info}
|
||||
return _execute_state
|
||||
|
||||
def get_report(self):
|
||||
if self.generate_report:
|
||||
@@ -1,16 +0,0 @@
|
||||
class BaseInterpreter:
|
||||
@staticmethod
|
||||
def interpret(**kwargs):
|
||||
raise NotImplementedError("interpret is not implemented!")
|
||||
|
||||
|
||||
class ActionInterpreter:
|
||||
@staticmethod
|
||||
def interpret(action, **kwargs):
|
||||
return action
|
||||
|
||||
|
||||
class StateInterpreter:
|
||||
@staticmethod
|
||||
def interpret(state, **kwargs):
|
||||
return state
|
||||
@@ -10,6 +10,7 @@ import warnings
|
||||
from ..log import get_module_logger
|
||||
from .backtest import get_exchange, backtest as backtest_func
|
||||
from ..utils import get_date_range
|
||||
from ..utils.sample import parse_freq
|
||||
|
||||
from ..data import D
|
||||
from ..config import C
|
||||
@@ -19,7 +20,7 @@ from ..data.dataset.utils import get_level_index
|
||||
logger = get_module_logger("Evaluate")
|
||||
|
||||
|
||||
def risk_analysis(r, N=252):
|
||||
def risk_analysis(r, N: int = None, freq: str = None):
|
||||
"""Risk Analysis
|
||||
|
||||
Parameters
|
||||
@@ -27,8 +28,26 @@ def risk_analysis(r, N=252):
|
||||
r : pandas.Series
|
||||
daily return series.
|
||||
N: int
|
||||
scaler for annualizing information_ratio (day: 250, week: 50, month: 12).
|
||||
scaler for annualizing information_ratio (day: 250, week: 50, month: 12), at least one of `N` and `freq` should exist
|
||||
freq: str
|
||||
analysis frequency used for calculating the scaler, at least one of `N` and `freq` should exist
|
||||
"""
|
||||
|
||||
def cal_risk_analysis_scaler(freq):
|
||||
_count, _freq = parse_freq(freq)
|
||||
_freq_scaler = {
|
||||
"minute": 240 * 250,
|
||||
"day": 250,
|
||||
"week": 50,
|
||||
"month": 12,
|
||||
}
|
||||
return _count * _freq_scaler[_freq]
|
||||
|
||||
if N is None and freq is None:
|
||||
raise ValueError("at least one of `N` and `freq` should exist")
|
||||
if N is None:
|
||||
N = cal_risk_analysis_scaler(freq)
|
||||
|
||||
mean = r.mean()
|
||||
std = r.std(ddof=1)
|
||||
annualized_return = mean * N
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
|
||||
import re
|
||||
import json
|
||||
import copy
|
||||
import pathlib
|
||||
import pandas as pd
|
||||
from ...data import D
|
||||
from ...utils import get_date_in_file_name
|
||||
from ...utils import get_pre_trading_date
|
||||
from ..backtest.order import Order
|
||||
|
||||
|
||||
class BaseExecutor:
|
||||
"""
|
||||
# Strategy framework document
|
||||
|
||||
class Executor(BaseExecutor):
|
||||
"""
|
||||
|
||||
def execute(self, trade_account, order_list, trade_date):
|
||||
"""
|
||||
return the executed result (trade_info) after trading at trade_date.
|
||||
NOTICE: trade_account will not be modified after executing.
|
||||
Parameter
|
||||
---------
|
||||
trade_account : Account()
|
||||
order_list : list
|
||||
[Order()]
|
||||
trade_date : pd.Timestamp
|
||||
Return
|
||||
---------
|
||||
trade_info : list
|
||||
[Order(), float, float, float]
|
||||
"""
|
||||
raise NotImplementedError("get_execute_result for this model is not implemented.")
|
||||
|
||||
def save_executed_file_from_trade_info(self, trade_info, user_path, trade_date):
|
||||
"""
|
||||
Save the trade_info to the .csv transaction file in disk
|
||||
the columns of result file is
|
||||
['date', 'stock_id', 'direction', 'trade_val', 'trade_cost', 'trade_price', 'factor']
|
||||
Parameter
|
||||
---------
|
||||
trade_info : list of [Order(), float, float, float]
|
||||
(order, trade_val, trade_cost, trade_price), trade_info with out factor
|
||||
user_path: str / pathlib.Path()
|
||||
the sub folder to save user data
|
||||
|
||||
transaction_path : string / pathlib.Path()
|
||||
"""
|
||||
YYYY, MM, DD = str(trade_date.date()).split("-")
|
||||
folder_path = pathlib.Path(user_path) / "trade" / YYYY / MM
|
||||
if not folder_path.exists():
|
||||
folder_path.mkdir(parents=True)
|
||||
transaction_path = folder_path / "transaction_{}.csv".format(str(trade_date.date()))
|
||||
columns = [
|
||||
"date",
|
||||
"stock_id",
|
||||
"direction",
|
||||
"amount",
|
||||
"trade_val",
|
||||
"trade_cost",
|
||||
"trade_price",
|
||||
"factor",
|
||||
]
|
||||
data = []
|
||||
for [order, trade_val, trade_cost, trade_price] in trade_info:
|
||||
data.append(
|
||||
[
|
||||
trade_date,
|
||||
order.stock_id,
|
||||
order.direction,
|
||||
order.amount,
|
||||
trade_val,
|
||||
trade_cost,
|
||||
trade_price,
|
||||
order.factor,
|
||||
]
|
||||
)
|
||||
df = pd.DataFrame(data, columns=columns)
|
||||
df.to_csv(transaction_path, index=False)
|
||||
|
||||
def load_trade_info_from_executed_file(self, user_path, trade_date):
|
||||
YYYY, MM, DD = str(trade_date.date()).split("-")
|
||||
file_path = pathlib.Path(user_path) / "trade" / YYYY / MM / "transaction_{}.csv".format(str(trade_date.date()))
|
||||
if not file_path.exists():
|
||||
raise ValueError("File {} not exists!".format(file_path))
|
||||
|
||||
filedate = get_date_in_file_name(file_path)
|
||||
transaction = pd.read_csv(file_path)
|
||||
trade_info = []
|
||||
for i in range(len(transaction)):
|
||||
date = transaction.loc[i]["date"]
|
||||
if not date == filedate:
|
||||
continue
|
||||
# raise ValueError("date in transaction file {} not equal to it's file date{}".format(date, filedate))
|
||||
order = Order(
|
||||
stock_id=transaction.loc[i]["stock_id"],
|
||||
amount=transaction.loc[i]["amount"],
|
||||
trade_date=transaction.loc[i]["date"],
|
||||
direction=transaction.loc[i]["direction"],
|
||||
factor=transaction.loc[i]["factor"],
|
||||
)
|
||||
trade_val = transaction.loc[i]["trade_val"]
|
||||
trade_cost = transaction.loc[i]["trade_cost"]
|
||||
trade_price = transaction.loc[i]["trade_price"]
|
||||
trade_info.append([order, trade_val, trade_cost, trade_price])
|
||||
return trade_info
|
||||
|
||||
|
||||
class SimulatorExecutor(BaseExecutor):
|
||||
def __init__(self, trade_exchange, verbose=False):
|
||||
self.trade_exchange = trade_exchange
|
||||
self.verbose = verbose
|
||||
self.order_list = []
|
||||
|
||||
def execute(self, trade_account, order_list, trade_date):
|
||||
"""
|
||||
execute the order list, do the trading wil exchange at date.
|
||||
Will not modify the trade_account.
|
||||
Parameter
|
||||
trade_account : Account()
|
||||
order_list : list
|
||||
list or orders
|
||||
trade_date : pd.Timestamp
|
||||
:return:
|
||||
trade_info : list of [Order(), float, float, float]
|
||||
(order, trade_val, trade_cost, trade_price), trade_info with out factor
|
||||
"""
|
||||
account = copy.deepcopy(trade_account)
|
||||
trade_info = []
|
||||
|
||||
for order in order_list:
|
||||
# check holding thresh is done in strategy
|
||||
# if order.direction==0: # sell order
|
||||
# # checking holding thresh limit for sell order
|
||||
# if trade_account.current.get_stock_count(order.stock_id) < thresh:
|
||||
# # can not sell this code
|
||||
# continue
|
||||
# is order executable
|
||||
# check order
|
||||
if self.trade_exchange.check_order(order) is True:
|
||||
# execute the order
|
||||
trade_val, trade_cost, trade_price = self.trade_exchange.deal_order(order, trade_account=account)
|
||||
trade_info.append([order, trade_val, trade_cost, trade_price])
|
||||
if self.verbose:
|
||||
if order.direction == Order.SELL: # sell
|
||||
print(
|
||||
"[I {:%Y-%m-%d}]: sell {}, price {:.2f}, amount {}, value {:.2f}.".format(
|
||||
trade_date,
|
||||
order.stock_id,
|
||||
trade_price,
|
||||
order.deal_amount,
|
||||
trade_val,
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"[I {:%Y-%m-%d}]: buy {}, price {:.2f}, amount {}, value {:.2f}.".format(
|
||||
trade_date,
|
||||
order.stock_id,
|
||||
trade_price,
|
||||
order.deal_amount,
|
||||
trade_val,
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
if self.verbose:
|
||||
print("[W {:%Y-%m-%d}]: {} wrong.".format(trade_date, order.stock_id))
|
||||
# do nothing
|
||||
pass
|
||||
return trade_info
|
||||
|
||||
|
||||
def save_score_series(score_series, user_path, trade_date):
|
||||
"""Save the score_series into a .csv file.
|
||||
The columns of saved file is
|
||||
[stock_id, score]
|
||||
|
||||
Parameter
|
||||
---------
|
||||
order_list: [Order()]
|
||||
list of Order()
|
||||
date: pd.Timestamp
|
||||
the date to save the order list
|
||||
user_path: str / pathlib.Path()
|
||||
the sub folder to save user data
|
||||
"""
|
||||
user_path = pathlib.Path(user_path)
|
||||
YYYY, MM, DD = str(trade_date.date()).split("-")
|
||||
folder_path = user_path / "score" / YYYY / MM
|
||||
if not folder_path.exists():
|
||||
folder_path.mkdir(parents=True)
|
||||
file_path = folder_path / "score_{}.csv".format(str(trade_date.date()))
|
||||
score_series.to_csv(file_path)
|
||||
|
||||
|
||||
def load_score_series(user_path, trade_date):
|
||||
"""Save the score_series into a .csv file.
|
||||
The columns of saved file is
|
||||
[stock_id, score]
|
||||
|
||||
Parameter
|
||||
---------
|
||||
order_list: [Order()]
|
||||
list of Order()
|
||||
date: pd.Timestamp
|
||||
the date to save the order list
|
||||
user_path: str / pathlib.Path()
|
||||
the sub folder to save user data
|
||||
"""
|
||||
user_path = pathlib.Path(user_path)
|
||||
YYYY, MM, DD = str(trade_date.date()).split("-")
|
||||
folder_path = user_path / "score" / YYYY / MM
|
||||
if not folder_path.exists():
|
||||
folder_path.mkdir(parents=True)
|
||||
file_path = folder_path / "score_{}.csv".format(str(trade_date.date()))
|
||||
score_series = pd.read_csv(file_path, index_col=0, header=None, names=["instrument", "score"])
|
||||
return score_series
|
||||
|
||||
|
||||
def save_order_list(order_list, user_path, trade_date):
|
||||
"""
|
||||
Save the order list into a json file.
|
||||
Will calculate the real amount in order according to factors at date.
|
||||
|
||||
The format in json file like
|
||||
{"sell": {"stock_id": amount, ...}
|
||||
,"buy": {"stock_id": amount, ...}}
|
||||
|
||||
:param
|
||||
order_list: [Order()]
|
||||
list of Order()
|
||||
date: pd.Timestamp
|
||||
the date to save the order list
|
||||
user_path: str / pathlib.Path()
|
||||
the sub folder to save user data
|
||||
"""
|
||||
user_path = pathlib.Path(user_path)
|
||||
YYYY, MM, DD = str(trade_date.date()).split("-")
|
||||
folder_path = user_path / "trade" / YYYY / MM
|
||||
if not folder_path.exists():
|
||||
folder_path.mkdir(parents=True)
|
||||
sell = {}
|
||||
buy = {}
|
||||
for order in order_list:
|
||||
if order.direction == 0: # sell
|
||||
sell[order.stock_id] = [order.amount, order.factor]
|
||||
else:
|
||||
buy[order.stock_id] = [order.amount, order.factor]
|
||||
order_dict = {"sell": sell, "buy": buy}
|
||||
file_path = folder_path / "orderlist_{}.json".format(str(trade_date.date()))
|
||||
with file_path.open("w") as fp:
|
||||
json.dump(order_dict, fp)
|
||||
|
||||
|
||||
def load_order_list(user_path, trade_date):
|
||||
user_path = pathlib.Path(user_path)
|
||||
YYYY, MM, DD = str(trade_date.date()).split("-")
|
||||
path = user_path / "trade" / YYYY / MM / "orderlist_{}.json".format(str(trade_date.date()))
|
||||
if not path.exists():
|
||||
raise ValueError("File {} not exists!".format(path))
|
||||
# get orders
|
||||
with path.open("r") as fp:
|
||||
order_dict = json.load(fp)
|
||||
order_list = []
|
||||
for stock_id in order_dict["sell"]:
|
||||
amount, factor = order_dict["sell"][stock_id]
|
||||
order = Order(
|
||||
stock_id=stock_id,
|
||||
amount=amount,
|
||||
trade_date=pd.Timestamp(trade_date),
|
||||
direction=Order.SELL,
|
||||
factor=factor,
|
||||
)
|
||||
order_list.append(order)
|
||||
for stock_id in order_dict["buy"]:
|
||||
amount, factor = order_dict["buy"][stock_id]
|
||||
order = Order(
|
||||
stock_id=stock_id,
|
||||
amount=amount,
|
||||
trade_date=pd.Timestamp(trade_date),
|
||||
direction=Order.BUY,
|
||||
factor=factor,
|
||||
)
|
||||
order_list.append(order)
|
||||
return order_list
|
||||
@@ -3,7 +3,7 @@ import warnings
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from ...utils import sample_feature
|
||||
from ...utils.sample import sample_feature
|
||||
from ...strategy.base import ModelStrategy
|
||||
from ..backtest.order import Order
|
||||
from .order_generator import OrderGenWInteract
|
||||
@@ -66,7 +66,7 @@ class TopkDropoutStrategy(ModelStrategy):
|
||||
if trade_exchange:
|
||||
self.trade_exchange = trade_exchange
|
||||
|
||||
def get_risk_degree(self, trade_index):
|
||||
def get_risk_degree(self, trade_index=None):
|
||||
"""get_risk_degree
|
||||
Return the proportion of your total value you will used in investment.
|
||||
Dynamically risk_degree will result in Market timing.
|
||||
@@ -74,7 +74,7 @@ class TopkDropoutStrategy(ModelStrategy):
|
||||
# It will use 95% amoutn of your total value by default
|
||||
return self.risk_degree
|
||||
|
||||
def generate_order_list(self, current, **kwargs):
|
||||
def generate_order_list(self, execute_state):
|
||||
super(TopkDropoutStrategy, self).step()
|
||||
trade_start_time, trade_end_time = self._get_calendar_time(self.trade_index)
|
||||
pred_start_time, pred_end_time = self._get_calendar_time(self.trade_index, shift=1)
|
||||
@@ -120,6 +120,7 @@ class TopkDropoutStrategy(ModelStrategy):
|
||||
def filter_stock(l):
|
||||
return l
|
||||
|
||||
current = execute_state.get("current")
|
||||
current_temp = copy.deepcopy(current)
|
||||
# generate order list for this adjust date
|
||||
sell_order_list = []
|
||||
@@ -163,6 +164,7 @@ class TopkDropoutStrategy(ModelStrategy):
|
||||
|
||||
# Get the stock list we really want to buy
|
||||
buy = today[: len(sell) + self.topk - len(last)]
|
||||
print("INTRANEL BAR", len(sell), len(sell) + self.topk - len(last), len(last))
|
||||
# print("flag", len(sell), len(buy), self.topk, len(last))
|
||||
for code in current_stock_list:
|
||||
if not self.trade_exchange.is_stock_tradable(
|
||||
@@ -175,13 +177,17 @@ class TopkDropoutStrategy(ModelStrategy):
|
||||
continue
|
||||
# sell order
|
||||
sell_amount = current_temp.get_stock_amount(code=code)
|
||||
factor = self.trade_exchange.get_factor(
|
||||
stock_id=code, start_time=trade_start_time, end_time=trade_end_time
|
||||
)
|
||||
# sell_amount = self.trade_exchange.round_amount_by_trade_unit(sell_amount, factor)
|
||||
sell_order = Order(
|
||||
stock_id=code,
|
||||
amount=sell_amount,
|
||||
start_time=trade_start_time,
|
||||
end_time=trade_end_time,
|
||||
direction=Order.SELL, # 0 for sell, 1 for buy
|
||||
factor=self.trade_exchange.get_factor(code, trade_start_time, trade_end_time),
|
||||
factor=factor,
|
||||
)
|
||||
# is order executable
|
||||
if self.trade_exchange.check_order(sell_order):
|
||||
@@ -228,19 +234,36 @@ class WeightStrategyBase(ModelStrategy):
|
||||
def __init__(
|
||||
self,
|
||||
step_bar,
|
||||
model,
|
||||
dataset,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
order_generator_cls_or_obj=OrderGenWInteract,
|
||||
trade_exchange=None,
|
||||
**kwargs,
|
||||
):
|
||||
super(WeightStrategyBase, self).__init__(step_bar, start_time, end_time)
|
||||
self.trade_exchange = trade_exchange
|
||||
super(WeightStrategyBase, self).__init__(
|
||||
step_bar, model, dataset, start_time, end_time, trade_exchange=trade_exchange, **kwargs
|
||||
)
|
||||
|
||||
if isinstance(order_generator_cls_or_obj, type):
|
||||
self.order_generator = order_generator_cls_or_obj()
|
||||
else:
|
||||
self.order_generator = order_generator_cls_or_obj
|
||||
|
||||
def reset(self, trade_exchange=None, **kwargs):
|
||||
super(WeightStrategyBase, self).reset(**kwargs)
|
||||
if trade_exchange:
|
||||
self.trade_exchange = trade_exchange
|
||||
|
||||
def get_risk_degree(self, trade_index=None):
|
||||
"""get_risk_degree
|
||||
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
|
||||
return 0.95
|
||||
|
||||
def generate_target_weight_position(self, score, current, trade_start_time, trade_end_time):
|
||||
"""
|
||||
Generate target position from score for this date and the current position.The cash is not considered in the position
|
||||
@@ -256,7 +279,7 @@ class WeightStrategyBase(ModelStrategy):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def generate_order_list(self, current, **kwargs):
|
||||
def generate_order_list(self, execute_state):
|
||||
"""
|
||||
Parameters
|
||||
-----------
|
||||
@@ -277,7 +300,8 @@ class WeightStrategyBase(ModelStrategy):
|
||||
pred_score = sample_feature(self.pred_scores, start_time=pred_start_time, end_time=pred_end_time, method="last")
|
||||
if pred_score is None:
|
||||
return []
|
||||
current_temp = copy.deepcopy(trade_account.current)
|
||||
current = execute_state.get("current")
|
||||
current_temp = copy.deepcopy(current)
|
||||
target_weight_position = self.generate_target_weight_position(
|
||||
score=pred_score, current=current_temp, trade_start_time=trade_start_time, trade_end_time=trade_end_time
|
||||
)
|
||||
|
||||
@@ -3,14 +3,15 @@ import warnings
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from ...utils import sample_feature
|
||||
|
||||
from ...utils.sample import sample_feature
|
||||
from ...data.data import D
|
||||
from ...data.dataset.utils import get_level_index
|
||||
from ...strategy.base import RuleStrategy, TradingEnhancement
|
||||
from ...data.dataset.utils import convert_index_format
|
||||
from ...strategy.base import RuleStrategy, OrderEnhancement
|
||||
from ..backtest.order import Order
|
||||
|
||||
|
||||
class TWAPStrategy(RuleStrategy, TradingEnhancement):
|
||||
class TWAPStrategy(RuleStrategy, OrderEnhancement):
|
||||
def __init__(
|
||||
self,
|
||||
step_bar,
|
||||
@@ -23,7 +24,7 @@ class TWAPStrategy(RuleStrategy, TradingEnhancement):
|
||||
|
||||
def reset(self, trade_order_list=None, trade_exchange=None, **kwargs):
|
||||
super(TWAPStrategy, self).reset(**kwargs)
|
||||
TradingEnhancement.reset(self, trade_order_list=trade_order_list)
|
||||
OrderEnhancement.reset(self, trade_order_list=trade_order_list)
|
||||
if trade_exchange:
|
||||
self.trade_exchange = trade_exchange
|
||||
if trade_order_list:
|
||||
@@ -31,7 +32,7 @@ class TWAPStrategy(RuleStrategy, TradingEnhancement):
|
||||
for order in self.trade_order_list:
|
||||
self.trade_amount[(order.stock_id, order.direction)] = order.amount
|
||||
|
||||
def generate_order_list(self, **kwargs):
|
||||
def generate_order_list(self, execute_state):
|
||||
super(TWAPStrategy, self).step()
|
||||
trade_start_time, trade_end_time = self._get_calendar_time(self.trade_index)
|
||||
order_list = []
|
||||
@@ -66,7 +67,7 @@ class TWAPStrategy(RuleStrategy, TradingEnhancement):
|
||||
return order_list
|
||||
|
||||
|
||||
class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||
class SBBStrategyBase(RuleStrategy, OrderEnhancement):
|
||||
"""
|
||||
(S)elect the (B)etter one among every two adjacent trading (B)ars to sell or buy.
|
||||
"""
|
||||
@@ -87,7 +88,7 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||
|
||||
def reset(self, trade_order_list=None, trade_exchange=None, **kwargs):
|
||||
super(SBBStrategyBase, self).reset(**kwargs)
|
||||
TradingEnhancement.reset(self, trade_order_list=trade_order_list)
|
||||
OrderEnhancement.reset(self, trade_order_list=trade_order_list)
|
||||
if trade_exchange:
|
||||
self.trade_exchange = trade_exchange
|
||||
if trade_order_list is not None:
|
||||
@@ -100,7 +101,7 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||
def _pred_price_trend(self, stock_id, pred_start_time=None, pred_end_time=None):
|
||||
raise NotImplementedError("pred_price_trend method is not implemented!")
|
||||
|
||||
def generate_order_list(self, **kwargs):
|
||||
def generate_order_list(self, execute_state):
|
||||
super(SBBStrategyBase, self).step()
|
||||
if not self.trade_order_list:
|
||||
return []
|
||||
@@ -109,7 +110,7 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||
order_list = []
|
||||
for order in self.trade_order_list:
|
||||
if self.trade_index % 2 == 1:
|
||||
_pred_trend = self._pred_price_trend(order.stock_id)
|
||||
_pred_trend = self._pred_price_trend(order.stock_id, pred_start_time, pred_end_time)
|
||||
else:
|
||||
_pred_trend = self.trade_trend[(order.stock_id, order.direction)]
|
||||
|
||||
@@ -127,7 +128,7 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||
_order_amount = self.trade_amount[(order.stock_id, order.direction)] / (
|
||||
self.trade_len - self.trade_index
|
||||
)
|
||||
if self.trade_amount[(order.stock_id, order.direction)] >= _amount_trade_unit:
|
||||
elif self.trade_amount[(order.stock_id, order.direction)] >= _amount_trade_unit:
|
||||
trade_unit_cnt = int(self.trade_amount[(order.stock_id, order.direction)] // _amount_trade_unit)
|
||||
_order_amount = (
|
||||
(trade_unit_cnt + self.trade_len - self.trade_index - 1)
|
||||
@@ -146,6 +147,7 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||
factor=order.factor,
|
||||
)
|
||||
order_list.append(_order)
|
||||
# print("DEBUG AMOUNT", _order_amount, self.trade_amount[(order.stock_id, order.direction)], _amount_trade_unit)
|
||||
else:
|
||||
_order_amount = None
|
||||
if _amount_trade_unit is None:
|
||||
@@ -154,12 +156,12 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||
* self.trade_amount[(order.stock_id, order.direction)]
|
||||
/ (self.trade_len - self.trade_index + 1)
|
||||
)
|
||||
if self.trade_amount[(order.stock_id, order.direction)] >= _amount_trade_unit:
|
||||
elif self.trade_amount[(order.stock_id, order.direction)] >= _amount_trade_unit:
|
||||
trade_unit_cnt = int(self.trade_amount[(order.stock_id, order.direction)] // _amount_trade_unit)
|
||||
_order_amount = (
|
||||
2
|
||||
* (trade_unit_cnt + self.trade_len - self.trade_index)
|
||||
(trade_unit_cnt + self.trade_len - self.trade_index)
|
||||
// (self.trade_len - self.trade_index + 1)
|
||||
* 2
|
||||
* _amount_trade_unit
|
||||
)
|
||||
if _order_amount:
|
||||
@@ -197,6 +199,7 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||
factor=order.factor,
|
||||
)
|
||||
order_list.append(_order)
|
||||
# print("DEBUG AMOUNT", _order_amount, self.trade_amount[(order.stock_id, order.direction)], _amount_trade_unit)
|
||||
if self.trade_index % 2 == 1:
|
||||
self.trade_trend[(order.stock_id, order.direction)] = _pred_trend
|
||||
|
||||
@@ -226,20 +229,15 @@ class SBBStrategyEMA(SBBStrategyBase):
|
||||
self.instruments = D.instruments(instruments)
|
||||
self.freq = freq
|
||||
|
||||
def _convert_index_format(self, df):
|
||||
if get_level_index(df, level="datetime") == 1:
|
||||
df = df.swaplevel().sort_index()
|
||||
return df
|
||||
|
||||
def _reset_trade_calendar(self, start_time=None, end_time=None):
|
||||
super(SBBStrategyEMA, self)._reset_trade_calendar(start_time=start_time, end_time=end_time)
|
||||
def reset(self, start_time=None, end_time=None, **kwargs):
|
||||
super(SBBStrategyEMA, self).reset(start_time=start_time, end_time=end_time, **kwargs)
|
||||
if self.start_time and self.end_time:
|
||||
fields = ["EMA($close, 10)-EMA($close, 20)"]
|
||||
signal_start_time, _ = self._get_calendar_time(trade_index=self.trade_index, shift=1)
|
||||
signal_df = D.features(
|
||||
self.instruments, fields, start_time=signal_start_time, end_time=self.end_time, freq=self.freq
|
||||
)
|
||||
signal_df = self._convert_index_format(signal_df)
|
||||
signal_df = convert_index_format(signal_df)
|
||||
signal_df.columns = ["signal"]
|
||||
self.signal = {}
|
||||
for stock_id, stock_val in signal_df.groupby(level="instrument"):
|
||||
|
||||
Reference in New Issue
Block a user