Note

This page was generated from a Jupyter notebook.

[ ]:
import os
import pandas as pd
from tstrends.label_tuning import RemainingValueTuner
from tstrends.trend_labelling import OracleTernaryTrendLabeller
from tstrends.visualization import plot_trend_labels

Getting the labels to tune

First, we’ll perform standard ternary trend labeling using the OracleTernaryTrendLabeller to generate initial labels. This is a straightforward labeling process without any optimization or returns estimation.

[18]:
df_list = []
for file in os.listdir('/workspaces/python-trend-labeller/notebooks/data'):
    if file.endswith('.parquet'):
        df_list.append(pd.read_parquet(f'/workspaces/python-trend-labeller/notebooks/data/{file}'))

[19]:
df_concat = pd.concat(df_list).sort_values(by='t')
df_concat.drop_duplicates(subset='t', keep='first', inplace=True)
[20]:
labels = OracleTernaryTrendLabeller(transaction_cost=0.006, neutral_reward_factor=0.03).get_labels(df_concat.c.tolist())
plot_trend_labels(df_concat.c.tolist(), labels)
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_5_0.png

Tuning the labels

Trend labeling is often performed as an intermediate step in training a predictive model. However, binary (or ternary) systems may not capture some important nuances. For example, in trading use cases:

  • Correctly predicting an uptrend is critical at the beginning of the trend, but becomes less relevant near the end.

  • The impact of a correct prediction depends on the magnitude of the trend’s total price change.

Hence, we can use the RemainingValueTuner to transform the labels into a float that expresses, for each time i, the difference between the maximum/minimum of the trend and the actual value at time i.

[21]:
from tstrends.visualization.utils import plot_trend_labels_with_gradation

tuned_labels = RemainingValueTuner().tune(df_concat.c.tolist(), labels)

plot_trend_labels_with_gradation(df_concat.c.tolist(), tuned_labels, title="Standard tuning")
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_9_0.png

To normalize the values within each trend interval to a common scale, use the normalize_over_interval parameter.

[22]:
tuned_labels = RemainingValueTuner().tune(df_concat.c.tolist(), labels, normalize_over_interval=True)

plot_trend_labels_with_gradation(df_concat.c.tolist(), tuned_labels, title="Tuning with normalization")
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_11_0.png

For some use cases, having non-monotonic value sequences can be problematic. We can enforce monotonicity of the generated series using the enforce_monotonicity parameter.

[23]:
tuned_labels = RemainingValueTuner().tune(df_concat.c.tolist(), labels, enforce_monotonicity=True, normalize_over_interval=True)

plot_trend_labels_with_gradation(df_concat.c.tolist(), tuned_labels)
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_13_0.png

Smoothing and shifting

Smoothing the series can help to reduce some harmful noise in the signal. As in v0.3.0 there are two main smoothers inplemented:

  • SimpleMovingAverage: Applies equal weights to all values in the window, resulting in uniform smoothing.

  • LinearWeightedAverage: Applies linearly increasing weights to values in the window, giving more importance to recent values and less to older ones. This creates a more responsive smoothing that better preserves the shape of trends.

They have two main parameters, the window size and the direction (“left” shifting the tuned labels to preceed the trend or “centered”).

[24]:
from tstrends.label_tuning.smoothing import SimpleMovingAverage

smoother = SimpleMovingAverage(window_size=300)

tuned_labels_with_smoothing = RemainingValueTuner(postprocessors=[smoother]).tune(
    df_concat.c.tolist(), labels, normalize_over_interval=True
)
plot_trend_labels_with_gradation(
    df_concat.c.tolist(),
    tuned_labels_with_smoothing,
    title="Tuned labels with left MA smoothing",
)
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_17_0.png
[25]:
from tstrends.label_tuning.smoothing import LinearWeightedAverage


smoother = LinearWeightedAverage(window_size=300, direction="centered")
tuned_labels_with_smoothing = RemainingValueTuner(postprocessors=[smoother]).tune(
    df_concat.c.tolist(), labels, normalize_over_interval=True
)
plot_trend_labels_with_gradation(
    df_concat.c.tolist(),
    tuned_labels_with_smoothing,
    title="Tuned labels with left LW centered smoothing",
)
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_18_0.png

Lastly, the shifting effect can be achieved explicitly with the shift_periods parameter of the tune method. Note that the shifting is done before the smoothing to avoid a sharp decay on the shifted side.

[26]:
from tstrends.label_tuning.shifting import Shifter

smoother = SimpleMovingAverage(window_size=300, direction="centered")
shifter = Shifter(periods=-200)

tuned_labels_with_smoothing = RemainingValueTuner(postprocessors=[smoother, shifter]).tune(
    df_concat.c.tolist(),
    labels,
    normalize_over_interval=True,
)
plot_trend_labels_with_gradation(
    df_concat.c.tolist(),
    tuned_labels_with_smoothing,
    title="Tuned labels with centered MA smoothing and left shifting",
)
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_20_0.png

ForwardLookingFilter: Tuning down early signal

A detected binary trend can also be turned into an effective set of tuned labels by using the ForwardLookingFilter, combined with the RemainingValueTuner. The filter tunes down a tuned label (a.k.a. the remaining potential delta in the trend interval) depending on if the immediatly upcoming value delta in the time series.

Let’s start with an OracleBinaryTrendLabeller:

[27]:
from tstrends.trend_labelling import OracleBinaryTrendLabeller


labels = OracleBinaryTrendLabeller(transaction_cost=0.006).get_labels(df_concat.c.tolist())
plot_trend_labels(df_concat.c.tolist(), labels)
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_24_0.png

Although providing a good framework for unbiased trend segmentation, binary levels are not the most expressive signal. If we want to switch from:

  • This data point is within an upwards/downwards trend

  • This data point is within an upwards/downwards trend, and the remaining trend to climb/fall (weighted by the imminent trend velocity) is X

we can use the ForwardLookingFilter to tune the labels.

[28]:
from tstrends.label_tuning.filtering import ForwardLookingFilter


forward_looking_filter = ForwardLookingFilter(forward_window_rel=0.2, smoothing_window_rel=0.1)
smoother = SimpleMovingAverage(window_size=150, direction="left")

tuned_labels_with_smoothing = RemainingValueTuner(
    postprocessors=[forward_looking_filter, smoother]
).tune(
    df_concat.c.tolist(),
    labels,
    normalize_over_interval=True,
)
plot_trend_labels_with_gradation(
    df_concat.c.tolist(),
    tuned_labels_with_smoothing,
    title="Tuned labels with forward looking filter and left MA smoothing",
)
../../_build/doctrees/nbsphinx/notebooks_label_tuner_example_26_0.png

Note: If your downstream usage of labels supports floats, this approach offers a straightforward and effective way to generate informative trend labels. Why this is advantageous:

  • Binary labeller parametrization is generally more robust than ternary alternatives.

  • You gain simplicity and interpretability by configuring a transparent tuner, instead of adding additional label states.Overall, parametrizing a tuner is clear and reliable.