
Financial News Keeps You Poor. Here's Why.
The scandalous truth: Most market news is designed to inform you about what already happened, not help you profit from what's coming next.
When CNBC reports "Stock XYZ surges 287%"—you missed it.
What you actually need:
Tomorrow's IPO calendar (not yesterday's launches)
Crowdfunding deals opening this week (not closed rounds)
What real traders are positioning for (not TV talking heads)
Economic data that moves markets (before it's released)
The financial media industrial complex profits from keeping you one step behind.
Stocks & Income flips this backwards. We focus entirely on forward-looking intel that helps you get positioned before the crowd, not informed after the move.
Stop chasing trades that happened already.
Start prepping for the next one.
Stocks & Income is for informational purposes only and is not intended to be used as investment advice. Do your own research.
🚀 Your Investing Journey Just Got Better: Premium Subscriptions Are Here! 🚀
It’s been 4 months since we launched our premium subscription plans at GuruFinance Insights, and the results have been phenomenal! Now, we’re making it even better for you to take your investing game to the next level. Whether you’re just starting out or you’re a seasoned trader, our updated plans are designed to give you the tools, insights, and support you need to succeed.
Here’s what you’ll get as a premium member:
Exclusive Trading Strategies: Unlock proven methods to maximize your returns.
In-Depth Research Analysis: Stay ahead with insights from the latest market trends.
Ad-Free Experience: Focus on what matters most—your investments.
Monthly AMA Sessions: Get your questions answered by top industry experts.
Coding Tutorials: Learn how to automate your trading strategies like a pro.
Masterclasses & One-on-One Consultations: Elevate your skills with personalized guidance.
Our three tailored plans—Starter Investor, Pro Trader, and Elite Investor—are designed to fit your unique needs and goals. Whether you’re looking for foundational tools or advanced strategies, we’ve got you covered.
Don’t wait any longer to transform your investment strategy. The last 4 months have shown just how powerful these tools can be—now it’s your turn to experience the difference.

MSFT Stock Chart With Simulated Annealing Optimized Kaufman Adaptive Moving Average Trading Strategy
Introduction
Moving averages are a staple in trading, but standard simple or exponential moving averages often lag behind the market. They can generate late signals that make you miss profitable trades or get stuck in sideways noise.
Enter the Kaufman Adaptive Moving Average (KAMA), an indicator that adjusts its sensitivity depending on market conditions. KAMA reacts quickly to strong trends while filtering out noise in sideways markets.
In this article, I’ll show how to implement a KAMA-based trading strategy in Python, optimize its parameters using Simulated Annealing, and evaluate its performance using train-test splits and visualizations.
Understanding the Kaufman Adaptive Moving Average (KAMA)
KAMA was developed by Perry Kaufman to solve a key problem:
Slow moving averages filter out noise but react too late, while fast moving averages react quickly but produce many false signals.
KAMA adapts based on the Efficiency Ratio (ER), which measures trend strength:
ER approximately 1: strong trend, KAMA reacts faster
ER approximately 0: sideways market, KAMA slows down
The smoothing constant (SC) is dynamically adjusted using ER, allowing KAMA to follow meaningful price movements while reducing whipsaws.
By using KAMA instead of a regular moving average, you can generate more timely buy and sell signals and capture trend opportunities with less noise.
Kickstart your holiday campaigns
CTV should be central to any growth marketer’s Q4 strategy. And with Roku Ads Manager, launching high-performing holiday campaigns is simple and effective.
With our intuitive interface, you can set up A/B tests to dial in the most effective messages and offers, then drive direct on-screen purchases via the remote with shoppable Action Ads that integrate with your Shopify store for a seamless checkout experience.
Don’t wait to get started. Streaming on Roku picks up sharply in early October. By launching your campaign now, you can capture early shopping demand and be top of mind as the seasonal spirit kicks in.
Get a $500 ad credit when you spend your first $500 today with code: ROKUADS500. Terms apply.
Simulated Annealing for Strategy Optimization
Choosing the right KAMA parameters (n
, fast
, slow
) is critical. Rather than manually testing combinations, we can use Simulated Annealing (SA), a probabilistic optimization technique inspired by the annealing process in metallurgy.
SA explores the parameter space while occasionally accepting worse solutions to avoid local maxima, gradually cooling toward a solution that maximizes our objective.
In our case, the objective is the total return on the training period.
Code Implementation
The github repo is available only for paid subs. Upgrade here
We’ll now implement the strategy step by step in Python. Each code block includes explanations of what we are doing.
1. Install necessary packages
We need yfinance
for stock data, pandas
/numpy
for data manipulation, matplotlib
for visualization, and tabulate
for nicely formatted outputs.
%pip install yfinance matplotlib pandas numpy tabulate -q
2. Import libraries
Here, we import all required libraries, including Python’s built-in modules for randomization, math, and timing, which we’ll use for Simulated Annealing.
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random, math, time
from tabulate import tabulate
3. Download historical stock data
We’ll use Microsoft (MSFT
) as an example. The data is downloaded from 2020 to 2024 and only includes OHLCV columns.
symbol = "MSFT"
data = yf.download(symbol, start="2020-01-01", end="2024-12-31")
data.columns = data.columns.get_level_values(0)
data = data[['Open','High','Low','Close','Volume']]
data.head()
This gives us a clean DataFrame with dates as the index and the price data needed for KAMA calculations.
4. Split into train and test sets
We separate the data to test the strategy’s ability to generalize. Training is 2020–2023, and 2024 is reserved for testing.
train = data.loc[:'2023-12-31'].copy()
test = data.loc['2024-01-01':].copy()
print("Train period:", train.index.min().date(), "to", train.index.max().date())
print("Test period:", test.index.min().date(), "to", test.index.max().date())
print("Train rows:", len(train), "Test rows:", len(test))
Train period: 2020-01-02 to 2023-12-29
Test period: 2024-01-02 to 2024-12-30
Train rows: 1006 Test rows: 251
This ensures our optimization only uses past data, mimicking real-world trading scenarios.
200+ AI Side Hustles to Start Right Now
AI isn't just changing business—it's creating entirely new income opportunities. The Hustle's guide features 200+ ways to make money with AI, from beginner-friendly gigs to advanced ventures. Each comes with realistic income projections and resource requirements. Join 1.5M professionals getting daily insights on emerging tech and business opportunities.
5. Define KAMA calculation function
We implement KAMA in Python. The function calculates the adaptive moving average based on n
, fast
, and slow
parameters.
def kama(prices, n=10, fast=2, slow=30):
prices = prices.copy().astype(float)
kama = pd.Series(index=prices.index, dtype=float)
# seed initial value as rolling mean for first n points
if len(prices) < n:
raise ValueError("Price series shorter than n")
kama.iloc[:n] = prices.iloc[:n].rolling(window=n, min_periods=1).mean()
# smoothing constants (convert to smoothing constants inside loop using fast/slow periods)
fast_sc_base = 2 / (fast + 1)
slow_sc_base = 2 / (slow + 1)
for i in range(n, len(prices)):
change = abs(prices.iloc[i] - prices.iloc[i - n])
volatility = prices.iloc[i - n + 1:i + 1].diff().abs().sum()
ER = change / volatility if volatility != 0 else 0.0
SC = (ER * (fast_sc_base - slow_sc_base) + slow_sc_base) ** 2
kama.iloc[i] = kama.iloc[i - 1] + SC * (prices.iloc[i] - kama.iloc[i - 1])
return kama
This allows dynamic adaptation to trends while ignoring sideways noise.
6. Define backtesting function
This function calculates signals, positions, equity, and key metrics such as total return, max drawdown, Sharpe ratio, and number of trades.
def backtest_with_kama(df, n=10, fast=2, slow=30, initial_balance=10000.0):
df = df.copy()
df['KAMA'] = kama(df['Close'], n=n, fast=fast, slow=slow)
# generate signals safely
df['Signal'] = 0
mask = df.index[1:]
df.loc[mask, 'Signal'] = np.where(df['Close'].iloc[1:] > df['KAMA'].iloc[1:], 1, 0)
df['Position'] = df['Signal'].diff().fillna(0)
balance = initial_balance
position = 0.0
equity_curve = []
cash = balance
for i in range(len(df)):
price = df['Close'].iloc[i]
pos_change = df['Position'].iloc[i]
# buy
if pos_change == 1 and position == 0:
position = cash / price
cash = 0.0
# sell
elif pos_change == -1 and position > 0:
cash = position * price
position = 0.0
equity_curve.append(cash + position * price)
df['Equity'] = equity_curve
final_value = df['Equity'].iloc[-1]
total_return = (final_value - initial_balance) / initial_balance * 100.0
# max drawdown
cummax = df['Equity'].cummax()
drawdown = (df['Equity'] - cummax) / cummax
max_drawdown = drawdown.min() * 100.0
# daily returns for Sharpe-like metric (use daily returns of equity)
daily_returns = pd.Series(df['Equity']).pct_change().replace([np.inf, -np.inf], 0).fillna(0)
if daily_returns.std() != 0:
sharpe = (daily_returns.mean() / daily_returns.std()) * np.sqrt(252)
else:
sharpe = 0.0
metrics = {
'final_value': final_value,
'total_return_pct': total_return,
'max_drawdown_pct': max_drawdown,
'sharpe': sharpe,
'trades': int((df['Position'].abs() == 1).sum())
}
return df, metrics
7. Baseline backtest with default parameters
Before we optimize the strategy, we first run it using standard KAMA parameters (n=10
, fast=2
, slow=30
) to establish a baseline performance. This gives us a reference point to compare against the results after optimization.
default_params = {'n':10, 'fast':2, 'slow':30}
train_df, train_metrics = backtest_with_kama(train, **default_params)
test_df, test_metrics = backtest_with_kama(test, **default_params)
print("Baseline (default params) — Train metrics")
print(tabulate(train_metrics.items(), tablefmt="rounded_outline"))
print("\nBaseline (default params) — Test metrics")
print(tabulate(test_metrics.items(), tablefmt="rounded_outline"))
Baseline (default params) — Train metrics
╭──────────────────┬──────────────╮
│ final_value │ 12314.3 │
│ total_return_pct │ 23.1428 │
│ max_drawdown_pct │ -39.8458 │
│ sharpe │ 0.358648 │
│ trades │ 147 │
╰──────────────────┴──────────────╯
Baseline (default params) — Test metrics
╭──────────────────┬──────────────╮
│ final_value │ 10119.5 │
│ total_return_pct │ 1.19544 │
│ max_drawdown_pct │ -11.9838 │
│ sharpe │ 0.155035 │
│ trades │ 38 │
╰──────────────────┴──────────────╯
Looking at the results, the baseline strategy achieves a modest 23% return on the training period, but the test period barely grows, with only around 1% total return.
The maximum drawdown during training is significant (approximately 40%), suggesting the strategy experiences large intra-period dips even with default parameters.
The Sharpe ratios are low, reflecting limited risk-adjusted performance.
Overall, the default KAMA settings act as a neutral starting point: they demonstrate the strategy’s potential but clearly leave room for improvement through parameter optimization.
8. Visualize KAMA and train-test split
We plot the stock price and KAMA with a vertical line marking the train-test boundary.
plt.figure(figsize=(14,6))
plt.plot(data.index, data['Close'], label='Close', color='black', alpha=0.6)
full_kama = kama(data['Close'], **default_params)
plt.plot(data.index, full_kama, label='KAMA (default)', color='blue', linewidth=1.3)
plt.axvline(train.index.max(), color='gray', linestyle='--', label='Train/Test split')
plt.title(f"{symbol} Close and KAMA (default params) — Train/Test split")
plt.xlabel("Date")
plt.ylabel("Price")
plt.legend()
plt.savefig("kama_default.png", dpi=300)
plt.show()

MSFT Stock Chart with Default KAMA Parameters: Train/Test Split
9. Simulated Annealing: neighbor and optimization functions
We define a small neighbor perturbation and the main SA loop to explore parameter combinations.
def random_neighbor(params):
# propose small integer perturbation within bounds
n, fast, slow = params
n = int(np.clip(n + random.choice([-2,-1,0,1,2]), 5, 30))
fast = int(np.clip(fast + random.choice([-1,0,1]), 2, 8))
slow = int(np.clip(slow + random.choice([-3,-2,-1,0,1,2,3]), 10, 60))
# ensure logical relation: fast < slow
if fast >= slow:
fast = max(2, slow - 1)
return (n, fast, slow)
def sa_optimize(data_train, init_params=(10,2,30), initial_temp=5.0, cooling_rate=0.95, iterations=200):
current = init_params
_, metrics = backtest_with_kama(data_train, n=current[0], fast=current[1], slow=current[2])
current_score = metrics['total_return_pct'] # maximize this
best = current
best_score = current_score
trace = []
temp = initial_temp
for i in range(iterations):
candidate = random_neighbor(current)
_, cand_metrics = backtest_with_kama(data_train, n=candidate[0], fast=candidate[1], slow=candidate[2])
cand_score = cand_metrics['total_return_pct']
delta = cand_score - current_score
# acceptance rule
if delta > 0 or math.exp(delta / max(temp, 1e-9)) > random.random():
current = candidate
current_score = cand_score
if cand_score > best_score:
best = candidate
best_score = cand_score
trace.append({'iter': i, 'temp': temp, 'current_score': current_score, 'best_score': best_score, 'params': current})
temp *= cooling_rate
return best, best_score, trace
10. Run Simulated Annealing to find optimized parameters
This produces the parameter combination that maximizes total return on the training set.
random.seed(42)
np.random.seed(42)
start_time = time.time()
init = (10,2,30)
best_params, best_score, trace = sa_optimize(train, init_params=init, initial_temp=5.0, cooling_rate=0.95, iterations=300)
elapsed = time.time() - start_time
print("SA finished in {:.1f}s".format(elapsed))
print("Best params found (n, fast, slow):", best_params)
print("Best training total return (%):", round(best_score,2))
SA finished in 173.8s
Best params found (n, fast, slow): (12, 3, 29)
Best training total return (%): 64.96
The optimization finds a parameter set (12, 3, 29)
that significantly improves the training return to nearly 65%, showing that tuning KAMA can dramatically enhance performance over the default settings.
11. Evaluate optimized strategy
We backtest with optimized parameters and compare baseline vs optimized performance.
opt_params_dict = {'n': best_params[0], 'fast': best_params[1], 'slow': best_params[2]}
train_opt_df, train_opt_metrics = backtest_with_kama(train, **opt_params_dict)
test_opt_df, test_opt_metrics = backtest_with_kama(test, **opt_params_dict)
print("Optimized — Train metrics")
print(tabulate(train_opt_metrics.items(), tablefmt="rounded_outline"))
print("\nOptimized — Test metrics")
print(tabulate(test_opt_metrics.items(), tablefmt="rounded_outline"))
print("\nBaseline vs Optimized (Test Total Return %):")
print(f"Baseline test return: {test_metrics['total_return_pct']:.2f}%")
print(f"Optimized test return: {test_opt_metrics['total_return_pct']:.2f}%")
Optimized — Train metrics
╭──────────────────┬──────────────╮
│ final_value │ 16496.4 │
│ total_return_pct │ 64.964 │
│ max_drawdown_pct │ -33.6871 │
│ sharpe │ 0.726949 │
│ trades │ 91 │
╰──────────────────┴──────────────╯
Optimized — Test metrics
╭──────────────────┬──────────────╮
│ final_value │ 10195.7 │
│ total_return_pct │ 1.95663 │
│ max_drawdown_pct │ -14.5546 │
│ sharpe │ 0.203949 │
│ trades │ 26 │
╰──────────────────┴──────────────╯
Baseline vs Optimized (Test Total Return %):
Baseline test return: 1.20%
Optimized test return: 1.96%
On the training set, the optimized parameters yield a total return of nearly 65%, a substantial improvement over the baseline 23%, while also reducing drawdowns slightly and improving the Sharpe ratio. This indicates that tuning KAMA can significantly enhance trend capture during periods where strong signals exist.
On the test set (2024), the improvement is more modest, baseline return was about 1.2%, while optimized parameters increase it to around 1.96%.
This small gain highlights the inherent challenge of optimizing for out-of-sample performance, especially in relatively flat or low-volatility periods. Still, the optimized KAMA provides a measurable improvement with fewer trades, suggesting more efficient signal generation.
12. Plot equity curve comparison
plt.figure(figsize=(14,6))
# baseline test equity (recompute baseline on test to ensure same df)
baseline_test_df, _ = backtest_with_kama(test, **default_params)
plt.plot(baseline_test_df.index, baseline_test_df['Equity'], label='Baseline (test)', color='gray', linewidth=1.5, alpha=0.8)
plt.plot(test_opt_df.index, test_opt_df['Equity'], label='Optimized (test)', color='green', linewidth=2)
plt.title(f"{symbol} Equity Curve — Baseline vs Optimized (Test)")
plt.xlabel("Date")
plt.ylabel("Equity ($)")
plt.legend()
plt.savefig("equity_comparison.png", dpi=300)
plt.show()

Equity Curve: Baseline vs Optimized (Test)
13. Plot optimized trades
plt.figure(figsize=(14,6))
plt.plot(test_opt_df.index, test_opt_df['Close'], label='Price', color='black', linewidth=1.2)
plt.plot(test_opt_df.index, test_opt_df['KAMA'], label='KAMA (opt)', color='blue', linewidth=1.0)
buy_pts = test_opt_df[test_opt_df['Position'] == 1]
sell_pts = test_opt_df[test_opt_df['Position'] == -1]
plt.scatter(buy_pts.index, buy_pts['Close'], marker='^', color='lime', s=90, label='Buy', zorder=5)
plt.scatter(sell_pts.index, sell_pts['Close'], marker='v', color='red', s=90, label='Sell', zorder=5)
plt.title(f"{symbol} Price + KAMA + Trades (Optimized) — Test Period")
plt.xlabel("Date")
plt.ylabel("Price")
plt.legend()
plt.savefig("kama_optimized_trades.png", dpi=300)
plt.show()

Buy and Sell Signals
14. Summary of results
print("Summary")
print("Default params (test):")
print(f" Return: {test_metrics['total_return_pct']:.2f}% Drawdown: {test_metrics['max_drawdown_pct']:.2f}% Sharpe: {test_metrics['sharpe']:.2f}")
print("\nOptimized params (test):")
print(f" Params: {best_params}")
print(f" Return: {test_opt_metrics['total_return_pct']:.2f}% Drawdown: {test_opt_metrics['max_drawdown_pct']:.2f}% Sharpe: {test_opt_metrics['sharpe']:.2f}")
Summary
Default params (test):
Return: 1.20% Drawdown: -11.98% Sharpe: 0.16
Optimized params (test):
Params: (12, 3, 29)
Return: 1.96% Drawdown: -14.55% Sharpe: 0.20
The summary highlights that the optimized KAMA parameters provide a clear improvement over the baseline.
Total return on the test period increases from 1.2% to nearly 2%, with a slight improvement in the Sharpe ratio.
Drawdown is marginally higher, but the overall efficiency and signal quality are better, confirming the value of parameter optimization even in challenging market conditions.
The Kaufman Adaptive Moving Average adapts to market trends more effectively than standard moving averages.
Using Simulated Annealing, we can efficiently optimize strategy parameters without brute-force searches.
Splitting data into train and test sets ensures realistic performance evaluation and guards against overfitting.
Visualizing equity curves and trades helps make strategy behavior intuitive.
With just a few lines of Python, you can build, optimize, and analyze a smarter trend-following strategy.
Subscribe to our premium content to get the full code snippet.
Become a paying subscriber to get access to this post and other subscriber-only content.
Upgrade