mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-15 16:56:54 +08:00
black format
This commit is contained in:
@@ -28,7 +28,7 @@ if __name__ == "__main__":
|
|||||||
###################################
|
###################################
|
||||||
# train model
|
# train model
|
||||||
###################################
|
###################################
|
||||||
|
|
||||||
data_handler_config = {
|
data_handler_config = {
|
||||||
"start_time": "2008-01-01",
|
"start_time": "2008-01-01",
|
||||||
"end_time": "2020-08-01",
|
"end_time": "2020-08-01",
|
||||||
@@ -70,7 +70,7 @@ if __name__ == "__main__":
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# model initialization
|
# model initialization
|
||||||
model = init_instance_by_config(task["model"])
|
model = init_instance_by_config(task["model"])
|
||||||
dataset = init_instance_by_config(task["dataset"])
|
dataset = init_instance_by_config(task["dataset"])
|
||||||
model.fit(dataset)
|
model.fit(dataset)
|
||||||
@@ -78,7 +78,7 @@ if __name__ == "__main__":
|
|||||||
trade_start_time = "2017-01-31"
|
trade_start_time = "2017-01-31"
|
||||||
trade_end_time = "2018-01-31"
|
trade_end_time = "2018-01-31"
|
||||||
|
|
||||||
backtest_config={
|
backtest_config = {
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"class": "TopkDropoutStrategy",
|
"class": "TopkDropoutStrategy",
|
||||||
"module_path": "qlib.contrib.strategy.model_strategy",
|
"module_path": "qlib.contrib.strategy.model_strategy",
|
||||||
@@ -90,7 +90,7 @@ if __name__ == "__main__":
|
|||||||
"n_drop": 5,
|
"n_drop": 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"env":{
|
"env": {
|
||||||
"class": "SplitEnv",
|
"class": "SplitEnv",
|
||||||
"module_path": "qlib.contrib.backtest.env",
|
"module_path": "qlib.contrib.backtest.env",
|
||||||
"kwargs": {
|
"kwargs": {
|
||||||
@@ -101,7 +101,7 @@ if __name__ == "__main__":
|
|||||||
"kwargs": {
|
"kwargs": {
|
||||||
"step_bar": "day",
|
"step_bar": "day",
|
||||||
"verbose": True,
|
"verbose": True,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"sub_strategy": {
|
"sub_strategy": {
|
||||||
"class": "SBBStrategyEMA",
|
"class": "SBBStrategyEMA",
|
||||||
@@ -110,11 +110,11 @@ if __name__ == "__main__":
|
|||||||
"step_bar": "day",
|
"step_bar": "day",
|
||||||
"freq": "day",
|
"freq": "day",
|
||||||
"instruments": "csi300",
|
"instruments": "csi300",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"backtest":{
|
"backtest": {
|
||||||
"start_time": trade_start_time,
|
"start_time": trade_start_time,
|
||||||
"end_time": trade_end_time,
|
"end_time": trade_end_time,
|
||||||
"verbose": False,
|
"verbose": False,
|
||||||
@@ -125,8 +125,14 @@ if __name__ == "__main__":
|
|||||||
"open_cost": 0.0005,
|
"open_cost": 0.0005,
|
||||||
"close_cost": 0.0015,
|
"close_cost": 0.0015,
|
||||||
"min_cost": 5,
|
"min_cost": 5,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
report_dict = backtest(
|
||||||
report_dict = backtest(start_time=trade_start_time, end_time=trade_end_time, **backtest_config, account=1e8, deal_price="$close", verbose=False)
|
start_time=trade_start_time,
|
||||||
|
end_time=trade_end_time,
|
||||||
|
**backtest_config,
|
||||||
|
account=1e8,
|
||||||
|
deal_price="$close",
|
||||||
|
verbose=False,
|
||||||
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def get_exchange(
|
|||||||
freq="day",
|
freq="day",
|
||||||
start_time=None,
|
start_time=None,
|
||||||
end_time=None,
|
end_time=None,
|
||||||
codes = "all",
|
codes="all",
|
||||||
subscribe_fields=[],
|
subscribe_fields=[],
|
||||||
open_cost=0.0015,
|
open_cost=0.0015,
|
||||||
close_cost=0.0025,
|
close_cost=0.0025,
|
||||||
@@ -89,6 +89,7 @@ def get_exchange(
|
|||||||
else:
|
else:
|
||||||
return init_instance_by_config(exchange, accept_types=Exchange)
|
return init_instance_by_config(exchange, accept_types=Exchange)
|
||||||
|
|
||||||
|
|
||||||
def init_env_instance_by_config(env):
|
def init_env_instance_by_config(env):
|
||||||
if isinstance(env, dict):
|
if isinstance(env, dict):
|
||||||
env_config = copy.copy(env)
|
env_config = copy.copy(env)
|
||||||
@@ -103,6 +104,7 @@ def init_env_instance_by_config(env):
|
|||||||
else:
|
else:
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
def setup_exchange(root_instance, trade_exchange=None, force=False):
|
def setup_exchange(root_instance, trade_exchange=None, force=False):
|
||||||
if "trade_exchange" in inspect.getfullargspec(root_instance.__class__).args:
|
if "trade_exchange" in inspect.getfullargspec(root_instance.__class__).args:
|
||||||
if force:
|
if force:
|
||||||
@@ -114,8 +116,8 @@ def setup_exchange(root_instance, trade_exchange=None, force=False):
|
|||||||
setup_exchange(root_instance.sub_env, trade_exchange)
|
setup_exchange(root_instance.sub_env, trade_exchange)
|
||||||
if hasattr(root_instance, "sub_strategy"):
|
if hasattr(root_instance, "sub_strategy"):
|
||||||
setup_exchange(root_instance.sub_strategy, trade_exchange)
|
setup_exchange(root_instance.sub_strategy, trade_exchange)
|
||||||
|
|
||||||
|
|
||||||
def backtest(start_time, end_time, strategy, env, benchmark=None, account=1e9, **kwargs):
|
def backtest(start_time, end_time, strategy, env, benchmark=None, account=1e9, **kwargs):
|
||||||
trade_strategy = init_instance_by_config(strategy)
|
trade_strategy = init_instance_by_config(strategy)
|
||||||
trade_env = init_env_instance_by_config(env)
|
trade_env = init_env_instance_by_config(env)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from .order import Order
|
|||||||
from ...utils import parse_freq, sample_feature
|
from ...utils import parse_freq, sample_feature
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
rtn & earning in the Account
|
rtn & earning in the Account
|
||||||
rtn:
|
rtn:
|
||||||
@@ -87,7 +86,7 @@ class Account:
|
|||||||
elif norm_freq == "minute":
|
elif norm_freq == "minute":
|
||||||
_temp_result = D.features(_codes, fields, start_time, end_time, freq="minute", disk_cache=1)
|
_temp_result = D.features(_codes, fields, start_time, end_time, freq="minute", disk_cache=1)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"benchmark freq {freq} is not supported")
|
raise ValueError(f"benchmark freq {freq} is not supported")
|
||||||
if len(_temp_result) == 0:
|
if len(_temp_result) == 0:
|
||||||
raise ValueError(f"The benchmark {_codes} does not exist. Please provide the right benchmark")
|
raise ValueError(f"The benchmark {_codes} does not exist. Please provide the right benchmark")
|
||||||
return _temp_result.groupby(level="datetime")[_temp_result.columns.tolist()[0]].mean().fillna(0)
|
return _temp_result.groupby(level="datetime")[_temp_result.columns.tolist()[0]].mean().fillna(0)
|
||||||
@@ -95,20 +94,20 @@ class Account:
|
|||||||
def _sample_benchmark(self, bench, trade_start_time, trade_end_time):
|
def _sample_benchmark(self, bench, trade_start_time, trade_end_time):
|
||||||
def cal_change(x):
|
def cal_change(x):
|
||||||
return x.prod() - 1
|
return x.prod() - 1
|
||||||
|
|
||||||
return sample_feature(bench, trade_start_time, trade_end_time, method=cal_change)
|
return sample_feature(bench, trade_start_time, trade_end_time, method=cal_change)
|
||||||
|
|
||||||
def reset(self, benchmark=None, freq=None,**kwargs):
|
def reset(self, benchmark=None, freq=None, **kwargs):
|
||||||
if benchmark:
|
if benchmark:
|
||||||
self.benchmark = benchmark
|
self.benchmark = benchmark
|
||||||
if freq:
|
if freq:
|
||||||
self.freq = freq
|
self.freq = freq
|
||||||
if self.freq and self.benchmark and (freq or benchmark)
|
if self.freq and self.benchmark and (freq or benchmark):
|
||||||
self.bench = self._cal_benchmark(self.benchmark, self.start_time, self.end_time, self.freq)
|
self.bench = self._cal_benchmark(self.benchmark, self.start_time, self.end_time, self.freq)
|
||||||
|
|
||||||
for k, v in kwargs:
|
for k, v in kwargs:
|
||||||
if hasattr(k):
|
if hasattr(k):
|
||||||
setattr(k, v)
|
setattr(k, v)
|
||||||
|
|
||||||
|
|
||||||
def get_positions(self):
|
def get_positions(self):
|
||||||
return self.positions
|
return self.positions
|
||||||
@@ -203,7 +202,7 @@ class Account:
|
|||||||
turnover_rate=self.to / last_account_value,
|
turnover_rate=self.to / last_account_value,
|
||||||
cost_rate=self.ct / last_account_value,
|
cost_rate=self.ct / last_account_value,
|
||||||
stock_value=now_stock_value,
|
stock_value=now_stock_value,
|
||||||
bench_value=self._sample_benchmark(self.bench, trade_start_time, trade_end_time)
|
bench_value=self._sample_benchmark(self.bench, trade_start_time, trade_end_time),
|
||||||
)
|
)
|
||||||
# set now_account_value to position
|
# set now_account_value to position
|
||||||
self.current.position["now_account_value"] = now_account_value
|
self.current.position["now_account_value"] = now_account_value
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import pandas as pd
|
|||||||
|
|
||||||
from .account import Account
|
from .account import Account
|
||||||
|
|
||||||
|
|
||||||
def backtest(start_time, end_time, trade_strategy, trade_env, benchmark, account):
|
def backtest(start_time, end_time, trade_strategy, trade_env, benchmark, account):
|
||||||
|
|
||||||
trade_account = Account(init_cash=account, benchmark=benchmark, start_time=start_time, end_time=end_time)
|
trade_account = Account(init_cash=account, benchmark=benchmark, start_time=start_time, end_time=end_time)
|
||||||
@@ -17,10 +18,9 @@ def backtest(start_time, end_time, trade_strategy, trade_env, benchmark, account
|
|||||||
while not trade_env.finished():
|
while not trade_env.finished():
|
||||||
_order_list = trade_strategy.generate_order_list(**trade_state)
|
_order_list = trade_strategy.generate_order_list(**trade_state)
|
||||||
trade_state, trade_info = trade_env.execute(_order_list)
|
trade_state, trade_info = trade_env.execute(_order_list)
|
||||||
|
|
||||||
report_df = trade_account.report.generate_report_dataframe()
|
report_df = trade_account.report.generate_report_dataframe()
|
||||||
positions = trade_account.get_positions()
|
positions = trade_account.get_positions()
|
||||||
report_dict = {"report_df": report_df, "positions": positions}
|
report_dict = {"report_df": report_df, "positions": positions}
|
||||||
|
|
||||||
return report_dict
|
return report_dict
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import copy
|
import copy
|
||||||
@@ -14,15 +12,8 @@ from .report import Report
|
|||||||
from .order import Order
|
from .order import Order
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTradeCalendar:
|
class BaseTradeCalendar:
|
||||||
def __init__(
|
def __init__(self, step_bar, start_time=None, end_time=None, **kwargs):
|
||||||
self,
|
|
||||||
step_bar,
|
|
||||||
start_time=None,
|
|
||||||
end_time=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
self.step_bar = step_bar
|
self.step_bar = step_bar
|
||||||
self.reset(start_time=start_time, end_time=end_time)
|
self.reset(start_time=start_time, end_time=end_time)
|
||||||
|
|
||||||
@@ -36,8 +27,10 @@ class BaseTradeCalendar:
|
|||||||
if self.start_time and self.end_time:
|
if self.start_time and self.end_time:
|
||||||
_calendar, freq, freq_sam = get_sample_freq_calendar(freq=self.step_bar)
|
_calendar, freq, freq_sam = get_sample_freq_calendar(freq=self.step_bar)
|
||||||
self.calendar = _calendar
|
self.calendar = _calendar
|
||||||
_start_time, _end_time, _start_index, _end_index = Cal.locate_index(self.start_time, self.end_time, freq=freq, freq_sam=freq_sam)
|
_start_time, _end_time, _start_index, _end_index = Cal.locate_index(
|
||||||
_trade_calendar = self.calendar[_start_index: _end_index + 1]
|
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.start_index = _start_index
|
||||||
self.end_index = _end_index
|
self.end_index = _end_index
|
||||||
self.trade_len = _end_index - _start_index + 1
|
self.trade_len = _end_index - _start_index + 1
|
||||||
@@ -52,7 +45,7 @@ class BaseTradeCalendar:
|
|||||||
for k, v in kwargs:
|
for k, v in kwargs:
|
||||||
if hasattr(self, k):
|
if hasattr(self, k):
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
def _get_calendar_time(self, trade_index=1, shift=0):
|
def _get_calendar_time(self, trade_index=1, shift=0):
|
||||||
trade_index = trade_index - shift
|
trade_index = trade_index - shift
|
||||||
calendar_index = self.start_index + trade_index
|
calendar_index = self.start_index + trade_index
|
||||||
@@ -64,6 +57,7 @@ class BaseTradeCalendar:
|
|||||||
def step(self):
|
def step(self):
|
||||||
self.trade_index = self.trade_index + 1
|
self.trade_index = self.trade_index + 1
|
||||||
|
|
||||||
|
|
||||||
class BaseEnv(BaseTradeCalendar):
|
class BaseEnv(BaseTradeCalendar):
|
||||||
"""
|
"""
|
||||||
# Strategy framework document
|
# Strategy framework document
|
||||||
@@ -83,8 +77,10 @@ class BaseEnv(BaseTradeCalendar):
|
|||||||
):
|
):
|
||||||
self.generate_report = update_report
|
self.generate_report = update_report
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
super(BaseEnv, self).__init__(step_bar=step_bar, start_time=start_time, end_time=end_time, trade_account=trade_account, **kwargs)
|
super(BaseEnv, self).__init__(
|
||||||
|
step_bar=step_bar, start_time=start_time, end_time=end_time, trade_account=trade_account, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def reset(self, trade_account=None, **kwargs):
|
def reset(self, trade_account=None, **kwargs):
|
||||||
super(BaseEnv, self).reset(**kwargs)
|
super(BaseEnv, self).reset(**kwargs)
|
||||||
if trade_account:
|
if trade_account:
|
||||||
@@ -94,7 +90,7 @@ class BaseEnv(BaseTradeCalendar):
|
|||||||
def get_init_state(self):
|
def get_init_state(self):
|
||||||
init_state = {"current": self.trade_account.current}
|
init_state = {"current": self.trade_account.current}
|
||||||
return init_state
|
return init_state
|
||||||
|
|
||||||
def execute(self, **kwargs):
|
def execute(self, **kwargs):
|
||||||
raise NotImplementedError("execute is not implemented!")
|
raise NotImplementedError("execute is not implemented!")
|
||||||
|
|
||||||
@@ -104,23 +100,32 @@ class BaseEnv(BaseTradeCalendar):
|
|||||||
def get_report(self):
|
def get_report(self):
|
||||||
raise NotImplementedError("get_report is not implemented!")
|
raise NotImplementedError("get_report is not implemented!")
|
||||||
|
|
||||||
|
|
||||||
class SplitEnv(BaseEnv):
|
class SplitEnv(BaseEnv):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
step_bar,
|
step_bar,
|
||||||
sub_env,
|
sub_env,
|
||||||
sub_strategy,
|
sub_strategy,
|
||||||
start_time=None,
|
start_time=None,
|
||||||
end_time=None,
|
end_time=None,
|
||||||
trade_account=None,
|
trade_account=None,
|
||||||
update_report=False,
|
update_report=False,
|
||||||
verbose=False,
|
verbose=False,
|
||||||
**kwargs
|
**kwargs,
|
||||||
):
|
):
|
||||||
self.sub_env = sub_env
|
self.sub_env = sub_env
|
||||||
self.sub_strategy = sub_strategy
|
self.sub_strategy = sub_strategy
|
||||||
super(SplitEnv, self).__init__(step_bar=step_bar, start_time=start_time, end_time=end_time, trade_account=trade_account, update_report=update_report, verbose=verbose, **kwargs)
|
super(SplitEnv, self).__init__(
|
||||||
|
step_bar=step_bar,
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=end_time,
|
||||||
|
trade_account=trade_account,
|
||||||
|
update_report=update_report,
|
||||||
|
verbose=verbose,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
def reset(self, trade_account=None, **kwargs):
|
def reset(self, trade_account=None, **kwargs):
|
||||||
super(SplitEnv, self).reset(trade_account=trade_account, **kwargs)
|
super(SplitEnv, self).reset(trade_account=trade_account, **kwargs)
|
||||||
if trade_account:
|
if trade_account:
|
||||||
@@ -129,9 +134,9 @@ class SplitEnv(BaseEnv):
|
|||||||
def execute(self, order_list, **kwargs):
|
def execute(self, order_list, **kwargs):
|
||||||
if self.finished():
|
if self.finished():
|
||||||
raise StopIteration(f"this env has completed its task, please reset it if you want to call it!")
|
raise StopIteration(f"this env has completed its task, please reset it if you want to call it!")
|
||||||
#if self.track:
|
# if self.track:
|
||||||
# yield action
|
# yield action
|
||||||
#episode_reward = 0
|
# episode_reward = 0
|
||||||
super(SplitEnv, self).step()
|
super(SplitEnv, self).step()
|
||||||
trade_start_time, trade_end_time = self._get_calendar_time(self.trade_index)
|
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_env.reset(start_time=trade_start_time, end_time=trade_end_time)
|
||||||
@@ -140,9 +145,11 @@ class SplitEnv(BaseEnv):
|
|||||||
while not self.sub_env.finished():
|
while not self.sub_env.finished():
|
||||||
_order_list = self.sub_strategy.generate_order_list(**trade_state)
|
_order_list = self.sub_strategy.generate_order_list(**trade_state)
|
||||||
trade_state, trade_info = self.sub_env.execute(order_list=_order_list)
|
trade_state, trade_info = self.sub_env.execute(order_list=_order_list)
|
||||||
|
|
||||||
if self.generate_report:
|
if self.generate_report:
|
||||||
self.trade_account.update_report(trade_start_time=trade_start_time, trade_end_time=trade_end_time, trade_exchange=self.trade_exchange)
|
self.trade_account.update_report(
|
||||||
|
trade_start_time=trade_start_time, trade_end_time=trade_end_time, trade_exchange=self.trade_exchange
|
||||||
|
)
|
||||||
_obs = {"current": self.trade_account.current}
|
_obs = {"current": self.trade_account.current}
|
||||||
_info = {}
|
_info = {}
|
||||||
return _obs, _info
|
return _obs, _info
|
||||||
@@ -150,31 +157,40 @@ class SplitEnv(BaseEnv):
|
|||||||
def get_report(self):
|
def get_report(self):
|
||||||
_report = self.trade_account.report.generate_report_dataframe() if self.generate_report else None
|
_report = self.trade_account.report.generate_report_dataframe() if self.generate_report else None
|
||||||
_positions = self.trade_account.get_positions() if self.generate_report else None
|
_positions = self.trade_account.get_positions() if self.generate_report else None
|
||||||
return [(_report,_positions), *sub_env.get_report()]
|
return [(_report, _positions), *sub_env.get_report()]
|
||||||
|
|
||||||
class SimulatorEnv(BaseEnv):
|
|
||||||
|
|
||||||
|
|
||||||
|
class SimulatorEnv(BaseEnv):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
step_bar,
|
step_bar,
|
||||||
start_time=None,
|
start_time=None,
|
||||||
end_time=None,
|
end_time=None,
|
||||||
trade_account=None,
|
trade_account=None,
|
||||||
trade_exchange=None,
|
trade_exchange=None,
|
||||||
update_report=False,
|
update_report=False,
|
||||||
verbose=False,
|
verbose=False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super(SimulatorEnv, self).__init__(step_bar=step_bar, start_time=start_time, end_time=end_time, trade_account=trade_account, trade_exchange=trade_exchange, update_report=update_report, verbose=verbose, **kwargs)
|
super(SimulatorEnv, self).__init__(
|
||||||
|
step_bar=step_bar,
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=end_time,
|
||||||
|
trade_account=trade_account,
|
||||||
|
trade_exchange=trade_exchange,
|
||||||
|
update_report=update_report,
|
||||||
|
verbose=verbose,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
def reset(self, trade_exchange=None, **kwargs):
|
def reset(self, trade_exchange=None, **kwargs):
|
||||||
super(SimulatorEnv, self).reset(**kwargs)
|
super(SimulatorEnv, self).reset(**kwargs)
|
||||||
if trade_exchange:
|
if trade_exchange:
|
||||||
self.trade_exchange=trade_exchange
|
self.trade_exchange = trade_exchange
|
||||||
|
|
||||||
def execute(self, order_list, **kwargs):
|
def execute(self, order_list, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return: obs, done, info
|
Return: obs, done, info
|
||||||
"""
|
"""
|
||||||
if self.finished():
|
if self.finished():
|
||||||
raise StopIteration(f"this env has completed its task, please reset it if you want to call it!")
|
raise StopIteration(f"this env has completed its task, please reset it if you want to call it!")
|
||||||
@@ -184,7 +200,9 @@ class SimulatorEnv(BaseEnv):
|
|||||||
for order in order_list:
|
for order in order_list:
|
||||||
if self.trade_exchange.check_order(order) is True:
|
if self.trade_exchange.check_order(order) is True:
|
||||||
# execute the order
|
# execute the order
|
||||||
trade_val, trade_cost, trade_price = self.trade_exchange.deal_order(order, trade_account=self.trade_account)
|
trade_val, trade_cost, trade_price = self.trade_exchange.deal_order(
|
||||||
|
order, trade_account=self.trade_account
|
||||||
|
)
|
||||||
trade_info.append((order, trade_val, trade_cost, trade_price))
|
trade_info.append((order, trade_val, trade_cost, trade_price))
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
if order.direction == Order.SELL: # sell
|
if order.direction == Order.SELL: # sell
|
||||||
@@ -214,7 +232,9 @@ class SimulatorEnv(BaseEnv):
|
|||||||
# do nothing
|
# do nothing
|
||||||
pass
|
pass
|
||||||
if self.generate_report:
|
if self.generate_report:
|
||||||
self.trade_account.update_report(trade_start_time=trade_start_time, trade_end_time=trade_end_time, trade_exchange=self.trade_exchange)
|
self.trade_account.update_report(
|
||||||
|
trade_start_time=trade_start_time, trade_end_time=trade_end_time, trade_exchange=self.trade_exchange
|
||||||
|
)
|
||||||
_obs = {"current": self.trade_account.current}
|
_obs = {"current": self.trade_account.current}
|
||||||
_info = {"trade_info": trade_info}
|
_info = {"trade_info": trade_info}
|
||||||
return _obs, _info
|
return _obs, _info
|
||||||
@@ -222,9 +242,4 @@ class SimulatorEnv(BaseEnv):
|
|||||||
def get_report(self):
|
def get_report(self):
|
||||||
_report = self.trade_account.report.generate_report_dataframe() if self.generate_report else None
|
_report = self.trade_account.report.generate_report_dataframe() if self.generate_report else None
|
||||||
_positions = self.trade_account.get_positions() if self.generate_report else None
|
_positions = self.trade_account.get_positions() if self.generate_report else None
|
||||||
return [
|
return [{"report": _report, "positions": _positions}]
|
||||||
{
|
|
||||||
"report": _report,
|
|
||||||
"positions": _positions
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from ...log import get_module_logger
|
|||||||
from .order import Order
|
from .order import Order
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Exchange:
|
class Exchange:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -101,14 +100,15 @@ class Exchange:
|
|||||||
self.min_cost = min_cost
|
self.min_cost = min_cost
|
||||||
self.limit_threshold = limit_threshold
|
self.limit_threshold = limit_threshold
|
||||||
|
|
||||||
|
|
||||||
self.extra_quote = extra_quote
|
self.extra_quote = extra_quote
|
||||||
self.set_quote(codes, start_time, end_time)
|
self.set_quote(codes, start_time, end_time)
|
||||||
|
|
||||||
def set_quote(self, codes, start_time, end_time):
|
def set_quote(self, codes, start_time, end_time):
|
||||||
if len(codes) == 0:
|
if len(codes) == 0:
|
||||||
codes = D.instruments()
|
codes = D.instruments()
|
||||||
self.quote = D.features(codes, self.all_fields, start_time, end_time, freq=self.freq, disk_cache=True).dropna(subset=["$close"])
|
self.quote = D.features(codes, self.all_fields, start_time, end_time, freq=self.freq, disk_cache=True).dropna(
|
||||||
|
subset=["$close"]
|
||||||
|
)
|
||||||
self.quote.columns = self.all_fields
|
self.quote.columns = self.all_fields
|
||||||
|
|
||||||
if self.quote[self.deal_price].isna().any():
|
if self.quote[self.deal_price].isna().any():
|
||||||
@@ -168,7 +168,6 @@ class Exchange:
|
|||||||
is limtited
|
is limtited
|
||||||
"""
|
"""
|
||||||
return sample_feature(self.quote[stock_id], start_time, end_time, fields="limit", method="all").iloc[0]
|
return sample_feature(self.quote[stock_id], start_time, end_time, fields="limit", method="all").iloc[0]
|
||||||
|
|
||||||
|
|
||||||
def check_stock_suspended(self, stock_id, start_time, end_time):
|
def check_stock_suspended(self, stock_id, start_time, end_time):
|
||||||
# is suspended
|
# is suspended
|
||||||
@@ -180,7 +179,9 @@ class Exchange:
|
|||||||
def is_stock_tradable(self, stock_id, start_time, end_time):
|
def is_stock_tradable(self, stock_id, start_time, end_time):
|
||||||
# check if stock can be traded
|
# check if stock can be traded
|
||||||
# same as check in check_order
|
# same as check in check_order
|
||||||
if self.check_stock_suspended(stock_id, start_time, end_time) or self.check_stock_limit(stock_id, start_time, end_time):
|
if self.check_stock_suspended(stock_id, start_time, end_time) or self.check_stock_limit(
|
||||||
|
stock_id, start_time, end_time
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
@@ -235,9 +236,13 @@ class Exchange:
|
|||||||
return sample_feature(self.quote[stock_id], start_time, end_time, fields="$close", method="last").iloc[0]
|
return sample_feature(self.quote[stock_id], start_time, end_time, fields="$close", method="last").iloc[0]
|
||||||
|
|
||||||
def get_deal_price(self, stock_id, start_time, end_time):
|
def get_deal_price(self, stock_id, start_time, end_time):
|
||||||
deal_price = sample_feature(self.quote[stock_id], start_time, end_time, fields=self.deal_price, method="last").iloc[0]
|
deal_price = sample_feature(
|
||||||
|
self.quote[stock_id], start_time, end_time, fields=self.deal_price, method="last"
|
||||||
|
).iloc[0]
|
||||||
if np.isclose(deal_price, 0.0) or np.isnan(deal_price):
|
if np.isclose(deal_price, 0.0) or np.isnan(deal_price):
|
||||||
self.logger.warning(f"(stock_id:{stock_id}, trade_time:{(start_time, end_time)}, {self.deal_price}): {deal_price}!!!")
|
self.logger.warning(
|
||||||
|
f"(stock_id:{stock_id}, trade_time:{(start_time, end_time)}, {self.deal_price}): {deal_price}!!!"
|
||||||
|
)
|
||||||
self.logger.warning(f"setting deal_price to close price")
|
self.logger.warning(f"setting deal_price to close price")
|
||||||
deal_price = self.get_close(stock_id, start_time, end_time)
|
deal_price = self.get_close(stock_id, start_time, end_time)
|
||||||
return deal_price
|
return deal_price
|
||||||
@@ -274,7 +279,9 @@ class Exchange:
|
|||||||
|
|
||||||
amount_dict = {}
|
amount_dict = {}
|
||||||
for stock_id in weight_position:
|
for stock_id in weight_position:
|
||||||
if weight_position[stock_id] > 0.0 and self.is_stock_tradable(stock_id=stock_id, start_time=start_time, end_time=end_time):
|
if weight_position[stock_id] > 0.0 and self.is_stock_tradable(
|
||||||
|
stock_id=stock_id, start_time=start_time, end_time=end_time
|
||||||
|
):
|
||||||
amount_dict[stock_id] = (
|
amount_dict[stock_id] = (
|
||||||
cash
|
cash
|
||||||
* weight_position[stock_id]
|
* weight_position[stock_id]
|
||||||
@@ -377,7 +384,10 @@ class Exchange:
|
|||||||
self.check_stock_suspended(stock_id=stock_id, start_time=start_time, end_time=end_time) is False
|
self.check_stock_suspended(stock_id=stock_id, start_time=start_time, end_time=end_time) is False
|
||||||
and self.check_stock_limit(stock_id=stock_id, start_time=start_time, end_time=end_time) is False
|
and self.check_stock_limit(stock_id=stock_id, start_time=start_time, end_time=end_time) is False
|
||||||
):
|
):
|
||||||
value += self.get_deal_price(stock_id=stock_id, start_time=start_time, end_time=end_time) * amount_dict[stock_id]
|
value += (
|
||||||
|
self.get_deal_price(stock_id=stock_id, start_time=start_time, end_time=end_time)
|
||||||
|
* amount_dict[stock_id]
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def round_amount_by_trade_unit(self, deal_amount, factor):
|
def round_amount_by_trade_unit(self, deal_amount, factor):
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
|
|
||||||
class BaseInterpreter:
|
class BaseInterpreter:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def interpret(**kwargs):
|
def interpret(**kwargs):
|
||||||
raise NotImplementedError("interpret is not implemented!")
|
raise NotImplementedError("interpret is not implemented!")
|
||||||
|
|
||||||
|
|
||||||
class ActionInterpreter:
|
class ActionInterpreter:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def interpret(action, **kwargs):
|
def interpret(action, **kwargs):
|
||||||
return action
|
return action
|
||||||
|
|
||||||
|
|
||||||
class StateInterpreter:
|
class StateInterpreter:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def interpret(state, **kwargs):
|
def interpret(state, **kwargs):
|
||||||
return state
|
return state
|
||||||
|
|||||||
@@ -45,16 +45,7 @@ class Report:
|
|||||||
bench_value=None,
|
bench_value=None,
|
||||||
):
|
):
|
||||||
# check data
|
# check data
|
||||||
if None in [
|
if None in [trade_time, account_value, cash, return_rate, turnover_rate, cost_rate, stock_value, bench_value]:
|
||||||
trade_time,
|
|
||||||
account_value,
|
|
||||||
cash,
|
|
||||||
return_rate,
|
|
||||||
turnover_rate,
|
|
||||||
cost_rate,
|
|
||||||
stock_value,
|
|
||||||
bench_value
|
|
||||||
]:
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"None in [trade_date, account_value, cash, return_rate, turnover_rate, cost_rate, stock_value, bench_value]"
|
"None in [trade_date, account_value, cash, return_rate, turnover_rate, cost_rate, stock_value, bench_value]"
|
||||||
)
|
)
|
||||||
@@ -108,5 +99,5 @@ class Report:
|
|||||||
turnover_rate=r.loc[trade_time]["turnover"],
|
turnover_rate=r.loc[trade_time]["turnover"],
|
||||||
cost_rate=r.loc[trade_time]["cost"],
|
cost_rate=r.loc[trade_time]["cost"],
|
||||||
stock_value=r.loc[trade_time]["value"],
|
stock_value=r.loc[trade_time]["value"],
|
||||||
bench_value=r.loc[trade_time]["bench"]
|
bench_value=r.loc[trade_time]["bench"],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ from .model_strategy import (
|
|||||||
WeightStrategyBase,
|
WeightStrategyBase,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .rule_strategy import(
|
from .rule_strategy import (
|
||||||
TWAPStrategy,
|
TWAPStrategy,
|
||||||
SBBStrategyBase,
|
SBBStrategyBase,
|
||||||
SBBStrategyEMA,
|
SBBStrategyEMA,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .cost_control import (
|
from .cost_control import SoftTopkStrategy
|
||||||
SoftTopkStrategy
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
else:
|
else:
|
||||||
strategy will make decision with the tradable state of the stock info and avoid buy and sell them.
|
strategy will make decision with the tradable state of the stock info and avoid buy and sell them.
|
||||||
"""
|
"""
|
||||||
super(TopkDropoutStrategy, self).__init__(step_bar, model, dataset, start_time, end_time, trade_exchange=trade_exchange)
|
super(TopkDropoutStrategy, self).__init__(
|
||||||
|
step_bar, model, dataset, start_time, end_time, trade_exchange=trade_exchange
|
||||||
|
)
|
||||||
self.topk = topk
|
self.topk = topk
|
||||||
self.n_drop = n_drop
|
self.n_drop = n_drop
|
||||||
self.method_sell = method_sell
|
self.method_sell = method_sell
|
||||||
@@ -65,8 +67,7 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
self.stock_count = {}
|
self.stock_count = {}
|
||||||
self.hold_thresh = hold_thresh
|
self.hold_thresh = hold_thresh
|
||||||
self.only_tradable = only_tradable
|
self.only_tradable = only_tradable
|
||||||
|
|
||||||
|
|
||||||
def reset(self, trade_exchange=None, **kwargs):
|
def reset(self, trade_exchange=None, **kwargs):
|
||||||
super(TopkDropoutStrategy, self).reset(**kwargs)
|
super(TopkDropoutStrategy, self).reset(**kwargs)
|
||||||
if trade_exchange:
|
if trade_exchange:
|
||||||
@@ -94,7 +95,9 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
cur_n = 0
|
cur_n = 0
|
||||||
res = []
|
res = []
|
||||||
for si in reversed(l) if reverse else l:
|
for si in reversed(l) if reverse else l:
|
||||||
if self.trade_exchange.is_stock_tradable(stock_id=si, start_time=trade_start_time, end_time=trade_end_time):
|
if self.trade_exchange.is_stock_tradable(
|
||||||
|
stock_id=si, start_time=trade_start_time, end_time=trade_end_time
|
||||||
|
):
|
||||||
res.append(si)
|
res.append(si)
|
||||||
cur_n += 1
|
cur_n += 1
|
||||||
if cur_n >= n:
|
if cur_n >= n:
|
||||||
@@ -105,7 +108,13 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
return get_first_n(l, n, reverse=True)
|
return get_first_n(l, n, reverse=True)
|
||||||
|
|
||||||
def filter_stock(l):
|
def filter_stock(l):
|
||||||
return [si for si in l if self.trade_exchange.is_stock_tradable(stock_id=si, start_time=trade_start_time, end_time=trade_end_time)]
|
return [
|
||||||
|
si
|
||||||
|
for si in l
|
||||||
|
if self.trade_exchange.is_stock_tradable(
|
||||||
|
stock_id=si, start_time=trade_start_time, end_time=trade_end_time
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Otherwise, the stock will make decision with out the stock tradable info
|
# Otherwise, the stock will make decision with out the stock tradable info
|
||||||
@@ -166,11 +175,16 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
buy_signal = pred_score.sort_values(ascending=False).iloc[: self.topk].index
|
buy_signal = pred_score.sort_values(ascending=False).iloc[: self.topk].index
|
||||||
|
|
||||||
for code in current_stock_list:
|
for code in current_stock_list:
|
||||||
if not self.trade_exchange.is_stock_tradable(stock_id=code, start_time=trade_start_time, end_time=trade_end_time):
|
if not self.trade_exchange.is_stock_tradable(
|
||||||
|
stock_id=code, start_time=trade_start_time, end_time=trade_end_time
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
if code in sell:
|
if code in sell:
|
||||||
# check hold limit
|
# check hold limit
|
||||||
if self.stock_count[code] < self.thresh or current_temp.get_stock_count(code, bar=self.step_bar) < self.hold_thresh:
|
if (
|
||||||
|
self.stock_count[code] < self.thresh
|
||||||
|
or current_temp.get_stock_count(code, bar=self.step_bar) < self.hold_thresh
|
||||||
|
):
|
||||||
# can not sell this code
|
# can not sell this code
|
||||||
# no buy signal, but the stock is kept
|
# no buy signal, but the stock is kept
|
||||||
self.stock_count[code] += 1
|
self.stock_count[code] += 1
|
||||||
@@ -188,7 +202,9 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
# is order executable
|
# is order executable
|
||||||
if self.trade_exchange.check_order(sell_order):
|
if self.trade_exchange.check_order(sell_order):
|
||||||
sell_order_list.append(sell_order)
|
sell_order_list.append(sell_order)
|
||||||
trade_val, trade_cost, trade_price = self.trade_exchange.deal_order(sell_order, position=current_temp)
|
trade_val, trade_cost, trade_price = self.trade_exchange.deal_order(
|
||||||
|
sell_order, position=current_temp
|
||||||
|
)
|
||||||
# update cash
|
# update cash
|
||||||
cash += trade_val - trade_cost
|
cash += trade_val - trade_cost
|
||||||
# sold
|
# sold
|
||||||
@@ -213,10 +229,14 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
# value = value / (1+self.trade_exchange.open_cost) # set open_cost limit
|
# value = value / (1+self.trade_exchange.open_cost) # set open_cost limit
|
||||||
for code in buy:
|
for code in buy:
|
||||||
# check is stock suspended
|
# check is stock suspended
|
||||||
if not self.trade_exchange.is_stock_tradable(stock_id=code, start_time=trade_start_time, end_time=trade_end_time):
|
if not self.trade_exchange.is_stock_tradable(
|
||||||
|
stock_id=code, start_time=trade_start_time, end_time=trade_end_time
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
# buy order
|
# buy order
|
||||||
buy_price = self.trade_exchange.get_deal_price(stock_id=code, start_time=trade_start_time, end_time=trade_end_time)
|
buy_price = self.trade_exchange.get_deal_price(
|
||||||
|
stock_id=code, start_time=trade_start_time, end_time=trade_end_time
|
||||||
|
)
|
||||||
buy_amount = value / buy_price
|
buy_amount = value / buy_price
|
||||||
factor = self.trade_exchange.get_factor(stock_id=code, start_time=trade_start_time, end_time=trade_end_time)
|
factor = self.trade_exchange.get_factor(stock_id=code, start_time=trade_start_time, end_time=trade_end_time)
|
||||||
buy_amount = self.trade_exchange.round_amount_by_trade_unit(buy_amount, factor)
|
buy_amount = self.trade_exchange.round_amount_by_trade_unit(buy_amount, factor)
|
||||||
@@ -231,17 +251,24 @@ class TopkDropoutStrategy(ModelStrategy):
|
|||||||
buy_order_list.append(buy_order)
|
buy_order_list.append(buy_order)
|
||||||
self.stock_count[code] = 1
|
self.stock_count[code] = 1
|
||||||
return sell_order_list + buy_order_list
|
return sell_order_list + buy_order_list
|
||||||
|
|
||||||
|
|
||||||
class WeightStrategyBase(ModelStrategy):
|
class WeightStrategyBase(ModelStrategy):
|
||||||
def __init__(self, step_bar, start_time=None, end_time=None, order_generator_cls_or_obj=OrderGenWInteract, trade_exchange=None, **kwargs):
|
def __init__(
|
||||||
|
self,
|
||||||
|
step_bar,
|
||||||
|
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)
|
super(WeightStrategyBase, self).__init__(step_bar, start_time, end_time)
|
||||||
self.trade_exchange = trade_exchange
|
self.trade_exchange = trade_exchange
|
||||||
if isinstance(order_generator_cls_or_obj, type):
|
if isinstance(order_generator_cls_or_obj, type):
|
||||||
self.order_generator = order_generator_cls_or_obj()
|
self.order_generator = order_generator_cls_or_obj()
|
||||||
else:
|
else:
|
||||||
self.order_generator = order_generator_cls_or_obj
|
self.order_generator = order_generator_cls_or_obj
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def generate_target_weight_position(self, score, current, trade_start_time, trade_end_time):
|
def generate_target_weight_position(self, score, current, trade_start_time, trade_end_time):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -81,10 +81,16 @@ class OrderGenWInteract(OrderGenerator):
|
|||||||
# calculate current_tradable_value
|
# calculate current_tradable_value
|
||||||
current_amount_dict = current.get_stock_amount_dict()
|
current_amount_dict = current.get_stock_amount_dict()
|
||||||
current_total_value = trade_exchange.calculate_amount_position_value(
|
current_total_value = trade_exchange.calculate_amount_position_value(
|
||||||
amount_dict=current_amount_dict, trade_start_time=trade_start_time, trade_end_time=trade_end_time, only_tradable=False
|
amount_dict=current_amount_dict,
|
||||||
|
trade_start_time=trade_start_time,
|
||||||
|
trade_end_time=trade_end_time,
|
||||||
|
only_tradable=False,
|
||||||
)
|
)
|
||||||
current_tradable_value = trade_exchange.calculate_amount_position_value(
|
current_tradable_value = trade_exchange.calculate_amount_position_value(
|
||||||
amount_dict=current_amount_dict, trade_start_time=trade_start_time, trade_end_time=trade_end_time, only_tradable=True
|
amount_dict=current_amount_dict,
|
||||||
|
trade_start_time=trade_start_time,
|
||||||
|
trade_end_time=trade_end_time,
|
||||||
|
only_tradable=True,
|
||||||
)
|
)
|
||||||
# add cash
|
# add cash
|
||||||
current_tradable_value += current.get_cash()
|
current_tradable_value += current.get_cash()
|
||||||
@@ -97,7 +103,9 @@ class OrderGenWInteract(OrderGenerator):
|
|||||||
# value. Then just sell all the stocks
|
# value. Then just sell all the stocks
|
||||||
target_amount_dict = copy.deepcopy(current_amount_dict.copy())
|
target_amount_dict = copy.deepcopy(current_amount_dict.copy())
|
||||||
for stock_id in list(target_amount_dict.keys()):
|
for stock_id in list(target_amount_dict.keys()):
|
||||||
if trade_exchange.is_stock_tradable(stock_id, trade_start_time=trade_start_time, trade_end_time=trade_end_time):
|
if trade_exchange.is_stock_tradable(
|
||||||
|
stock_id, trade_start_time=trade_start_time, trade_end_time=trade_end_time
|
||||||
|
):
|
||||||
del target_amount_dict[stock_id]
|
del target_amount_dict[stock_id]
|
||||||
else:
|
else:
|
||||||
# consider cost rate
|
# consider cost rate
|
||||||
@@ -108,13 +116,13 @@ class OrderGenWInteract(OrderGenerator):
|
|||||||
target_amount_dict = trade_exchange.generate_amount_position_from_weight_position(
|
target_amount_dict = trade_exchange.generate_amount_position_from_weight_position(
|
||||||
weight_position=target_weight_position,
|
weight_position=target_weight_position,
|
||||||
cash=current_tradable_value,
|
cash=current_tradable_value,
|
||||||
trade_start_time=trade_start_time,
|
trade_start_time=trade_start_time,
|
||||||
trade_end_time=trade_end_time,
|
trade_end_time=trade_end_time,
|
||||||
)
|
)
|
||||||
order_list = trade_exchange.generate_order_for_target_amount_position(
|
order_list = trade_exchange.generate_order_for_target_amount_position(
|
||||||
target_position=target_amount_dict,
|
target_position=target_amount_dict,
|
||||||
current_position=current_amount_dict,
|
current_position=current_amount_dict,
|
||||||
trade_start_time=trade_start_time,
|
trade_start_time=trade_start_time,
|
||||||
trade_end_time=trade_end_time,
|
trade_end_time=trade_end_time,
|
||||||
)
|
)
|
||||||
return order_list
|
return order_list
|
||||||
@@ -161,7 +169,9 @@ class OrderGenWOInteract(OrderGenerator):
|
|||||||
amount_dict = {}
|
amount_dict = {}
|
||||||
for stock_id in target_weight_position:
|
for stock_id in target_weight_position:
|
||||||
# Current rule will ignore the stock that not hold and cannot be traded at predict date
|
# Current rule will ignore the stock that not hold and cannot be traded at predict date
|
||||||
if trade_exchange.is_stock_tradable(stock_id=stock_id, trade_start_time=trade_start_time, trade_end_time=trade_end_time):
|
if trade_exchange.is_stock_tradable(
|
||||||
|
stock_id=stock_id, trade_start_time=trade_start_time, trade_end_time=trade_end_time
|
||||||
|
):
|
||||||
amount_dict[stock_id] = (
|
amount_dict[stock_id] = (
|
||||||
risk_total_value * target_weight_position[stock_id] / trade_exchange.get_close(stock_id, pred_date)
|
risk_total_value * target_weight_position[stock_id] / trade_exchange.get_close(stock_id, pred_date)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from ..backtest.order import Order
|
|||||||
|
|
||||||
|
|
||||||
class TWAPStrategy(RuleStrategy, TradingEnhancement):
|
class TWAPStrategy(RuleStrategy, TradingEnhancement):
|
||||||
|
|
||||||
def reset(self, trade_order_list=None, **kwargs):
|
def reset(self, trade_order_list=None, **kwargs):
|
||||||
super(TWAPStrategy, self).reset(**kwargs)
|
super(TWAPStrategy, self).reset(**kwargs)
|
||||||
TradingEnhancement.reset(self, trade_order_list=trade_order_list)
|
TradingEnhancement.reset(self, trade_order_list=trade_order_list)
|
||||||
@@ -19,7 +18,6 @@ class TWAPStrategy(RuleStrategy, TradingEnhancement):
|
|||||||
self.trade_amount = {}
|
self.trade_amount = {}
|
||||||
for order in self.trade_order_list:
|
for order in self.trade_order_list:
|
||||||
self.trade_amount[(order.stock_id, order.direction)] = order.amount // self.trade_len
|
self.trade_amount[(order.stock_id, order.direction)] = order.amount // self.trade_len
|
||||||
|
|
||||||
|
|
||||||
def generate_order_list(self, **kwargs):
|
def generate_order_list(self, **kwargs):
|
||||||
super(TopkDropoutStrategy, self).step()
|
super(TopkDropoutStrategy, self).step()
|
||||||
@@ -37,10 +35,12 @@ class TWAPStrategy(RuleStrategy, TradingEnhancement):
|
|||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
return order_list
|
return order_list
|
||||||
|
|
||||||
|
|
||||||
class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
||||||
"""
|
"""
|
||||||
(S)elect the (B)etter one among every two adjacent trading (B)ars to sell or buy.
|
(S)elect the (B)etter one among every two adjacent trading (B)ars to sell or buy.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TREND_MID = 0
|
TREND_MID = 0
|
||||||
TREND_SHORT = 1
|
TREND_SHORT = 1
|
||||||
TREND_LONG = 2
|
TREND_LONG = 2
|
||||||
@@ -50,11 +50,10 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
|||||||
TradingEnhancement.reset(self, trade_order_list=trade_order_list)
|
TradingEnhancement.reset(self, trade_order_list=trade_order_list)
|
||||||
if trade_order_list:
|
if trade_order_list:
|
||||||
self.trade_amount = {}
|
self.trade_amount = {}
|
||||||
self.trade_trend = {}
|
self.trade_trend = {}
|
||||||
for order in self.trade_order_list:
|
for order in self.trade_order_list:
|
||||||
self.trade_amount[(order.stock_id, order.direction)] = order.amount // self.trade_len
|
self.trade_amount[(order.stock_id, order.direction)] = order.amount // self.trade_len
|
||||||
self.trade_trend[(order.stock_id, order.direction)] = self.TREND_MID
|
self.trade_trend[(order.stock_id, order.direction)] = self.TREND_MID
|
||||||
|
|
||||||
|
|
||||||
def _pred_price_trend(self, stock_id, pred_start_time=None, pred_end_time=None):
|
def _pred_price_trend(self, stock_id, pred_start_time=None, pred_end_time=None):
|
||||||
raise NotImplementedError("pred_price_trend method is not implemented!")
|
raise NotImplementedError("pred_price_trend method is not implemented!")
|
||||||
@@ -81,10 +80,15 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
|||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
else:
|
else:
|
||||||
if self.trade_index % 2 == 1:
|
if self.trade_index % 2 == 1:
|
||||||
if _pred_trend == self.TREND_SHORT and order.direction == order.SELL or _pred_trend == self.TREND_LONG and order.direction == order.BUY:
|
if (
|
||||||
|
_pred_trend == self.TREND_SHORT
|
||||||
|
and order.direction == order.SELL
|
||||||
|
or _pred_trend == self.TREND_LONG
|
||||||
|
and order.direction == order.BUY
|
||||||
|
):
|
||||||
_order = Order(
|
_order = Order(
|
||||||
stock_id=order.stock_id,
|
stock_id=order.stock_id,
|
||||||
amount=2*self.trade_amount[(order.stock_id, order.direction)],
|
amount=2 * self.trade_amount[(order.stock_id, order.direction)],
|
||||||
start_time=trade_start_time,
|
start_time=trade_start_time,
|
||||||
end_time=trade_end_time,
|
end_time=trade_end_time,
|
||||||
direction=order.direction, # 1 for buy
|
direction=order.direction, # 1 for buy
|
||||||
@@ -92,31 +96,37 @@ class SBBStrategyBase(RuleStrategy, TradingEnhancement):
|
|||||||
)
|
)
|
||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
else:
|
else:
|
||||||
if _pred_trend == self.TREND_SHORT and order.direction == order.BUY or _pred_trend == self.TREND_LONG and order.direction == order.SELL:
|
if (
|
||||||
|
_pred_trend == self.TREND_SHORT
|
||||||
|
and order.direction == order.BUY
|
||||||
|
or _pred_trend == self.TREND_LONG
|
||||||
|
and order.direction == order.SELL
|
||||||
|
):
|
||||||
_order = Order(
|
_order = Order(
|
||||||
stock_id=order.stock_id,
|
stock_id=order.stock_id,
|
||||||
amount=2*self.trade_amount[(order.stock_id, order.direction)],
|
amount=2 * self.trade_amount[(order.stock_id, order.direction)],
|
||||||
start_time=trade_start_time,
|
start_time=trade_start_time,
|
||||||
end_time=trade_end_time,
|
end_time=trade_end_time,
|
||||||
direction=order.direction, # 1 for buy
|
direction=order.direction, # 1 for buy
|
||||||
factor=order.factor,
|
factor=order.factor,
|
||||||
)
|
)
|
||||||
order_list.append(_order)
|
order_list.append(_order)
|
||||||
if self.trade_index % 2 == 1:
|
if self.trade_index % 2 == 1:
|
||||||
self.trade_trend[(order.stock_id, order.direction)] = _pred_trend
|
self.trade_trend[(order.stock_id, order.direction)] = _pred_trend
|
||||||
|
|
||||||
return order_list
|
return order_list
|
||||||
|
|
||||||
|
|
||||||
class SBBStrategyEMA(SBBStrategyBase):
|
class SBBStrategyEMA(SBBStrategyBase):
|
||||||
"""
|
"""
|
||||||
(S)elect the (B)etter one among every two adjacent trading (B)ars to sell or buy with (EMA).
|
(S)elect the (B)etter one among every two adjacent trading (B)ars to sell or buy with (EMA).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
step_bar,
|
step_bar,
|
||||||
start_time=None,
|
start_time=None,
|
||||||
end_time=None,
|
end_time=None,
|
||||||
instruments="csi300",
|
instruments="csi300",
|
||||||
freq="day",
|
freq="day",
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -139,22 +149,25 @@ class SBBStrategyEMA(SBBStrategyBase):
|
|||||||
if self.start_time and self.end_time:
|
if self.start_time and self.end_time:
|
||||||
fields = ["EMA($close, 10)-EMA($close, 20)"]
|
fields = ["EMA($close, 10)-EMA($close, 20)"]
|
||||||
signal_start_time, _ = self._get_calendar_time(trade_index=self.trade_index, shift=1)
|
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 = 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 = self._convert_index_format(signal_df)
|
||||||
signal_df.columns = ["signal"]
|
signal_df.columns = ["signal"]
|
||||||
self.signal = {}
|
self.signal = {}
|
||||||
for stock_id, stock_val in signal_df.groupby(level="instrument"):
|
for stock_id, stock_val in signal_df.groupby(level="instrument"):
|
||||||
self.signal[stock_id] = stock_val
|
self.signal[stock_id] = stock_val
|
||||||
|
|
||||||
def _pred_price_trend(self, stock_id, pred_start_time=None, pred_end_time=None):
|
def _pred_price_trend(self, stock_id, pred_start_time=None, pred_end_time=None):
|
||||||
if stock_id not in self.signal:
|
if stock_id not in self.signal:
|
||||||
return self.TREND_MID
|
return self.TREND_MID
|
||||||
else:
|
else:
|
||||||
_sample_signal = sample_feature(self.signal[stock_id], pred_start_time, pred_end_time, fields="signal", method="last")
|
_sample_signal = sample_feature(
|
||||||
|
self.signal[stock_id], pred_start_time, pred_end_time, fields="signal", method="last"
|
||||||
|
)
|
||||||
if _sample_signal is None or _sample_signal.iloc[0] == 0:
|
if _sample_signal is None or _sample_signal.iloc[0] == 0:
|
||||||
return self.TREND_MID
|
return self.TREND_MID
|
||||||
elif _sample_signal.iloc[0] > 0:
|
elif _sample_signal.iloc[0] > 0:
|
||||||
return self.TREND_LONG
|
return self.TREND_LONG
|
||||||
else:
|
else:
|
||||||
return self.TREND_SHORT
|
return self.TREND_SHORT
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ class CalendarProvider(abc.ABC):
|
|||||||
_calendar = np.array(self.load_calendar(freq, future))
|
_calendar = np.array(self.load_calendar(freq, future))
|
||||||
_calendar_index = {x: i for i, x in enumerate(_calendar)} # for fast search
|
_calendar_index = {x: i for i, x in enumerate(_calendar)} # for fast search
|
||||||
H["c"][flag_raw] = _calendar, _calendar_index
|
H["c"][flag_raw] = _calendar, _calendar_index
|
||||||
|
|
||||||
if freq_sam is None:
|
if freq_sam is None:
|
||||||
return _calendar, _calendar_index
|
return _calendar, _calendar_index
|
||||||
else:
|
else:
|
||||||
@@ -134,7 +134,6 @@ class CalendarProvider(abc.ABC):
|
|||||||
_calendar_sam_index = {x: i for i, x in enumerate(_calendar_sam)}
|
_calendar_sam_index = {x: i for i, x in enumerate(_calendar_sam)}
|
||||||
H["c"][flag] = _calendar_sam, _calendar_sam_index
|
H["c"][flag] = _calendar_sam, _calendar_sam_index
|
||||||
return _calendar_sam, _calendar_sam_index
|
return _calendar_sam, _calendar_sam_index
|
||||||
|
|
||||||
|
|
||||||
def _uri(self, start_time, end_time, freq, future=False):
|
def _uri(self, start_time, end_time, freq, future=False):
|
||||||
"""Get the uri of calendar generation task."""
|
"""Get the uri of calendar generation task."""
|
||||||
@@ -560,7 +559,8 @@ class LocalCalendarProvider(CalendarProvider):
|
|||||||
else:
|
else:
|
||||||
end_time = _calendar[-1]
|
end_time = _calendar[-1]
|
||||||
st, et, si, ei = self.locate_index(start_time, end_time, freq=freq, freq_sam=freq_sam, future=future)
|
st, et, si, ei = self.locate_index(start_time, end_time, freq=freq, freq_sam=freq_sam, future=future)
|
||||||
return _calendar[si : ei + 1]
|
return _calendar[si : ei + 1]
|
||||||
|
|
||||||
|
|
||||||
class LocalInstrumentProvider(InstrumentProvider):
|
class LocalInstrumentProvider(InstrumentProvider):
|
||||||
"""Local instrument data provider class
|
"""Local instrument data provider class
|
||||||
@@ -767,7 +767,7 @@ class ClientCalendarProvider(CalendarProvider):
|
|||||||
self.conn = conn
|
self.conn = conn
|
||||||
|
|
||||||
def calendar(self, start_time=None, end_time=None, freq="day", future=False):
|
def calendar(self, start_time=None, end_time=None, freq="day", future=False):
|
||||||
|
|
||||||
self.conn.send_request(
|
self.conn.send_request(
|
||||||
request_type="calendar",
|
request_type="calendar",
|
||||||
request_content={
|
request_content={
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ from ..contrib.backtest.env import BaseTradeCalendar
|
|||||||
- adjust_dates这个东西啥用
|
- adjust_dates这个东西啥用
|
||||||
- label和freq和strategy的bar分离,这个如何决策呢
|
- label和freq和strategy的bar分离,这个如何决策呢
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BaseStrategy(BaseTradeCalendar):
|
class BaseStrategy(BaseTradeCalendar):
|
||||||
|
|
||||||
def generate_order_list(self, **kwargs):
|
def generate_order_list(self, **kwargs):
|
||||||
raise NotImplementedError("generator_order_list is not implemented!")
|
raise NotImplementedError("generator_order_list is not implemented!")
|
||||||
|
|
||||||
@@ -29,12 +30,13 @@ class BaseStrategy(BaseTradeCalendar):
|
|||||||
class RuleStrategy(BaseStrategy):
|
class RuleStrategy(BaseStrategy):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ModelStrategy(BaseStrategy):
|
class ModelStrategy(BaseStrategy):
|
||||||
def __init__(self, step_bar, model, dataset:DatasetH, start_time=None, end_time=None, **kwargs):
|
def __init__(self, step_bar, model, dataset: DatasetH, start_time=None, end_time=None, **kwargs):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.dataset = dataset
|
self.dataset = dataset
|
||||||
self.pred_scores = self._convert_index_format(self.model.predict(dataset))
|
self.pred_scores = self._convert_index_format(self.model.predict(dataset))
|
||||||
#pred_score_dates = self.pred_scores.index.get_level_values(level="datetime")
|
# pred_score_dates = self.pred_scores.index.get_level_values(level="datetime")
|
||||||
super(ModelStrategy, self).__init__(step_bar, start_time, end_time, **kwargs)
|
super(ModelStrategy, self).__init__(step_bar, start_time, end_time, **kwargs)
|
||||||
|
|
||||||
def _convert_index_format(self, df):
|
def _convert_index_format(self, df):
|
||||||
@@ -43,12 +45,11 @@ class ModelStrategy(BaseStrategy):
|
|||||||
return df
|
return df
|
||||||
|
|
||||||
def _update_model(self):
|
def _update_model(self):
|
||||||
"""update pred score
|
"""update pred score"""
|
||||||
"""
|
|
||||||
raise NotImplementedError("_update_model is not implemented!")
|
raise NotImplementedError("_update_model is not implemented!")
|
||||||
|
|
||||||
|
|
||||||
class TradingEnhancement:
|
class TradingEnhancement:
|
||||||
def reset(self, trade_order_list=None):
|
def reset(self, trade_order_list=None):
|
||||||
if trade_order_list:
|
if trade_order_list:
|
||||||
self.trade_order_list = trade_order_list
|
self.trade_order_list = trade_order_list
|
||||||
|
|
||||||
|
|||||||
@@ -801,6 +801,7 @@ def fname_to_code(fname: str):
|
|||||||
fname = fname.lstrip(prefix)
|
fname = fname.lstrip(prefix)
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
|
|
||||||
########################## Sample ############################
|
########################## Sample ############################
|
||||||
def sample_calendar_bac(calendar_raw, freq_raw, freq_sam):
|
def sample_calendar_bac(calendar_raw, freq_raw, freq_sam):
|
||||||
"""
|
"""
|
||||||
@@ -810,16 +811,17 @@ def sample_calendar_bac(calendar_raw, freq_raw, freq_sam):
|
|||||||
freq_sam = "1" + freq_sam if re.match("^[0-9]", freq_sam) is None else freq_sam
|
freq_sam = "1" + freq_sam if re.match("^[0-9]", freq_sam) is None else freq_sam
|
||||||
|
|
||||||
if freq_sam.endswith(("minute", "min")):
|
if freq_sam.endswith(("minute", "min")):
|
||||||
|
|
||||||
def cal_next_sam_minute(x, sam_minutes):
|
def cal_next_sam_minute(x, sam_minutes):
|
||||||
hour = x.hour
|
hour = x.hour
|
||||||
minute = x.minute
|
minute = x.minute
|
||||||
if 9 <= hour <= 11:
|
if 9 <= hour <= 11:
|
||||||
minute_index = (11 - hour)*60 + 30 - minute + 120
|
minute_index = (11 - hour) * 60 + 30 - minute + 120
|
||||||
elif 13 <= hour <= 15:
|
elif 13 <= hour <= 15:
|
||||||
minute_index = (15 - hour)*60 - minute
|
minute_index = (15 - hour) * 60 - minute
|
||||||
else:
|
else:
|
||||||
raise ValueError("calendar hour must be in [9, 11] or [13, 15]")
|
raise ValueError("calendar hour must be in [9, 11] or [13, 15]")
|
||||||
|
|
||||||
minute_index = minute_index // sam_minutes * sam_minutes
|
minute_index = minute_index // sam_minutes * sam_minutes
|
||||||
|
|
||||||
if 0 <= minute_index < 120:
|
if 0 <= minute_index < 120:
|
||||||
@@ -838,32 +840,40 @@ def sample_calendar_bac(calendar_raw, freq_raw, freq_sam):
|
|||||||
if raw_minutes > sam_minutes:
|
if raw_minutes > sam_minutes:
|
||||||
raise ValueError("raw freq must be higher than sample freq")
|
raise ValueError("raw freq must be higher than sample freq")
|
||||||
|
|
||||||
_calendar_minute = np.unique(list(map(lambda x: pd.Timestamp(x.year, x.month, x.day, *cal_next_sam_minute(x, sam_minutes), 59), calendar_raw)))
|
_calendar_minute = np.unique(
|
||||||
|
list(
|
||||||
|
map(
|
||||||
|
lambda x: pd.Timestamp(x.year, x.month, x.day, *cal_next_sam_minute(x, sam_minutes), 59),
|
||||||
|
calendar_raw,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
return _calendar_minute
|
return _calendar_minute
|
||||||
else:
|
else:
|
||||||
|
|
||||||
_calendar_day = np.unique(list(map(lambda x: pd.Timestamp(x.year, x.month, x.day, 23, 59, 59), calendar_raw)))
|
_calendar_day = np.unique(list(map(lambda x: pd.Timestamp(x.year, x.month, x.day, 23, 59, 59), calendar_raw)))
|
||||||
if freq_sam.endswith(("day", "d")):
|
if freq_sam.endswith(("day", "d")):
|
||||||
sam_days = int(freq_sam[:-1]) if freq_sam.endswith("d") else int(freq_sam[:-3])
|
sam_days = int(freq_sam[:-1]) if freq_sam.endswith("d") else int(freq_sam[:-3])
|
||||||
return _calendar_day[(len(_calendar_day) + sam_days - 1)%sam_days::sam_days]
|
return _calendar_day[(len(_calendar_day) + sam_days - 1) % sam_days :: sam_days]
|
||||||
|
|
||||||
elif freq_sam.endswith(("week", "w")):
|
elif freq_sam.endswith(("week", "w")):
|
||||||
sam_weeks = int(freq_sam[:-1]) if freq_sam.endswith("w") else int(freq_sam[:-4])
|
sam_weeks = int(freq_sam[:-1]) if freq_sam.endswith("w") else int(freq_sam[:-4])
|
||||||
_day_in_week = np.array(list(map(lambda x: x.dayofweek, _calendar_day)))
|
_day_in_week = np.array(list(map(lambda x: x.dayofweek, _calendar_day)))
|
||||||
_calendar_week = _calendar_day[np.ediff1d(_day_in_week[::-1], to_begin=1)[::-1] > 0]
|
_calendar_week = _calendar_day[np.ediff1d(_day_in_week[::-1], to_begin=1)[::-1] > 0]
|
||||||
return _calendar_week[(len(_calendar_week) + sam_weeks - 1)%sam_weeks::sam_weeks]
|
return _calendar_week[(len(_calendar_week) + sam_weeks - 1) % sam_weeks :: sam_weeks]
|
||||||
|
|
||||||
elif freq_sam.endswith(("month", "m")):
|
elif freq_sam.endswith(("month", "m")):
|
||||||
sam_months = int(freq_sam[:-1]) if freq_sam.endswith("m") else int(freq_sam[:-5])
|
sam_months = int(freq_sam[:-1]) if freq_sam.endswith("m") else int(freq_sam[:-5])
|
||||||
_day_in_month = np.array(list(map(lambda x: x.day, _calendar_day)))
|
_day_in_month = np.array(list(map(lambda x: x.day, _calendar_day)))
|
||||||
_calendar_month = _calendar_day[np.ediff1d(_day_in_month[::-1], to_begin=1)[::-1] > 0]
|
_calendar_month = _calendar_day[np.ediff1d(_day_in_month[::-1], to_begin=1)[::-1] > 0]
|
||||||
return _calendar_month[(len(_calendar_month) + sam_months - 1)%sam_months::sam_months]
|
return _calendar_month[(len(_calendar_month) + sam_months - 1) % sam_months :: sam_months]
|
||||||
else:
|
else:
|
||||||
raise ValueError("sample freq must be xmin, xd, xw, xm")
|
raise ValueError("sample freq must be xmin, xd, xw, xm")
|
||||||
|
|
||||||
|
|
||||||
def parse_freq(freq):
|
def parse_freq(freq):
|
||||||
freq = freq.lower()
|
freq = freq.lower()
|
||||||
search_obj =re.search("^([0-9]*)([a-z]+)", freq)
|
search_obj = re.search("^([0-9]*)([a-z]+)", freq)
|
||||||
if search_obj is None:
|
if search_obj is None:
|
||||||
raise ValueError("freq format is not supported")
|
raise ValueError("freq format is not supported")
|
||||||
_count = int(search_obj.group(1) if search_obj.group(1) else "1")
|
_count = int(search_obj.group(1) if search_obj.group(1) else "1")
|
||||||
@@ -881,9 +891,12 @@ def parse_freq(freq):
|
|||||||
try:
|
try:
|
||||||
_freq = _freq_format_dict.get(_freq)
|
_freq = _freq_format_dict.get(_freq)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("freq format is not supported, the supported freq includes (x)month/m, (x)day/d, (x)minute/min")
|
raise ValueError(
|
||||||
|
"freq format is not supported, the supported freq includes (x)month/m, (x)day/d, (x)minute/min"
|
||||||
|
)
|
||||||
return _count, _freq
|
return _count, _freq
|
||||||
|
|
||||||
|
|
||||||
def sample_calendar(calendar_raw, freq_raw, freq_sam):
|
def sample_calendar(calendar_raw, freq_raw, freq_sam):
|
||||||
"""
|
"""
|
||||||
freq_raw : "min" or "day"
|
freq_raw : "min" or "day"
|
||||||
@@ -893,16 +906,17 @@ def sample_calendar(calendar_raw, freq_raw, freq_sam):
|
|||||||
if not len(calendar_raw):
|
if not len(calendar_raw):
|
||||||
return calendar_raw
|
return calendar_raw
|
||||||
if freq_sam == "minute":
|
if freq_sam == "minute":
|
||||||
|
|
||||||
def cal_next_sam_minute(x, sam_minutes):
|
def cal_next_sam_minute(x, sam_minutes):
|
||||||
hour = x.hour
|
hour = x.hour
|
||||||
minute = x.minute
|
minute = x.minute
|
||||||
if (hour == 9 and minute >= 30) or (9 < hour < 11) or (hour == 11 and minute < 30):
|
if (hour == 9 and minute >= 30) or (9 < hour < 11) or (hour == 11 and minute < 30):
|
||||||
minute_index = (hour - 9)*60 + minute - 30
|
minute_index = (hour - 9) * 60 + minute - 30
|
||||||
elif 13 <= hour < 15:
|
elif 13 <= hour < 15:
|
||||||
minute_index = (hour - 13)*60 + minute + 120
|
minute_index = (hour - 13) * 60 + minute + 120
|
||||||
else:
|
else:
|
||||||
raise ValueError("calendar hour must be in [9, 11] or [13, 15]")
|
raise ValueError("calendar hour must be in [9, 11] or [13, 15]")
|
||||||
|
|
||||||
minute_index = minute_index // sam_minutes * sam_minutes
|
minute_index = minute_index // sam_minutes * sam_minutes
|
||||||
|
|
||||||
if 0 <= minute_index < 120:
|
if 0 <= minute_index < 120:
|
||||||
@@ -917,7 +931,11 @@ def sample_calendar(calendar_raw, freq_raw, freq_sam):
|
|||||||
else:
|
else:
|
||||||
if raw_count > sam_count:
|
if raw_count > sam_count:
|
||||||
raise ValueError("raw freq must be higher than sample freq")
|
raise ValueError("raw freq must be higher than sample freq")
|
||||||
_calendar_minute = np.unique(list(map(lambda x: pd.Timestamp(x.year, x.month, x.day, *cal_next_sam_minute(x, sam_count), 0), calendar_raw)))
|
_calendar_minute = np.unique(
|
||||||
|
list(
|
||||||
|
map(lambda x: pd.Timestamp(x.year, x.month, x.day, *cal_next_sam_minute(x, sam_count), 0), calendar_raw)
|
||||||
|
)
|
||||||
|
)
|
||||||
if calendar_raw[0] > _calendar_minute[0]:
|
if calendar_raw[0] > _calendar_minute[0]:
|
||||||
_calendar_minute[0] = calendar_raw[0]
|
_calendar_minute[0] = calendar_raw[0]
|
||||||
return _calendar_minute
|
return _calendar_minute
|
||||||
@@ -937,7 +955,8 @@ def sample_calendar(calendar_raw, freq_raw, freq_sam):
|
|||||||
return _calendar_month[::sam_count]
|
return _calendar_month[::sam_count]
|
||||||
else:
|
else:
|
||||||
raise ValueError("sample freq must be xmin, xd, xw, xm")
|
raise ValueError("sample freq must be xmin, xd, xw, xm")
|
||||||
|
|
||||||
|
|
||||||
def get_sample_freq_calendar(start_time=None, end_time=None, freq="day", **kwargs):
|
def get_sample_freq_calendar(start_time=None, end_time=None, freq="day", **kwargs):
|
||||||
_, norm_freq = parse_freq(freq)
|
_, norm_freq = parse_freq(freq)
|
||||||
|
|
||||||
@@ -963,23 +982,28 @@ def get_sample_freq_calendar(start_time=None, end_time=None, freq="day", **kwarg
|
|||||||
raise ValueError(f"freq {freq} is not supported")
|
raise ValueError(f"freq {freq} is not supported")
|
||||||
return _calendar, freq, freq_sam
|
return _calendar, freq, freq_sam
|
||||||
|
|
||||||
|
|
||||||
def sample_feature(feature, start_time=None, end_time=None, fields=None, method="last", method_kwargs={}):
|
def sample_feature(feature, start_time=None, end_time=None, fields=None, method="last", method_kwargs={}):
|
||||||
selector_datetime = slice(start_time, end_time)
|
selector_datetime = slice(start_time, end_time)
|
||||||
fields = fields if fields else slice(None)
|
fields = fields if fields else slice(None)
|
||||||
|
|
||||||
from ..data.dataset.utils import get_level_index
|
from ..data.dataset.utils import get_level_index
|
||||||
|
|
||||||
datetime_level = get_level_index(feature, level="datetime") == 0
|
datetime_level = get_level_index(feature, level="datetime") == 0
|
||||||
if isinstance(feature, pd.Series):
|
if isinstance(feature, pd.Series):
|
||||||
feature = feature.loc[selector_datetime] if datetime_level else feature.loc[(slice(None), selector_datetime)]
|
feature = feature.loc[selector_datetime] if datetime_level else feature.loc[(slice(None), selector_datetime)]
|
||||||
elif isinstance(feature, pd.DataFrame):
|
elif isinstance(feature, pd.DataFrame):
|
||||||
feature = feature.loc[selector_datetime, fields] if datetime_level else feature.loc[(slice(None), selector_datetime), fields]
|
feature = (
|
||||||
|
feature.loc[selector_datetime, fields]
|
||||||
|
if datetime_level
|
||||||
|
else feature.loc[(slice(None), selector_datetime), fields]
|
||||||
|
)
|
||||||
if feature.empty:
|
if feature.empty:
|
||||||
return None
|
return None
|
||||||
if isinstance(feature.index, pd.MultiIndex):
|
if isinstance(feature.index, pd.MultiIndex):
|
||||||
if callable(method):
|
if callable(method):
|
||||||
method_func = method
|
method_func = method
|
||||||
return feature.groupby(level="instrument").apply(lambda x:method_func(x, **method_kwargs))
|
return feature.groupby(level="instrument").apply(lambda x: method_func(x, **method_kwargs))
|
||||||
elif isinstance(method, str):
|
elif isinstance(method, str):
|
||||||
return getattr(feature.groupby(level="instrument"), method)(**method_kwargs)
|
return getattr(feature.groupby(level="instrument"), method)(**method_kwargs)
|
||||||
else:
|
else:
|
||||||
@@ -988,7 +1012,5 @@ def sample_feature(feature, start_time=None, end_time=None, fields=None, method=
|
|||||||
return method_func(feature, **method_kwargs)
|
return method_func(feature, **method_kwargs)
|
||||||
elif isinstance(method, str):
|
elif isinstance(method, str):
|
||||||
return getattr(feature, method)(**method_kwargs)
|
return getattr(feature, method)(**method_kwargs)
|
||||||
|
|
||||||
return feature
|
|
||||||
|
|
||||||
|
return feature
|
||||||
|
|||||||
@@ -254,13 +254,19 @@ class PortAnaRecord(SignalRecord):
|
|||||||
for report_dep, (report_normal, positions_normal) in enumerate(report_list):
|
for report_dep, (report_normal, positions_normal) in enumerate(report_list):
|
||||||
if report_dict is None:
|
if report_dict is None:
|
||||||
if self.risk_analysis_dep == report_dep:
|
if self.risk_analysis_dep == report_dep:
|
||||||
warnings.warn(f"the report in dep {risk_analysis_dep} is None, please set the corresponding env with `generate_report==True`")
|
warnings.warn(
|
||||||
|
f"the report in dep {risk_analysis_dep} is None, please set the corresponding env with `generate_report==True`"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.recorder.save_objects(**{f"report_normal_{report_dep}.pkl": report_normal}, artifact_path=PortAnaRecord.get_path())
|
self.recorder.save_objects(
|
||||||
self.recorder.save_objects(**{f"positions_norma_{report_dep}l.pkl": positions_normal}, artifact_path=PortAnaRecord.get_path())
|
**{f"report_normal_{report_dep}.pkl": report_normal}, artifact_path=PortAnaRecord.get_path()
|
||||||
|
)
|
||||||
|
self.recorder.save_objects(
|
||||||
|
**{f"positions_norma_{report_dep}l.pkl": positions_normal}, artifact_path=PortAnaRecord.get_path()
|
||||||
|
)
|
||||||
# analysis
|
# analysis
|
||||||
self.risk_analysis_dep == report_dep:
|
if self.risk_analysis_dep == report_dep:
|
||||||
analysis = dict()
|
analysis = dict()
|
||||||
analysis["excess_return_without_cost"] = risk_analysis(report_normal["return"] - report_normal["bench"])
|
analysis["excess_return_without_cost"] = risk_analysis(report_normal["return"] - report_normal["bench"])
|
||||||
analysis["excess_return_with_cost"] = risk_analysis(
|
analysis["excess_return_with_cost"] = risk_analysis(
|
||||||
@@ -270,7 +276,9 @@ class PortAnaRecord(SignalRecord):
|
|||||||
# log metrics
|
# log metrics
|
||||||
self.recorder.log_metrics(**flatten_dict(analysis_df["risk"].unstack().T.to_dict()))
|
self.recorder.log_metrics(**flatten_dict(analysis_df["risk"].unstack().T.to_dict()))
|
||||||
# save results
|
# save results
|
||||||
self.recorder.save_objects(**{f"port_analysis.pkl_{report_dep}": analysis_df}, artifact_path=PortAnaRecord.get_path())
|
self.recorder.save_objects(
|
||||||
|
**{f"port_analysis.pkl_{report_dep}": analysis_df}, artifact_path=PortAnaRecord.get_path()
|
||||||
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Portfolio analysis record 'port_analysis_{report_dep}.pkl' has been saved as the artifact of the Experiment {self.recorder.experiment_id}"
|
f"Portfolio analysis record 'port_analysis_{report_dep}.pkl' has been saved as the artifact of the Experiment {self.recorder.experiment_id}"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user