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