Smart Trade (Germany)
SaaS-автоматизация торговли. Backend на Django + 30 REST endpoints, конструктор стратегий на React.
Метрики
Контекст
Smart Trade — немецкий SaaS для retail-трейдеров, которые хотят автоматизировать стратегии на Interactive Brokers, Binance, Bitfinex без написания кода. Аудитория — те же люди, что покупают TradingView Premium за $2K: технически любопытные, но не программисты.
Когда я пришёл, настройка стратегии занимала 2 часа: вложенные YAML, три разных страницы, ручная настройка webhook-ов. Drop-off после регистрации был жёстким.
Что я сделал
- Спроектировал и выпустил Django + DRF backend с 30+ REST-эндпоинтами: стратегии, бэктесты, история сигналов, брокер-коннекторы, биллинг.
- Сделал React + Redux drag-and-drop конструктор стратегий. Юзеры собирают стратегии визуально из блоков (условие входа → сайзинг → правило выхода). Время настройки упало с 2 часов до 15 минут — измерено в воронке онбординга.
- Интегрировал брокеров: Interactive Brokers (FIX gateway), Binance, Bitfinex. Нормализовал схемы ордеров/позиций/балансов между венчами, чтобы конструктор не зависел от выбора брокера.
- Вёл E2E-фреймворк на Selenium: page-object pattern, параллельный запуск через Selenium Grid в Docker, результаты как JUnit XML в Jenkins. Регрессионные баги в проде упали примерно на 50% за 6 месяцев.
- Был единственным Python-разработчиком в Agile-команде из 8 человек. Тесно работал с двумя фронтендерами и product owner-ом; участвовал в planning, demo, retro.
Технические решения
Валидация стратегий — на backend, не в React-конструкторе. Фронт отправляет непрозрачный граф стратегии; backend прогоняет через детерминированный валидатор перед сохранением. Source of truth — на сервере, и валидатор можно эволюционировать без релиза фронта.
Бэктесты — отдельным worker-пулом, никогда в web-процессе. Даже маленький бэктест может занять 30 секунд — в синхронном HTTP это выглядело бы как сломанный API.
Selenium page-objects с explicit waits, не implicit. Implicit прячут flakiness; explicit делают failure mode понятным («кнопка не стала кликабельной» — реальный сигнал).
Из кода
class StrategyValidator:
def validate(self, graph: StrategyGraph) -> ValidationResult:
issues = []
if not graph.entry_blocks:
issues.append("strategy needs at least one entry condition")
if not graph.exit_blocks:
issues.append("strategy needs at least one exit rule")
for block in graph.position_sizing_blocks:
if block.risk_per_trade_pct > 10:
issues.append(
f"{block.id}: risk-per-trade {block.risk_per_trade_pct}% "
f"exceeds safety limit of 10%"
)
# Reject orphan blocks: every block must be reachable from entry.
unreachable = self._find_unreachable(graph)
for block_id in unreachable:
issues.append(f"block {block_id} is unreachable from entry")
return ValidationResult(ok=not issues, issues=issues)
class StrategyBuilderPage(BasePage):
URL = "/builder"
ADD_BLOCK_BUTTON = (By.CSS_SELECTOR, "[data-test='add-block']")
SAVE_BUTTON = (By.CSS_SELECTOR, "[data-test='save-strategy']")
def add_entry_block(self, indicator: str, threshold: float) -> "StrategyBuilderPage":
self.wait_clickable(self.ADD_BLOCK_BUTTON).click()
self.select_dropdown("[data-test='block-type']", "entry")
self.select_dropdown("[data-test='indicator']", indicator)
self.fill("[data-test='threshold']", str(threshold))
return self
def save(self) -> "StrategyListPage":
self.wait_clickable(self.SAVE_BUTTON).click()
self.wait_for_toast("Strategy saved")
return StrategyListPage(self.driver)
Результат
Время настройки стратегии: 2ч → 15 мин. Регрессионные баги: −50%. Конверсия онбординга заметно выросла (команда не публиковала точные цифры).
Первая коммерческая backend-роль после QA — проект, который научил меня думать про API-контракты и транзакции БД, а не только тест-кейсы.