Scoring

TFire uses a scoring system to determine portfolio allocations during backtesting. The process involves two main steps:

  1. Creating and modifying SignalScores
  2. Converting SignalScores into PositionScores for portfolio weighting

SignalScores Creation and Modification

SignalScores represent trading signals and can be created in several ways:

Types of SignalScores

There are three types of trading signals:

  • SignalScores{BuySignal} - Increases position scores
  • SignalScores{SellSignal} - Decreases position scores
  • SignalScores{CloseSignal} - Reduces scores toward zero in both positive and negative directions

Creating SignalScores

SignalScores can be created through several methods.

From Collections (often done in conjecture with filtering the Collection) using any of the following:

# Example: Create buy SignalScores{BuySignal} score 1.0 for all assets in collection
buy_signals = BuyScores(collection, 1.0)

PortfolioAtDate using any of the following:

# Example: Create sell signals for current portfolio holdings
sell_signals = SellScores(portfolio_at_date, 0.5)

Using Custom Scoring Functions that act on DataViews using any of the following

# Example: Score based on moving average crossover
function crossover_score(values)
    fast_ma, slow_ma = values
    return fast_ma > slow_ma ? 1.0 : 0.0
end

buy_signals = BuyScores(collection, [fast_ma_view, slow_ma_view], crossover_score)

Modifying SignalScores

SignalScores can be modified in several ways.

Signal Arithmetic using standard operators:

# Example: Combine multiple signal sources
combined_buy_signals = momentum_buy_scores + value_buy_scores

Extending Scores using extend_scores(signal_scores::SignalScores, td::TradingDates):

# Example: Hold signals constant for 3 days then decay over 2 days
extended_scores = extend_scores(buy_scores, trading_dates,
                              constant_ticks=3,
                              linear_ticks=2)

Shifting Signals using signal_scores_shift(ss::SignalScores, signal_type, score::Float64):

# Example: Create delayed sell signals one day later
delayed_sells = signal_scores_shift(original_scores, SellSignal, 1.0,
                                  shift_ticks=1,
                                  resolution="1d")

PositionScores and Cash Management

Creating PositionScores

PositionScores determine the actual portfolio weightings and can be created in a few different ways.

From a single BuyScore:

PositionScores(buy_scores::SignalScores).

From one each of the three types of SignalScores using PositionScores(buy_scores::SignalScores, sell_scores::SignalScores, close_scores::SignalScores).

When a holding is given a score from BuyScores the PositionScores keeps that score for all future trading dates until either a new higher score is obtained from BuyScores or the score is subtracted by a score in SellScores or CloseScores.

An example may make this more clear. Say we have a Portfolio with only one potential holding.

Trading DayPositionScoresBuyScoresSellScoresCloseScores
10.---
21.1.--
31.0.7--
42.2.--
52.---
61.5-0.5-
70.-3.-
80.1.3.-
91.1.--
100.5--0.5

Buy and Hold Strategy** using buy_and_hold(score_buy::SignalScores{BuySignal}, td::TradingDates, ticks::Int):

# Example: Create 10-day buy-and-hold positions
hold_positions = buy_and_hold(entry_signals, trading_dates, 10)

Cash Score Management

Cash scores can be managed using several functions:

Creating Cash Scores using CashScore(ss::SignalScores, score::Float64):

# Example: Set cash score of 1.0 for all signal dates
cash_scores = CashScore(signal_scores, 1.0)

Automatic Cash Fill using fill_in_with_cash_score_where_empty!(ps::PositionScores):

# Example: Add cash when no positions exist
fill_in_with_cash_score_where_empty!(position_scores)

Position Management Functions

Several utility functions are available for managing positions:

limit_maximum_allocation!(position_scores::PositionScores, max_share::Float64) Limits the maximum allocation of a portfolio to the specified max_share value. This function modifies the position_scores in-place to ensure that the sum of the absolute values of the scores does not exceed max_share.

add_up_to!(ps::PositionScores, target_sum::Float64) and add_up_to(ps::PositionScores, target_sum::Float64): Adjusts the position_scores to sum up to the specified target_sum value. The add_up_to! function modifies the position_scores in-place, while add_up_to returns a new PositionScores instance with the adjusted scores.

LeverageScore

LeverageScore is a mechanism used to adjust the overall exposure of a portfolio, allowing for leveraged or deleveraged positions. It acts as a scaling factor applied to the change in portfolio value between consecutive trading dates. LeverageScores can be applied to an already propagated PortfolioHistory

If the relative change in portfolio value between trading date $k$ and $k+1$ is given by:

\[\delta_k = \frac{\text{PV}_{k+1} - \text{PV}_k}{\text{PV}_k}\]

where $\text{PV}_k$ is the portfolio value at trading date $k$.

then the leverage modification factor $M_k$ for each step is calculated as:

\[M_k = \delta_k \cdot (L_k - 1)\]

The scaled portfolio value $\text{PVL}_{k+1}$ for trading date $k+1$, is then calculated as:

\[\text{PVL}_{k+1} = PV_{k+1} \cdot (1 + M_k)\]

All quantities of the portfolio holdings, cash, margin cash etc are scaled in equal ammounts by the factor $(1 + M_k)$.

Inspecting Scores

TFire provides several tools for visualizing and inspecting both SignalScores and PositionScores. These tools help analyze trading signals and position allocations throughout your backtest period.

Tabular Visualization

The print_table(score::Union{PositionScores, SignalScores}) function creates an interactive HTML table showing scores across time:

# View all scores
print_table(position_scores)

# View specific date range
print_table(signal_scores, 
           first_date=DateTime("2023-01-01"), 
           last_date=DateTime("2023-03-31"))

The table features:

  • Color-coded cells indicating score magnitude and direction:
    • Green shades for positive scores
    • Red shades for negative scores
    • Gray for zero scores
  • Dates as rows and tickers as columns
  • Cash column for PositionScores
  • Adjustable date range using first_date and last_date parameters
  • Automatic row limit to maintain performance (displays first 10,000 rows by default)

Time Series Visualization

The plot_scores(position_scores::PositionScores, ticker::String) function creates an interactive plot showing how scores evolve over time for a specific ticker:

# Plot full history for a ticker
plot_scores(position_scores, "AAPL")

# Plot specific date range
plot_scores(position_scores, "AAPL",
           start_date=DateTime("2023-01-01"),
           end_date=DateTime("2023-03-31"))

The plot includes:

  • Price history on a log scale
  • Score markers overlaid on price
  • Color-coded score values:
    • Green for positive scores
    • Red for negative scores
    • Blue for zero scores
  • Score values displayed above each marker
  • Optional date range filtering using start_date and end_date

These visualization tools are particularly useful for:

  • Verifying signal generation logic
  • Understanding position sizing decisions
  • Debugging strategy behavior
  • Analyzing score patterns and transitions
  • Inspecting cash management

Functions

For a list of Scoring related functions see Scoring - Functions.