Разработка бота на базе скользящих средних (MA/EMA)
Скользящие средние (Moving Average) — базовый и один из наиболее надёжных инструментов технического анализа. Бот на MA/EMA генерирует сигналы на основе пересечений и положения цены относительно средней. Прост в реализации, понятен в логике, и при правильной параметризации даёт стабильные результаты на трендовых рынках.
MA vs EMA: в чём разница
SMA (Simple MA) — простое среднее за N периодов. Все свечи имеют одинаковый вес.
EMA (Exponential MA) — взвешенное среднее, последние свечи имеют больший вес. Быстрее реагирует на изменения цены.
import pandas as pd
def sma(close: pd.Series, period: int) -> pd.Series:
return close.rolling(period).mean()
def ema(close: pd.Series, period: int) -> pd.Series:
return close.ewm(span=period, adjust=False).mean()
Для трейдинга EMA предпочтительнее: быстрее сигнализирует о разворотах тренда.
Классические стратегии
Golden Cross / Death Cross
Пересечение EMA 50 и EMA 200:
- Golden Cross: EMA50 пересекает EMA200 снизу вверх → бычий сигнал
- Death Cross: EMA50 пересекает EMA200 сверху вниз → медвежий сигнал
class GoldenCrossStrategy:
def generate_signal(self, df: pd.DataFrame) -> str:
ema_fast = ema(df['close'], 50).shift(1)
ema_slow = ema(df['close'], 200).shift(1)
# Пересечение: на предыдущей свече было одно соотношение, на текущей — другое
prev_fast = ema_fast.iloc[-2]
prev_slow = ema_slow.iloc[-2]
curr_fast = ema_fast.iloc[-1]
curr_slow = ema_slow.iloc[-1]
if prev_fast <= prev_slow and curr_fast > curr_slow:
return 'BUY' # Golden Cross
if prev_fast >= prev_slow and curr_fast < curr_slow:
return 'SELL' # Death Cross
return 'HOLD'
Triple EMA strategy (9/21/55)
Три средних дают больше подтверждений:
class TripleEMAStrategy:
def generate_signal(self, df: pd.DataFrame) -> str:
e9 = ema(df['close'], 9).shift(1)
e21 = ema(df['close'], 21).shift(1)
e55 = ema(df['close'], 55).shift(1)
last_9 = e9.iloc[-1]
last_21 = e21.iloc[-1]
last_55 = e55.iloc[-1]
price = df['close'].iloc[-1]
# Бычий сигнал: все EMA выстроены по порядку
if last_9 > last_21 > last_55 and price > last_9:
return 'BUY'
# Медвежий сигнал: обратный порядок
if last_9 < last_21 < last_55 and price < last_9:
return 'SELL'
return 'HOLD'
Реализация бота
class MABot:
def __init__(self, strategy, exchange_client, config):
self.strategy = strategy
self.exchange = exchange_client
self.config = config
self.position = None
self.candles = []
async def on_candle(self, candle: dict):
self.candles.append(candle)
if len(self.candles) > 300:
self.candles = self.candles[-300:] # держим историю
if len(self.candles) < 210: # нужен прогрев для EMA 200
return
df = pd.DataFrame(self.candles)
signal = self.strategy.generate_signal(df)
if signal == 'BUY' and not self.position:
order = await self.exchange.place_market_order(
self.config.symbol, 'buy', self.config.position_size
)
self.position = {'entry': order.fill_price, 'side': 'long'}
elif signal == 'SELL' and self.position and self.position['side'] == 'long':
await self.exchange.place_market_order(
self.config.symbol, 'sell', self.config.position_size
)
pnl = (order.fill_price - self.position['entry']) / self.position['entry'] * 100
logger.info(f"Closed long, PnL: {pnl:.2f}%")
self.position = None
Параметризация и оптимизация
Оптимальные периоды EMA зависят от таймфрейма:
| Таймфрейм | Fast EMA | Slow EMA | Применение |
|---|---|---|---|
| 1h | 9 | 21 | Интрадей |
| 4h | 21 | 55 | Свинг |
| Daily | 50 | 200 | Golden/Death Cross |
| Weekly | 20 | 50 | Долгосрок |
EMA-стратегии работают плохо на боковом рынке — дают много ложных сигналов. Добавьте фильтр ADX (Average Directional Index): торговать только когда ADX > 25 (рынок в тренде). Это снижает количество сделок, но улучшает качество.







