
You're overpaying for crypto.
Every exchange has different prices for the same crypto. Most people stick with one and pay whatever it costs.
CoW Swap checks them all automatically. Finds the best price. Executes your trade. Takes 30 seconds.
Stop leaving money on the table.
Elite Quant Plan – 14-Day Free Trial (This Week Only)
No card needed. Cancel anytime. Zero risk.
You get immediate access to:
Full code from every article (including today’s HMM notebook)
Private GitHub repos & templates
All premium deep dives (3–5 per month)
2 × 1-on-1 calls with me
One custom bot built/fixed for you
Try the entire Elite experience for 14 days — completely free.
→ Start your free trial now 👇
(Doors close in 7 days or when the post goes out of the spotlight — whichever comes first.)
See you on the inside.
👉 Upgrade Now →
🔔 Limited-Time Holiday Deal: 20% Off Our Complete 2026 Playbook! 🔔
Level up before the year ends!
AlgoEdge Insights: 30+ Python-Powered Trading Strategies – The Complete 2026 Playbook
30+ battle-tested algorithmic trading strategies from the AlgoEdge Insights newsletter – fully coded in Python, backtested, and ready to deploy. Your full arsenal for dominating 2026 markets.
Special Promo: Use code DECEMBER2025 for 20% off
Valid only until December 20, 2025 — act fast!
👇 Buy Now & Save 👇
Instant access to every strategy we've shared, plus exclusive extras.
— AlgoEdge Insights Team
Premium Members – Your Full Notebook Is Ready
The complete Google Colab notebook from today’s article (with live data, full Hidden Markov Model, interactive charts, statistics, and one-click CSV export) is waiting for you.
Preview of what you’ll get:

Inside:
Define the Ichimoku strategy and backtest logic
Run Bayesian Optimization
Apply optimized parameters to train and test data
Beautiful interactive Plotly charts
Regime duration & performance tables
Ready-to-use CSV export
Bonus: works on Bitcoin, SPX, or any ticker with one line change
Free readers – you already got the full breakdown and visuals in the article. Paid members – you get the actual tool.
Not upgraded yet? Fix that in 10 seconds here👇
Google Collab Notebook With Full Code Is Available In the End Of The Article Behind The Paywall 👇 (For Paid Subs Only)

Ichimoku Cloud with optimized parameters and executed trade signals.
In a previous article — I Optimized the Ichimoku Cloud Trading Strategy with Bayesian Optimization from 32% to 168% Returns — I showed how Bayesian Optimization could dramatically improve the performance of a classic Ichimoku Cloud trading strategy.
The results looked impressive. Too impressive.
So I decided to run the only test that really matters:
Can this optimized strategy beat simply buying the market and doing nothing?
This article documents that experiment end-to-end, using strict out-of-sample testing and a brutally simple benchmark. Every code cell below is shown exactly as used, with explanations of intent before each one.
Methodology at a glance
Asset: SPY (S&P 500)
Training data: 2000–2023
Testing data (out-of-sample): 2024 onward
Strategy: Optimized Ichimoku Cloud with Bayesian Optimization
Benchmark: Buy & Hold
Starting capital: $10,000
No re-optimization on test data
Code implementation
Install required packages
We begin by installing all dependencies used for data acquisition, optimization, backtesting, and visualization.
# Install necessary packages
%pip install yfinance matplotlib tabulate scikit-optimize -qStop Drowning In AI Information Overload
Your inbox is flooded with newsletters. Your feed is chaos. Somewhere in that noise are the insights that could transform your work—but who has time to find them?
The Deep View solves this. We read everything, analyze what matters, and deliver only the intelligence you need. No duplicate stories, no filler content, no wasted time. Just the essential AI developments that impact your industry, explained clearly and concisely.
Replace hours of scattered reading with five focused minutes. While others scramble to keep up, you'll stay ahead of developments that matter. 600,000+ professionals at top companies have already made this switch.
Import libraries
These libraries cover market data, numerical computation, plotting, and Bayesian optimization.
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
from tabulate import tabulate
from skopt import gp_minimize
from skopt.space import Integer
import numpy as npDownload historical price data
We fetch SPY data from 2000 onward to capture multiple market regimes.
# Download historical stock data
symbol = "SPY"
data = yf.download(symbol, start="2000-01-01")
# Flatten multi-level columns
data.columns = data.columns.get_level_values(0)
data.head()Visualize the raw price series
Before modeling anything, it helps to visually inspect the underlying asset.
# Plot raw closing price
plt.figure(figsize=(14,6))
plt.plot(data.index, data['Close'], color='black', linewidth=1.5, label=f"{symbol} Closing Price")
plt.title(f"{symbol} Closing Price (2020-2024)")
plt.xlabel("Date")
plt.ylabel("Price ($)")
plt.legend()
plt.savefig(f"figures/{symbol}_closing_price.png", dpi=300)
plt.show()
SPY closing price from 2000 onward, showing multiple bull and bear cycles.
Split data into training and testing sets
The model is optimized only on historical data and evaluated strictly out-of-sample.
# Split into training and testing sets
train = data.loc[:'2023-12-31'].copy() # Training: 2000–2023
test = data.loc['2024-01-01':].copy() # Testing: 2024-current onlyDefine the Ichimoku strategy and backtest logic
This function calculates Ichimoku components, generates trading signals, executes trades, and tracks portfolio equity.
def ichimoku_signals(data, tenkan_period, kijun_period, senkou_b_period):
df = data.copy()
high = df['High']
low = df['Low']
close = df['Close']
# Ichimoku lines
df['tenkan'] = (high.rolling(tenkan_period).max() + low.rolling(tenkan_period).min()) / 2
df['kijun'] = (high.rolling(kijun_period).max() + low.rolling(kijun_period).min()) / 2
df['senkou_a'] = ((df['tenkan'] + df['kijun']) / 2).shift(kijun_period)
df['senkou_b'] = ((high.rolling(senkou_b_period).max() + low.rolling(senkou_b_period).min()) / 2).shift(kijun_period)
# Signals
df['above_cloud'] = df['Close'] > df[['senkou_a', 'senkou_b']].max(axis=1)
df['tenkan_cross'] = (df['tenkan'] > df['kijun']) & (df['tenkan'].shift(1) <= df['kijun'].shift(1))
df['signal'] = np.where(df['above_cloud'] & df['tenkan_cross'], 1, 0)
# Backtest
balance = 10000
position = 0
equity_curve = []
for i in range(len(df)):
if df['signal'].iloc[i] == 1 and position == 0:
position = balance / df['Close'].iloc[i]
balance = 0
elif position > 0 and df['Close'].iloc[i] < df[['senkou_a','senkou_b']].min(axis=1).iloc[i]:
balance = position * df['Close'].iloc[i]
position = 0
equity_curve.append(balance + position * df['Close'].iloc[i])
df['equity'] = equity_curve
df['daily_return'] = df['equity'].pct_change().fillna(0)
final_equity = df['equity'].iloc[-1]
total_return = (final_equity - 10000) / 10000 * 100
return df, total_return, final_equityDefine the optimization objective
The optimizer maximizes total return on the training dataset only.
# Use training data only
def objective(params):
tenkan, kijun, senkou_b = params
_, total_return, _ = ichimoku_signals(train, tenkan, kijun, senkou_b) # uses train data
return -total_return # gp_minimize minimizesMost coverage tells you what happened. Fintech Takes is the free newsletter that tells you why it matters. Each week, I break down the trends, deals, and regulatory shifts shaping the industry — minus the spin. Clear analysis, smart context, and a little humor so you actually enjoy reading it. Subscribe free.
Run Bayesian Optimization
We search across reasonable Ichimoku parameter ranges.
search_space = [
Integer(5, 20, name='tenkan_period'), # Tenkan period
Integer(20, 50, name='kijun_period'), # Kijun period
Integer(40, 100, name='senkou_b_period') # Senkou B period
]
result = gp_minimize(objective, search_space, n_calls=25, random_state=42)Apply optimized parameters to train and test data
Once optimized, the parameters are frozen and applied out-of-sample.
# Apply optimized params to both train and test sets
best_tenkan, best_kijun, best_senkou_b = result.x
best_train_return = -result.fun
# Apply to test (out-of-sample)
train_result, train_return, train_final_equity = ichimoku_signals(
train.copy(), best_tenkan, best_kijun, best_senkou_b
)
test_result, test_return, test_final_equity = ichimoku_signals(
test.copy(), best_tenkan, best_kijun, best_senkou_b
)
# Calculate max drawdown on test
max_equity = test_result['equity'].cummax()
drawdown = (test_result['equity'] - max_equity) / max_equity
max_drawdown = drawdown.min() * 100
stats = [
["Optimized Tenkan Period", best_tenkan],
["Optimized Kijun Period", best_kijun],
["Optimized Senkou B Period", best_senkou_b],
["Training Return", f"{train_return:.1f}%"],
["Testing Return", f"{test_return:.1f}%"],
["Max Drawdown (Test)", f"{max_drawdown:.1f}%"]
]
print(tabulate(stats, headers=["Metric", "Value"], tablefmt="rounded_outline"))Optimized Ichimoku parameters and in-sample vs out-of-sample performance.
╭───────────────────────────┬─────────╮
│ Metric │ Value │
├───────────────────────────┼─────────┤
│ Optimized Tenkan Period │ 20 │
│ Optimized Kijun Period │ 27 │
│ Optimized Senkou B Period │ 100 │
│ Training Return │ 288.3% │
│ Testing Return │ 17.5% │
│ Max Drawdown (Test) │ -10.1% │
╰───────────────────────────┴─────────╯Implement buy & hold benchmark
This benchmark represents full exposure with zero decision-making.
# Buy & Hold strategy (out-of-sample period only)
buy_hold = test.copy()
initial_capital = 10000
shares = initial_capital / buy_hold['Close'].iloc[0]
buy_hold['equity'] = shares * buy_hold['Close']
buy_hold['daily_return'] = buy_hold['equity'].pct_change().fillna(0)
bh_final_equity = buy_hold['equity'].iloc[-1]
bh_total_return = (bh_final_equity - initial_capital) / initial_capital * 100Compare drawdowns
Risk matters as much as return.
def max_drawdown(equity):
peak = equity.cummax()
drawdown = (equity - peak) / peak
return drawdown.min() * 100
ichimoku_dd = max_drawdown(test_result['equity'])
buy_hold_dd = max_drawdown(buy_hold['equity'])Side-by-side performance comparison
comparison = [
["Total Return (Test)", f"{test_return:.1f}%", f"{bh_total_return:.1f}%"],
["Final Equity", f"${test_result['equity'].iloc[-1]:,.0f}", f"${bh_final_equity:,.0f}"],
["Max Drawdown", f"{ichimoku_dd:.1f}%", f"{buy_hold_dd:.1f}%"],
]
print(tabulate(
comparison,
headers=["Metric", "Optimized Ichimoku", "Buy & Hold"],
tablefmt="rounded_outline"
))Out-of-sample performance comparison between optimized Ichimoku and buy & hold.
╭─────────────────────┬──────────────────────┬──────────────╮
│ Metric │ Optimized Ichimoku │ Buy & Hold │
├─────────────────────┼──────────────────────┼──────────────┤
│ Total Return (Test) │ 17.5% │ 49.7% │
│ Final Equity │ $11,745 │ $14,973 │
│ Max Drawdown │ -10.1% │ -18.8% │
╰─────────────────────┴──────────────────────┴──────────────╯Plot equity curves
This chart makes the trade-off visually obvious.
plt.figure(figsize=(14,7))
plt.plot(test_result.index, test_result['equity'], label="Optimized Ichimoku", linewidth=2)
plt.plot(buy_hold.index, buy_hold['equity'], label="Buy & Hold", linewidth=2, linestyle="--")
plt.title(f"{symbol}: Optimized Ichimoku vs Buy & Hold (Out-of-Sample)")
plt.xlabel("Date")
plt.ylabel("Portfolio Value ($)")
plt.legend()
plt.grid(alpha=0.3)
plt.savefig(f"figures/{symbol}_ichimoku_vs_buy_hold.png", dpi=300)
plt.show()
Equity curves showing smoother risk control vs higher absolute returns.
Visualize Ichimoku signals on price
Finally, we inspect how the strategy behaves on price itself.
data_optimized = test_result
buy_points = []
sell_points = []
position = 0
for i in range(len(data_optimized)):
if data_optimized['signal'].iloc[i] == 1 and position == 0:
buy_points.append((data_optimized.index[i], data_optimized['Close'].iloc[i]))
position = 1
elif position == 1 and data_optimized['Close'].iloc[i] < data_optimized[['senkou_a','senkou_b']].min(axis=1).iloc[i]:
sell_points.append((data_optimized.index[i], data_optimized['Close'].iloc[i]))
position = 0
buy_df = pd.DataFrame(buy_points, columns=["Date","Price"]).set_index("Date")
sell_df = pd.DataFrame(sell_points, columns=["Date","Price"]).set_index("Date")
plt.figure(figsize=(14,8))
plt.plot(data_optimized.index, data_optimized['Close'], label="Close", color='black', linewidth=1)
plt.plot(data_optimized.index, data_optimized['tenkan'], label="Tenkan (Conversion)", color='blue', linewidth=1.2)
plt.plot(data_optimized.index, data_optimized['kijun'], label="Kijun (Base)", color='red', linewidth=1.2)
plt.fill_between(
data_optimized.index, data_optimized['senkou_a'], data_optimized['senkou_b'],
where=data_optimized['senkou_a'] >= data_optimized['senkou_b'],
color='lightgreen', alpha=0.4
)
plt.fill_between(
data_optimized.index, data_optimized['senkou_a'], data_optimized['senkou_b'],
where=data_optimized['senkou_a'] < data_optimized['senkou_b'],
color='lightcoral', alpha=0.4
)
plt.scatter(buy_df.index, buy_df['Price'], marker="^", color="lime", s=100, label="Buy Signal", zorder=5)
plt.scatter(sell_df.index, sell_df['Price'], marker="v", color="red", s=100, label="Sell Signal", zorder=5)
plt.title(f"{symbol} Ichimoku Cloud (Out-of-Sample, 2024)")
plt.legend(loc="upper left")
plt.savefig(f"figures/{symbol}_ichimoku_out_of_sample.png", dpi=300)
plt.show()
Ichimoku Cloud with optimized parameters and executed trade signals.
Final takeaway
The optimized Ichimoku strategy worked — just not better than doing nothing. On the out-of-sample period in 2024, the strategy returned 17.5%, with a maximum drawdown of -10.1%. In comparison, a simple buy-and-hold approach returned 49.7% over the same period, with a larger drawdown of -18.8%.
This shows that while the optimized strategy reduced risk and smoothed the equity curve, it underperformed in absolute returns during a strong market year. The Ichimoku approach avoided volatility and behaved exactly as a trend-following system should, but full market exposure beat the carefully optimized signals by more than 30 percentage points.
Sometimes, the most sophisticated strategy loses to patience and simplicity, and seeing these numbers side by side makes that point undeniable. This is why out-of-sample testing against real-world benchmarks is invaluable, and why even advanced technical methods must be measured against the simplest baseline: doing nothing.
Subscribe to our premium content to read the rest.
Become a paying subscriber to get access to this post and other subscriber-only content.
Upgrade






