mirror of
https://github.com/microsoft/qlib.git
synced 2026-07-13 07:46:53 +08:00
Merge branch 'main' of github.com:you-n-g/qlib into main
This commit is contained in:
@@ -10,6 +10,28 @@ from inspect import getfullargspec
|
||||
import copy
|
||||
|
||||
|
||||
def check_transform_proc(proc_l, fit_start_time, fit_end_time):
|
||||
new_l = []
|
||||
for p in proc_l:
|
||||
if not isinstance(p, Processor):
|
||||
klass, pkwargs = get_cls_kwargs(p, processor_module)
|
||||
args = getfullargspec(klass).args
|
||||
if "fit_start_time" in args and "fit_end_time" in args:
|
||||
assert (
|
||||
fit_start_time is not None and fit_end_time is not None
|
||||
), "Make sure `fit_start_time` and `fit_end_time` are not None."
|
||||
pkwargs.update(
|
||||
{
|
||||
"fit_start_time": fit_start_time,
|
||||
"fit_end_time": fit_end_time,
|
||||
}
|
||||
)
|
||||
new_l.append({"class": klass.__name__, "kwargs": pkwargs})
|
||||
else:
|
||||
new_l.append(p)
|
||||
return new_l
|
||||
|
||||
|
||||
class ALPHA360_Denoise(DataHandlerLP):
|
||||
def __init__(self, instruments="csi500", start_time=None, end_time=None, fit_start_time=None, fit_end_time=None):
|
||||
data_loader = {
|
||||
@@ -83,8 +105,31 @@ class ALPHA360_Denoise(DataHandlerLP):
|
||||
return fields, names
|
||||
|
||||
|
||||
_DEFAULT_LEARN_PROCESSORS = [
|
||||
{"class": "DropnaLabel"},
|
||||
{"class": "CSZScoreNorm", "kwargs": {"fields_group": "label"}},
|
||||
]
|
||||
_DEFAULT_INFER_PROCESSORS = [
|
||||
{"class": "ProcessInf", "kwargs": {}},
|
||||
{"class": "ZScoreNorm", "kwargs": {}},
|
||||
{"class": "Fillna", "kwargs": {}},
|
||||
]
|
||||
|
||||
|
||||
class ALPHA360(DataHandlerLP):
|
||||
def __init__(self, instruments="csi500", start_time=None, end_time=None, fit_start_time=None, fit_end_time=None):
|
||||
def __init__(
|
||||
self,
|
||||
instruments="csi500",
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
infer_processors=_DEFAULT_INFER_PROCESSORS,
|
||||
learn_processors=_DEFAULT_LEARN_PROCESSORS,
|
||||
fit_start_time=None,
|
||||
fit_end_time=None,
|
||||
):
|
||||
infer_processors = check_transform_proc(infer_processors, fit_start_time, fit_end_time)
|
||||
learn_processors = check_transform_proc(learn_processors, fit_start_time, fit_end_time)
|
||||
|
||||
data_loader = {
|
||||
"class": "QlibDataLoader",
|
||||
"kwargs": {
|
||||
@@ -95,16 +140,6 @@ class ALPHA360(DataHandlerLP):
|
||||
},
|
||||
}
|
||||
|
||||
learn_processors = [
|
||||
{"class": "DropnaLabel", "kwargs": {"fields_group": "label"}},
|
||||
{"class": "CSZScoreNorm", "kwargs": {"fields_group": "label"}},
|
||||
]
|
||||
infer_processors = [
|
||||
{"class": "ProcessInf", "kwargs": {}},
|
||||
{"class": "ZscoreNorm", "kwargs": {"fit_start_time": fit_start_time, "fit_end_time": fit_end_time}},
|
||||
{"class": "Fillna", "kwargs": {}},
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
instruments,
|
||||
start_time,
|
||||
@@ -168,33 +203,12 @@ class Alpha158(DataHandlerLP):
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
infer_processors=[],
|
||||
learn_processors=["DropnaLabel", {"class": "CSZScoreNorm", "kwargs": {"fields_group": "label"}}],
|
||||
learn_processors=_DEFAULT_LEARN_PROCESSORS,
|
||||
fit_start_time=None,
|
||||
fit_end_time=None,
|
||||
):
|
||||
def check_transform_proc(proc_l):
|
||||
new_l = []
|
||||
for p in proc_l:
|
||||
if not isinstance(p, Processor):
|
||||
klass, pkwargs = get_cls_kwargs(p, processor_module)
|
||||
args = getfullargspec(klass).args
|
||||
if "fit_start_time" in args and "fit_end_time" in args:
|
||||
assert (
|
||||
fit_start_time is not None and fit_end_time is not None
|
||||
), "Make sure `fit_start_time` and `fit_end_time` are not None."
|
||||
pkwargs.update(
|
||||
{
|
||||
"fit_start_time": fit_start_time,
|
||||
"fit_end_time": fit_end_time,
|
||||
}
|
||||
)
|
||||
new_l.append({"class": klass.__name__, "kwargs": pkwargs})
|
||||
else:
|
||||
new_l.append(p)
|
||||
return new_l
|
||||
|
||||
infer_processors = check_transform_proc(infer_processors)
|
||||
learn_processors = check_transform_proc(learn_processors)
|
||||
infer_processors = check_transform_proc(infer_processors, fit_start_time, fit_end_time)
|
||||
learn_processors = check_transform_proc(learn_processors, fit_start_time, fit_end_time)
|
||||
|
||||
data_loader = {
|
||||
"class": "QlibDataLoader",
|
||||
|
||||
@@ -26,9 +26,9 @@ def risk_analysis(r, N=252):
|
||||
Parameters
|
||||
----------
|
||||
r : pandas.Series
|
||||
daily return 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).
|
||||
"""
|
||||
mean = r.mean()
|
||||
std = r.std(ddof=1)
|
||||
@@ -61,7 +61,7 @@ def get_strategy(
|
||||
----------
|
||||
|
||||
strategy : Strategy()
|
||||
strategy used in backtest
|
||||
strategy used in backtest.
|
||||
topk : int (Default value: 50)
|
||||
top-N stocks to buy.
|
||||
margin : int or float(Default value: 0.5)
|
||||
@@ -73,14 +73,14 @@ def get_strategy(
|
||||
|
||||
sell_limit = pred_in_a_day.count() * margin
|
||||
|
||||
buffer margin, in single score_mode, continue holding stock if it is in nlargest(sell_limit)
|
||||
sell_limit should be no less than topk
|
||||
buffer margin, in single score_mode, continue holding stock if it is in nlargest(sell_limit).
|
||||
sell_limit should be no less than topk.
|
||||
n_drop : int
|
||||
number of stocks to be replaced in each trading date
|
||||
number of stocks to be replaced in each trading date.
|
||||
risk_degree: float
|
||||
0-1, 0.95 for example, use 95% money to trade
|
||||
0-1, 0.95 for example, use 95% money to trade.
|
||||
str_type: 'amount', 'weight' or 'dropout'
|
||||
strategy type: TopkAmountStrategy ,TopkWeightStrategy or TopkDropoutStrategy
|
||||
strategy type: TopkAmountStrategy ,TopkWeightStrategy or TopkDropoutStrategy.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -126,21 +126,21 @@ def get_exchange(
|
||||
----------
|
||||
|
||||
# exchange related arguments
|
||||
exchange: Exchange()
|
||||
exchange: Exchange().
|
||||
subscribe_fields: list
|
||||
subscribe fields
|
||||
subscribe fields.
|
||||
open_cost : float
|
||||
open transaction cost
|
||||
open transaction cost.
|
||||
close_cost : float
|
||||
close transaction cost
|
||||
close transaction cost.
|
||||
min_cost : float
|
||||
min transaction cost
|
||||
min transaction cost.
|
||||
trade_unit : int
|
||||
100 for China A
|
||||
100 for China A.
|
||||
deal_price: str
|
||||
dealing price type: 'close', 'open', 'vwap'
|
||||
dealing price type: 'close', 'open', 'vwap'.
|
||||
limit_threshold : float
|
||||
limit move 0.1 (10%) for example, long and short with same limit
|
||||
limit move 0.1 (10%) for example, long and short with same limit.
|
||||
extract_codes: bool
|
||||
will we pass the codes extracted from the pred to the exchange.
|
||||
NOTE: This will be faster with offline qlib.
|
||||
@@ -193,20 +193,20 @@ def backtest(pred, account=1e9, shift=1, benchmark="SH000905", verbose=True, **k
|
||||
- **backtest workflow related or commmon arguments**
|
||||
|
||||
pred : pandas.DataFrame
|
||||
predict should has <datetime, instrument> index and one `score` column
|
||||
predict should has <datetime, instrument> index and one `score` column.
|
||||
account : float
|
||||
init account value
|
||||
init account value.
|
||||
shift : int
|
||||
whether to shift prediction by one day
|
||||
whether to shift prediction by one day.
|
||||
benchmark : str
|
||||
benchmark code, default is SH000905 CSI 500
|
||||
benchmark code, default is SH000905 CSI 500.
|
||||
verbose : bool
|
||||
whether to print log
|
||||
whether to print log.
|
||||
|
||||
- **strategy related arguments**
|
||||
|
||||
strategy : Strategy()
|
||||
strategy used in backtest
|
||||
strategy used in backtest.
|
||||
topk : int (Default value: 50)
|
||||
top-N stocks to buy.
|
||||
margin : int or float(Default value: 0.5)
|
||||
@@ -218,33 +218,33 @@ def backtest(pred, account=1e9, shift=1, benchmark="SH000905", verbose=True, **k
|
||||
|
||||
sell_limit = pred_in_a_day.count() * margin
|
||||
|
||||
buffer margin, in single score_mode, continue holding stock if it is in nlargest(sell_limit)
|
||||
sell_limit should be no less than topk
|
||||
buffer margin, in single score_mode, continue holding stock if it is in nlargest(sell_limit).
|
||||
sell_limit should be no less than topk.
|
||||
n_drop : int
|
||||
number of stocks to be replaced in each trading date
|
||||
number of stocks to be replaced in each trading date.
|
||||
risk_degree: float
|
||||
0-1, 0.95 for example, use 95% money to trade
|
||||
0-1, 0.95 for example, use 95% money to trade.
|
||||
str_type: 'amount', 'weight' or 'dropout'
|
||||
strategy type: TopkAmountStrategy ,TopkWeightStrategy or TopkDropoutStrategy
|
||||
strategy type: TopkAmountStrategy ,TopkWeightStrategy or TopkDropoutStrategy.
|
||||
|
||||
- **exchange related arguments**
|
||||
|
||||
exchange: Exchange()
|
||||
pass the exchange for speeding up.
|
||||
subscribe_fields: list
|
||||
subscribe fields
|
||||
subscribe fields.
|
||||
open_cost : float
|
||||
open transaction cost. The default value is 0.002(0.2%).
|
||||
close_cost : float
|
||||
close transaction cost. The default value is 0.002(0.2%).
|
||||
min_cost : float
|
||||
min transaction cost
|
||||
min transaction cost.
|
||||
trade_unit : int
|
||||
100 for China A
|
||||
100 for China A.
|
||||
deal_price: str
|
||||
dealing price type: 'close', 'open', 'vwap'
|
||||
dealing price type: 'close', 'open', 'vwap'.
|
||||
limit_threshold : float
|
||||
limit move 0.1 (10%) for example, long and short with same limit
|
||||
limit move 0.1 (10%) for example, long and short with same limit.
|
||||
extract_codes: bool
|
||||
will we pass the codes extracted from the pred to the exchange.
|
||||
|
||||
@@ -291,17 +291,17 @@ def long_short_backtest(
|
||||
"""
|
||||
A backtest for long-short strategy
|
||||
|
||||
:param pred: The trading signal produced on day `T`
|
||||
:param topk: The short topk securities and long topk securities
|
||||
:param deal_price: The price to deal the trading
|
||||
:param pred: The trading signal produced on day `T`.
|
||||
:param topk: The short topk securities and long topk securities.
|
||||
:param deal_price: The price to deal the trading.
|
||||
:param shift: Whether to shift prediction by one day. The trading day will be T+1 if shift==1.
|
||||
:param open_cost: open transaction cost
|
||||
:param close_cost: close transaction cost
|
||||
:param trade_unit: 100 for China A
|
||||
:param limit_threshold: limit move 0.1 (10%) for example, long and short with same limit
|
||||
:param min_cost: min transaction cost
|
||||
:param subscribe_fields: subscribe fields
|
||||
:param extract_codes: bool
|
||||
:param open_cost: open transaction cost.
|
||||
:param close_cost: close transaction cost.
|
||||
:param trade_unit: 100 for China A.
|
||||
:param limit_threshold: limit move 0.1 (10%) for example, long and short with same limit.
|
||||
:param min_cost: min transaction cost.
|
||||
:param subscribe_fields: subscribe fields.
|
||||
:param extract_codes: bool.
|
||||
will we pass the codes extracted from the pred to the exchange.
|
||||
NOTE: This will be faster with offline qlib.
|
||||
:return: The result of backtest, it is represented by a dict.
|
||||
|
||||
@@ -9,10 +9,8 @@ import os
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import copy
|
||||
from sklearn.metrics import roc_auc_score, mean_squared_error
|
||||
import logging
|
||||
from ...utils import unpack_archive_with_buffer, save_multiple_parts_file, create_save_path, drop_nan_by_y_index
|
||||
from ...log import get_module_logger, TimeInspector
|
||||
from ...utils import create_save_path
|
||||
from ...log import get_module_logger
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
@@ -28,14 +26,10 @@ class ALSTM(Model):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input_dim : int
|
||||
input dimension
|
||||
output_dim : int
|
||||
output dimension
|
||||
layers : tuple
|
||||
layer sizes
|
||||
lr : float
|
||||
learning rate
|
||||
d_feat : int
|
||||
input dimension for each time step
|
||||
metric: str
|
||||
the evaluate metric used in early stop
|
||||
optimizer : str
|
||||
optimizer name
|
||||
GPU : str
|
||||
@@ -116,14 +110,9 @@ class ALSTM(Model):
|
||||
)
|
||||
)
|
||||
|
||||
if loss not in {"mse", "binary"}:
|
||||
raise NotImplementedError("loss {} is not supported!".format(loss))
|
||||
self._scorer = mean_squared_error if loss == "mse" else roc_auc_score
|
||||
|
||||
self.alstm_model = ALSTMModel(
|
||||
d_feat=self.d_feat, hidden_size=self.hidden_size, num_layers=self.num_layers, dropout=self.dropout
|
||||
)
|
||||
# def __init__(self, d_feat=6, hidden_size=64, num_layers=2, dropout=0.0, input_day=20, rnn_type="GRU"):
|
||||
|
||||
if optimizer.lower() == "adam":
|
||||
self.train_optimizer = optim.Adam(self.alstm_model.parameters(), lr=self.lr)
|
||||
@@ -152,7 +141,6 @@ class ALSTM(Model):
|
||||
raise ValueError("unknown loss `%s`" % self.loss)
|
||||
|
||||
def metric_fn(self, pred, label):
|
||||
|
||||
mask = torch.isfinite(label)
|
||||
if self.metric == "IC":
|
||||
return self.cal_ic(pred[mask], label[mask])
|
||||
@@ -197,7 +185,7 @@ class ALSTM(Model):
|
||||
|
||||
def test_epoch(self, data_x, data_y):
|
||||
|
||||
# prepare training data
|
||||
# prepare testing data
|
||||
x_values = data_x.values
|
||||
y_values = np.squeeze(data_y.values)
|
||||
|
||||
@@ -207,7 +195,6 @@ class ALSTM(Model):
|
||||
losses = []
|
||||
|
||||
indices = np.arange(len(x_values))
|
||||
np.random.shuffle(indices)
|
||||
|
||||
for i in range(len(indices))[:: self.batch_size]:
|
||||
|
||||
@@ -248,7 +235,6 @@ class ALSTM(Model):
|
||||
if save_path == None:
|
||||
save_path = create_save_path(save_path)
|
||||
stop_steps = 0
|
||||
train_loss = 0
|
||||
best_score = -np.inf
|
||||
best_epoch = 0
|
||||
evals_result["train"] = []
|
||||
@@ -257,7 +243,6 @@ class ALSTM(Model):
|
||||
# train
|
||||
self.logger.info("training...")
|
||||
self._fitted = True
|
||||
# return
|
||||
|
||||
for step in range(self.n_epochs):
|
||||
self.logger.info("Epoch%d:", step)
|
||||
@@ -334,11 +319,9 @@ class GRUModel(nn.Module):
|
||||
dropout=dropout,
|
||||
)
|
||||
self.fc_out = nn.Linear(hidden_size, 1)
|
||||
|
||||
self.d_feat = d_feat
|
||||
|
||||
def forward(self, x):
|
||||
# x: [N, F*T]
|
||||
x = x.reshape(len(x), self.d_feat, -1) # [N, F, T]
|
||||
x = x.permute(0, 2, 1) # [N, T, F]
|
||||
out, _ = self.rnn(x)
|
||||
@@ -371,7 +354,6 @@ class ALSTMModel(nn.Module):
|
||||
dropout=self.dropout,
|
||||
)
|
||||
self.fc_out = nn.Linear(in_features=self.hid_size * 2, out_features=1)
|
||||
# self.fc_out = nn.Linear(in_features=self.hid_size, out_features=1)
|
||||
self.att_net = nn.Sequential()
|
||||
self.att_net.add_module("att_fc_in", nn.Linear(in_features=self.hid_size, out_features=int(self.hid_size / 2)))
|
||||
self.att_net.add_module("att_dropout", torch.nn.Dropout(self.dropout))
|
||||
@@ -390,5 +372,4 @@ class ALSTMModel(nn.Module):
|
||||
out = self.fc_out(
|
||||
torch.cat((rnn_out[:, -1, :], out_att), dim=1)
|
||||
) # [batch, seq_len, num_directions * hidden_size] -> [batch, 1]
|
||||
# out = self.fc_out(rnn_out[:, -1, :] + out_att)
|
||||
return out[..., 0]
|
||||
|
||||
@@ -28,14 +28,12 @@ class GAT(Model):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input_dim : int
|
||||
input dimension
|
||||
output_dim : int
|
||||
output dimension
|
||||
layers : tuple
|
||||
layer sizes
|
||||
lr : float
|
||||
learning rate
|
||||
d_feat : int
|
||||
input dimensions for each time step
|
||||
metric : str
|
||||
the evaluate metric used in early stop
|
||||
optimizer : str
|
||||
optimizer name
|
||||
GPU : str
|
||||
@@ -120,10 +118,6 @@ class GAT(Model):
|
||||
)
|
||||
)
|
||||
|
||||
if loss not in {"mse", "binary"}:
|
||||
raise NotImplementedError("loss {} is not supported!".format(loss))
|
||||
self._scorer = mean_squared_error if loss == "mse" else roc_auc_score
|
||||
|
||||
self.GAT_model = GATModel(
|
||||
d_feat=self.d_feat,
|
||||
hidden_size=self.hidden_size,
|
||||
@@ -213,7 +207,6 @@ class GAT(Model):
|
||||
losses = []
|
||||
|
||||
indices = np.arange(len(x_values))
|
||||
np.random.shuffle(indices)
|
||||
|
||||
for i in range(len(indices))[:: self.batch_size]:
|
||||
|
||||
@@ -377,7 +370,6 @@ class GATModel(nn.Module):
|
||||
self.fc_out = nn.Linear(hidden_size, 1)
|
||||
self.leaky_relu = nn.LeakyReLU()
|
||||
self.softmax = nn.Softmax(dim=1)
|
||||
|
||||
self.d_feat = d_feat
|
||||
|
||||
def cal_convariance(self, x, y): # the 2nd dimension of x and y are the same
|
||||
@@ -396,12 +388,7 @@ class GATModel(nn.Module):
|
||||
out, _ = self.rnn(x)
|
||||
hidden = out[:, -1, :]
|
||||
hidden = self.bn1(hidden)
|
||||
|
||||
gamma = self.cal_convariance(hidden, hidden)
|
||||
# gamma = hidden.mm(torch.t(hidden))
|
||||
# gamma = self.leaky_relu(gamma)
|
||||
# gamma = self.softmax(gamma)
|
||||
# gamma = gamma * (torch.ones(x.shape[0], x.shape[0]).to(device) - torch.diag(torch.ones(x.shape[0])).to(device))
|
||||
output = gamma.mm(hidden)
|
||||
output = self.fc(output)
|
||||
output = self.bn2(output)
|
||||
|
||||
@@ -28,14 +28,10 @@ class GRU(Model):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input_dim : int
|
||||
input dimension
|
||||
output_dim : int
|
||||
output dimension
|
||||
layers : tuple
|
||||
layer sizes
|
||||
lr : float
|
||||
learning rate
|
||||
d_feat : int
|
||||
input dimension for each time step
|
||||
metric: str
|
||||
the evaluate metric used in early stop
|
||||
optimizer : str
|
||||
optimizer name
|
||||
GPU : str
|
||||
@@ -112,10 +108,6 @@ class GRU(Model):
|
||||
)
|
||||
)
|
||||
|
||||
if loss not in {"mse", "binary"}:
|
||||
raise NotImplementedError("loss {} is not supported!".format(loss))
|
||||
self._scorer = mean_squared_error if loss == "mse" else roc_auc_score
|
||||
|
||||
self.gru_model = GRUModel(
|
||||
d_feat=self.d_feat, hidden_size=self.hidden_size, num_layers=self.num_layers, dropout=self.dropout
|
||||
)
|
||||
@@ -251,7 +243,6 @@ class GRU(Model):
|
||||
# train
|
||||
self.logger.info("training...")
|
||||
self._fitted = True
|
||||
# return
|
||||
|
||||
for step in range(self.n_epochs):
|
||||
self.logger.info("Epoch%d:", step)
|
||||
|
||||
@@ -18,10 +18,8 @@ import os
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import copy
|
||||
from sklearn.metrics import roc_auc_score, mean_squared_error
|
||||
import logging
|
||||
from ...utils import unpack_archive_with_buffer, save_multiple_parts_file, create_save_path, drop_nan_by_y_index
|
||||
from ...log import get_module_logger, TimeInspector
|
||||
from ...utils import create_save_path
|
||||
from ...log import get_module_logger
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
@@ -37,14 +35,10 @@ class HATS(Model):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input_dim : int
|
||||
input dimension
|
||||
output_dim : int
|
||||
output dimension
|
||||
layers : tuple
|
||||
layer sizes
|
||||
lr : float
|
||||
learning rate
|
||||
d_feat : int
|
||||
input dimension for each time step
|
||||
metric: str
|
||||
the evaluate metric used in early stop
|
||||
optimizer : str
|
||||
optimizer name
|
||||
GPU : str
|
||||
@@ -87,7 +81,7 @@ class HATS(Model):
|
||||
self.optimizer = optimizer.lower()
|
||||
self.loss = loss
|
||||
self.base_model = base_model
|
||||
self.with_pretrain = with_pretrain #### True if train HATS with pretrained base model
|
||||
self.with_pretrain = with_pretrain
|
||||
self.visible_GPU = GPU
|
||||
self.use_gpu = torch.cuda.is_available()
|
||||
self.seed = seed
|
||||
@@ -106,7 +100,7 @@ class HATS(Model):
|
||||
"\noptimizer : {}"
|
||||
"\nloss_type : {}"
|
||||
"\nbase_model : {}"
|
||||
"\nwith_pretrain : {}" ##### debug
|
||||
"\nwith_pretrain : {}"
|
||||
"\nvisible_GPU : {}"
|
||||
"\nuse_GPU : {}"
|
||||
"\nseed : {}".format(
|
||||
@@ -122,17 +116,13 @@ class HATS(Model):
|
||||
optimizer.lower(),
|
||||
loss,
|
||||
base_model,
|
||||
with_pretrain, ### debug
|
||||
with_pretrain,
|
||||
GPU,
|
||||
self.use_gpu,
|
||||
seed,
|
||||
)
|
||||
)
|
||||
|
||||
if loss not in {"mse", "binary"}:
|
||||
raise NotImplementedError("loss {} is not supported!".format(loss))
|
||||
self._scorer = mean_squared_error if loss == "mse" else roc_auc_score
|
||||
|
||||
self.HATS_model = HATSModel(
|
||||
d_feat=self.d_feat,
|
||||
hidden_size=self.hidden_size,
|
||||
@@ -167,7 +157,6 @@ class HATS(Model):
|
||||
raise ValueError("unknown loss `%s`" % self.loss)
|
||||
|
||||
def metric_fn(self, pred, label):
|
||||
|
||||
mask = torch.isfinite(label)
|
||||
if self.metric == "IC":
|
||||
return self.cal_ic(pred[mask], label[mask])
|
||||
@@ -212,7 +201,7 @@ class HATS(Model):
|
||||
|
||||
def test_epoch(self, data_x, data_y):
|
||||
|
||||
# prepare training data
|
||||
# prepare testing data
|
||||
x_values = data_x.values
|
||||
y_values = np.squeeze(data_y.values)
|
||||
|
||||
@@ -222,7 +211,6 @@ class HATS(Model):
|
||||
losses = []
|
||||
|
||||
indices = np.arange(len(x_values))
|
||||
np.random.shuffle(indices)
|
||||
|
||||
for i in range(len(indices))[:: self.batch_size]:
|
||||
|
||||
@@ -263,7 +251,6 @@ class HATS(Model):
|
||||
if save_path == None:
|
||||
save_path = create_save_path(save_path)
|
||||
stop_steps = 0
|
||||
train_loss = 0
|
||||
best_score = -np.inf
|
||||
best_epoch = 0
|
||||
evals_result["train"] = []
|
||||
@@ -271,7 +258,7 @@ class HATS(Model):
|
||||
|
||||
# load pretrained base_model
|
||||
if self.with_pretrain:
|
||||
self.logger.info("loading pretrained model...")
|
||||
self.logger.info("Loading pretrained model...")
|
||||
if self.base_model == "LSTM":
|
||||
from ...contrib.model.pytorch_lstm import LSTMModel
|
||||
|
||||
@@ -283,19 +270,14 @@ class HATS(Model):
|
||||
pretrained_model = GRUModel()
|
||||
pretrained_model.load_state_dict(torch.load("benchmarks/GRU/model_gru_csi300.pkl"))
|
||||
model_dict = self.HATS_model.state_dict()
|
||||
|
||||
# filter unnecessary parameters
|
||||
pretrained_dict = {k: v for k, v in pretrained_model.state_dict().items() if k in model_dict}
|
||||
# overwrite entries in the existing state dict
|
||||
model_dict.update(pretrained_dict)
|
||||
# load the new state dict
|
||||
self.HATS_model.load_state_dict(model_dict)
|
||||
self.logger.info("loading pretrained model Done...")
|
||||
self.logger.info("Loading pretrained model Done...")
|
||||
|
||||
# train
|
||||
self.logger.info("training...")
|
||||
self._fitted = True
|
||||
# return
|
||||
|
||||
for step in range(self.n_epochs):
|
||||
self.logger.info("Epoch%d:", step)
|
||||
@@ -447,20 +429,20 @@ class GraphAttention(nn.Module):
|
||||
self.softmax = nn.Softmax(dim=0)
|
||||
self.leakyrelu = nn.LeakyReLU()
|
||||
|
||||
def forward(self, features, nodes, mapping, rows):
|
||||
def forward(self, features, nodes, mappings, rows):
|
||||
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
features : torch.Tensor
|
||||
An (n' x input_dim) tensor of input node features.
|
||||
node_layers : list of numpy array
|
||||
node_layers[i] is an array of the nodes in the ith layer of the
|
||||
nodes : list of numpy array
|
||||
nodes[i] is an array of the nodes in the ith layer of the
|
||||
computation graph.
|
||||
mappings : list of dictionary
|
||||
mappings[i] is a dictionary mapping node v (labelled 0 to |V|-1)
|
||||
in node_layers[i] to its position in node_layers[i]. For example,
|
||||
if node_layers[i] = [2,5], then mappings[i][2] = 0 and
|
||||
mappings[i] is a dictionary mappings node v (labelled 0 to |V|-1)
|
||||
in nodes[i] to its position in nodes[i]. For example,
|
||||
if nodes[i] = [2,5], then mappings[i][2] = 0 and
|
||||
mappings[i][5] = 1.
|
||||
rows : numpy array
|
||||
rows[i] is an array of neighbors of node i.
|
||||
@@ -471,9 +453,9 @@ class GraphAttention(nn.Module):
|
||||
"""
|
||||
|
||||
nprime = features.shape[0]
|
||||
rows = [np.array([mapping[v] for v in row], dtype=np.int64) for row in rows]
|
||||
rows = [np.array([mappings[v] for v in row], dtype=np.int64) for row in rows]
|
||||
sum_degs = np.hstack(([0], np.cumsum([len(row) for row in rows])))
|
||||
mapped_nodes = [mapping[v] for v in nodes]
|
||||
mapped_nodes = [mappings[v] for v in nodes]
|
||||
indices = torch.LongTensor([[v, c] for (v, row) in zip(mapped_nodes, rows) for c in row]).t()
|
||||
|
||||
out = []
|
||||
@@ -481,7 +463,9 @@ class GraphAttention(nn.Module):
|
||||
h = self.fcs[k](features)
|
||||
|
||||
nbr_h = torch.cat(tuple([h[row] for row in rows]), dim=0)
|
||||
self_h = torch.cat(tuple([h[mapping[nodes[i]]].repeat(len(row), 1) for (i, row) in enumerate(rows)]), dim=0)
|
||||
self_h = torch.cat(
|
||||
tuple([h[mappings[nodes[i]]].repeat(len(row), 1) for (i, row) in enumerate(rows)]), dim=0
|
||||
)
|
||||
cat_h = torch.cat((self_h, nbr_h), dim=1)
|
||||
|
||||
e = self.leakyrelu(self.a[k](cat_h))
|
||||
@@ -496,13 +480,11 @@ class GraphAttention(nn.Module):
|
||||
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def cal_attention(x, y):
|
||||
|
||||
att_x = torch.mean(x, dim=1).reshape(-1, 1)
|
||||
att_y = torch.mean(y, dim=1).reshape(-1, 1)
|
||||
att = att_x.mm(torch.t(att_y))
|
||||
x_att = x.reshape(x.shape[0], 1, x.shape[1]).repeat(1, y.shape[0], 1)
|
||||
y_att = y.reshape(1, y.shape[0], y.shape[1]).repeat(x.shape[0], 1, 1)
|
||||
return (
|
||||
torch.mean(
|
||||
x.reshape(x.shape[0], 1, x.shape[1]).repeat(1, y.shape[0], 1)
|
||||
|
||||
@@ -28,14 +28,10 @@ class LSTM(Model):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input_dim : int
|
||||
input dimension
|
||||
output_dim : int
|
||||
output dimension
|
||||
layers : tuple
|
||||
layer sizes
|
||||
lr : float
|
||||
learning rate
|
||||
d_feat : int
|
||||
input dimension for each time step
|
||||
metric: str
|
||||
the evaluate metric used in early stop
|
||||
optimizer : str
|
||||
optimizer name
|
||||
GPU : str
|
||||
@@ -112,10 +108,6 @@ class LSTM(Model):
|
||||
)
|
||||
)
|
||||
|
||||
if loss not in {"mse", "binary"}:
|
||||
raise NotImplementedError("loss {} is not supported!".format(loss))
|
||||
self._scorer = mean_squared_error if loss == "mse" else roc_auc_score
|
||||
|
||||
self.lstm_model = LSTMModel(
|
||||
d_feat=self.d_feat, hidden_size=self.hidden_size, num_layers=self.num_layers, dropout=self.dropout
|
||||
)
|
||||
@@ -251,7 +243,6 @@ class LSTM(Model):
|
||||
# train
|
||||
self.logger.info("training...")
|
||||
self._fitted = True
|
||||
# return
|
||||
|
||||
for step in range(self.n_epochs):
|
||||
self.logger.info("Epoch%d:", step)
|
||||
|
||||
@@ -203,6 +203,7 @@ class SFM(Model):
|
||||
dropout_U=0.0,
|
||||
n_epochs=200,
|
||||
lr=0.001,
|
||||
metric="",
|
||||
batch_size=2000,
|
||||
early_stop=20,
|
||||
eval_steps=5,
|
||||
@@ -227,13 +228,14 @@ class SFM(Model):
|
||||
self.dropout_U = dropout_U
|
||||
self.n_epochs = n_epochs
|
||||
self.lr = lr
|
||||
self.metric = metric
|
||||
self.batch_size = batch_size
|
||||
self.early_stop = early_stop
|
||||
self.eval_steps = eval_steps
|
||||
self.lr_decay = lr_decay
|
||||
self.lr_decay_steps = lr_decay_steps
|
||||
self.optimizer = optimizer.lower()
|
||||
self.loss_type = loss
|
||||
self.loss = loss
|
||||
self.device = "cuda:%d" % (GPU) if torch.cuda.is_available() else "cpu"
|
||||
self.use_gpu = torch.cuda.is_available()
|
||||
self.seed = seed
|
||||
@@ -248,6 +250,7 @@ class SFM(Model):
|
||||
"\ndropout_U: {}"
|
||||
"\nn_epochs : {}"
|
||||
"\nlr : {}"
|
||||
"\nmetric : {}"
|
||||
"\nbatch_size : {}"
|
||||
"\nearly_stop : {}"
|
||||
"\neval_steps : {}"
|
||||
@@ -266,6 +269,7 @@ class SFM(Model):
|
||||
dropout_U,
|
||||
n_epochs,
|
||||
lr,
|
||||
metric,
|
||||
batch_size,
|
||||
early_stop,
|
||||
eval_steps,
|
||||
@@ -299,24 +303,73 @@ class SFM(Model):
|
||||
else:
|
||||
raise NotImplementedError("optimizer {} is not supported!".format(optimizer))
|
||||
|
||||
# Reduce learning rate when loss has stopped decrease
|
||||
self.scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
|
||||
self.train_optimizer,
|
||||
mode="min",
|
||||
factor=0.5,
|
||||
patience=10,
|
||||
verbose=True,
|
||||
threshold=0.0001,
|
||||
threshold_mode="rel",
|
||||
cooldown=0,
|
||||
min_lr=0.00001,
|
||||
eps=1e-08,
|
||||
)
|
||||
|
||||
self._fitted = False
|
||||
self.sfm_model.to(self.device)
|
||||
|
||||
def fit(self, dataset: DatasetH, evals_result=dict(), verbose=True, save_path=None, **kwargs):
|
||||
def test_epoch(self, data_x, data_y):
|
||||
|
||||
# prepare training data
|
||||
x_values = data_x.values
|
||||
y_values = np.squeeze(data_y.values)
|
||||
|
||||
self.sfm_model.eval()
|
||||
|
||||
scores = []
|
||||
losses = []
|
||||
|
||||
indices = np.arange(len(x_values))
|
||||
np.random.shuffle(indices)
|
||||
|
||||
for i in range(len(indices))[:: self.batch_size]:
|
||||
|
||||
if len(indices) - i < self.batch_size:
|
||||
break
|
||||
|
||||
feature = torch.from_numpy(x_values[indices[i : i + self.batch_size]]).float().to(self.device)
|
||||
label = torch.from_numpy(y_values[indices[i : i + self.batch_size]]).float().to(self.device)
|
||||
|
||||
pred = self.sfm_model(feature)
|
||||
loss = self.loss_fn(pred, label)
|
||||
losses.append(loss.item())
|
||||
|
||||
score = self.metric_fn(pred, label)
|
||||
scores.append(score.item())
|
||||
|
||||
return np.mean(losses), np.mean(scores)
|
||||
|
||||
def train_epoch(self, x_train, y_train):
|
||||
|
||||
x_train_values = x_train.values
|
||||
y_train_values = np.squeeze(y_train.values) * 100
|
||||
|
||||
self.sfm_model.train()
|
||||
|
||||
indices = np.arange(len(x_train_values))
|
||||
np.random.shuffle(indices)
|
||||
|
||||
for i in range(len(indices))[:: self.batch_size]:
|
||||
|
||||
if len(indices) - i < self.batch_size:
|
||||
break
|
||||
|
||||
feature = torch.from_numpy(x_train_values[indices[i : i + self.batch_size]]).float().to(self.device)
|
||||
label = torch.from_numpy(y_train_values[indices[i : i + self.batch_size]]).float().to(self.device)
|
||||
|
||||
pred = self.sfm_model(feature)
|
||||
loss = self.loss_fn(pred, label)
|
||||
|
||||
self.train_optimizer.zero_grad()
|
||||
loss.backward()
|
||||
torch.nn.utils.clip_grad_value_(self.sfm_model.parameters(), 3.0)
|
||||
self.train_optimizer.step()
|
||||
|
||||
def fit(
|
||||
self,
|
||||
dataset: DatasetH,
|
||||
evals_result=dict(),
|
||||
verbose=True,
|
||||
save_path=None,
|
||||
):
|
||||
|
||||
df_train, df_valid = dataset.prepare(
|
||||
["train", "valid"], col_set=["feature", "label"], data_key=DataHandlerLP.DK_L
|
||||
@@ -324,10 +377,10 @@ class SFM(Model):
|
||||
x_train, y_train = df_train["feature"], df_train["label"]
|
||||
x_valid, y_valid = df_valid["feature"], df_valid["label"]
|
||||
|
||||
save_path = create_save_path(save_path)
|
||||
stop_steps = 0
|
||||
train_loss = 0
|
||||
best_loss = np.inf
|
||||
best_score = -np.inf
|
||||
best_epoch = 0
|
||||
evals_result["train"] = []
|
||||
evals_result["valid"] = []
|
||||
|
||||
@@ -335,89 +388,56 @@ class SFM(Model):
|
||||
self.logger.info("training...")
|
||||
self._fitted = True
|
||||
|
||||
# prepare training data
|
||||
x_train_values = torch.from_numpy(x_train.values).float()
|
||||
y_train_values = torch.from_numpy(np.squeeze(y_train.values)).float()
|
||||
train_num = y_train_values.shape[0]
|
||||
|
||||
# prepare validation data
|
||||
x_val_auto = torch.from_numpy(x_valid.values).float()
|
||||
y_val_auto = torch.from_numpy(np.squeeze(y_valid.values)).float()
|
||||
|
||||
x_val_auto = x_val_auto.to(self.device)
|
||||
y_val_auto = y_val_auto.to(self.device)
|
||||
|
||||
for step in range(self.n_epochs):
|
||||
if stop_steps >= self.early_stop:
|
||||
if verbose:
|
||||
self.logger.info("\tearly stop")
|
||||
break
|
||||
loss = AverageMeter()
|
||||
self.sfm_model.train()
|
||||
self.train_optimizer.zero_grad()
|
||||
self.logger.info("Epoch%d:", step)
|
||||
self.logger.info("training...")
|
||||
self.train_epoch(x_train, y_train)
|
||||
self.logger.info("evaluating...")
|
||||
train_loss, train_score = self.test_epoch(x_train, y_train)
|
||||
val_loss, val_score = self.test_epoch(x_valid, y_valid)
|
||||
self.logger.info("train %.6f, valid %.6f" % (train_score, val_score))
|
||||
evals_result["train"].append(train_score)
|
||||
evals_result["valid"].append(val_score)
|
||||
|
||||
choice = np.random.choice(train_num, self.batch_size)
|
||||
x_batch_auto = x_train_values[choice]
|
||||
y_batch_auto = y_train_values[choice]
|
||||
|
||||
x_batch_auto = x_batch_auto.to(self.device)
|
||||
y_batch_auto = y_batch_auto.to(self.device)
|
||||
|
||||
# forward
|
||||
preds = self.sfm_model(x_batch_auto)
|
||||
cur_loss = self.get_loss(preds, y_batch_auto, self.loss_type)
|
||||
cur_loss.backward()
|
||||
self.train_optimizer.step()
|
||||
loss.update(cur_loss.item())
|
||||
|
||||
# validation
|
||||
train_loss += loss.val
|
||||
if step and step % self.eval_steps == 0:
|
||||
if val_score > best_score:
|
||||
best_score = val_score
|
||||
stop_steps = 0
|
||||
best_epoch = step
|
||||
best_param = copy.deepcopy(self.sfm_model.state_dict())
|
||||
else:
|
||||
stop_steps += 1
|
||||
train_loss /= self.eval_steps
|
||||
|
||||
with torch.no_grad():
|
||||
self.sfm_model.eval()
|
||||
loss_val = AverageMeter()
|
||||
|
||||
# forward
|
||||
preds = self.sfm_model(x_val_auto)
|
||||
cur_loss_val = self.get_loss(preds, y_val_auto, self.loss_type)
|
||||
loss_val.update(cur_loss_val.item())
|
||||
|
||||
if verbose:
|
||||
self.logger.info(
|
||||
"[Epoch {}]: train_loss {:.6f}, valid_loss {:.6f}".format(step, train_loss, loss_val.val)
|
||||
)
|
||||
evals_result["train"].append(train_loss)
|
||||
evals_result["valid"].append(loss_val.val)
|
||||
if loss_val.val < best_loss:
|
||||
if verbose:
|
||||
self.logger.info(
|
||||
"\tvalid loss update from {:.6f} to {:.6f}, save checkpoint.".format(
|
||||
best_loss, loss_val.val
|
||||
)
|
||||
)
|
||||
best_loss = loss_val.val
|
||||
stop_steps = 0
|
||||
torch.save(self.sfm_model.state_dict(), save_path)
|
||||
train_loss = 0
|
||||
# update learning rate
|
||||
self.scheduler.step(cur_loss_val)
|
||||
|
||||
if stop_steps >= self.early_stop:
|
||||
self.logger.info("early stop")
|
||||
break
|
||||
self.logger.info("best score: %.6lf @ %d" % (best_score, best_epoch))
|
||||
if self.device != "cpu":
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
def get_loss(self, pred, target, loss_type):
|
||||
if loss_type == "mse":
|
||||
sqr_loss = (pred - target) ** 2
|
||||
loss = sqr_loss.mean()
|
||||
return loss
|
||||
elif loss_type == "binary":
|
||||
loss = nn.BCELoss()
|
||||
return loss(pred, target)
|
||||
else:
|
||||
raise NotImplementedError("loss {} is not supported!".format(loss_type))
|
||||
def mse(self, pred, label):
|
||||
loss = (pred - label) ** 2
|
||||
return torch.mean(loss)
|
||||
|
||||
def loss_fn(self, pred, label):
|
||||
mask = ~torch.isnan(label)
|
||||
|
||||
if self.loss == "mse":
|
||||
return self.mse(pred[mask], label[mask])
|
||||
|
||||
raise ValueError("unknown loss `%s`" % self.loss)
|
||||
|
||||
def metric_fn(self, pred, label):
|
||||
|
||||
mask = torch.isfinite(label)
|
||||
if self.metric == "IC":
|
||||
return self.cal_ic(pred[mask], label[mask])
|
||||
|
||||
if self.metric == "" or self.metric == "loss": # use loss
|
||||
return -self.loss_fn(pred[mask], label[mask])
|
||||
|
||||
raise ValueError("unknown metric `%s`" % self.metric)
|
||||
|
||||
def cal_ic(self, pred, label):
|
||||
return torch.mean(pred * label)
|
||||
|
||||
def predict(self, dataset):
|
||||
if not self._fitted:
|
||||
@@ -442,31 +462,12 @@ class SFM(Model):
|
||||
x_batch = x_batch.to(self.device)
|
||||
|
||||
with torch.no_grad():
|
||||
if self.device != "cpu":
|
||||
pred = self.sfm_model(x_batch).detach().cpu().numpy()
|
||||
else:
|
||||
pred = self.sfm_model(x_batch).detach().cpu().numpy()
|
||||
pred = self.sfm_model(x_batch).detach().cpu().numpy()
|
||||
|
||||
preds.append(pred)
|
||||
|
||||
return pd.Series(np.concatenate(preds), index=index)
|
||||
|
||||
def save(self, filename, **kwargs):
|
||||
with save_multiple_parts_file(filename) as model_dir:
|
||||
model_path = os.path.join(model_dir, os.path.split(model_dir)[-1])
|
||||
# Save model
|
||||
torch.save(self.sfm_model.state_dict(), model_path)
|
||||
|
||||
def load(self, buffer, **kwargs):
|
||||
with unpack_archive_with_buffer(buffer) as model_dir:
|
||||
# Get model name
|
||||
_model_name = os.path.splitext(list(filter(lambda x: x.startswith("model.bin"), os.listdir(model_dir)))[0])[
|
||||
0
|
||||
]
|
||||
_model_path = os.path.join(model_dir, _model_name)
|
||||
# Load model
|
||||
self.sfm_model.load_state_dict(torch.load(_model_path))
|
||||
self._fitted = True
|
||||
|
||||
|
||||
class AverageMeter(object):
|
||||
"""Computes and stores the average and current value"""
|
||||
|
||||
@@ -22,10 +22,8 @@ from ...data.dataset.handler import DataHandlerLP
|
||||
class XGBModel(Model):
|
||||
"""XGBModel Model"""
|
||||
|
||||
def __init__(self, obj="mse", **kwargs):
|
||||
if obj not in {"mse", "binary"}:
|
||||
raise NotImplementedError
|
||||
self._params = {"obj": obj}
|
||||
def __init__(self, **kwargs):
|
||||
self._params = {}
|
||||
self._params.update(kwargs)
|
||||
self.model = None
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ def model_performance_graph(
|
||||
"""Model performance
|
||||
|
||||
:param pred_label: index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[score,
|
||||
label]**. It is usually same as the label of model training(e.g. "Ref($close, -2)/Ref($close, -1) - 1")
|
||||
label]**. It is usually same as the label of model training(e.g. "Ref($close, -2)/Ref($close, -1) - 1").
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
@@ -266,13 +266,13 @@ def model_performance_graph(
|
||||
|
||||
|
||||
:param lag: `pred.groupby(level='instrument')['score'].shift(lag)`. It will be only used in the auto-correlation computing.
|
||||
:param N: group number, default 5
|
||||
:param reverse: if `True`, `pred['score'] *= -1`
|
||||
:param rank: if **True**, calculate rank ic
|
||||
:param graph_names: graph names; default ['cumulative_return', 'pred_ic', 'pred_autocorr', 'pred_turnover']
|
||||
:param show_notebook: whether to display graphics in notebook, the default is `True`
|
||||
:param show_nature_day: whether to display the abscissa of non-trading day
|
||||
:return: if show_notebook is True, display in notebook; else return `plotly.graph_objs.Figure` list
|
||||
:param N: group number, default 5.
|
||||
:param reverse: if `True`, `pred['score'] *= -1`.
|
||||
:param rank: if **True**, calculate rank ic.
|
||||
:param graph_names: graph names; default ['cumulative_return', 'pred_ic', 'pred_autocorr', 'pred_turnover'].
|
||||
:param show_notebook: whether to display graphics in notebook, the default is `True`.
|
||||
:param show_nature_day: whether to display the abscissa of non-trading day.
|
||||
:return: if show_notebook is True, display in notebook; else return `plotly.graph_objs.Figure` list.
|
||||
"""
|
||||
figure_list = []
|
||||
for graph_name in graph_names:
|
||||
|
||||
@@ -218,10 +218,10 @@ def cumulative_return_graph(
|
||||
|
||||
|
||||
Graph desc:
|
||||
- Axis X: Trading day
|
||||
- Axis X: Trading day.
|
||||
- Axis Y:
|
||||
- Above axis Y: `(((Ref($close, -1)/$close - 1) * weight).sum() / weight.sum()).cumsum()`
|
||||
- Below axis Y: Daily weight sum
|
||||
- Above axis Y: `(((Ref($close, -1)/$close - 1) * weight).sum() / weight.sum()).cumsum()`.
|
||||
- Below axis Y: Daily weight sum.
|
||||
- In the **sell** graph, `y < 0` stands for profit; in other cases, `y > 0` stands for profit.
|
||||
- In the **buy_minus_sell** graph, the **y** value of the **weight** graph at the bottom is `buy_weight + sell_weight`.
|
||||
- In each graph, the **red line** in the histogram on the right represents the average.
|
||||
|
||||
@@ -97,9 +97,9 @@ def rank_label_graph(
|
||||
qcr.rank_label_graph(positions, features_df, pred_df_dates.min(), pred_df_dates.max())
|
||||
|
||||
|
||||
:param position: position data; **qlib.contrib.backtest.backtest.backtest** result
|
||||
:param position: position data; **qlib.contrib.backtest.backtest.backtest** result.
|
||||
:param label_data: **D.features** result; index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[label]**.
|
||||
**The label T is the change from T to T+1**, it is recommended to use ``close``, example: `D.features(D.instruments('csi500'), ['Ref($close, -1)/$close-1'])`
|
||||
**The label T is the change from T to T+1**, it is recommended to use ``close``, example: `D.features(D.instruments('csi500'), ['Ref($close, -1)/$close-1'])`.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
@@ -115,7 +115,7 @@ def rank_label_graph(
|
||||
|
||||
:param start_date: start date
|
||||
:param end_date: end_date
|
||||
:param show_notebook: **True** or **False**. If True, show graph in notebook, else return figures
|
||||
:param show_notebook: **True** or **False**. If True, show graph in notebook, else return figures.
|
||||
:return:
|
||||
"""
|
||||
position = copy.deepcopy(position)
|
||||
|
||||
@@ -186,7 +186,7 @@ def report_graph(report_df: pd.DataFrame, show_notebook: bool = True) -> [list,
|
||||
|
||||
qcr.report_graph(report_normal_df)
|
||||
|
||||
:param report_df: **df.index.name** must be **date**, **df.columns** must contain **return**, **turnover**, **cost**, **bench**
|
||||
:param report_df: **df.index.name** must be **date**, **df.columns** must contain **return**, **turnover**, **cost**, **bench**.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
@@ -200,8 +200,8 @@ def report_graph(report_df: pd.DataFrame, show_notebook: bool = True) -> [list,
|
||||
2017-01-10 -0.000416 0.000440 -0.003350 0.208396
|
||||
|
||||
|
||||
:param show_notebook: whether to display graphics in notebook, the default is **True**
|
||||
:return: if show_notebook is True, display in notebook; else return **plotly.graph_objs.Figure** list
|
||||
:param show_notebook: whether to display graphics in notebook, the default is **True**.
|
||||
:return: if show_notebook is True, display in notebook; else return **plotly.graph_objs.Figure** list.
|
||||
"""
|
||||
report_df = report_df.copy()
|
||||
fig_list = _report_figure(report_df)
|
||||
|
||||
@@ -218,7 +218,7 @@ def risk_analysis_graph(
|
||||
max_drawdown -0.088263
|
||||
|
||||
|
||||
:param report_normal_df: **df.index.name** must be **date**, df.columns must contain **return**, **turnover**, **cost**, **bench**
|
||||
:param report_normal_df: **df.index.name** must be **date**, df.columns must contain **return**, **turnover**, **cost**, **bench**.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
@@ -232,7 +232,7 @@ def risk_analysis_graph(
|
||||
2017-01-10 -0.000416 0.000440 -0.003350 0.208396
|
||||
|
||||
|
||||
:param report_long_short_df: **df.index.name** must be **date**, df.columns contain **long**, **short**, **long_short**
|
||||
:param report_long_short_df: **df.index.name** must be **date**, df.columns contain **long**, **short**, **long_short**.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
@@ -246,7 +246,7 @@ def risk_analysis_graph(
|
||||
2017-01-10 0.000824 -0.001944 -0.001120
|
||||
|
||||
|
||||
:param show_notebook: Whether to display graphics in a notebook, default **True**
|
||||
:param show_notebook: Whether to display graphics in a notebook, default **True**.
|
||||
If True, show graph in notebook
|
||||
If False, return graph figure
|
||||
:return:
|
||||
|
||||
@@ -36,7 +36,7 @@ def score_ic_graph(pred_label: pd.DataFrame, show_notebook: bool = True) -> [lis
|
||||
analysis_position.score_ic_graph(pred_label)
|
||||
|
||||
|
||||
:param pred_label: index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[score, label]**
|
||||
:param pred_label: index is **pd.MultiIndex**, index name is **[instrument, datetime]**; columns names is **[score, label]**.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
@@ -49,8 +49,8 @@ def score_ic_graph(pred_label: pd.DataFrame, show_notebook: bool = True) -> [lis
|
||||
2017-12-15 -0.102778 -0.102778
|
||||
|
||||
|
||||
:param show_notebook: whether to display graphics in notebook, the default is **True**
|
||||
:return: if show_notebook is True, display in notebook; else return **plotly.graph_objs.Figure** list
|
||||
:param show_notebook: whether to display graphics in notebook, the default is **True**.
|
||||
:return: if show_notebook is True, display in notebook; else return **plotly.graph_objs.Figure** list.
|
||||
"""
|
||||
_ic_df = _get_score_ic(pred_label)
|
||||
# FIXME: support HIGH-FREQ
|
||||
|
||||
@@ -31,16 +31,16 @@ class BaseStrategy:
|
||||
Parameters
|
||||
-----------
|
||||
score_series : pd.Seires
|
||||
stock_id , score
|
||||
stock_id , score.
|
||||
current : Position()
|
||||
current state of position
|
||||
DO NOT directly change the state of current
|
||||
current state of position.
|
||||
DO NOT directly change the state of current.
|
||||
trade_exchange : Exchange()
|
||||
trade exchange
|
||||
trade exchange.
|
||||
pred_date : pd.Timestamp
|
||||
predict date
|
||||
predict date.
|
||||
trade_date : pd.Timestamp
|
||||
trade date
|
||||
trade date.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -49,11 +49,11 @@ class BaseStrategy:
|
||||
Parameters
|
||||
-----------
|
||||
score_series : pd.Series
|
||||
stock_id , score
|
||||
stock_id , score.
|
||||
pred_date : pd.Timestamp
|
||||
oredict date
|
||||
oredict date.
|
||||
trade_date : pd.Timestamp
|
||||
trade date
|
||||
trade date.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -67,7 +67,7 @@ class BaseStrategy:
|
||||
"""
|
||||
This method only be used in 'online' module, it will generate the *args to initial the strategy.
|
||||
:param
|
||||
mode : model used in 'online' module
|
||||
mode : model used in 'online' module.
|
||||
"""
|
||||
return {}
|
||||
|
||||
@@ -82,7 +82,7 @@ class StrategyWrapper:
|
||||
def __init__(self, inner_strategy):
|
||||
"""__init__
|
||||
|
||||
:param inner_strategy: set the inner strategy
|
||||
:param inner_strategy: set the inner strategy.
|
||||
"""
|
||||
self.inner_strategy = inner_strategy
|
||||
|
||||
@@ -99,9 +99,9 @@ class AdjustTimer:
|
||||
Responsible for timing of position adjusting
|
||||
|
||||
This is designed as multiple inheritance mechanism due to:
|
||||
- the is_adjust may need access to the internel state of a strategy
|
||||
- the is_adjust may need access to the internel state of a strategy.
|
||||
|
||||
- it can be reguard as a enhancement to the existing strategy
|
||||
- it can be reguard as a enhancement to the existing strategy.
|
||||
"""
|
||||
|
||||
# adjust position in each trade date
|
||||
@@ -146,12 +146,12 @@ class WeightStrategyBase(BaseStrategy, AdjustTimer):
|
||||
Parameters
|
||||
-----------
|
||||
score : pd.Series
|
||||
pred score for this trade date, index is stock_id, contain 'score' column
|
||||
pred score for this trade date, index is stock_id, contain 'score' column.
|
||||
current : Position()
|
||||
current position
|
||||
current position.
|
||||
trade_exchange : Exchange()
|
||||
trade_date : pd.Timestamp
|
||||
trade date
|
||||
trade date.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -160,13 +160,13 @@ class WeightStrategyBase(BaseStrategy, AdjustTimer):
|
||||
Parameters
|
||||
-----------
|
||||
score_series : pd.Seires
|
||||
stock_id , score
|
||||
stock_id , score.
|
||||
current : Position()
|
||||
current of account
|
||||
current of account.
|
||||
trade_exchange : Exchange()
|
||||
exchange
|
||||
exchange.
|
||||
trade_date : pd.Timestamp
|
||||
date
|
||||
date.
|
||||
"""
|
||||
# judge if to adjust
|
||||
if not self.is_adjust(trade_date):
|
||||
@@ -206,26 +206,26 @@ class TopkDropoutStrategy(BaseStrategy, ListAdjustTimer):
|
||||
Parameters
|
||||
-----------
|
||||
topk : int
|
||||
The number of stocks in the portfolio
|
||||
the number of stocks in the portfolio.
|
||||
n_drop : int
|
||||
number of stocks to be replaced in each trading date
|
||||
number of stocks to be replaced in each trading date.
|
||||
method_sell : str
|
||||
dropout method_sell, random/bottom
|
||||
dropout method_sell, random/bottom.
|
||||
method_buy : str
|
||||
dropout method_buy, random/top
|
||||
dropout method_buy, random/top.
|
||||
risk_degree : float
|
||||
position percentage of total value
|
||||
position percentage of total value.
|
||||
thresh : int
|
||||
minimun holding days since last buy singal of the stock
|
||||
minimun holding days since last buy singal of the stock.
|
||||
hold_thresh : int
|
||||
minimum holding days
|
||||
before sell stock , will check current.get_stock_count(order.stock_id) >= self.thresh
|
||||
before sell stock , will check current.get_stock_count(order.stock_id) >= self.thresh.
|
||||
only_tradable : bool
|
||||
will the strategy only consider the tradable stock when buying and selling.
|
||||
if only_tradable:
|
||||
strategy will make buy sell decision without checking the tradable state of the stock
|
||||
strategy will make buy sell decision without checking the tradable state of the stock.
|
||||
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__()
|
||||
ListAdjustTimer.__init__(self, kwargs.get("adjust_dates", None))
|
||||
@@ -245,7 +245,7 @@ class TopkDropoutStrategy(BaseStrategy, ListAdjustTimer):
|
||||
def get_risk_degree(self, date):
|
||||
"""get_risk_degree
|
||||
Return the proportion of your total value you will used in investment.
|
||||
Dynamically risk_degree will result in Market timing
|
||||
Dynamically risk_degree will result in Market timing.
|
||||
"""
|
||||
# It will use 95% amoutn of your total value by default
|
||||
return self.risk_degree
|
||||
@@ -257,15 +257,15 @@ class TopkDropoutStrategy(BaseStrategy, ListAdjustTimer):
|
||||
Parameters
|
||||
-----------
|
||||
score_series : pd.Series
|
||||
stock_id , score
|
||||
stock_id , score.
|
||||
current : Position()
|
||||
current of account
|
||||
current of account.
|
||||
trade_exchange : Exchange()
|
||||
exchange
|
||||
exchange.
|
||||
pred_date : pd.Timestamp
|
||||
predict date
|
||||
predict date.
|
||||
trade_date : pd.Timestamp
|
||||
trade date
|
||||
trade date.
|
||||
"""
|
||||
if not self.is_adjust(trade_date):
|
||||
return []
|
||||
|
||||
@@ -129,13 +129,13 @@ class Expression(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
instrument : str
|
||||
instrument code
|
||||
instrument code.
|
||||
start_index : str
|
||||
feature start index [in calendar]
|
||||
feature start index [in calendar].
|
||||
end_index : str
|
||||
feature end index [in calendar]
|
||||
feature end index [in calendar].
|
||||
freq : str
|
||||
feature frequency
|
||||
feature frequency.
|
||||
|
||||
Returns
|
||||
----------
|
||||
|
||||
@@ -76,8 +76,8 @@ class MemCache(object):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mem_cache_size_limit: cache max size
|
||||
limit_type: length or sizeof; length(call fun: len), size(call fun: sys.getsizeof)
|
||||
mem_cache_size_limit: cache max size.
|
||||
limit_type: length or sizeof; length(call fun: len), size(call fun: sys.getsizeof).
|
||||
"""
|
||||
if limit_type not in ["length", "sizeof"]:
|
||||
raise ValueError(f"limit_type must be length or sizeof, your limit_type is {limit_type}")
|
||||
@@ -118,9 +118,9 @@ class MemCacheExpire:
|
||||
def set_cache(mem_cache, key, value):
|
||||
"""set cache
|
||||
|
||||
:param mem_cache: MemCache attribute('c'/'i'/'f')
|
||||
:param key: cache key
|
||||
:param value: cache value
|
||||
:param mem_cache: MemCache attribute('c'/'i'/'f').
|
||||
:param key: cache key.
|
||||
:param value: cache value.
|
||||
"""
|
||||
mem_cache[key] = value, time.time()
|
||||
|
||||
@@ -128,9 +128,9 @@ class MemCacheExpire:
|
||||
def get_cache(mem_cache, key):
|
||||
"""get mem cache
|
||||
|
||||
:param mem_cache: MemCache attribute('c'/'i'/'f')
|
||||
:param key: cache key
|
||||
:return: cache value; if cache not exist, return None
|
||||
:param mem_cache: MemCache attribute('c'/'i'/'f').
|
||||
:param key: cache key.
|
||||
:return: cache value; if cache not exist, return None.
|
||||
"""
|
||||
value = None
|
||||
expire = False
|
||||
@@ -275,12 +275,12 @@ class ExpressionCache(BaseProviderCache):
|
||||
Parameters
|
||||
----------
|
||||
cache_uri : str
|
||||
the complete uri of expression cache file (include dir path)
|
||||
the complete uri of expression cache file (include dir path).
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
0(successful update)/ 1(no need to update)/ 2(update failure)
|
||||
0(successful update)/ 1(no need to update)/ 2(update failure).
|
||||
"""
|
||||
raise NotImplementedError("Implement this method if you want to make expression cache up to date")
|
||||
|
||||
@@ -348,7 +348,7 @@ class DatasetCache(BaseProviderCache):
|
||||
Parameters
|
||||
----------
|
||||
cache_uri : str
|
||||
the complete uri of dataset cache file (include dir path)
|
||||
the complete uri of dataset cache file (include dir path).
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -361,9 +361,9 @@ class DatasetCache(BaseProviderCache):
|
||||
def cache_to_origin_data(data, fields):
|
||||
"""cache data to origin data
|
||||
|
||||
:param data: pd.DataFrame, cache data
|
||||
:param fields: feature fields
|
||||
:return: pd.DataFrame
|
||||
:param data: pd.DataFrame, cache data.
|
||||
:param fields: feature fields.
|
||||
:return: pd.DataFrame.
|
||||
"""
|
||||
not_space_fields = remove_fields_space(fields)
|
||||
data = data.loc[:, not_space_fields]
|
||||
@@ -583,7 +583,7 @@ class DiskDatasetCache(DatasetCache):
|
||||
:param cache_path:
|
||||
:param start_time:
|
||||
:param end_time:
|
||||
:param fields: The fields order of the dataset cache is sorted. So rearrange the columns to make it consistent
|
||||
:param fields: The fields order of the dataset cache is sorted. So rearrange the columns to make it consistent.
|
||||
:return:
|
||||
"""
|
||||
|
||||
@@ -771,12 +771,12 @@ class DiskDatasetCache(DatasetCache):
|
||||
|
||||
- This is a hdf file sorted by datetime
|
||||
|
||||
:param cache_path: The path to store the cache
|
||||
:param instruments: The instruments to store the cache
|
||||
:param fields: The fields to store the cache
|
||||
:param freq: The freq to store the cache
|
||||
:param cache_path: The path to store the cache.
|
||||
:param instruments: The instruments to store the cache.
|
||||
:param fields: The fields to store the cache.
|
||||
:param freq: The freq to store the cache.
|
||||
|
||||
:return type pd.DataFrame; The fields of the returned DataFrame are consistent with the parameters of the function
|
||||
:return type pd.DataFrame; The fields of the returned DataFrame are consistent with the parameters of the function.
|
||||
"""
|
||||
# get calendar
|
||||
from .data import Cal
|
||||
|
||||
@@ -51,13 +51,13 @@ class Client(object):
|
||||
Parameters
|
||||
----------
|
||||
request_type : str
|
||||
type of proposed request, 'calendar'/'instrument'/'feature'
|
||||
type of proposed request, 'calendar'/'instrument'/'feature'.
|
||||
request_content : dict
|
||||
records the information of the request
|
||||
records the information of the request.
|
||||
msg_proc_func : func
|
||||
the function to process the message when receiving response, should have arg `*args`
|
||||
the function to process the message when receiving response, should have arg `*args`.
|
||||
msg_queue: Queue
|
||||
The queue to pass the messsage after callback
|
||||
The queue to pass the messsage after callback.
|
||||
"""
|
||||
head_info = {"version": qlib.__version__}
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@ class CalendarProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
start_time : str
|
||||
start of the time range
|
||||
start of the time range.
|
||||
end_time : str
|
||||
end of the time range
|
||||
end of the time range.
|
||||
freq : str
|
||||
time frequency, available: year/quarter/month/week/day
|
||||
time frequency, available: year/quarter/month/week/day.
|
||||
future : bool
|
||||
whether including future trading day
|
||||
whether including future trading day.
|
||||
|
||||
Returns
|
||||
----------
|
||||
@@ -62,24 +62,24 @@ class CalendarProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
start_time : str
|
||||
start of the time range
|
||||
start of the time range.
|
||||
end_time : str
|
||||
end of the time range
|
||||
end of the time range.
|
||||
freq : str
|
||||
time frequency, available: year/quarter/month/week/day
|
||||
time frequency, available: year/quarter/month/week/day.
|
||||
future : bool
|
||||
whether including future trading day
|
||||
whether including future trading day.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.Timestamp
|
||||
the real start time
|
||||
the real start time.
|
||||
pd.Timestamp
|
||||
the real end time
|
||||
the real end time.
|
||||
int
|
||||
the index of start time
|
||||
the index of start time.
|
||||
int
|
||||
the index of end time
|
||||
the index of end time.
|
||||
"""
|
||||
start_time = pd.Timestamp(start_time)
|
||||
end_time = pd.Timestamp(end_time)
|
||||
@@ -103,16 +103,16 @@ class CalendarProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
freq : str
|
||||
frequency of read calendar file
|
||||
frequency of read calendar file.
|
||||
future : bool
|
||||
whether including future trading day
|
||||
whether including future trading day.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
list of timestamps
|
||||
list of timestamps.
|
||||
dict
|
||||
dict composed by timestamp as key and index as value for fast search
|
||||
dict composed by timestamp as key and index as value for fast search.
|
||||
"""
|
||||
flag = f"{freq}_future_{future}"
|
||||
if flag in H["c"]:
|
||||
@@ -141,14 +141,14 @@ class InstrumentProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
market : str
|
||||
market/industry/index shortname, e.g. all/sse/szse/sse50/csi300/csi500
|
||||
market/industry/index shortname, e.g. all/sse/szse/sse50/csi300/csi500.
|
||||
filter_pipe : list
|
||||
the list of dynamic filters
|
||||
the list of dynamic filters.
|
||||
|
||||
Returns
|
||||
----------
|
||||
dict
|
||||
dict of stockpool config
|
||||
dict of stockpool config.
|
||||
{`market`=>base market name, `filter_pipe`=>list of filters}
|
||||
|
||||
example :
|
||||
@@ -182,13 +182,13 @@ class InstrumentProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
instruments : dict
|
||||
stockpool config
|
||||
stockpool config.
|
||||
start_time : str
|
||||
start of the time range
|
||||
start of the time range.
|
||||
end_time : str
|
||||
end of the time range
|
||||
end of the time range.
|
||||
as_list : bool
|
||||
return instruments as list or dict
|
||||
return instruments as list or dict.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -243,15 +243,15 @@ class FeatureProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
instrument : str
|
||||
a certain instrument
|
||||
a certain instrument.
|
||||
field : str
|
||||
a certain field of feature
|
||||
a certain field of feature.
|
||||
start_time : str
|
||||
start of the time range
|
||||
start of the time range.
|
||||
end_time : str
|
||||
end of the time range
|
||||
end of the time range.
|
||||
freq : str
|
||||
time frequency, available: year/quarter/month/week/day
|
||||
time frequency, available: year/quarter/month/week/day.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -294,15 +294,15 @@ class ExpressionProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
instrument : str
|
||||
a certain instrument
|
||||
a certain instrument.
|
||||
field : str
|
||||
a certain field of feature
|
||||
a certain field of feature.
|
||||
start_time : str
|
||||
start of the time range
|
||||
start of the time range.
|
||||
end_time : str
|
||||
end of the time range
|
||||
end of the time range.
|
||||
freq : str
|
||||
time frequency, available: year/quarter/month/week/day
|
||||
time frequency, available: year/quarter/month/week/day.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -325,20 +325,20 @@ class DatasetProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
instruments : list or dict
|
||||
list/dict of instruments or dict of stockpool config
|
||||
list/dict of instruments or dict of stockpool config.
|
||||
fields : list
|
||||
list of feature instances
|
||||
list of feature instances.
|
||||
start_time : str
|
||||
start of the time range
|
||||
start of the time range.
|
||||
end_time : str
|
||||
end of the time range
|
||||
end of the time range.
|
||||
freq : str
|
||||
time frequency
|
||||
time frequency.
|
||||
|
||||
Returns
|
||||
----------
|
||||
pd.DataFrame
|
||||
a pandas dataframe with <instrument, datetime> index
|
||||
a pandas dataframe with <instrument, datetime> index.
|
||||
"""
|
||||
raise NotImplementedError("Subclass of DatasetProvider must implement `Dataset` method")
|
||||
|
||||
@@ -357,17 +357,17 @@ class DatasetProvider(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
instruments : list or dict
|
||||
list/dict of instruments or dict of stockpool config
|
||||
list/dict of instruments or dict of stockpool config.
|
||||
fields : list
|
||||
list of feature instances
|
||||
list of feature instances.
|
||||
start_time : str
|
||||
start of the time range
|
||||
start of the time range.
|
||||
end_time : str
|
||||
end of the time range
|
||||
end of the time range.
|
||||
freq : str
|
||||
time frequency
|
||||
time frequency.
|
||||
disk_cache : int
|
||||
whether to skip(0)/use(1)/replace(2) disk_cache
|
||||
whether to skip(0)/use(1)/replace(2) disk_cache.
|
||||
|
||||
"""
|
||||
return DiskDatasetCache._uri(instruments, fields, start_time, end_time, freq, disk_cache)
|
||||
@@ -526,7 +526,7 @@ class LocalCalendarProvider(CalendarProvider):
|
||||
Parameters
|
||||
----------
|
||||
freq : str
|
||||
frequency of read calendar file
|
||||
frequency of read calendar file.
|
||||
|
||||
Returns
|
||||
----------
|
||||
|
||||
@@ -17,7 +17,7 @@ class Dataset(Serializable):
|
||||
init is designed to finish following steps:
|
||||
|
||||
- setup data
|
||||
- The data related attributes' names should start with '_' so that it will not be saved on disk when serializing
|
||||
- The data related attributes' names should start with '_' so that it will not be saved on disk when serializing.
|
||||
|
||||
- initialize the state of the dataset(info to prepare the data)
|
||||
- The name of essential state for preparing data should not start with '_' so that it could be serialized on disk when serializing.
|
||||
@@ -29,17 +29,17 @@ class Dataset(Serializable):
|
||||
|
||||
def setup_data(self, *args, **kwargs):
|
||||
"""
|
||||
setup the data
|
||||
Setup the data.
|
||||
|
||||
We split the setup_data function for following situation:
|
||||
|
||||
- User have a Dataset object with learned status on disk
|
||||
- User have a Dataset object with learned status on disk.
|
||||
|
||||
- User load the Dataset object from the disk(Note the init function is skiped)
|
||||
- User load the Dataset object from the disk(Note the init function is skiped).
|
||||
|
||||
- User call `setup_data` to load new data
|
||||
- User call `setup_data` to load new data.
|
||||
|
||||
- User prepare data for model based on previous status
|
||||
- User prepare data for model based on previous status.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -66,9 +66,10 @@ class DatasetH(Dataset):
|
||||
|
||||
User should try to put the data preprocessing functions into handler.
|
||||
Only following data processing functions should be placed in Dataset:
|
||||
|
||||
- The processing is related to specific model.
|
||||
|
||||
- The processing is related to data split
|
||||
- The processing is related to data split.
|
||||
"""
|
||||
|
||||
def __init__(self, handler: Union[dict, DataHandler], segments: list):
|
||||
@@ -76,15 +77,15 @@ class DatasetH(Dataset):
|
||||
Parameters
|
||||
----------
|
||||
handler : Union[dict, DataHandler]
|
||||
handler will be passed into setup_data
|
||||
handler will be passed into setup_data.
|
||||
segments : list
|
||||
handler will be passed into setup_data
|
||||
handler will be passed into setup_data.
|
||||
"""
|
||||
super().__init__(handler, segments)
|
||||
|
||||
def setup_data(self, handler: Union[dict, DataHandler], segments: list):
|
||||
"""
|
||||
setup the underlying data
|
||||
Setup the underlying data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -121,7 +122,7 @@ class DatasetH(Dataset):
|
||||
**kwargs,
|
||||
) -> Union[List[pd.DataFrame], pd.DataFrame]:
|
||||
"""
|
||||
prepare the data for learning and inference
|
||||
Prepare the data for learning and inference.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -132,11 +133,12 @@ class DatasetH(Dataset):
|
||||
- 'train'
|
||||
|
||||
- ['train', 'valid']
|
||||
|
||||
col_set : str
|
||||
The col_set will be passed to self._handler when fetching data
|
||||
data_key: str
|
||||
The col_set will be passed to self._handler when fetching data.
|
||||
data_key : str
|
||||
The data to fetch: DK_*
|
||||
Default is DK_I, which indicate fetching data for **inference**
|
||||
Default is DK_I, which indicate fetching data for **inference**.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
@@ -29,7 +29,7 @@ class DataHandler(Serializable):
|
||||
"""
|
||||
The steps to using a handler
|
||||
1. initialized data handler (call by `init`).
|
||||
2. use the data
|
||||
2. use the data.
|
||||
|
||||
|
||||
The data handler try to maintain a handler with 2 level.
|
||||
@@ -65,17 +65,17 @@ class DataHandler(Serializable):
|
||||
Parameters
|
||||
----------
|
||||
instruments :
|
||||
The stock list to retrive
|
||||
The stock list to retrive.
|
||||
start_time :
|
||||
start_time of the original data
|
||||
start_time of the original data.
|
||||
end_time :
|
||||
end_time of the original data
|
||||
end_time of the original data.
|
||||
data_loader : Tuple[dict, str, DataLoader]
|
||||
data loader to load the data
|
||||
data loader to load the data.
|
||||
init_data :
|
||||
intialize the original data in the constructor
|
||||
intialize the original data in the constructor.
|
||||
fetch_orig : bool
|
||||
Return the original data instead of copy if possible
|
||||
Return the original data instead of copy if possible.
|
||||
"""
|
||||
# Set logger
|
||||
self.logger = get_module_logger("DataHandler")
|
||||
@@ -219,9 +219,9 @@ class DataHandler(Serializable):
|
||||
get a iterator of sliced data with given periods
|
||||
|
||||
Args:
|
||||
periods (int): number of periods
|
||||
min_periods (int): minimum periods for sliced dataframe
|
||||
kwargs (dict): will be passed to `self.fetch`
|
||||
periods (int): number of periods.
|
||||
min_periods (int): minimum periods for sliced dataframe.
|
||||
kwargs (dict): will be passed to `self.fetch`.
|
||||
"""
|
||||
trading_dates = self._data.index.unique(level="datetime")
|
||||
if min_periods is None:
|
||||
@@ -377,7 +377,7 @@ class DataHandlerLP(DataHandler):
|
||||
Parameters
|
||||
----------
|
||||
init_type : str
|
||||
The type `IT_*` listed above
|
||||
The type `IT_*` listed above.
|
||||
enable_cache : bool
|
||||
default value is false:
|
||||
|
||||
@@ -419,13 +419,13 @@ class DataHandlerLP(DataHandler):
|
||||
Parameters
|
||||
----------
|
||||
selector : Union[pd.Timestamp, slice, str]
|
||||
describe how to select data by index
|
||||
describe how to select data by index.
|
||||
level : Union[str, int]
|
||||
which index level to select the data
|
||||
which index level to select the data.
|
||||
col_set : str
|
||||
select a set of meaningful columns.(e.g. features, columns)
|
||||
data_key: str
|
||||
The data to fetch: DK_*
|
||||
select a set of meaningful columns.(e.g. features, columns).
|
||||
data_key : str
|
||||
the data to fetch: DK_*.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -443,9 +443,9 @@ class DataHandlerLP(DataHandler):
|
||||
Parameters
|
||||
----------
|
||||
col_set : str
|
||||
select a set of meaningful columns.(e.g. features, columns)
|
||||
data_key: str
|
||||
The data to fetch: DK_*
|
||||
select a set of meaningful columns.(e.g. features, columns).
|
||||
data_key : str
|
||||
the data to fetch: DK_*.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
@@ -100,16 +100,16 @@ class DLWParser(DataLoader):
|
||||
Parameters
|
||||
----------
|
||||
instruments :
|
||||
the instruments
|
||||
the instruments.
|
||||
exprs : list
|
||||
The expressions to describe the content of the data
|
||||
the expressions to describe the content of the data.
|
||||
names : list
|
||||
The name of the data
|
||||
the name of the data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame:
|
||||
the queried dataframe
|
||||
the queried dataframe.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ def get_group_columns(df: pd.DataFrame, group: str):
|
||||
Parameters
|
||||
----------
|
||||
df : pd.DataFrame
|
||||
with multi of columns
|
||||
with multi of columns.
|
||||
group : str
|
||||
the name of the feature group, i.e. the first level value of the group index.
|
||||
"""
|
||||
@@ -56,7 +56,7 @@ class Processor(Serializable):
|
||||
Parameters
|
||||
----------
|
||||
df : pd.DataFrame
|
||||
The raw_df of handler or result from previous processor
|
||||
The raw_df of handler or result from previous processor.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -68,7 +68,7 @@ class Processor(Serializable):
|
||||
Returns
|
||||
-------
|
||||
bool:
|
||||
if it is usable for infenrece
|
||||
if it is usable for infenrece.
|
||||
"""
|
||||
return True
|
||||
|
||||
@@ -166,7 +166,9 @@ class MinMaxNorm(Processor):
|
||||
return df
|
||||
|
||||
|
||||
class ZscoreNorm(Processor):
|
||||
class ZScoreNorm(Processor):
|
||||
"""ZScore Normalization"""
|
||||
|
||||
def __init__(self, fit_start_time, fit_end_time, fields_group=None):
|
||||
self.fit_start_time = fit_start_time
|
||||
self.fit_end_time = fit_end_time
|
||||
@@ -193,6 +195,42 @@ class ZscoreNorm(Processor):
|
||||
return df
|
||||
|
||||
|
||||
class RobustZScoreNorm(Processor):
|
||||
"""Robust ZScore Normalization
|
||||
|
||||
Use robust statistics for Z-Score normalization:
|
||||
mean(x) = median(x)
|
||||
std(x) = MAD(x) * 1.4826
|
||||
|
||||
Reference:
|
||||
https://en.wikipedia.org/wiki/Median_absolute_deviation.
|
||||
"""
|
||||
|
||||
def __init__(self, fit_start_time, fit_end_time, fields_group=None, clip_outlier=True):
|
||||
self.fit_start_time = fit_start_time
|
||||
self.fit_end_time = fit_end_time
|
||||
self.fields_group = fields_group
|
||||
self.clip_outlier = clip_outlier
|
||||
|
||||
def fit(self, df):
|
||||
df = fetch_df_by_index(df, slice(self.fit_start_time, self.fit_end_time), level="datetime")
|
||||
self.cols = get_group_columns(df, self.fields_group)
|
||||
X = df[self.cols].values
|
||||
self.mean_train = np.nanmedian(X, axis=0)
|
||||
self.std_train = np.nanmedian(np.abs(X - self.mean_train), axis=0)
|
||||
self.std_train += EPS
|
||||
self.std_train *= 1.4826
|
||||
|
||||
def __call__(self, df):
|
||||
X = df[self.cols]
|
||||
X -= self.mean_train
|
||||
X /= self.std_train
|
||||
df[self.cols] = X
|
||||
if self.clip_outlier:
|
||||
df.clip(-3, 3, inplace=True)
|
||||
return df
|
||||
|
||||
|
||||
class CSZScoreNorm(Processor):
|
||||
"""Cross Sectional ZScore Normalization"""
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class BaseDFilter(abc.ABC):
|
||||
Parameters
|
||||
----------
|
||||
config : dict
|
||||
dict of config parameters
|
||||
dict of config parameters.
|
||||
"""
|
||||
raise NotImplementedError("Subclass of BaseDFilter must reimplement `from_config` method")
|
||||
|
||||
@@ -43,7 +43,7 @@ class BaseDFilter(abc.ABC):
|
||||
Returns
|
||||
----------
|
||||
dict
|
||||
return the dict of config parameters
|
||||
return the dict of config parameters.
|
||||
"""
|
||||
raise NotImplementedError("Subclass of BaseDFilter must reimplement `to_config` method")
|
||||
|
||||
@@ -69,9 +69,9 @@ class SeriesDFilter(BaseDFilter):
|
||||
Parameters
|
||||
----------
|
||||
fstart_time: str
|
||||
the time for the filter rule to start filter the instruments
|
||||
the time for the filter rule to start filter the instruments.
|
||||
fend_time: str
|
||||
the time for the filter rule to stop filter the instruments
|
||||
the time for the filter rule to stop filter the instruments.
|
||||
"""
|
||||
super(SeriesDFilter, self).__init__()
|
||||
self.filter_start_time = pd.Timestamp(fstart_time) if fstart_time else None
|
||||
@@ -83,12 +83,12 @@ class SeriesDFilter(BaseDFilter):
|
||||
Parameters
|
||||
----------
|
||||
instruments: dict
|
||||
the dict of instruments in the form {instrument_name => list of timestamp tuple}
|
||||
the dict of instruments in the form {instrument_name => list of timestamp tuple}.
|
||||
|
||||
Returns
|
||||
----------
|
||||
pd.Timestamp, pd.Timestamp
|
||||
the lower time bound and upper time bound of all the instruments
|
||||
the lower time bound and upper time bound of all the instruments.
|
||||
"""
|
||||
trange = Cal.calendar(freq=self.filter_freq)
|
||||
ubound, lbound = trange[0], trange[-1]
|
||||
@@ -105,14 +105,14 @@ class SeriesDFilter(BaseDFilter):
|
||||
Parameters
|
||||
----------
|
||||
time_range : D.calendar
|
||||
the time range of the instruments
|
||||
the time range of the instruments.
|
||||
target_timestamp : list
|
||||
the list of tuple (timestamp, timestamp)
|
||||
the list of tuple (timestamp, timestamp).
|
||||
|
||||
Returns
|
||||
----------
|
||||
pd.Series
|
||||
the series of bool value for an instrument
|
||||
the series of bool value for an instrument.
|
||||
"""
|
||||
# Construct a whole dict of {date => bool}
|
||||
timestamp_series = {timestamp: False for timestamp in time_range}
|
||||
@@ -124,19 +124,19 @@ class SeriesDFilter(BaseDFilter):
|
||||
return timestamp_series
|
||||
|
||||
def _filterSeries(self, timestamp_series, filter_series):
|
||||
"""Filter the timestamp series with filter series by using element-wise AND operation of the two series
|
||||
"""Filter the timestamp series with filter series by using element-wise AND operation of the two series.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
timestamp_series : pd.Series
|
||||
the series of bool value indicating existing time
|
||||
the series of bool value indicating existing time.
|
||||
filter_series : pd.Series
|
||||
the series of bool value indicating filter feature
|
||||
the series of bool value indicating filter feature.
|
||||
|
||||
Returns
|
||||
----------
|
||||
pd.Series
|
||||
the series of bool value indicating whether the date satisfies the filter condition and exists in target timestamp
|
||||
the series of bool value indicating whether the date satisfies the filter condition and exists in target timestamp.
|
||||
"""
|
||||
fstart, fend = list(filter_series.keys())[0], list(filter_series.keys())[-1]
|
||||
filter_series = filter_series.astype("bool") # Make sure the filter_series is boolean
|
||||
@@ -144,17 +144,17 @@ class SeriesDFilter(BaseDFilter):
|
||||
return timestamp_series
|
||||
|
||||
def _toTimestamp(self, timestamp_series):
|
||||
"""Convert the timestamp series to a list of tuple (timestamp, timestamp) indicating a continuous range of TRUE
|
||||
"""Convert the timestamp series to a list of tuple (timestamp, timestamp) indicating a continuous range of TRUE.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
timestamp_series: pd.Series
|
||||
the series of bool value after being filtered
|
||||
the series of bool value after being filtered.
|
||||
|
||||
Returns
|
||||
----------
|
||||
list
|
||||
the list of tuple (timestamp, timestamp)
|
||||
the list of tuple (timestamp, timestamp).
|
||||
"""
|
||||
# sort the timestamp_series according to the timestamps
|
||||
timestamp_series.sort_index()
|
||||
@@ -194,18 +194,18 @@ class SeriesDFilter(BaseDFilter):
|
||||
Parameters
|
||||
----------
|
||||
instruments : dict
|
||||
the dict of instruments to be filtered
|
||||
the dict of instruments to be filtered.
|
||||
fstart : pd.Timestamp
|
||||
start time of filter
|
||||
start time of filter.
|
||||
fend : pd.Timestamp
|
||||
end time of filter
|
||||
end time of filter.
|
||||
|
||||
.. note:: fstart/fend indicates the intersection of instruments start/end time and filter start/end time
|
||||
.. note:: fstart/fend indicates the intersection of instruments start/end time and filter start/end time.
|
||||
|
||||
Returns
|
||||
----------
|
||||
pd.Dataframe
|
||||
a series of {pd.Timestamp => bool}
|
||||
a series of {pd.Timestamp => bool}.
|
||||
"""
|
||||
raise NotImplementedError("Subclass of SeriesDFilter must reimplement `getFilterSeries` method")
|
||||
|
||||
@@ -215,16 +215,16 @@ class SeriesDFilter(BaseDFilter):
|
||||
Parameters
|
||||
----------
|
||||
instruments: dict
|
||||
input instruments to be filtered
|
||||
input instruments to be filtered.
|
||||
start_time: str
|
||||
start of the time range
|
||||
start of the time range.
|
||||
end_time: str
|
||||
end of the time range
|
||||
end of the time range.
|
||||
|
||||
Returns
|
||||
----------
|
||||
dict
|
||||
filtered instruments, same structure as input instruments
|
||||
filtered instruments, same structure as input instruments.
|
||||
"""
|
||||
lbound, ubound = self._getTimeBound(instruments)
|
||||
start_time = pd.Timestamp(start_time or lbound)
|
||||
@@ -272,7 +272,7 @@ class NameDFilter(SeriesDFilter):
|
||||
params:
|
||||
------
|
||||
name_rule_re: str
|
||||
regular expression for the name rule
|
||||
regular expression for the name rule.
|
||||
"""
|
||||
super(NameDFilter, self).__init__(fstart_time, fend_time)
|
||||
self.name_rule_re = name_rule_re
|
||||
@@ -325,13 +325,13 @@ class ExpressionDFilter(SeriesDFilter):
|
||||
params:
|
||||
------
|
||||
fstart_time: str
|
||||
filter the feature starting from this time
|
||||
filter the feature starting from this time.
|
||||
fend_time: str
|
||||
filter the feature ending by this time
|
||||
filter the feature ending by this time.
|
||||
rule_expression: str
|
||||
an input expression for the rule
|
||||
an input expression for the rule.
|
||||
keep: bool
|
||||
whether to keep the instruments of which features don't exist in the filter time span
|
||||
whether to keep the instruments of which features don't exist in the filter time span.
|
||||
"""
|
||||
super(ExpressionDFilter, self).__init__(fstart_time, fend_time)
|
||||
self.rule_expression = rule_expression
|
||||
|
||||
@@ -33,7 +33,7 @@ class Model(BaseModel):
|
||||
Parameters
|
||||
----------
|
||||
dataset : Dataset
|
||||
dataset will generate the processed data from model training
|
||||
dataset will generate the processed data from model training.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -44,7 +44,7 @@ class Model(BaseModel):
|
||||
Parameters
|
||||
----------
|
||||
dataset : Dataset
|
||||
dataset will generate the processed dataset from model training
|
||||
dataset will generate the processed dataset from model training.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -59,6 +59,6 @@ class ModelFT(Model):
|
||||
Parameters
|
||||
----------
|
||||
dataset : Dataset
|
||||
dataset will generate the processed dataset from model training
|
||||
dataset will generate the processed dataset from model training.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -23,9 +23,9 @@ class RiskModel(BaseModel):
|
||||
def __init__(self, nan_option: str = "ignore", assume_centered: bool = False, scale_return: bool = True):
|
||||
"""
|
||||
Args:
|
||||
nan_option (str): nan handling option (`ignore`/`mask`/`fill`)
|
||||
assume_centered (bool): whether the data is assumed to be centered
|
||||
scale_return (bool): whether scale returns as percentage
|
||||
nan_option (str): nan handling option (`ignore`/`mask`/`fill`).
|
||||
assume_centered (bool): whether the data is assumed to be centered.
|
||||
scale_return (bool): whether scale returns as percentage.
|
||||
"""
|
||||
# nan
|
||||
assert nan_option in [
|
||||
@@ -45,11 +45,11 @@ class RiskModel(BaseModel):
|
||||
Args:
|
||||
X (pd.Series, pd.DataFrame or np.ndarray): data from which to estimate the covariance,
|
||||
with variables as columns and observations as rows.
|
||||
return_corr (bool): whether return the correlation matrix
|
||||
is_price (bool): whether `X` contains price (if not assume stock returns)
|
||||
return_corr (bool): whether return the correlation matrix.
|
||||
is_price (bool): whether `X` contains price (if not assume stock returns).
|
||||
|
||||
Returns:
|
||||
pd.DataFrame or np.ndarray: estimated covariance (or correlation)
|
||||
pd.DataFrame or np.ndarray: estimated covariance (or correlation).
|
||||
"""
|
||||
# transform input into 2D array
|
||||
if not isinstance(X, (pd.Series, pd.DataFrame)):
|
||||
@@ -101,10 +101,10 @@ class RiskModel(BaseModel):
|
||||
By default, this method implements the empirical covariance estimation.
|
||||
|
||||
Args:
|
||||
X (np.ndarray): data matrix containing multiple variables (columns) and observations (rows)
|
||||
X (np.ndarray): data matrix containing multiple variables (columns) and observations (rows).
|
||||
|
||||
Returns:
|
||||
np.ndarray: covariance matrix
|
||||
np.ndarray: covariance matrix.
|
||||
"""
|
||||
xTx = np.asarray(X.T.dot(X))
|
||||
N = len(X)
|
||||
@@ -117,7 +117,7 @@ class RiskModel(BaseModel):
|
||||
"""handle nan and centerize data
|
||||
|
||||
Note:
|
||||
if `nan_option='mask'` then the returned array will be `np.ma.MaskedArray`
|
||||
if `nan_option='mask'` then the returned array will be `np.ma.MaskedArray`.
|
||||
"""
|
||||
# handle nan
|
||||
if self.nan_option == self.FILL_NAN:
|
||||
@@ -139,15 +139,15 @@ class ShrinkCovEstimator(RiskModel):
|
||||
where `alpha` is the shrink parameter and `F` is the shrinking target.
|
||||
|
||||
The following shrinking parameters (`alpha`) are supported:
|
||||
- `lw` [1][2][3]: use Ledoit-Wolf shrinking parameter
|
||||
- `oas` [4]: use Oracle Approximating Shrinkage shrinking parameter
|
||||
- float: directly specify the shrink parameter, should be between [0, 1]
|
||||
- `lw` [1][2][3]: use Ledoit-Wolf shrinking parameter.
|
||||
- `oas` [4]: use Oracle Approximating Shrinkage shrinking parameter.
|
||||
- float: directly specify the shrink parameter, should be between [0, 1].
|
||||
|
||||
The following shrinking targets (`F`) are supported:
|
||||
- `const_var` [1][4][5]: assume stocks have the same constant variance and zero correlation
|
||||
- `const_corr` [2][6]: assume stocks have different variance but equal correlation
|
||||
- `single_factor` [3][7]: assume single factor model as the shrinking target
|
||||
- np.ndarray: provide the shrinking targets directly
|
||||
- `const_var` [1][4][5]: assume stocks have the same constant variance and zero correlation.
|
||||
- `const_corr` [2][6]: assume stocks have different variance but equal correlation.
|
||||
- `single_factor` [3][7]: assume single factor model as the shrinking target.
|
||||
- np.ndarray: provide the shrinking targets directly.
|
||||
|
||||
Note:
|
||||
- The optimal shrinking parameter depends on the selection of the shrinking target.
|
||||
@@ -402,13 +402,13 @@ class POETCovEstimator(RiskModel):
|
||||
def __init__(self, num_factors: int = 0, thresh: float = 1.0, thresh_method: str = "soft", **kwargs):
|
||||
"""
|
||||
Args:
|
||||
num_factors (int): number of factors (if set to zero, no factor model will be used)
|
||||
thresh (float): the positive constant for thresholding
|
||||
num_factors (int): number of factors (if set to zero, no factor model will be used).
|
||||
thresh (float): the positive constant for thresholding.
|
||||
thresh_method (str): thresholding method, which can be
|
||||
- 'soft': soft thresholding
|
||||
- 'hard': hard thresholding
|
||||
- 'scad': scad thresholding
|
||||
kwargs: see `RiskModel` for more information
|
||||
- 'soft': soft thresholding.
|
||||
- 'hard': hard thresholding.
|
||||
- 'scad': scad thresholding.
|
||||
kwargs: see `RiskModel` for more information.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def sys_config(config, config_path):
|
||||
Parameters
|
||||
----------
|
||||
config : dict
|
||||
configuration of the workflow
|
||||
configuration of the workflow.
|
||||
config_path : str
|
||||
path of the configuration
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user