Ichimoku Cloud

Comprehensive trend system providing support/resistance levels, momentum signals, and trend direction through five interrelated lines.

About the Ichimoku Cloud

The Ichimoku Cloud (Ichimoku Kinko Hyo, meaning "one glance equilibrium chart") is a comprehensive trend-following system developed by Japanese journalist Goichi Hosoda in the late 1930s and published in 1969. It provides a complete picture of market conditions in a single view by combining five interrelated lines that convey support and resistance levels, trend direction, and momentum signals simultaneously.

Unlike single-line indicators, the Ichimoku Cloud plots all components directly on the price chart, making it immediately clear whether price is above or below the cloud (bullish or bearish territory) and how the conversion and base lines relate to one another. The "cloud" (Kumo) formed between Senkou Span A and Senkou Span B acts as a dynamic support/resistance zone, with a thicker cloud indicating stronger support or resistance.

What It Measures

The Ichimoku Cloud measures trend direction and strength through five components: the Tenkan-sen (conversion line) tracks short-term price midpoints, the Kijun-sen (base line) tracks medium-term midpoints, Senkou Span A and B form the forward-projected cloud, and the Chikou Span (lagging span) plots the closing price shifted back in time. Together, these components reveal the balance between buyers and sellers across multiple time horizons.

When to Use

  • Trend confirmation: Price above the cloud signals a bullish trend; price below signals a bearish trend
  • Support and resistance: The upper and lower cloud boundaries act as dynamic support/resistance levels

Interpretation

  • Price above cloud: Strong bullish signal; cloud acts as support below
  • Price below cloud: Strong bearish signal; cloud acts as resistance above
  • Price inside cloud: Consolidation or transitional phase; reduced signal reliability
  • Chikou Span above price: Confirms bullish momentum; below price confirms bearish momentum

Example Usage

use centaur_technical_indicators::candle_indicators::bulk::ichimoku_cloud;

pub fn main() {
    // fetch the data in your preferred way
    // let highs = vec![...];   // high prices
    // let lows = vec![...];    // low prices
    // let close = vec![...];   // closing prices

    let ichimoku = ichimoku_cloud(&highs, &lows, &close, 9, 26, 52)
        .expect("Failed to calculate Ichimoku Cloud");
    // Returns Vec<(f64, f64, f64, f64, f64)> where each tuple is:
    // (conversion_line, base_line, leading_span_a, leading_span_b, lagging_span)
    println!("{:?}", ichimoku);
}
import centaur_technical_indicators as cti

# fetch the data in your preferred way
# highs = [...]   # high prices
# lows = [...]    # low prices
# close = [...]   # closing prices

ichimoku = cti.candle_indicators.bulk.ichimoku_cloud(highs, lows, close, conversion_period=9, base_period=26, span_b_period=52)
# Returns list of tuples (conversion_line, base_line, leading_span_a, leading_span_b, lagging_span)
print(ichimoku)
import init, {
    candle_bulk_ichimokuCloud
} from 'https://cdn.jsdelivr.net/npm/centaur-technical-indicators@latest/dist/web/centaur-technical-indicators.js';

await init();

// fetch the data in your preferred way
// const highs = [...];   // high prices
// const lows = [...];    // low prices
// const close = [...];   // closing prices

const ichimoku = candle_bulk_ichimokuCloud(highs, lows, close, 9, 26, 52);
// Returns array of arrays [[conversion_line, base_line, leading_span_a, leading_span_b, lagging_span], ...]
console.log(ichimoku);

Optimization

Even if the defaults have stood the test of time, tuning them to your specific asset and timeframe gives you signals that fit your market. Below shows you how to achieve this.

Optimization Code

use centaur_technical_indicators::candle_indicators::bulk::ichimoku_cloud;
use centaur_technical_indicators::chart_trends::{peaks, valleys};

fn proximity_rating(fuzzed_location: &usize, price_location: &usize) -> f64 {
    1.0 / (*fuzzed_location as f64 - *price_location as f64).abs()
}

pub fn main() {
    // fetch the data in your preferred way
    // let high = vec![...];
    // let low = vec![...];
    // let close = vec![...];

    let sell_points = peaks(&close, 20, 5)
        .expect("Failed to calculate peaks")
        .into_iter()
        .map(|(_, i)| i)
        .collect::<Vec<usize>>();
    let buy_points = valleys(&close, 20, 5)
        .expect("Failed to calculate valleys")
        .into_iter()
        .map(|(_, i)| i)
        .collect::<Vec<usize>>();

    let fuzz_parameter = 5;
    let mut best_rating = f64::NEG_INFINITY;
    let mut best_conversion_period = 0;
    let mut best_base_period = 0;
    let mut best_span_b_period = 0;
    let mut best_indicators = vec![];

    for conversion_period in 2..=30_usize {
        for base_period in 2..=60_usize {
            if base_period <= conversion_period { continue; }
            for span_b_period in 2..=120_usize {
                if span_b_period <= base_period { continue; }
                if close.len() < span_b_period + 1 { continue; }

                let indicators = ichimoku_cloud(&high, &low, &close, conversion_period, base_period, span_b_period)
                    .expect("Failed to calculate Ichimoku Cloud");

                let mut rating = vec![];
                let mut matched_sell = vec![];
                let mut matched_buy = vec![];

                for i in 1..indicators.len() {
                    let price_location = i + base_period - 1;
                    if price_location >= close.len() { break; }

                    // Each tuple: (conversion_line/tenkan, base_line/kijun, span_a, span_b, chikou)
                    let (tenkan, kijun, _, _, _) = indicators[i];
                    let (prev_tenkan, prev_kijun, _, _, _) = indicators[i - 1];

                    if tenkan < kijun && prev_tenkan >= prev_kijun {
                        // Bearish crossover: Tenkan crosses below Kijun
                        if sell_points.contains(&price_location) {
                            rating.push(1.0);
                            matched_sell.push(price_location);
                        } else if buy_points.contains(&price_location) {
                            rating.push(-1.0);
                        } else {
                            let mut found_sell = false;
                            for fuzzed in price_location.saturating_sub(fuzz_parameter)..=price_location + fuzz_parameter {
                                if sell_points.contains(&fuzzed) {
                                    rating.push(proximity_rating(&fuzzed, &price_location));
                                    matched_sell.push(fuzzed);
                                    found_sell = true;
                                }
                                if buy_points.contains(&fuzzed) && !matched_sell.contains(&fuzzed) {
                                    rating.push(-proximity_rating(&fuzzed, &price_location));
                                }
                            }
                            if !found_sell { rating.push(0.0); }
                        }
                    } else if tenkan > kijun && prev_tenkan <= prev_kijun {
                        // Bullish crossover: Tenkan crosses above Kijun
                        if buy_points.contains(&price_location) {
                            rating.push(1.0);
                            matched_buy.push(price_location);
                        } else if sell_points.contains(&price_location) {
                            rating.push(-1.0);
                        } else {
                            let mut found_buy = false;
                            for fuzzed in price_location.saturating_sub(fuzz_parameter)..=price_location + fuzz_parameter {
                                if buy_points.contains(&fuzzed) {
                                    rating.push(proximity_rating(&fuzzed, &price_location));
                                    matched_buy.push(fuzzed);
                                    found_buy = true;
                                }
                                if sell_points.contains(&fuzzed) && !matched_buy.contains(&fuzzed) {
                                    rating.push(-proximity_rating(&fuzzed, &price_location));
                                }
                            }
                            if !found_buy { rating.push(0.0); }
                        }
                    }
                }

                for missed in sell_points.iter() { if !matched_sell.contains(missed) { rating.push(-1.0); } }
                for missed in buy_points.iter()  { if !matched_buy.contains(missed)  { rating.push(-1.0); } }

                if !rating.is_empty() {
                    let total: f64 = rating.iter().sum::<f64>() / rating.len() as f64;
                    if total > best_rating {
                        best_rating = total;
                        best_conversion_period = conversion_period;
                        best_base_period = base_period;
                        best_span_b_period = span_b_period;
                        best_indicators = indicators.clone();
                    }
                }
            }
        }
    }

    println!("Best conversion_period: {}", best_conversion_period);
    println!("Best base_period: {}", best_base_period);
    println!("Best span_b_period: {}", best_span_b_period);
    println!("Rating: {:.4}", best_rating);
}

Optimization Output

Example output from running the optimization code above on a year of S&P data.

Best Ichimoku Cloud parameters found:
Conversion Period (Tenkan-sen): 5
Base Period (Kijun-sen): 6
Span B Period (Senkou Span B): 12
Rating: 0.2828

Trading Simulation

In order to determine whether the optimized parameters beat the defaults, a trading simulation was run, below are the results.

Optimized Trading Simulation

Initial Investment
$1000.00
Final Capital
$1075.13
Total P&L
$75.13
Open Position
  • SideSHORT
  • Shares0.0345
  • Entry$6129.58
  • Value$16.94

Default Trading Simulation

Initial Investment
$1000.00
Final Capital
$1011.15
Total P&L
$11.15
Open Position
  • SideSHORT
  • Shares0.0327
  • Entry$6101.24
  • Value$15.09

Analysis

The optimized Ichimoku Cloud parameters (conversion=5, base=6, span_b=12) produce significantly more trading signals than the traditional settings (9, 26, 52), generating 23 trades versus only 5 trades over the same period.

Both strategies use Tenkan-sen/Kijun-sen crossovers with 20% capital allocation per trade. Starting from $1,000, the optimized strategy achieved a final capital of $1,075.13 (P/L: +$75.13 with an open short position worth +$16.94 in unrealized profit), while the default parameters reached $1,011.15 (P/L: +$11.15 with an open short worth +$15.09 in unrealized profit).

The optimized parameters substantially outperform the defaults by being more responsive to short-term price movements, capturing more frequent but smaller crossover signals. The shorter periods reduce the traditional lag of the Ichimoku system, resulting in earlier entries and exits at the cost of increased trade frequency.

Trading Simulation Code

For those who want to run their own simulation to compare results.

use centaur_technical_indicators::candle_indicators::bulk::ichimoku_cloud;

fn simulate_trading(
    indicators: &[(f64, f64, f64, f64, f64)],
    base_period: usize,
    close: &[f64],
) {
    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;

    println!("{:<5} | {:<19} | {:<10} | {:<10} | {:<10} | {:<12} | {:<15} | {:<10}",
        "Day", "Event", "Tenkan", "Kijun", "Price", "Shares", "Capital", "P/L");

    for i in 1..indicators.len() {
        let price_index = i + base_period - 1;
        if price_index >= close.len() { break; }

        let (tenkan, kijun, _, _, _) = indicators[i];
        let (prev_tenkan, prev_kijun, _, _, _) = indicators[i - 1];
        let price = close[price_index];

        if let Some(pos) = open_long.take() {
            if tenkan < kijun && prev_tenkan >= prev_kijun {
                let profit = pos.shares * (price - pos.entry_price);
                capital += pos.shares * price;
                println!("{:<5} | Sell (Close Long)    | {:<10.2} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | ${:.2}", i + base_period - 1, tenkan, kijun, price, pos.shares, capital, profit);
            } else { open_long = Some(pos); }
        } else if tenkan > kijun && prev_tenkan <= prev_kijun && open_short.is_none() {
            let shares = (capital * investment_pct) / price;
            capital -= shares * price;
            open_long = Some(Position { entry_price: price, shares });
            println!("{:<5} | Buy (Open Long)      | {:<10.2} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | -", i + base_period - 1, tenkan, kijun, price, shares, capital);
        }

        if let Some(pos) = open_short.take() {
            if tenkan > kijun && prev_tenkan <= prev_kijun {
                let profit = pos.shares * (pos.entry_price - price);
                capital += profit;
                println!("{:<5} | Cover (Close Short) | {:<10.2} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | ${:.2}", i + base_period - 1, tenkan, kijun, price, pos.shares, capital, profit);
            } else { open_short = Some(pos); }
        } else if tenkan < kijun && prev_tenkan >= prev_kijun && open_long.is_none() {
            let shares = (capital * investment_pct) / price;
            open_short = Some(Position { entry_price: price, shares });
            println!("{:<5} | Short (Open Short)  | {:<10.2} | {:<10.2} | ${:<9.2} | {:<12.4} | ${:<14.2} | -", i + base_period - 1, tenkan, kijun, price, shares, capital);
        }
    }
}

fn main() {
    // fetch the data (highs, lows, close)

    // Optimized parameters
    let optimized = ichimoku_cloud(&highs, &lows, &close, 5, 6, 12)
        .expect("Failed to calculate Ichimoku Cloud");
    simulate_trading(&optimized, 6, &close);

    // Default parameters
    let default = ichimoku_cloud(&highs, &lows, &close, 9, 26, 52)
        .expect("Failed to calculate Ichimoku Cloud");
    simulate_trading(&default, 26, &close);
}