Moving Constant Envelopes
Creates upper and lower envelopes at fixed distances from a moving average.
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 Indicator parameters found:
best_period = 23
model = SimpleMovingMedian
difference = 2.3
Rating: 0.356842105263158
Best Indicator values: [(5083.89766, 5203.58, 5323.262339999999), (5083.89766, 5203.58, 5323.262339999999), (5083.89766, 5203.58, 5323.262339999999), ...]
Analysis
The optimized Moving Constant Envelopes strategy (23-period Simple Moving Median with 2.3 difference) significantly outperformed the default configuration (10-period Simple Moving Average with 2.0 difference) in a backtested trading simulation. Starting with $1,000 and investing 20% per trade, the optimized parameters generated a profit of $20.70 (2.07% gain) with 15 trades executed, while the default parameters resulted in a loss of $26.03 (-2.60% loss) with only 3 trades. The optimized strategy's use of the median instead of mean reduces sensitivity to price outliers, and the wider envelope bands (2.3 vs 2.0) with a longer period (23 vs 10) provided more reliable signals that better captured meaningful trend reversals rather than minor price fluctuations.
Optimized Trading Simulation
- SideLONG
- Shares0.03515072466512056
- Entry$5849.72
- Value$198.21
Default Trading Simulation
- SideLONG
- Shares0.0335414626154579
- Entry$5849.72
- Value$189.14
Trading Simulation Code
For those who want to run their own simulation to compare results.
use centaur_technical_indicators::chart_trends::{peaks, valleys};
use centaur_technical_indicators::candle_indicators::bulk::moving_constant_envelopes;
use centaur_technical_indicators::ConstantModelType::SimpleMovingAverage;
fn chart_simulate_trading(best_indicator: &[(f64, f64, 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", "Indicator", "Price", "Shares", "Capital", "P/L");
println!("{}", "-".repeat(95));
for i in 0..best_indicator.len() {
let price_index = i + best_period + 1;
if price_index >= close.len() { break; }
let indicator_overbought = best_indicator[i].2;
let indicator_oversold = best_indicator[i].0;
let current_price = close[price_index];
let day = price_index;
// --- Handle Long Position ---
if let Some(long_pos) = open_long.take() {
if current_price > indicator_overbought as f64 {
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.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | ${:<9.2}",
day, "Sell (Close Long)", indicator_overbought, current_price, long_pos.shares, capital, profit);
} else {
open_long = Some(long_pos); // Put it back if not selling
}
} else if current_price < indicator_oversold as f64 && open_short.is_none() { // Don't buy if short is open
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.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | {}",
day, "Buy (Open Long)", indicator_oversold, current_price, shares_bought, capital, "-");
}
// --- Handle Short Position ---
if let Some(short_pos) = open_short.take() {
if current_price < indicator_oversold as f64 {
let cost_to_cover = short_pos.shares * current_price;
let profit = (short_pos.shares * short_pos.entry_price) - cost_to_cover;
capital += profit; // Add profit to capital
println!("{:<5} | {:<19} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | ${:<9.2}",
day, "Cover (Close Short)", indicator_oversold, current_price, short_pos.shares, capital, profit);
} else {
open_short = Some(short_pos); // Put it back if not covering
}
} else if current_price > indicator_overbought as f64 && open_long.is_none() { // Don't short if long is open
let short_value = capital * investment_pct;
let shares_shorted = short_value / current_price;
open_short = Some(Position { entry_price: current_price, shares: shares_shorted });
// Capital doesn't change when opening a short, it's held as collateral
println!("{:<5} | {:<19} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | {}",
day, "Short (Open Short)", indicator_overbought, 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);
println!("{{ position = "LONG", shares = {}, entry_price = "${:.2}", position_value_at_last_price = "${:.2}" }}", pos.shares, pos.entry_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);
println!("{{ position = "SHORT", shares = {}, entry_price = "${:.2}", position_value_at_last_price = "${:.2}" }}", pos.shares, pos.entry_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
chart_simulate_trading(&best_indicators, best_period, &close);
println!("
Default Indicator values for comparison:");
let default_dc = moving_constant_envelopes(&close, ConstantModelType::SimpleMovingAverage, 2.0, 10);
println!("{:?}", default_dc);
chart_simulate_trading(&default_dc, 10, &close);
}