Отличный план. Вы правильно выделили два главных сценария (базовый и копитрейдинг). Чтобы покрытие было полным и гарантировало стабильность системы, я предлагаю дополнить их сценариями на обработку ошибок и граничные случаи.
Вот Топ-5 идей для тестов backtest-runner, отсортированные от базовых к сложным:
1. "Smoke Test": Запуск примитивной стратегии (Infrastructure Check)
Суть: Проверка всей цепочки без сложной логики. Стратегия должна быть максимально глупой (например, покупать на каждой четной свече и продавать на нечетной).
- Цель: Убедиться, что Docker запускается, вольюмы монтируются, конфиг валиден, и Freqtrade в принципе способен завершить работу.
- Ожидаемый результат:
- Инструмент возвращает
success: true. - В логах (
logTail) присутствует фразаBacktesting finished. - В результатах есть хотя бы одна сделка.
- Инструмент возвращает
2. "Data Fetcher Test": Копитрейдинг с реальными данными (SanR Integration)
Суть: Тест стратегии, которая импортирует sdk.py и запрашивает сигналы для реального пользователя (например, semaca) и реальной пары (BTC/USDT:USDT).
- Цель: Проверить работу
prepareMarketData. Это самое узкое место. Тест должен убедиться, что:- Система парсит конфиг и находит нужные пары.
- Делает запрос к SanR API / Santiment API.
- Скачивает JSON-файлы в
market_cache. - Freqtrade внутри Docker видит эти данные.
- Ожидаемый результат: Бэктест проходит успешно, не падает с ошибкой
No data found.
3. "The One-Day Edge Case": Граничный случай с датами
Суть: Запуск бэктеста, где дата начала равна дате конца (например, timerange: "20241001-20241001").
- Почему это важно: В вашем коде (
src/mastra/pro/tools/freqtrade.tsиtools.ts) есть специальная логика, которая расширяет такой диапазон на +1 день. Если это сломается, Freqtrade загрузит 0 свечей и выдаст ошибку. - Ожидаемый результат: Система должна автоматически расширить диапазон до
20241002, загрузить данные за сутки и провести тест (24 свечи на 1h таймфрейме).
4. "The Crash Test": Обработка Runtime-ошибки в Python
Суть: Подсунуть стратегию с синтаксически верным кодом, но с логической ошибкой, которая вызовет краш во время выполнения (например, деление на ноль в populate_indicators или обращение к несуществующей колонке DataFrame).
- Цель: Проверить, как
backtest-runnerобрабатывает падение контейнера или Python-исключения. Агент не должен падать сам, он должен вернуть лог с ошибкой. - Ожидаемый результат:
- Инструмент возвращает
success: false(илиtrue, но с явным описанием ошибки в логах). - В
logTailсодержится Python Traceback (например,ZeroDivisionError).
- Инструмент возвращает
5. "Missing Data Handling": Запрос несуществующей пары
Суть: В конфиге указать пару, которой не существует на бирже или в Santiment (например, NONEXISTENTCOIN/USDT:USDT).
- Цель: Проверить валидацию данных до запуска тяжелого Docker-контейнера.
- Ожидаемый результат:
- Инструмент должен выбросить ошибку на этапе
prepareMarketData. - Ошибка должна быть понятной: "Missing slugs for pairs..." или "Failed to fetch data".
- Docker запускаться не должен (экономия ресурсов).
- Инструмент должен выбросить ошибку на этапе
Как это реализовать в коде (Пример для Vitest)
Вы можете создать файл src/mastra/super/skills/backtest-runner/scenarios.spec.ts и реализовать эти сценарии, используя моки для exec (чтобы не ждать реальный докер долго) или реальные интеграционные тесты (как в примере 2).
Пример структуры теста для кейса №1:
it('Smoke Test: should complete a simple backtest successfully', async () => {
// 1. Подготовка файлов (Strategy + Config)
await writeFile(strategyPath, minimalWorkingStrategy);
await writeFile(configPath, JSON.stringify(validConfig));
// 2. Выполнение
const result = await runBacktest.execute({ timerange: '20240101-20240105' }, context);
// 3. Проверки
expect(result.success).toBe(true);
expect(result.logTail).toContain('Backtesting finished');
expect(result.logTail).not.toContain('Traceback');
});