Как собрать стек open-source для алготрейдинга под MOEX: от данных до деплоя
“Я не хочу платить 60 тысяч в год за TSLab. Можно ли собрать всё на open-source?”
Можно. И я это сделал.
Последние 4 месяца я собирал стек для алготрейдинга на MOEX полностью из open-source инструментов.
Бесплатно. Независимо. Production-ready.
Вот что получилось.
Зачем нужен свой стек
Год назад я платил:
- TSLab: 60 тыс/год
- MOEX AlgoPack: 55 тыс/год
- VPS для роботов: 12 тыс/год
Итого: 127 тысяч в год.
Потом я задумался: зачем платить за закрытые инструменты, если можно собрать аналог на open-source?
Что я получил в итоге:
- Полный контроль: Я понимаю каждый компонент системы
- Независимость: Не привязан к лицензиям и вендорам
- Гибкость: Могу интегрировать ML, внешние API, криптовалюты
- Стоимость: 0 рублей за софт (только VPS за 12 тыс/год)
Что вошло в стек:
- Данные: MOEX ISS API, Finam Export (бесплатно)
- Backtesting: Backtrader (Python, open-source)
- Live-торговля: StockSharp, Alor API (open-source)
- База данных: TimescaleDB (PostgreSQL для временных рядов)
- Мониторинг: Grafana + Prometheus
- Деплой: Docker + docker-compose
- Очереди: Redis (для кэширования), RabbitMQ (для микросервисов)
Всё бесплатно. Всё работает.
Архитектура системы
Вот схема полного стека:
┌─────────────────────────────────────────────────────────┐
│ ИСТОЧНИКИ ДАННЫХ │
│ MOEX ISS API │ Finam Export │ Alor API │ Tinkoff API │
└──────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ СБОР И ХРАНЕНИЕ ДАННЫХ │
│ Python скрипт (aiomoex, requests) → TimescaleDB │
│ Исторические данные + Real-time стакан │
└──────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ БЭКТЕСТИНГ │
│ Backtrader / VectorBT PRO / StockSharp Designer │
│ Оптимизация параметров, walk-forward тесты │
└──────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ТОРГОВЫЕ РОБОТЫ │
│ Python (Alor API / Tinkoff API / StockSharp Connector) │
│ Логика стратегий, управление позициями, риск-менеджмент │
└──────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ МОНИТОРИНГ И ЛОГИ │
│ Grafana (дашборды) + Prometheus (метрики) │
│ Логи в файлы, alerts в Telegram │
└──────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ДЕПЛОЙ │
│ Docker (контейнеры) + docker-compose (оркестрация) │
│ VPS / собственный сервер │
└─────────────────────────────────────────────────────────┘
Разберём каждый слой.
Слой 1: Источники данных
Я уже писал подробно про данные. Вкратце:
MOEX ISS API (бесплатно)
Официальный API Московской биржи. Даёт:
- Исторические свечи (минутки, часы, дни)
- Стакан (10 уровней)
- Информацию об инструментах
Установка:
pip install aiomoex
Пример (получение свечей):
import aiomoex
import pandas as pd
import asyncio
async def get_candles(ticker, start_date, end_date):
async with aiomoex.ISSClientSession():
data = await aiomoex.get_board_candles(
ticker,
start=start_date,
end=end_date,
interval=60 # 60 минут
)
df = pd.DataFrame(data)
df['begin'] = pd.to_datetime(df['begin'])
return df
# Использование
df = asyncio.run(get_candles('SBER', '2024-01-01', '2025-01-01'))
print(df.head())
Ограничения:
- 10 запросов в секунду (для бесплатного доступа)
- Глубина истории: несколько лет (зависит от инструмента)
Finam Export (бесплатно)
Исторические данные для акций, фьючерсов.
Python библиотека:
pip install finam-export
Пример:
from finam.export import Exporter, Market, Timeframe
exporter = Exporter()
data = exporter.download(
market=Market.SHARES,
ticker='SBER',
start_date='2024-01-01',
end_date='2025-01-01',
timeframe=Timeframe.HOURLY
)
print(data.head())
MOEX AlgoPack (платно, 55 тыс/год)
Глубокий стакан (50 уровней), тиковые данные, все сделки.
pip install moexalgo
Пример:
import moexalgo
# Нужен токен от https://data.moex.com/
session = moexalgo.session(token='ваш_токен')
# Получаем стакан
orderbook = session.orderbook('SBER')
print(orderbook.head())
Моё решение: Для простых стратегий — MOEX ISS + Finam (бесплатно). Для HFT — AlgoPack (платно, но незаменимо).
Слой 2: База данных (TimescaleDB)
Для хранения исторических данных и тиков нужна база.
Почему TimescaleDB?
- Это PostgreSQL + расширение для временных рядов
- Быстрее InfluxDB на вставке данных
- SQL-запросы (знакомый синтаксис)
- Сжатие данных (экономия диска)
Установка (Docker):
# docker-compose.yml
version: '3.8'
services:
timescaledb:
image: timescale/timescaledb:latest-pg15
container_name: timescaledb
environment:
POSTGRES_PASSWORD: your_password
POSTGRES_DB: algotrading
ports:
- "5432:5432"
volumes:
- timescale_data:/var/lib/postgresql/data
volumes:
timescale_data:
Запуск:
docker-compose up -d
Создание таблицы для свечей:
CREATE TABLE candles (
time TIMESTAMPTZ NOT NULL,
ticker TEXT NOT NULL,
open NUMERIC,
high NUMERIC,
low NUMERIC,
close NUMERIC,
volume BIGINT
);
-- Превращаем в hypertable (TimescaleDB)
SELECT create_hypertable('candles', 'time');
-- Индекс для быстрого поиска
CREATE INDEX idx_ticker_time ON candles (ticker, time DESC);
Вставка данных из Python:
import psycopg2
import pandas as pd
conn = psycopg2.connect(
host="localhost",
database="algotrading",
user="postgres",
password="your_password"
)
def save_candles(df, ticker):
cursor = conn.cursor()
for _, row in df.iterrows():
cursor.execute("""
INSERT INTO candles (time, ticker, open, high, low, close, volume)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT DO NOTHING
""", (row['begin'], ticker, row['open'], row['high'],
row['low'], row['close'], row['volume']))
conn.commit()
# Сохраняем свечи SBER
df = asyncio.run(get_candles('SBER', '2024-01-01', '2025-01-01'))
save_candles(df, 'SBER')
Сжатие данных (экономия места):
-- Автоматическое сжатие данных старше 7 дней
SELECT add_compression_policy('candles', INTERVAL '7 days');
Результат: Хранение 5 лет дневных свечей по 100 акциям — ~500 МБ (без сжатия), ~150 МБ (со сжатием).
Слой 3: Бэктестинг (Backtrader)
Backtrader — самый популярный фреймворк для бэктестинга на Python.
Установка:
pip install backtrader
pip install git+https://github.com/WISEPLAT/backtrader_moexalgo # MOEX интеграция
Простая стратегия (SMA-кросс):
import backtrader as bt
class SmaCross(bt.Strategy):
params = (('fast', 20), ('slow', 50),)
def __init__(self):
self.fast_ma = bt.indicators.SMA(self.data.close, period=self.params.fast)
self.slow_ma = bt.indicators.SMA(self.data.close, period=self.params.slow)
self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma)
def next(self):
if self.crossover > 0: # Пересечение снизу вверх
if not self.position:
self.buy(size=10)
elif self.crossover < 0: # Пересечение сверху вниз
if self.position:
self.close()
# Загружаем данные из TimescaleDB
import psycopg2
import pandas as pd
conn = psycopg2.connect(
host="localhost", database="algotrading",
user="postgres", password="your_password"
)
df = pd.read_sql("""
SELECT time, open, high, low, close, volume
FROM candles
WHERE ticker = 'SBER'
ORDER BY time
""", conn, index_col='time', parse_dates=['time'])
# Backtrader Data Feed
data = bt.feeds.PandasData(dataname=df)
cerebro = bt.Cerebro()
cerebro.addstrategy(SmaCross)
cerebro.adddata(data)
cerebro.broker.setcash(1000000)
cerebro.broker.setcommission(commission=0.0005) # 0.05% комиссия
print(f'Начальный капитал: {cerebro.broker.getvalue():.2f}')
cerebro.run()
print(f'Конечный капитал: {cerebro.broker.getvalue():.2f}')
cerebro.plot()
Интеграция с MOEX через backtrader_moexalgo:
from backtrader_moexalgo import MoexAlgoStore
import backtrader as bt
# Подключение к MOEX AlgoPack
store = MoexAlgoStore(token='ваш_токен')
# Создаём datafeed
data = store.getdata(
dataname='SBER',
timeframe=bt.TimeFrame.Minutes,
compression=60, # 60 минут
fromdate=datetime(2024, 1, 1),
todate=datetime(2025, 1, 1)
)
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(SmaCross)
cerebro.run()
Walk-forward оптимизация:
# Оптимизация параметров (перебор)
cerebro.optstrategy(SmaCross, fast=range(10, 30, 5), slow=range(40, 60, 5))
results = cerebro.run()
# Лучшая стратегия
best = max(results, key=lambda x: x[0].broker.getvalue())
print(f'Лучшие параметры: fast={best[0].params.fast}, slow={best[0].params.slow}')
Слой 4: Live-торговля (Alor API, Tinkoff API, StockSharp)
Для боевой торговли нужно подключиться к брокеру.
Вариант 1: Alor API (WebSocket + REST)
Установка:
pip install aiohttp websockets
Пример (получение стакана через WebSocket):
import asyncio
import websockets
import json
TOKEN = "ваш_токен_alor"
async def subscribe_orderbook():
uri = "wss://api.alor.ru/ws"
async with websockets.connect(uri) as ws:
# Авторизация
await ws.send(json.dumps({
"opcode": "authorize",
"token": TOKEN
}))
# Подписка на стакан SBER
await ws.send(json.dumps({
"opcode": "subscribe",
"code": "SBER",
"market": "MOEX",
"depth": 20
}))
# Получаем обновления
while True:
message = await ws.recv()
data = json.loads(message)
if 'bids' in data and 'asks' in data:
print(f"BIDs: {data['bids'][:3]}")
print(f"ASKs: {data['asks'][:3]}")
asyncio.run(subscribe_orderbook())
Отправка заявки:
import requests
url = "https://api.alor.ru/commandapi/warptrans/TRADE/v2/client/orders/actions/market"
headers = {
"Authorization": f"Bearer {TOKEN}",
"X-ALOR-REQID": "unique_request_id"
}
payload = {
"instrument": {
"symbol": "SBER",
"exchange": "MOEX"
},
"side": "buy",
"type": "market",
"quantity": 10,
"user": {
"portfolio": "ваш_портфель"
}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
Вариант 2: Tinkoff Invest API
pip install tinkoff-investments
Пример:
from tinkoff.invest import Client, OrderDirection, OrderType
TOKEN = "ваш_токен_tinkoff"
with Client(TOKEN) as client:
# Получаем FIGI (идентификатор) SBER
instruments = client.instruments.shares()
sber = [i for i in instruments.instruments if i.ticker == "SBER"][0]
# Отправляем рыночную заявку на покупку
order = client.orders.post_order(
figi=sber.figi,
quantity=10,
direction=OrderDirection.ORDER_DIRECTION_BUY,
order_type=OrderType.ORDER_TYPE_MARKET,
account_id="ваш_account_id"
)
print(f"Заявка отправлена: {order.order_id}")
Вариант 3: StockSharp Connector (C#)
StockSharp — open-source платформа на C# с поддержкой 90+ бирж мира (включая MOEX, Binance, Interactive Brokers, Bybit).
Установка через NuGet:
dotnet add package StockSharp.Alor
dotnet add package StockSharp.MOEX
Пример подключения к Alor:
using StockSharp.Alor;
using StockSharp.Messages;
var connector = new AlorTrader();
connector.Login = "ваш_логин";
connector.Password = "ваш_пароль";
connector.Connected += () => {
Console.WriteLine("Подключено к Alor");
var security = new Security { Id = "SBER@TQBR" };
connector.RegisterSecurity(security);
connector.SubscribeMarketDepth(security);
};
connector.NewMarketDepth += depth => {
Console.WriteLine($"BID: {depth.Bids[0].Price}, ASK: {depth.Asks[0].Price}");
};
connector.Connect();
Моё решение:
- Для Python-стратегий: Alor API или Tinkoff API
- Для C# и интеграции со StockSharp Designer: StockSharp Connector
Слой 5: Мониторинг (Grafana + Prometheus)
Когда робот торгует на боевом счёте, нужен мониторинг.
Что отслеживаем:
- PnL (profit and loss) в реальном времени
- Количество открытых позиций
- Просадка (drawdown)
- Ошибки подключения к API
Установка Prometheus + Grafana (Docker):
# docker-compose.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
volumes:
grafana_data:
prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'trading_bot'
static_configs:
- targets: ['host.docker.internal:8000']
Экспорт метрик из Python-робота:
pip install prometheus-client
from prometheus_client import start_http_server, Gauge
import time
# Метрики
pnl_gauge = Gauge('trading_pnl', 'Profit and Loss')
position_gauge = Gauge('trading_position', 'Open Position Size')
drawdown_gauge = Gauge('trading_drawdown', 'Current Drawdown')
# Запускаем HTTP-сервер для Prometheus
start_http_server(8000)
# Обновляем метрики в торговом цикле
while True:
current_pnl = calculate_pnl() # Ваша функция
current_position = get_position_size()
current_drawdown = calculate_drawdown()
pnl_gauge.set(current_pnl)
position_gauge.set(current_position)
drawdown_gauge.set(current_drawdown)
time.sleep(10)
Настройка дашборда в Grafana:
- Открываем http://localhost:3000
- Логин:
admin, пароль:admin - Add Data Source → Prometheus (URL:
http://prometheus:9090) - Создаём дашборд с графиками:
trading_pnl(PnL во времени)trading_position(размер позиции)trading_drawdown(просадка)
Алерты в Telegram:
pip install python-telegram-bot
import telegram
bot = telegram.Bot(token='ваш_токен_telegram')
def send_alert(message):
bot.send_message(chat_id='ваш_chat_id', text=message)
# Пример: отправка алерта при просадке > 10%
if current_drawdown > 0.10:
send_alert(f"⚠️ Просадка превысила 10%: {current_drawdown:.2%}")
Слой 6: Деплой (Docker)
Всё собираем в Docker-контейнеры.
Структура проекта:
algotrading/
├── docker-compose.yml
├── prometheus.yml
├── trading_bot/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── bot.py
│ └── strategies/
│ └── sma_cross.py
├── data_collector/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── collector.py
└── grafana/
└── dashboards/
docker-compose.yml (полный стек):
version: '3.8'
services:
timescaledb:
image: timescale/timescaledb:latest-pg15
container_name: timescaledb
environment:
POSTGRES_PASSWORD: your_password
POSTGRES_DB: algotrading
ports:
- "5432:5432"
volumes:
- timescale_data:/var/lib/postgresql/data
redis:
image: redis:alpine
container_name: redis
ports:
- "6379:6379"
data_collector:
build: ./data_collector
container_name: data_collector
depends_on:
- timescaledb
- redis
environment:
DB_HOST: timescaledb
DB_PASSWORD: your_password
REDIS_HOST: redis
trading_bot:
build: ./trading_bot
container_name: trading_bot
depends_on:
- timescaledb
- redis
environment:
DB_HOST: timescaledb
DB_PASSWORD: your_password
REDIS_HOST: redis
ALOR_TOKEN: ${ALOR_TOKEN}
ports:
- "8000:8000" # Для Prometheus
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
volumes:
- grafana_data:/var/lib/grafana
volumes:
timescale_data:
grafana_data:
Dockerfile для торгового робота:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "bot.py"]
requirements.txt:
backtrader
aiomoex
aiohttp
websockets
psycopg2-binary
redis
prometheus-client
python-telegram-bot
Запуск всего стека:
docker-compose up -d
Проверка:
docker-compose ps # Все сервисы должны быть UP
docker-compose logs trading_bot # Логи робота
Полный пример: SMA-стратегия от бэктеста до live
Шаг 1: Собираем данные
# data_collector/collector.py
import asyncio
import aiomoex
import psycopg2
import pandas as pd
from datetime import datetime, timedelta
async def collect_data(ticker, days=365):
conn = psycopg2.connect(
host="timescaledb",
database="algotrading",
user="postgres",
password="your_password"
)
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
async with aiomoex.ISSClientSession():
data = await aiomoex.get_board_candles(
ticker,
start=start_date.strftime('%Y-%m-%d'),
end=end_date.strftime('%Y-%m-%d'),
interval=60
)
df = pd.DataFrame(data)
cursor = conn.cursor()
for _, row in df.iterrows():
cursor.execute("""
INSERT INTO candles (time, ticker, open, high, low, close, volume)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT DO NOTHING
""", (row['begin'], ticker, row['open'], row['high'],
row['low'], row['close'], row['volume']))
conn.commit()
print(f"Собрано {len(df)} свечей для {ticker}")
# Собираем данные для нескольких акций
asyncio.run(collect_data('SBER'))
asyncio.run(collect_data('GAZP'))
asyncio.run(collect_data('LKOH'))
Шаг 2: Бэктест
# backtest.py
import backtrader as bt
import psycopg2
import pandas as pd
class SmaCross(bt.Strategy):
params = (('fast', 20), ('slow', 50),)
def __init__(self):
self.fast_ma = bt.indicators.SMA(self.data.close, period=self.params.fast)
self.slow_ma = bt.indicators.SMA(self.data.close, period=self.params.slow)
self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma)
def next(self):
if self.crossover > 0:
if not self.position:
self.buy(size=10)
elif self.crossover < 0:
if self.position:
self.close()
# Загружаем данные
conn = psycopg2.connect(
host="localhost", database="algotrading",
user="postgres", password="your_password"
)
df = pd.read_sql("""
SELECT time, open, high, low, close, volume
FROM candles
WHERE ticker = 'SBER' AND time >= '2024-01-01'
ORDER BY time
""", conn, index_col='time', parse_dates=['time'])
data = bt.feeds.PandasData(dataname=df)
cerebro = bt.Cerebro()
cerebro.addstrategy(SmaCross)
cerebro.adddata(data)
cerebro.broker.setcash(1000000)
cerebro.broker.setcommission(commission=0.0005)
print(f'Начальный капитал: {cerebro.broker.getvalue():.2f}')
cerebro.run()
print(f'Конечный капитал: {cerebro.broker.getvalue():.2f}')
Шаг 3: Live-торговля
# trading_bot/bot.py
import asyncio
import websockets
import json
import requests
from collections import deque
from prometheus_client import start_http_server, Gauge
ALOR_TOKEN = "ваш_токен"
pnl_gauge = Gauge('trading_pnl', 'PnL')
class SmaCrossLive:
def __init__(self, ticker, fast=20, slow=50):
self.ticker = ticker
self.fast = fast
self.slow = slow
self.prices = deque(maxlen=slow)
self.position = 0
def on_price(self, price):
self.prices.append(price)
if len(self.prices) < self.slow:
return # Недостаточно данных
fast_ma = sum(list(self.prices)[-self.fast:]) / self.fast
slow_ma = sum(self.prices) / self.slow
# Сигнал на покупку
if fast_ma > slow_ma and self.position == 0:
self.buy()
# Сигнал на продажу
elif fast_ma < slow_ma and self.position > 0:
self.sell()
def buy(self):
url = "https://api.alor.ru/commandapi/warptrans/TRADE/v2/client/orders/actions/market"
payload = {
"instrument": {"symbol": self.ticker, "exchange": "MOEX"},
"side": "buy",
"type": "market",
"quantity": 10,
"user": {"portfolio": "ваш_портфель"}
}
headers = {"Authorization": f"Bearer {ALOR_TOKEN}"}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
self.position = 10
print(f"✅ Куплено {self.ticker}: 10 лотов")
def sell(self):
url = "https://api.alor.ru/commandapi/warptrans/TRADE/v2/client/orders/actions/market"
payload = {
"instrument": {"symbol": self.ticker, "exchange": "MOEX"},
"side": "sell",
"type": "market",
"quantity": 10,
"user": {"portfolio": "ваш_портфель"}
}
headers = {"Authorization": f"Bearer {ALOR_TOKEN}"}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
self.position = 0
print(f"✅ Продано {self.ticker}: 10 лотов")
async def run_bot():
strategy = SmaCrossLive('SBER')
uri = "wss://api.alor.ru/ws"
async with websockets.connect(uri) as ws:
await ws.send(json.dumps({"opcode": "authorize", "token": ALOR_TOKEN}))
await ws.send(json.dumps({
"opcode": "subscribe",
"code": "SBER",
"market": "MOEX",
"depth": 1
}))
while True:
message = await ws.recv()
data = json.loads(message)
if 'asks' in data and len(data['asks']) > 0:
price = data['asks'][0]['price']
strategy.on_price(price)
# Запуск
start_http_server(8000) # Для Prometheus
asyncio.run(run_bot())
Альтернативы и сравнение
VectorBT PRO vs Backtrader
| Критерий | VectorBT PRO | Backtrader |
|---|---|---|
| Скорость | Очень быстрый (NumPy) | Медленнее (Python loop) |
| Гибкость | Ограничена | Полная кастомизация |
| Оптимизация | Встроенная (grid search) | Нужно писать вручную |
| Live-торговля | Через StrateQueue | Нативная поддержка |
| Стоимость | Платная (от $50/мес) | Бесплатная (open-source) |
Моё мнение: Для быстрого бэктеста — VectorBT PRO. Для полного контроля — Backtrader.
StockSharp vs Backtrader
| Критерий | StockSharp | Backtrader |
|---|---|---|
| Язык | C# | Python |
| Экосистема | Меньше библиотек | Огромная (pandas, sklearn, TensorFlow) |
| GUI | Designer (визуальный) | Только код |
| Брокеры | QUIK, Transaq, Alor и т.д. | Нужна интеграция вручную |
Моё мнение: Если любите C# и нужен GUI — StockSharp. Если Python и ML — Backtrader.
Проблемы и решения
Проблема 1: MOEX ISS лимит 10 запросов/сек
Решение: Кэширование через Redis.
import redis
import json
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def get_candles_cached(ticker, start, end):
cache_key = f"{ticker}:{start}:{end}"
cached = r.get(cache_key)
if cached:
return json.loads(cached)
# Запрашиваем с MOEX
data = asyncio.run(get_candles(ticker, start, end))
r.setex(cache_key, 3600, json.dumps(data.to_dict())) # Кэш на 1 час
return data
Проблема 2: Docker контейнеры падают при разрыве соединения
Решение: Restart policy в docker-compose.
services:
trading_bot:
restart: unless-stopped # Автоматический перезапуск
И reconnect логика в коде:
async def run_bot_with_reconnect():
while True:
try:
await run_bot()
except Exception as e:
print(f"❌ Ошибка: {e}. Переподключение через 5 сек...")
await asyncio.sleep(5)
Проблема 3: TimescaleDB занимает много места
Решение: Политика удаления старых данных.
-- Удаляем данные старше 2 лет
SELECT add_retention_policy('candles', INTERVAL '2 years');
Чек-лист: собираем стек с нуля
День 1: Данные и база
- ✅ Установить Docker + docker-compose
- ✅ Запустить TimescaleDB (docker-compose up)
- ✅ Создать таблицу candles
- ✅ Написать скрипт сбора данных (aiomoex → TimescaleDB)
- ✅ Собрать историю по 5-10 инструментам
День 2-3: Бэктестинг
- ✅ Установить Backtrader
- ✅ Написать простую стратегию (SMA-кросс)
- ✅ Протестировать на исторических данных
- ✅ Оптимизировать параметры (optstrategy)
День 4-5: Live-торговля
- ✅ Получить токен от брокера (Alor/Tinkoff)
- ✅ Подключиться к WebSocket (получение стакана)
- ✅ Реализовать отправку заявок
- ✅ Протестировать на демо-счёте
День 6: Мониторинг
- ✅ Запустить Prometheus + Grafana
- ✅ Добавить экспорт метрик из робота
- ✅ Создать дашборд (PnL, позиция, просадка)
- ✅ Настроить алерты в Telegram
День 7: Деплой
- ✅ Упаковать всё в Docker
- ✅ Запустить на VPS
- ✅ Проверить логи и мониторинг
- ✅ Запустить на реальном счёте (с минимальным объёмом)
Итоги
Собрать свой стек для алготрейдинга на MOEX — реально.
Что нужно:
- Python (основной язык)
- TimescaleDB (данные)
- Backtrader (бэктестинг)
- Alor/Tinkoff API (live-торговля)
- Grafana + Prometheus (мониторинг)
- Docker (деплой)
Стоимость: 0 рублей за софт (только VPS ~12 тыс/год).
Время: 7 дней от идеи до боевой торговли (если знаете Python).
Плюсы:
- Полный контроль
- Независимость
- Гибкость (ML, внешние API, криптовалюты)
Минусы:
- Нужно знать программирование
- Нужно поддерживать инфраструктуру
- Нет визуального GUI (всё через код)
Моё мнение:
Если вы программист или готовы учить Python — собирайте свой стек. Это инвестиция в независимость.
Если новичок — начните с конструкторов, затем переходите на open-source.
Полезные ссылки:
Open-source платформы:
Библиотеки для Python:
Инфраструктура:
Статьи и исследования:
Обсуждение
Присоединяйтесь к обсуждению в нашем Telegram-чате!