9 KiB
Features
Column-oriented design
In a column-oriented design, a feature can be viewed as a single column.
In this project, all data is assumed to be stored in one table; therefore, all features exist as columns within this main table.
This primary data table is represented as a pandas DataFrame, where features correspond to columns and rows correspond to timestamps at a specific frequency.
Most of the system's data analysis logic is embodied in how its features are computed. The core concept is that for each new row appended to the main DataFrame, all (initially empty) feature values must be computed. Once these new values are calculated, trade decisions can be made.
Feature dependencies
How a feature computes its value based on existing (current and past) feature values is specified in a feature definition.
Generally, any feature definition uses—and thus depends on—other features, and its value may subsequently be used by other features.
In this sense, all features collectively define a computation graph.
A feature also depends on a specific number of previous rows. In the simplest case, a feature depends only on the current row. For example, we could define a feature equal to the difference between the high and low prices for the current day. This depends only on the current row and two other features (high and low), which must be set before this difference can be computed. Conversely, a feature that computes a moving average will depend on one feature (such as close price) and a certain number of previous values.
Defining features
All features are represented as a list in which each item is a dictionary containing a single feature definition:
"feature_sets": [
{...}, // First feature
{...}, // Second feature
{...} // Third feature
]
Features can also be defined in the signal_sets section, which is evaluated after trainable features.
A single feature definition is a dictionary with the following attributes:
{
"generator": "talib", // Name of the generator (pluggable) that processes and executes this feature
"column_prefix": "", // Prefix removed from column names before data is passed to the generator
"feature_prefix": "", // Appended to new features after generation
"config": {} // Parameters specific to this feature
}
The attributes of the feature definition have the following interpretations:
generator: A string representing either a predefined built-in generator name or a user-defined Python function implementing a custom feature generator.column_prefix: This prefix is removed from column names before data is passed to the generator. For example, if analyzing BTC and ETH data simultaneously, columns might includebtc_closeandeth_close. However, if a generator should process only theopencolumn, this can be achieved by specifyingcolumn_prefix: btcorcolumn_prefix: ethfor two features sharing the same configuration. Alternatively, the input column can be specified directly in the generator'sconfigparameters.feature_prefix: This prefix (followed by an underscore) is automatically appended to all features returned by the generator. It may match thecolumn_prefix. Again, theconfigsection can be used to provide a desired output name for the generated feature.config: A dictionary containing feature configuration parameters specific to each generator.
Currently available generators and their features
ITB provides a collection of ready-to-use feature generators that allow for the definition of standard technical indicators as well as some original features.
Features based on TA-Lib
TA-Lib is a native C/C++ library implementing approximately 200 indicators for financial analysis and trading applications. It includes a Python wrapper, which ITB uses to expose these indicators as feature definitions.
Note that using this feature generator requires the native TA-Lib binary library to be installed. This can be accomplished in several ways:
- Install TA-Lib as a Linux package.
- Build and install TA-Lib from source, ensuring the library is accessible from Python.
- Install the native TA-Lib library via a Python package manager. For example:
$ conda install -c conda-forge libta-lib. - Install the Python wrapper via a package manager that supports platform-specific libraries.
For example, Conda supports this for certain platforms and Python versions:
conda install -c conda-forge ta-lib. - In some cases, it may be possible to locate the library manually and copy it to a location accessible from Python.
To use TA-Lib, set the generator name to "talib": "generator": "talib".
The generator maps config attributes to TA-Lib indicator arguments, calls the appropriate TA-Lib functions, and returns the results as one or more pandas columns attached to the main DataFrame.
The generator config attributes are interpreted as follows (not all are required for every feature):
columns: A list of column (feature) names to pass to the TA-Lib function. For example,columns: ["close","high","low"]indicates that these three price columns will be used to compute the feature.functions: A list of functions as defined in the TA-Lib documentation. One feature is generated for each function name. For example,functions: ["SMA"]specifies Simple Moving Average, meaning each feature value is computed as the average of several previous values.windows: A list of integers interpreted as the window size (number of previous rows, including the current time). One feature is generated for each window value. For example,windows: [20]with theSMAfunction computes a 20-row simple moving average. For daily data, this represents a 20-day moving average.parameters: A dictionary of parameters for post-processing the series of indicators returned by TA-Lib (not specific to TA-Lib itself). These are applied to the time series resulting from differentwindowsvalues, typically to compute relative values within the sequence.rel_base: Possible values include:next: Relative to the next element in the sequence.last: Relative to the last element in the sequence.prev: Relative to the previous element in the sequence.first: Relative to the first element in the sequence.
rel_func: Defines how the new value is derived from the original and reference values:diff: Difference between the original and reference value (value - reference_value).rel: Ratio of the original to the reference value (value / reference_value).rel_diff: Relative difference ((value - reference_value) / reference_value).
In this example, three output features are computed: the 1-, 10-, and 20-period moving averages of the close price:
{
"generator": "talib",
"column_prefix": "", "feature_prefix": "",
"config": {
"columns": ["close"],
"functions": ["SMA"],
"windows": [1, 10, 20],
"parameters": {"rel_base": "next", "rel_func": "diff"}
}
}
The first output feature is the absolute difference between the close price (1-period MA) and its 10-period moving average. The second output feature is the absolute difference between the 10-period and 20-period MAs of the close price. The final feature is the 20-period MA without a relative value calculation, as there is no subsequent element in the MA series.
Note that some TA-Lib functions are characterized as having an unstable period.
Such functions may return incorrect results during this period and should therefore be used with caution.
These functions include a note in the documentation such as: The ADX function has an unstable period.
Defining custom (your own) features
Custom feature generators can be specified as user-defined Python functions.
In this case, the generator attribute in the feature definition must be a fully qualified Python function name consisting of the module name and function name separated by a colon.
Here is an example of a custom feature definition:
{
"generator": "common.my_feature_example:my_feature_example",
"column_prefix": "", "feature_prefix": "",
"config": {"columns": "close", "function": "add", "parameter": 2.0, "names": "close_add"}
}
This function can be implemented as follows:
def my_feature_example(df, config: dict, global_config: dict, model_store: ModelStore, last_rows: int = 0):
# Parse feature config. See source code for details
if function == 'add':
df[names] = df[column_name] + parameter
elif function == 'mul':
df[names] = df[column_name] * parameter
return df, [names]
This function retrieves the input column name and operation type from the config. Depending on the operation name, it either adds a constant parameter to the input column or performs multiplication. The resulting column is appended to the input DataFrame and returned alongside the new feature name.
Note that while the configuration dictionary may have an arbitrary format and attributes, the feature generator must adhere to this specific signature.