Python Backtesting Setup

Python Backtesting Setup
Setting up the Python environment for algorithmic trading backtests

Ichimoku Trading Series: Part 7 of 10 | ← Previous | View Full Series

Required Libraries

from __future__ import annotations
import warnings
warnings.filterwarnings("ignore", category=FutureWarning)

import numpy as np
import pandas as pd
import pandas_ta as ta        # Technical analysis
import yfinance as yf         # Free market data
from backtesting import Backtest, Strategy

Installation

pip install numpy pandas pandas-ta yfinance backtesting plotly

Fetching Market Data

def fetch_data(symbol: str, start: str, end: str, interval: str) -> pd.DataFrame:
    """
    Fetch OHLCV data from Yahoo Finance.
    
    Parameters:
    - symbol: "EURUSD=X", "AAPL", "BTC-USD", etc.
    - start/end: "2023-01-01" format
    - interval: "1h", "4h", "1d"
    """
    df = yf.download(symbol, start=start, end=end, interval=interval,
                     auto_adjust=True, progress=False, threads=False)
    
    if df.empty:
        raise ValueError(f"No data returned for {symbol}")
    
    # Handle MultiIndex columns (newer yfinance versions)
    if isinstance(df.columns, pd.MultiIndex):
        df = df.xs(symbol, axis=1, level=1)
    
    # Standardize column names
    df.columns = [c.title() for c in df.columns]
    return df.dropna()

Adding Ichimoku Indicators

def add_ichimoku(df: pd.DataFrame,
                 tenkan: int = 9,
                 kijun: int = 26,
                 senkou_b: int = 52) -> pd.DataFrame:
    """
    Build bias-safe Ichimoku columns for SIGNAL logic.
    - Raw spans (no forward shift) to avoid look-ahead bias
    """
    out = df.copy()
    h, l, c = out["High"], out["Low"], out["Close"]
    
    # Compute lines manually (bias-free)
    tenkan_series = (h.rolling(tenkan).max() + l.rolling(tenkan).min()) / 2.0
    kijun_series  = (h.rolling(kijun).max() + l.rolling(kijun).min()) / 2.0
    
    # Raw spans (NO forward shift!)
    span_a_raw = (tenkan_series + kijun_series) / 2.0
    span_b_raw = (h.rolling(senkou_b).max() + l.rolling(senkou_b).min()) / 2.0
    
    out["ich_tenkan"] = tenkan_series
    out["ich_kijun"]  = kijun_series
    out["ich_spanA"]  = span_a_raw
    out["ich_spanB"]  = span_b_raw
    
    # Add ATR for trade management
    out["ATR"] = ta.atr(out["High"], out["Low"], out["Close"], length=14)
    
    # Add EMA for trend filter
    out["EMA"] = ta.ema(out["Close"], length=100)
    
    # Drop warmup NaNs
    return out.dropna()

Putting It Together

# Configuration
SYMBOL   = "EURUSD=X"
START    = "2023-10-01"
END      = "2024-10-01"
INTERVAL = "4h"
CASH     = 100_000
COMMISSION = 0.0002  # 0.02%

# Fetch and prepare data
df = fetch_data(SYMBOL, START, END, INTERVAL)
df = add_ichimoku(df)
df = MovingAverageSignal(df, back_candles=7)
df = createSignals(df, lookback_window=10, min_confirm=7)
df = df.dropna()

print(f"Data shape: {df.shape}")
print(df.tail())

Data Verification

Your DataFrame should now have these columns:

Open, High, Low, Close, Volume,
ich_tenkan, ich_kijun, ich_spanA, ich_spanB,
ATR, EMA, EMA_signal, signal

Coming Up Next: With data flowing, let us build our Strategy class that the backtester will execute. Continue to Part 8 →

CategoriesAI

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.