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
- SideSHORT
- Shares0.0345
- Entry$6129.58
- Value$16.94
Default Trading Simulation
- 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);
}