Usage Examples

This page provides detailed examples of using TStrends in various scenarios, as a quickstart overview of the right syntax and capabilities of the package.

The labelling results might not be interesting nor significant, since these are just dummy time series.

Note

For interactive examples with visualization and more interesting labelling results, check out the Notebook Examples page which contains Jupyter notebooks with runnable code and detailed explanations.

Trend Labelling Examples

All labellers are parametrized by one or more parameters, which tune up their behavior.

Binary CTL with Different Omega Values

The omega parameter in Binary CTL controls the sensitivity to trend changes. Here’s how different values affect the labelling:

import numpy as np
from tstrends.trend_labelling import BinaryCTL
import matplotlib.pyplot as plt

# Generate sample data (labellers require a Python list of ints/floats, not an ndarray)
np.random.seed(42)
prices = (np.random.randn(100).cumsum() + 100).tolist()

# Test different omega values
omega_values = [0.001, 0.005, 0.01, 0.015]
labels = {}

for omega in omega_values:
    labeller = BinaryCTL(omega=omega)
    labels[omega] = labeller.get_labels(prices)

Ternary CTL Parameter Effects

The TernaryCTL labeller uses two main parameters:

import numpy as np
from tstrends.trend_labelling import TernaryCTL

np.random.seed(42)
prices = (np.random.randn(100).cumsum() + 100).tolist()

# Different parameter combinations
configs = [
    {'marginal_change_thres': 0.01, 'window_size': 10},
    {'marginal_change_thres': 0.02, 'window_size': 20},
    {'marginal_change_thres': 0.005, 'window_size': 5}
]

for config in configs:
    labeller = TernaryCTL(**config)
    labels = labeller.get_labels(prices)

Returns Estimation Examples

Return estimators provide a convenient way to calculate the extent to which the labelling set up captures the actual time series shifts. A simple return estimator is provided by the SimpleReturnEstimator class, while the ReturnsEstimatorWithFees class allows for the estimation of returns taking into account transaction and holding costs.

Simple Returns vs Transaction Costs

Compare returns with and without transaction costs:

from tstrends.trend_labelling import BinaryCTL
from tstrends.returns_estimation import (
    SimpleReturnEstimator,
    ReturnsEstimatorWithFees,
    FeesConfig,
)

prices = [100.0 + 0.5 * i for i in range(50)]
labels = BinaryCTL(omega=0.01).get_labels(prices)

# Simple returns
simple_estimator = SimpleReturnEstimator()
simple_returns = simple_estimator.estimate_return(prices, labels)

# Returns with fees
fees_config = FeesConfig(
    lp_transaction_fees=0.001,
    sp_transaction_fees=0.001,
    lp_holding_fees=0.0001,
    sp_holding_fees=0.0001
)
fees_estimator = ReturnsEstimatorWithFees(fees_config)
fees_returns = fees_estimator.estimate_return(prices, labels)

Position-Specific Fee Structures

Example with different fees for long and short positions:

from tstrends.trend_labelling import BinaryCTL
from tstrends.returns_estimation import ReturnsEstimatorWithFees, FeesConfig

prices = [100.0 + 0.5 * i for i in range(50)]
labels = BinaryCTL(omega=0.01).get_labels(prices)

# Asymmetric fees configuration
asymmetric_fees = FeesConfig(
    lp_transaction_fees=0.001,  # 0.1% for long positions
    sp_transaction_fees=0.002,  # 0.2% for short positions
    lp_holding_fees=0.0001,    # Lower holding fee for longs
    sp_holding_fees=0.0002     # Higher holding fee for shorts
)

estimator = ReturnsEstimatorWithFees(asymmetric_fees)
returns = estimator.estimate_return(prices, labels)

Parameter Optimization Example

The optimization process allows to find the labeller parameters that lead to the highest returns, given a specific returns estimator. Therefore, tweaking the fees structure (for instance to penalise downtrends more) will lead to different optimal parameters and labelling behaviour.

Optimize parameters for a single time series:

import numpy as np
from tstrends.optimization import Optimizer
from tstrends.returns_estimation import SimpleReturnEstimator
from tstrends.trend_labelling import BinaryCTL

np.random.seed(42)
prices = (np.random.randn(100).cumsum() + 100).tolist()

# Create optimizer (pass a returns estimator instance, not the class)
optimizer = Optimizer(
    returns_estimator=SimpleReturnEstimator(),
    initial_points=5,
    nb_iter=100,
)

# Define custom bounds
bounds = {
    'omega': (0.001, 0.1)
}

# Optimize
result = optimizer.optimize(
    labeller_class=BinaryCTL,
    time_series_list=prices,
    bounds=bounds,
    verbose=1
)

Label Tuning Examples

Label tuning transforms discrete trend labels into continuous values that express the potential of the trend at each point. Optional postprocessors run in order after the magnitudes are computed: use ForwardLookingFilter to downweight low forward-looking efficiency within each trend leg, Shifter to align signals in time, and smoothers from tstrends.label_tuning.smoothing to pool neighboring values.

Remaining Value Tuning

The RemainingValueTuner transforms labels based on the difference between the current value and the maximum/minimum value reached by the end of the trend:

import numpy as np
from tstrends.trend_labelling import OracleTernaryTrendLabeller
from tstrends.label_tuning import RemainingValueTuner
from tstrends.label_tuning.smoothing import LinearWeightedAverage

np.random.seed(42)
prices = (np.random.randn(80).cumsum() + 100).tolist()

# Generate trend labels
labeller = OracleTernaryTrendLabeller(transaction_cost=0.006, neutral_reward_factor=0.03)
labels = labeller.get_labels(prices)

# Create a smoother for enhancing the tuned labels (optional)
smoother = LinearWeightedAverage(window_size=5, direction="left")

# Tune the labels (optional smoother via postprocessors)
tuner = RemainingValueTuner(postprocessors=[smoother])
tuned_labels = tuner.tune(
    time_series=prices,
    labels=labels,
    enforce_monotonicity=True,
    normalize_over_interval=False,
)

Forward-looking filter: tuning down early signal

A binary segmentation can still yield rich float labels if you pass ForwardLookingFilter (and optional smoothers) as postprocessors to RemainingValueTuner. After remaining-value magnitudes are computed, the filter rescales them using a forward-looking efficiency measure: strong net move over the next few steps, relative to the path length, keeps the signal; weak or choppy forward paths damp it. Horizons can be set in bars or as a fraction of each non-neutral interval.

Typical pattern: OracleBinaryTrendLabeller for labels, relative filter windows, then a left-looking moving average as the last postprocessor (postprocessors run in order).

import numpy as np
from tstrends.trend_labelling import OracleBinaryTrendLabeller
from tstrends.label_tuning import RemainingValueTuner, ForwardLookingFilter
from tstrends.label_tuning.smoothing import SimpleMovingAverage

np.random.seed(42)
prices = (np.random.randn(120).cumsum() + 100).tolist()
labels = OracleBinaryTrendLabeller(transaction_cost=0.006).get_labels(prices)

forward_filter = ForwardLookingFilter(
    forward_window_rel=0.2,
    smoothing_window_rel=0.1,
)
# Wider windows suit longer series; the label_tuner_example notebook uses 150 on synthetic data.
smoother = SimpleMovingAverage(window_size=25, direction="left")

tuner = RemainingValueTuner(
    postprocessors=[forward_filter, smoother],
)
tuned_labels = tuner.tune(
    time_series=prices,
    labels=labels,
    normalize_over_interval=True,
)

If your downstream model accepts floats, this keeps binary labeller settings (often simpler to tune than multi-class labellers) while encoding “how much is left, weighted by near-term path quality” without extra discrete label states.

Absolute windows, shifting, and pipeline order

You can use integer forward_window and smoothing_window instead of the *_rel arguments, optionally chain Shifter so positive periods shift values later in time (earlier indices padded with zero), and mix in other smoothers—each step receives the output of the previous one:

import numpy as np
from tstrends.label_tuning import (
    RemainingValueTuner,
    ForwardLookingFilter,
    Shifter,
)
from tstrends.label_tuning.smoothing import LinearWeightedAverage
from tstrends.trend_labelling import OracleTernaryTrendLabeller

np.random.seed(42)
prices = (np.random.randn(80).cumsum() + 100).tolist()
labeller = OracleTernaryTrendLabeller(transaction_cost=0.006, neutral_reward_factor=0.03)
labels = labeller.get_labels(prices)

forward_filter = ForwardLookingFilter(
    forward_window=5,
    smoothing_window=3,
    quantile=0.95,
)
shifter = Shifter(periods=2)
smoother = LinearWeightedAverage(window_size=5, direction="left")

tuner = RemainingValueTuner(
    postprocessors=[forward_filter, shifter, smoother],
)
tuned_labels = tuner.tune(
    time_series=prices,
    labels=labels,
    enforce_monotonicity=True,
    normalize_over_interval=False,
)

Smoothing Options

Apply different smoothing options to the tuned labels:

from tstrends.label_tuning.smoothing import SimpleMovingAverage, LinearWeightedAverage

# Example tuned series (e.g. from RemainingValueTuner); list of floats
tuned_labels = [0.1, 0.2, 0.15, -0.1, -0.05] * 10

# Simple moving average (equal weights)
simple_smoother = SimpleMovingAverage(window_size=5, direction="left")
simple_smoothed = simple_smoother.smooth(tuned_labels)

# Linear weighted average (higher weights on more recent values)
weighted_smoother = LinearWeightedAverage(window_size=5, direction="centered")
weighted_smoothed = weighted_smoother.smooth(tuned_labels)

Real-World Application

Here’s a complete example combining all components for a real-world scenario:

import yfinance as yf
from tstrends.trend_labelling import TernaryCTL
from tstrends.returns_estimation import ReturnsEstimatorWithFees, FeesConfig
from tstrends.optimization import Optimizer

# Download stock data
stock = yf.Ticker("AAPL")
df = stock.history(period="1y")
prices = df["Close"].tolist()

# Setup fees configuration
fees_config = FeesConfig(
    lp_transaction_fees=0.001,
    sp_transaction_fees=0.001,
    lp_holding_fees=0.0001,
    sp_holding_fees=0.0001
)

# Create returns estimator
estimator = ReturnsEstimatorWithFees(fees_config)

# Create and configure optimizer
optimizer = Optimizer(
    returns_estimator=estimator,
    initial_points=10,
    nb_iter=200
)

# Optimize parameters
result = optimizer.optimize(
    labeller_class=TernaryCTL,
    time_series_list=prices
)

# Create labeller with optimal parameters
optimal_labeller = TernaryCTL(**result['params'])
labels = optimal_labeller.get_labels(prices)

# Calculate final returns
final_returns = estimator.estimate_return(prices, labels)

print(f"Optimal parameters: {result['params']}")
print(f"Total returns: {final_returns}")