Relative Vigor Index
Compares closing price to opening price range to measure trend strength.
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 Relative Vigor Index parameters found:
period = 4
model = SimpleMovingAverage
Rating: 0.026502732240437168
Best Indicator values: [-0.03682707097342103, 0.4282213600163352, 0.5730887325585908, ...]
Analysis
The optimized RVI strategy (period=4) significantly outperformed the default configuration (period=10), achieving a total profit of $2.22 versus a loss of $-1.14. The shorter period allowed the optimized strategy to capture more trading opportunities with 47 trades compared to 23 trades for the default settings, resulting in more frequent but smaller position adjustments that better matched the market's momentum shifts. While both strategies ended with open short positions, the optimized parameters demonstrated superior timing and conviction detection, turning price momentum crossovers into profitable entries and exits.
Optimized Trading Simulation
- SideSHORT
- Shares0.0361
- Entry$5572.07
- Value$5638.94
Default Trading Simulation
- SideSHORT
- Shares0.0332
- Entry$5956.06
- Value$5638.94
Trading Simulation Code
For those who want to run their own simulation to compare results.
use centaur_technical_indicators::strength_indicators::bulk::relative_vigor_index;
use centaur_technical_indicators::ConstantModelType;
fn simulate_trading(best_indicator: &[f64], best_period: usize, close: &[f64]) {
println!("
--- Trading Simulation ---");
let initial_capital = 1000.0;
let mut capital = initial_capital;
let investment_pct = 0.20;
struct Position {
entry_price: f64,
shares: f64,
}
let mut open_long: Option<Position> = None;
let mut open_short: Option<Position> = None;
// Print table header
println!(
"{:<5} | {:<19} | {:<10} | {:<10} | {:<12} | {:<15} | {:<10}",
"Day", "Event", "RVI", "Price", "Shares", "Capital", "P/L"
);
println!("{}", "-".repeat(95));
for i in 1..best_indicator.len() {
let price_index = i + best_period;
if price_index >= close.len() {
break;
}
let rvi_current = best_indicator[i];
let rvi_previous = best_indicator[i - 1];
let current_price = close[price_index];
let day = price_index;
// --- Handle Long Position ---
if let Some(long_pos) = open_long.take() {
// Sell signal: RVI crosses below zero
if rvi_current < 0.0 && rvi_previous >= 0.0 {
let sale_value = long_pos.shares * current_price;
let profit = sale_value - (long_pos.shares * long_pos.entry_price);
capital += sale_value;
println!(
"{:<5} | {:<19} | {:<10.4} | ${:<9.2} | {:<12.4} | ${:<14.2} | ${:<9.2}",
day,
"Sell (Close Long)",
rvi_current,
current_price,
long_pos.shares,
capital,
profit
);
} else {
open_long = Some(long_pos); // Put it back if not selling
}
} else if rvi_current > 0.0 && rvi_previous <= 0.0 && open_short.is_none() {
// Buy signal: RVI crosses above zero
let investment = capital * investment_pct;
let shares_bought = investment / current_price;
open_long = Some(Position {
entry_price: current_price,
shares: shares_bought,
});
capital -= investment;
println!(
"{:<5} | {:<19} | {:<10.4} | ${:<9.2} | {:<12.4} | ${:<14.2} | {}",
day,
"Buy (Open Long)",
rvi_current,
current_price,
shares_bought,
capital,
"-"
);
}
// --- Handle Short Position ---
if let Some(short_pos) = open_short.take() {
// Cover signal: RVI crosses above zero
if rvi_current > 0.0 && rvi_previous <= 0.0 {
let cost_to_cover = short_pos.shares * current_price;
let profit = (short_pos.shares * short_pos.entry_price) - cost_to_cover;
capital += profit;
println!(
"{:<5} | {:<19} | {:<10.4} | ${:<9.2} | {:<12.4} | ${:<14.2} | ${:<9.2}",
day,
"Cover (Close Short)",
rvi_current,
current_price,
short_pos.shares,
capital,
profit
);
} else {
open_short = Some(short_pos); // Put it back if not covering
}
} else if rvi_current < 0.0 && rvi_previous >= 0.0 && open_long.is_none() {
// Short signal: RVI crosses below zero
let short_value = capital * investment_pct;
let shares_shorted = short_value / current_price;
open_short = Some(Position {
entry_price: current_price,
shares: shares_shorted,
});
println!(
"{:<5} | {:<19} | {:<10.4} | ${:<9.2} | {:<12.4} | ${:<14.2} | {}",
day,
"Short (Open Short)",
rvi_current,
current_price,
shares_shorted,
capital,
"-"
);
}
}
println!("
--- Final Results ---");
if let Some(pos) = open_long {
println!("Simulation ended with an OPEN LONG position:");
println!(" - Shares: {:.4}", pos.shares);
println!(" - Entry Price: ${:.2}", pos.entry_price);
let last_price = close.last().unwrap_or(&0.0);
let current_value = pos.shares * last_price;
capital += current_value;
println!(
" - Position value at last price (${:.2}): ${:.2}",
last_price, current_value
);
}
if let Some(pos) = open_short {
println!("Simulation ended with an OPEN SHORT position:");
println!(" - Shares: {:.4}", pos.shares);
println!(" - Entry Price: ${:.2}", pos.entry_price);
let last_price = close.last().unwrap_or(&0.0);
let cost_to_cover = pos.shares * last_price;
let pnl = (pos.shares * pos.entry_price) - cost_to_cover;
capital += pnl;
println!(
" - Unrealized P/L at last price (${:.2}): ${:.2}",
last_price, pnl
);
}
let final_pnl = capital - initial_capital;
println!("
Initial Capital: ${:.2}", initial_capital);
println!("Final Capital: ${:.2}", capital);
println!("Total P/L: ${:.2}", final_pnl);
}
fn main() {
// Fetch data and perform optimization as shown in the optimization code above
simulate_trading(&best_indicators, best_period, &close);
// Compare with default parameters (typically period=10, SimpleMovingAverage)
println!("
Default Indicator values for comparison:");
let default_rvi = relative_vigor_index(
&open,
&high,
&low,
&close,
ConstantModelType::SimpleMovingAverage,
10,
);
println!("{:?}", default_rvi);
simulate_trading(&default_rvi, 10, &close);
}