Moving Constant Bands
Fixed-percentage bands around a moving average for support and resistance 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 Indicator parameters found:
period = 22
model = SimpleMovingMedian
constant_multiplier = 1.88
deviation_model = MeanAbsoluteDeviation
Rating: 0.35457227138643066
Best Indicator values: [(5129.586889256198, 5203.96, 5278.333110743802), (5118.812003305785, 5203.96, 5289.107996694215), (5108.492823140496, 5203.96, 5299.4271768595045), ...]
Analysis
The optimized Moving Constant Bands strategy (22-period Simple Moving Median with 1.88 multiplier and Mean Absolute Deviation) slightly outperformed the default configuration (20-period Simple Moving Average with 2.0 multiplier and Standard Deviation). Starting with $1,000 and investing 20% per trade, the optimized parameters generated a profit of $9.07 (0.91% gain) with 17 trades executed, while the default parameters resulted in a profit of $7.12 (0.71% gain) with 13 trades. The optimized strategy's use of the median instead of mean and MAD instead of standard deviation reduces sensitivity to price outliers, while the slightly tighter bands (1.88 vs 2.0 multiplier) generated more trading signals that captured smaller price movements effectively.
Optimized Trading Simulation
- SideLONG
- Shares0.0345315909273988
- Entry$5955.25
- Value$194.72
Default Trading Simulation
- SideLONG
- Shares0.034186107222101976
- Entry$5955.25
- Value$192.77
Trading Simulation Code
For those who want to run their own simulation to compare results.
use centaur_technical_indicators::candle_indicators::bulk::moving_constant_bands;
use centaur_technical_indicators::{ConstantModelType, DeviationModel};
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_mcb = moving_constant_bands(&close, ConstantModelType::SimpleMovingAverage, DeviationModel::StandardDeviation, 2.0, 20);
println!("{:?}", default_mcb);
chart_simulate_trading(&default_mcb, 20, &close);
}