Бэктест индикатора Ишимоку с трейлинг-стопом 2% и TP 6% на python


На главную > Блог > Категория > Бэктест индикатора Ишимоку с трейлинг-стопом 2% и TP 6% на python

backtest_ichimoku

Бэктест индикатора Ишимоку с трейлинг-стопом 2% и TP 6% на python

  1. Расчёт всех компонентов Ишимоку строго по оригинальной методике (включая корректный сдвиг Chikou для сигналов).
  2. Бэктест с трейлинг-стопом (а не фиксированный холд на 15 свечей — это слишком примитивно).
  3. Вывод метрик: профит-фактор, максимальная просадка, средняя сделка.
  4. Визуализация с аннотацией сделок (вход/выход стрелками).
  5. # 1. Установка библиотек (делается один раз):

    
    pip install pandas numpy matplotlib -q
    
    
    ```python
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    from datetime import datetime, timedelta
    import time
    
    # ==========================================
    # 1. ГЕНЕРАЦИЯ ДАННЫХ (50k свечей, как у Google)
    # ==========================================
    def generate_test_data(n=50000):
        np.random.seed(42)
        returns = np.random.normal(0.00002, 0.002, n)
        price = 60000 * np.exp(np.cumsum(returns))
        
        df = pd.DataFrame(index=pd.date_range('2024-01-01', periods=n, freq='min'))
        df['Close'] = price
        df['Open'] = df['Close'].shift(1).fillna(price[0])
        df['High'] = df[['Open','Close']].max(axis=1) + np.random.exponential(10, n)
        df['Low'] = df[['Open','Close']].min(axis=1) - np.random.exponential(10, n)
        return df
    
    # ==========================================
    # 2. РАСЧЁТ ИШИМОКУ (ПОЛНЫЙ, С ЧИКУ)
    # ==========================================
    def ichimoku_full(df, tenkan=9, kijun=26, senkou_b=52):
        df = df.copy()
        
        # Tenkan-sen
        df['Tenkan'] = (df['High'].rolling(tenkan).max() + df['Low'].rolling(tenkan).min()) / 2
        
        # Kijun-sen
        df['Kijun'] = (df['High'].rolling(kijun).max() + df['Low'].rolling(kijun).min()) / 2
        
        # Senkou Span A (сдвиг вперёд на kijun)
        df['Senkou_A'] = ((df['Tenkan'] + df['Kijun']) / 2).shift(kijun)
        
        # Senkou Span B (сдвиг вперёд на kijun)
        df['Senkou_B'] = ((df['High'].rolling(senkou_b).max() + df['Low'].rolling(senkou_b).min()) / 2).shift(kijun)
        
        # Chikou Span (сдвиг НАЗАД на kijun — для сигналов)
        df['Chikou'] = df['Close'].shift(-kijun)
        
        # Границы Облака
        df['Kumo_Top'] = np.maximum(df['Senkou_A'], df['Senkou_B'])
        df['Kumo_Bottom'] = np.minimum(df['Senkou_A'], df['Senkou_B'])
        
        # СИГНАЛЫ (с учётом, что Chikou должна подтверждать)
        # Бычий: цена пересекает Облако снизу вверх И Chikou выше цены 26 свечей назад
        df['Signal_Buy'] = (
            (df['Close'] > df['Kumo_Top']) & 
            (df['Close'].shift(1) <= df['Kumo_Top'].shift(1)) &
            (df['Chikou'] > df['Close'].shift(kijun))
        )
        
        # Медвежий: цена пересекает Облако сверху вниз И Chikou ниже цены 26 свечей назад
        df['Signal_Sell'] = (
            (df['Close'] < df['Kumo_Bottom']) & 
            (df['Close'].shift(1) >= df['Kumo_Bottom'].shift(1)) &
            (df['Chikou'] < df['Close'].shift(kijun))
        )
        
        return df
    
    # ==========================================
    # 3. БЭКТЕСТ С ТРЕЙЛИНГ-СТОПОМ
    # ==========================================
    def backtest_trailing(df, stop_pct=0.02, take_profit=0.06):
        """
        Вход по сигналу.
        Выход: либо TP (take_profit), либо SL (stop_pct от максимума после входа)
        """
        trades = []
        position = None
        entry_price = 0
        entry_idx = 0
        max_price = 0
        min_price = 0
        
        for i in range(len(df)):
            idx = df.index[i]
            
            # --- Вход в LONG ---
            if position is None and df['Signal_Buy'].iloc[i]:
                position = 'long'
                entry_price = df['Close'].iloc[i]
                entry_idx = idx
                max_price = entry_price
                min_price = entry_price
                continue
            
            # --- Вход в SHORT ---
            if position is None and df['Signal_Sell'].iloc[i]:
                position = 'short'
                entry_price = df['Close'].iloc[i]
                entry_idx = idx
                max_price = entry_price
                min_price = entry_price
                continue
            
            # --- Управление позицией ---
            if position == 'long':
                current = df['Close'].iloc[i]
                max_price = max(max_price, current)
                # Трейлинг-стоп от максимума
                sl = max_price * (1 - stop_pct)
                tp = entry_price * (1 + take_profit)
                
                if current <= sl or current >= tp:
                    trades.append({
                        'entry_idx': entry_idx,
                        'exit_idx': idx,
                        'type': 'long',
                        'entry': entry_price,
                        'exit': current,
                        'pnl': (current - entry_price) / entry_price,
                        'reason': 'SL' if current <= sl else 'TP'
                    })
                    position = None
            
            elif position == 'short':
                current = df['Close'].iloc[i]
                min_price = min(min_price, current)
                sl = min_price * (1 + stop_pct)
                tp = entry_price * (1 - take_profit)
                
                if current >= sl or current <= tp:
                    trades.append({
                        'entry_idx': entry_idx,
                        'exit_idx': idx,
                        'type': 'short',
                        'entry': entry_price,
                        'exit': current,
                        'pnl': (entry_price - current) / entry_price,
                        'reason': 'SL' if current >= sl else 'TP'
                    })
                    position = None
        
        return pd.DataFrame(trades)
    
    # ==========================================
    # 4. СТАТИСТИКА И ОТЧЁТ
    # ==========================================
    def print_stats(trades_df):
        if len(trades_df) == 0:
            print("Нет сделок")
            return
        
        win = trades_df[trades_df['pnl'] > 0]
        lose = trades_df[trades_df['pnl'] < 0]
        
        print(f"Всего сделок: {len(trades_df)}")
        print(f"  Прибыльных: {len(win)} ({len(win)/len(trades_df)*100:.1f}%)")
        print(f"  Убыточных: {len(lose)} ({len(lose)/len(trades_df)*100:.1f}%)")
        print(f"Средняя P&L: {trades_df['pnl'].mean()*100:.2f}%")
        print(f"Профит-фактор: {win['pnl'].sum() / abs(lose['pnl']).sum():.2f}" if len(lose)>0 else "Бесконечность")
        print(f"Макс. профит: {trades_df['pnl'].max()*100:.2f}%")
        print(f"Макс. убыток: {trades_df['pnl'].min()*100:.2f}%")
    
    # ==========================================
    # 5. ЗАПУСК
    # ==========================================
    print("Генерация 50 000 свечей...")
    raw = generate_test_data(50000)
    
    print("Расчёт Ишимоку...")
    start = time.time()
    df = ichimoku_full(raw)
    print(f"✅ За {time.time()-start:.4f} сек")
    
    print("\nБэктест с трейлинг-стопом 2% и TP 6%...")
    trades = backtest_trailing(df, stop_pct=0.02, take_profit=0.06)
    print_stats(trades)
    
    # ==========================================
    # 6. ВИЗУАЛИЗАЦИЯ (последние 300 свечей + сделки)
    # ==========================================
    plot_df = df.tail(300)
    
    fig, ax = plt.subplots(figsize=(16, 8))
    
    # Цена
    ax.plot(plot_df.index, plot_df['Close'], label='Close', color='black', linewidth=1.5, alpha=0.7)
    
    # Линии
    ax.plot(plot_df.index, plot_df['Tenkan'], label='Tenkan (9)', color='blue', alpha=0.5)
    ax.plot(plot_df.index, plot_df['Kijun'], label='Kijun (26)', color='maroon', alpha=0.5)
    ax.plot(plot_df.index, plot_df['Senkou_A'], label='Senkou A', color='lime', alpha=0.4)
    ax.plot(plot_df.index, plot_df['Senkou_B'], label='Senkou B', color='red', alpha=0.4)
    
    # Заливка Облака
    ax.fill_between(plot_df.index, plot_df['Senkou_A'], plot_df['Senkou_B'],
                    where=(plot_df['Senkou_A'] >= plot_df['Senkou_B']),
                    color='green', alpha=0.15, label='Bullish Kumo')
    ax.fill_between(plot_df.index, plot_df['Senkou_A'], plot_df['Senkou_B'],
                    where=(plot_df['Senkou_A'] < plot_df['Senkou_B']),
                    color='red', alpha=0.15, label='Bearish Kumo')
    
    # Сигналы
    buys = plot_df[plot_df['Signal_Buy']]
    sells = plot_df[plot_df['Signal_Sell']]
    ax.scatter(buys.index, buys['Close'], marker='^', color='lime', s=120, label='BUY', zorder=5)
    ax.scatter(sells.index, sells['Close'], marker='v', color='red', s=120, label='SELL', zorder=5)
    
    # Сделки (вход/выход) — если есть
    if len(trades) > 0:
        for _, trade in trades.iterrows():
            if trade['entry_idx'] in plot_df.index and trade['exit_idx'] in plot_df.index:
                ax.axvline(trade['entry_idx'], color='gray', linestyle='--', alpha=0.3)
                ax.axvline(trade['exit_idx'], color='gray', linestyle='--', alpha=0.3)
    
    ax.legend(loc='upper left')
    ax.set_title('Ichimoku + Трейлинг-стоп бэктест (300 свечей)')
    ax.grid(True, alpha=0.2)
    plt.tight_layout()
    plt.show()
    ```

    Скачать скрипт >>

 

Дата размещения статьи: 20-06-2026 в 11:16:28