True Strength Index
Dual-smoothed momentum indicator that measures trend direction and overbought/oversold levels.
Export Optimization Code
Advanced Options & Examples
Optimization Output Example
Example output from running the optimization code above on a year of S&P data.
Best TSI parameters found:
First period: 12
Second period: 5
First model: ExponentialMovingAverage
Second model: ExponentialMovingAverage
Oversold threshold: -0.00
Overbought threshold: 28.00
Rating: 0.5402
Analysis
The optimized TSI parameters generate significantly more trading signals (17 trades) compared to the default parameters (3 trades), providing more opportunities to capture short-term price movements. The optimized strategy achieved a total profit of $13.10 with a final capital of $1013.10, slightly outperforming the default strategy's profit of $11.40 and final capital of $1011.40. However, when considering the open positions at the last price, both strategies hold similar position values ($5638.94), meaning the optimized parameters' advantage comes from more frequent trading that accumulated small gains over time. The higher frequency of trades in the optimized strategy suggests it is more sensitive to momentum shifts, though this also means more exposure to transaction costs in real-world trading scenarios.
Optimized Trading Simulation
- SideLONG
- Shares0.0344
- Entry$5955.25
- Value$5638.94
Default Trading Simulation
- SideLONG
- Shares0.0352
- Entry$5770.20
- Value$5638.94
Trading Simulation Code
For those who want to run their own simulation to compare results.
use centaur_technical_indicators::trend_indicators::bulk::true_strength_index;
use centaur_technical_indicators::ConstantModelType;
fn main() {
// Fetch data and perform optimization as shown in the optimization code above
// Run trading simulation with optimized parameters
simulate_trading(
&best_indicators,
best_first_period,
best_second_period,
best_oversold,
best_overbought,
&close,
);
// Compare with default parameters (25 EMA, 13 EMA, -25/+25 thresholds)
println!("
Default Indicator values for comparison:");
let default_tsi = true_strength_index(
&close,
ConstantModelType::ExponentialMovingAverage,
25,
ConstantModelType::ExponentialMovingAverage,
13,
);
simulate_trading(&default_tsi, 25, 13, -25.0, 25.0, &close);
}
fn simulate_trading(
indicators: &[f64],
first_period: usize,
second_period: usize,
oversold: f64,
overbought: f64,
close: &[f64],
) {
println!("
--- Trading Simulation ---");
println!("Using oversold: {:.2}, overbought: {:.2}", oversold, overbought);
let initial_capital = 1000.0;
let mut capital = initial_capital;
let investment_pct = 0.20;
let mut position: Option<(f64, f64)> = None; // (entry_price, shares)
let min_length = first_period + second_period;
println!(
"{:<5} | {:<12} | {:<10} | {:<10} | {:<10} | {:<12} | {:<10}",
"Day", "Event", "TSI", "Price", "Shares", "Capital", "P/L"
);
println!("{}", "-".repeat(80));
for i in 0..indicators.len() {
let price_location = i + min_length;
if price_location >= close.len() {
break;
}
let tsi = indicators[i];
let current_price = close[price_location];
// Sell signal: TSI crosses above overbought threshold
if let Some((entry_price, shares)) = position.take() {
if tsi > overbought {
let sale_value = shares * current_price;
let profit = sale_value - (shares * entry_price);
capital += sale_value;
println!(
"{:<5} | {:<12} | {:<10.4} | ${:<9.2} | {:<10.4} | ${:<11.2} | ${:<9.2}",
price_location, "Sell", tsi, current_price, shares, capital, profit
);
} else {
position = Some((entry_price, shares));
}
}
// Buy signal: TSI crosses below oversold threshold
else if tsi < oversold {
let investment = capital * investment_pct;
let shares = investment / current_price;
position = Some((current_price, shares));
capital -= investment;
println!(
"{:<5} | {:<12} | {:<10.4} | ${:<9.2} | {:<10.4} | ${:<11.2} | {}",
price_location, "Buy", tsi, current_price, shares, capital, "-"
);
}
}
println!("
--- Final Results ---");
if let Some((entry_price, shares)) = position {
let last_price = close.last().unwrap_or(&0.0);
let current_value = shares * last_price;
capital += current_value;
let unrealized_pnl = current_value - (shares * entry_price);
println!("Position still open:");
println!(" Entry: ${:.2}, Current: ${:.2}", entry_price, last_price);
println!(" Unrealized P/L: ${:.2}", unrealized_pnl);
}
let final_pnl = capital - initial_capital;
println!("
Initial Capital: ${:.2}", initial_capital);
println!("Final Capital: ${:.2}", capital);
println!("Total P/L: ${:.2} ({:.2}%)", final_pnl, (final_pnl / initial_capital) * 100.0);
}