intelligent-trading-bot/docs/trainable-features.md

8.8 KiB

Trainable features

Why train?

A conventional feature produces an output column from an input column using manually specified parameters. For example, a feature computing the moving average of the close price has one parameter: the window size. This parameter is specified in the feature configuration and does not depend on any external factors. Its value must be known in advance and is typically derived from experience. If we want to produce a feature representing the deviation of the current price from the average price, we need to know this average value. We can manually compute the average price by loading historic prices and then setting it in the feature configuration. This configured value is then used during the evaluation process to compute the deviation.

In contrast to conventional features, trainable features learn their parameters from historic data. The complete set of these parameters is referred to as a model. A model can be as simple as a single number or as complex as a deep neural network. Crucially, it is derived by analyzing historic data; this process is referred to as training. Therefore, features that use trained models are referred to as trainable features or ML features.

One example of a trainable feature is one where the goal is to determine the average price from historic data. This model stores only one number: the average price value. Feature evaluation relies on this model to compute the deviation from the average. Unlike the previous example, where the average value had to be known in advance, the average value is now part of the automated procedure. The feature itself determines how to derive its model parameters from historic data before using those parameters to generate output.

Trainable features operate in two modes by implementing two types of logic:

  • How to derive feature parameters by analyzing historic data and generating a model
  • How to use feature parameters (the model) to generate output values (predictions)

Advantages of trainable features:

  • They identify optimal parameters rather than relying on intuition or educated guesses
  • They can be regularly retrained so their values reflect the latest history and adapt to drift
  • They can learn significantly more complex dependencies than manually defined features

Drawbacks of trainable features:

  • They require a substantial amount of historic data, which may be unavailable or insufficient for training complex models without overfitting
  • Manually defined features frequently represent the perspective of traders and domain experts. They constitute compressed knowledge with highly informative representations, making them difficult to replicate through automatic learning

Our approach combines both worlds: it provides a rich mechanism for defining manually parameterized features while also enabling the definition of features that automatically learn their parameters by applying statistical or machine learning algorithms.

Defining trainable features

Trainable features are defined separately from other features as a list where each item is a dictionary containing a single feature definition:

"train_feature_sets": [
  {...}, // First trainable feature
  {...}, // Second trainable feature
  {...} // Third trainable feature
]

A single feature definition is a dictionary with the following attributes:

{
  "generator": "train_features", // This generator handles the trainable nature of the feature
  "columns": [], // Columns used for training and prediction. If empty, the 'train_features' list is used
  "labels": ["bot_2", "top_2"], // Columns used as labels for training
  "functions": [
    {
      "name": "mysvc",
      "algo": "svc", // Arbitrary name and predefined algorithm type
      "params": {"is_scale": true, "length": 0}, // Preprocessing parameters
      "train": {"C": 1.0, "gamma": 0.005} // Algorithm arguments
    }
  ]
}

The purposes of these attributes are as follows:

  • generator: A built-in generator function that evaluates trainable features
  • columns: A list of column names selected for training. If the list is empty, columns from train_features are used. This attribute enumerates all input columns (excluding labels)
  • labels: Column names used as ground truth values during training. If the list is empty, columns from labels are used. This attribute enumerates all columns serving as labels
  • functions: A list of algorithm descriptions, each being a dictionary with the following attributes:
    • name: An arbitrary unique identifier for this algorithm entry. It serves as a suffix in the predicted column names (alongside the label name), indicating which algorithm (and label) generated a specific output column
    • algo: The algorithm type, which resolves to a specific Python function. Currently supported types include:
      • svc: Support Vector Machines
      • nn: Neural Network
      • lc: Linear Classifier
      • gb: Gradient Boosting
    • params: Parameters for the generator used to prepare the training dataset:
      • is_scale: If true, all columns will be normalized
      • length: The number of records to use for training. If 0, train_length is used. If omitted, all available data is used
      • every_nth_row: Enables selection of a smaller subset for training via downsampling
      • is_regression: If true, the label is treated as a numeric value and a regression model is trained
    • train: A dictionary of parameters passed directly to the algorithm

For each entry in train_feature_sets, the number of generated features equals the number of labels. Each output feature follows the naming schema label_algo, where label is a value from the labels list and algo is the name specified in the name attribute. In the example above, two features and two output columns are generated: bot_2_mysvc and top_2_mysvc. These same names are used for the trained models stored as files.

Custom trainable features

An alternative method for defining trainable features is compatible with standard feature definitions. Such feature definitions are placed in the feature_sets or train_features sections, but not in the train_feature_sets section of the configuration.

This approach requires a custom generator function that must handle two modes: train and predict. It must check whether it is running in train mode via the global boolean train attribute. If true, it must train its model using available data and then perform feature evaluation using this newly trained model. If running in predict mode (when the train attribute is false), it reuses the previously trained model.

Below is an example of how to define such a trainable feature to train a model that finds an average value, which is then used to calculate the deviation of current values from that average.

{
  "generator": "myextensions.stats:deviation_feature",
  "config": { // Passed to the generator function
    "columns": "close", // Column for which to find the average
    "function": "mean", // Other functions could be supported, e.g., median
    "names": "deviation", // Predicted column name
    "parameters": {} // Additional parameters if needed
  }
}

This generator function can be implemented as follows:

def deviation_feature(df, config: dict, global_config: dict, model_store: ModelStore):
    column_name = config.get('columns')
    function = config.get('function')
    names = config.get('names')  # Output feature name
    if not names:
        names = f"{column_name}_{function}"

    # Load model
    model_name = config.get('model_name', names)
    model = model_store.get_model(model_name)

    # Determine if training is required before prediction
    is_train = global_config.get('train')

    # If in train mode, derive scale parameters from data and store in the model for subsequent prediction
    if is_train:
        mean = df[column_name].mean()  # Calculate mean value
        model = dict(mean=mean)  # Create model as a dictionary
        model_store.put_model(model_name, model)  # Persistently store the model

    # Perform evaluation using either the loaded or newly trained model
    if function == 'mean':
        out_column = df[column_name] - model.get("mean")

    df[names] = out_column

    return df, [names]

The distinguishing aspect of this code is the training block, which executes only when train mode is detected. In train mode, data from the specified column is used to calculate the mean value, which constitutes the sole parameter of the model. Subsequently, the column is transformed by subtracting this mean value, and the resulting column is returned as a new generated feature.

When executed in predict mode, the feature behaves like any standard feature, except that its model (the mean value) is loaded from the model store.