拓展 FinOL

FinOL 在设计时考虑了可扩展性,允许用户集成自己的模型和数据集以进行基准测试。本节提供了就如何拓展 FinOL 框架的逐步指南。

添加新的数据集

要将自己的数据集集成到 FinOL 中,请遵循以下步骤:

  1. 导航到 {ROOT_PATH}\data\datasets\CustomDataset 目录。

  2. 为不同的资产创建 .xlsx 文件,格式如下:

+------------+----------+----------+----------+----------+---------+
| DATE       | OPEN     |  HIGH    | LOW      | CLOSE    | VOLUME  |
|------------+----------+----------+----------+----------+---------|
| 2017-11-09 | 0.025160 | 0.035060 | 0.025006 | 0.032053 | 1871620 |
| 2017-11-10 | 0.032219 | 0.033348 | 0.026450 | 0.027119 | 6766780 |
| 2017-11-11 | 0.026891 | 0.029658 | 0.025684 | 0.027437 | 5532220 |
| 2017-11-12 | 0.027480 | 0.027952 | 0.022591 | 0.023977 | 7280250 |
| 2017-11-13 | 0.024364 | 0.026300 | 0.023495 | 0.025807 | 4419440 |
| 2017-11-14 | 0.025797 | 0.026788 | 0.025342 | 0.026230 | 3033290 |
| 2017-11-15 | 0.026116 | 0.027773 | 0.025261 | 0.026445 | 6858800 |
| ......     | ......   | ......   | ......   | ......   | ......  |
| 2024-02-29 | 0.630859 | 0.705280 | 0.625720 | 0.655646 | 1639531 |
| 2024-03-01 | 0.655440 | 0.719080 | 0.654592 | 0.719080 | 9353798 |
+-----------+-----------+----------+----------+----------+---------+
  1. 对于每个资产,确保数据格式正确,没有缺失值。

  2. Define the configuration for your custom dataset in the {ROOT_PATH}\config.json file, under the config["DATASET_SPLIT_CONFIG"]["CustomModel"] and config["NUM_DAYS_PER_YEAR"]["CustomModel"] sections. For splitting your dataset, it is recommended to use a ratio of 0.6:0.2:0.2 for training, validation, and testing datasets, respectively.

config.json
"DATASET_SPLIT_CONFIG": {
    // other datasets...
    "CustomDataset": {
        "TRAIN_START_TIMESTAMP": "",
        "TRAIN_END_TIMESTAMP": "",
        "VAL_START_TIMESTAMP": "",
        "VAL_END_TIMESTAMP": "",
        "TEST_START_TIMESTAMP": "",
        "TEST_END_TIMESTAMP": ""
    }
},
"NUM_DAYS_PER_YEAR": {
    // other datasets...
    "CustomDataset": 252  // set an appropriate number of days per year
}

备注

与其自己定制数据集,我们建议您提出问题或通过电子邮件联系我们,以便我们进行评估,并可能将您的数据集纳入 FinOL 项目。 这将确保基准结果得到支持。

添加新的方法

  1. 导航到 FinOL 代码库中的 {ROOT_PATH}\model_layer\CustomModel.py 文件。

  2. 通过扩展 CustomModel 类来定制你的模型。你将在这里实现自定义数据驱动 OLPS 模型的逻辑。请确保它与 FinOL 所定义的接口保持一致和兼容。

CustomModel.py
>>> import torch
>>> import torch.nn as nn

>>> from einops import rearrange
>>> from finol.data_layer.scaler_selector import ScalerSelector
>>> from finol.utils import load_config


>>> # User-defined model class
>>> class CustomModel(nn.Module):
>>>     """
>>>     Class to serve as a base neural network model for portfolio selection. This class provides users with a framework
>>>     to extend and implement their own model architectures and functionality,
>>>     allowing for customization to meet specific requirements and objectives in financial modeling.

>>>     :param model_args: Dictionary containing model arguments, such as the number of features.
>>>     :param model_params: Dictionary containing model hyper-parameters, such as the parameter1, parameter2, etc.

>>>     Example:
>>>         .. code:: python
>>>         >>> from finol.data_layer.dataset_loader import DatasetLoader
>>>         >>> from finol.model_layer.model_instantiator import ModelInstantiator
>>>         >>> from finol.utils import load_config, update_config, portfolio_selection
>>>         >>>
>>>         >>> # Configuration
>>>         >>> config = load_config()
>>>         >>> config["MODEL_NAME"] = "CustomModel"
>>>         >>> config["MODEL_PARAMS"]["CustomModel"]["PARAMETER1"] = 2
>>>         >>> config["MODEL_PARAMS"]["CustomModel"]["PARAMETER1"] = 128
>>>         >>> update_config(config)
>>>         >>>
>>>         >>> # Data Layer
>>>         >>> load_dataset_output = DatasetLoader().load_dataset()
>>>         >>>
>>>         >>> # Model Layer & Optimization Layer
>>>         >>> ...
>>>         >>> model = ModelInstantiator(load_dataset_output).instantiate_model()
>>>         >>> print(f"model: {model}")
>>>         >>> ...
>>>         >>> train_loader = load_dataset_output["train_loader"]
>>>         >>> for i, data in enumerate(train_loader, 1):
>>>         ...     x_data, label = data
>>>         ...     final_scores = model(x_data.float())
>>>         ...     portfolio = portfolio_selection(final_scores)
>>>         ...     print(f"batch {i} input shape: {x_data.shape}")
>>>         ...     print(f"batch {i} label shape: {label.shape}")
>>>         ...     print(f"batch {i} output shape: {portfolio.shape}")
>>>         ...     print("-"*50)

>>>     .. warning::
>>>         When users define their own model, besides modifying this class, they must add different parameter keys and values
>>>         in the ``config.json`` at the location ``config["MODEL_PARAMS"]["CustomModel"]``. Similarly, if users want to implement
>>>         automatic hyper-parameters tuning for their custom model, they also need to specify the range and type of different
>>>         parameters at ``config["MODEL_PARAMS_SPACE"]["CustomModel"]``
>>>     """

>>>     def __init__(self, model_args, model_params):
>>>         super().__init__()
>>>         self.config = load_config()
>>>         self.model_args = model_args
>>>         self.model_parms = model_params
>>>         # Define your model architecture here

>>>     def forward(self, x: torch.Tensor) -> torch.Tensor:
>>>         """
>>>         Forward pass of the model.

>>>         :param x: Input tensor of shape ``(batch_size, num_assets, num_features_augmented)``.
>>>         :return: Output tensor of shape ``(batch_size, num_assets)`` containing the predicted scores for each asset.
>>>         """
>>>         batch_size, num_assets, num_features_augmented = x.shape

>>>         """Input Transformation"""
>>>         x = x.view(batch_size, num_assets, self.model_args["window_size"], self.model_args["num_features_original"])
>>>         x = rearrange(x, "b m n d -> (b m) n d")
>>>         """Input Transformation"""
>>>         if self.config["SCALER"].startswith("Window"):
>>>             x = ScalerSelector().window_normalize(x)

>>>         ...

>>>         final_scores = x

>>>         return final_scores
  1. 在配置 {ROOT_PATH}\config.json 处的 config["MODEL_PARAMS"]["CustomModel"] 中定义必要的超参数。

config.json
"MODEL_PARAMS": {
    // other models...
    "CustomModel": {
        "PARAMETER1": 4,
        "PARAMETER2": 128,
        // other hyper-parameters...
    }
},
  1. (可选)如果希望 FinOL 自动调优自定义模型的超参数,请在 config.json 文件的 MODEL_PARAMS_SPACE["CustomModel"] 部分指定不同参数的范围。

config.json
"MODEL_PARAMS_SPACE": {
    // other models...
    "CustomModel": {
        "PARAMETER1": {
            "type": "int",
            "range": [
                1,
                4
            ],
            "step": 1
        },
        "PARAMETER2": {
            "type": "int",
            "range": [
                32,
                256
            ],
            "step": 32
        },
        // other hyper-parameters...
    }
}

请参考 CustomModel 中的示例实现,以获得有关自定义模型类的预期结构和接口的指导。此外,FinOL 文档提供了详细的教程和API参考,以帮助您开始。

添加新的投资准则

  1. 导航到 FinOL 代码库中的 {ROOT_PATH}\mptimization_layer\Criterion_selector.py 文件。

  2. 找到 CriterionSelector 类,并通过改写 compute_custom_criterion_loss() 方法来定义自己的自定义投资准则。确保它符合 FinOL 定义的接口,以保持一致性和兼容性。

criterion_selector.py
>>> import time
>>> import torch

>>> from finol.utils import load_config


>>> class CriterionSelector:
>>>     """
>>>     Class to select and compute different loss criteria for portfolio selection.
>>>     """
>>>     def __init__(self) -> None:
>>>         self.config = load_config()
>>>         self.criterion_dict = {
>>>             "LogWealth": self.compute_log_wealth_loss,
>>>             "LogWealthL2Diversification": self.compute_log_wealth_l2_diversification_loss,
>>>             "LogWealthL2Concentration": self.compute_log_wealth_l2_concentration_loss,
>>>             "L2Diversification": self.compute_l2_diversification_loss,
>>>             "L2Concentration": self.compute_l2_concentration_loss,
>>>             "SharpeRatio": self.compute_sharpe_ratio_loss,
>>>             "Volatility": self.compute_volatility_loss,
>>>             "CustomCriterion": self.compute_custom_criterion_loss,
>>>         }

>>>         ...

>>>     def compute_custom_criterion_loss(self, portfolios: torch.Tensor, labels: torch.Tensor) -> torch.Tensor:
>>>         """
>>>         Compute the ``CustomCriterion`` loss,  which is left for the user to define.

>>>         This loss function is a placeholder for the user to implement their own custom loss criterion.

>>>         :param portfolios: Portfolio weights tensor of shape (batch_size, num_assets).
>>>         :param labels: Label tensor representing asset returns of shape (batch_size, num_assets).
>>>         :return: ``CustomCriteria`` loss tensor, representing the user-defined loss criterion.
>>>         """
>>>         # This is a placeholder for the user to implement their own custom loss function.
>>>         # The implementation of the custom loss function is left to the user.
>>>         loss = torch.tensor(0.0, requires_grad=True)
>>>         return loss

>>>     def __call__(self, portfolios: torch.Tensor, labels: torch.Tensor) -> torch.Tensor:
>>>         criterion_cls = self.criterion_dict.get(self.config["CRITERION_NAME"], None)
>>>         if criterion_cls is None:
>>>            raise ValueError(f"Invalid criterion name: {self.config['CRITERION_NAME']}. Supported criteria are: {self.criterion_dict.keys()}")
>>>         return criterion_cls(portfolios, labels)