Блог им. empenoso
Когда закончил писать механизм своего торгового робота обнаружил, что самое главное всё таки не сам механизм, а стратегия, по которой этот механизм будет работать.
Первый тесты на истории показали что с доходностью и тем более с тем как доходность портфеля компенсирует принимаемый риск (коэффициент Шарпа) проблемы, но неудачный опыт тоже опыт, поэтому решил описать его в статье.
Первый и самый важный вопрос — при помощи чего проводить тесты торговой стратегии на исторических данных? В какой программе или при помощи какой библиотеки создавать стратегию и потом прогонять её на истории?
Раз мой торговый робот создан в среде исполнения JavaScript Node.js, то и тесты в идеале должны проводится на чём-то схожем. Но забегая немного вперёд скажу что получилось по другому.
Раз сам механизм робота кросс-платформенный, то хотелось чтобы и тесты можно было проводить при помощи кросс-платформенной утилиты. Однако когда рассматривал самые популярные программы, то обнаружилось что все программы из списка только для Windows. Кроме TradingView, который является веб-сервисом и Excel — который есть и для macOS.

Но похоже что веб-вервис и тем более Microsoft Excel — не лучший выбор. Тем не менее вот варианты, которые я рассматривал:
Ни один из этих вариантов мне не приглянулся из-за отсутствия кросс-платформенности или этот вариант был Экселем.
После этого стал смотреть библиотеки для Node.js. Выбор оказался небольшой и более-менее живыми мне показались:
grademark: github.com/Grademark/grademark
Библиотека Node.js для бэктестинга торговых стратегий на исторических данных.
Fugle Backtest: github.com/fugle-dev/fugle-backtest-node
Библиотека Node.js для бэктестинга стратегий торговли акциями.
CCXT — CryptoCurrency eXchange Trading Library: github.com/ccxt/ccxt
Библиотека Node.js для торговли криптовалютой, которая предоставляет унифицированный API для подключения и торговли на нескольких криптовалютных биржах, поддерживая как торговлю в реальном времени, так и доступ к историческим данным.

Для Grademark набросал через ChatGPT конкретный пример использования:
<code>const fs = require('fs');
const csvParser = require('csv-parser');
const grademark = require('grademark');
// Параметры стратегии
let trailingStopPercent = 1; // Процент падения для трейлинг-стопа
let buyThreshold = 1; // Минимальный процент для покупки
// Функция для чтения данных из CSV-файла
function readCsvData(filePath) {
return new Promise((resolve, reject) => {
let data = [];
fs.createReadStream(filePath)
.pipe(csvParser())
.on('data', (row) => {
// Преобразуем каждую строку CSV в объект данных
data.push({
time: new Date(row[1]),
open: parseFloat(row[2]),
high: parseFloat(row[3]),
low: parseFloat(row[4]),
close: parseFloat(row[5]),
volume: parseInt(row[6])
});
})
.on('end', () => resolve(data))
.on('error', reject);
});
}
// Функция для агрегирования минутных данных в 5-минутные и часовые свечи
function aggregateData(minuteData, interval) {
const aggregated = [];
let temp = [];
minuteData.forEach((entry, index) => {
temp.push(entry);
if ((index + 1) % interval === 0) {
const aggCandle = {
open: temp[0].open,
high: Math.max(...temp.map(t => t.high)),
low: Math.min(...temp.map(t => t.low)),
close: temp[temp.length - 1].close,
volume: temp.reduce((acc, t) => acc + t.volume, 0),
time: temp[temp.length - 1].time
};
aggregated.push(aggCandle);
temp = [];
}
});
return aggregated;
}
// Функция для запуска стратегии
async function runBacktest(startMonth, testMonth) {
let strategy = grademark({
buy: ({ fiveMinuteCandle, hourlyCandle }) => {
return (
fiveMinuteCandle.close > calculateSMA(fiveMinuteCandle, 5) &&
hourlyCandle.close > calculateSMA(hourlyCandle, 60)
);
},
sell: ({ price, maxPrice }) => {
return price < maxPrice * (1 - trailingStopPercent / 100);
},
});
// Сначала оптимизируем стратегию на данных за месяц
let januaryData = await readCsvData(`data/e6123145-9665-43e0-8413-cd61b8aa9b13_2024${startMonth}.csv`);
let fiveMinuteCandles = aggregateData(januaryData, 5);
let hourlyCandles = aggregateData(januaryData, 60);
strategy.optimize({ fiveMinuteCandles, hourlyCandles });
// Далее, проводим тестирование на следующем месяце (например, февраль)
let februaryData = await readCsvData(`data/e6123145-9665-43e0-8413-cd61b8aa9b13_2024${testMonth}.csv`);
let testFiveMinuteCandles = aggregateData(februaryData, 5);
let testHourlyCandles = aggregateData(februaryData, 60);
let testResults = strategy.run({ fiveMinuteCandles: testFiveMinuteCandles, hourlyCandles: testHourlyCandles });
console.log('Результаты теста:', testResults);
}
// Пример вызова функции для тестирования
runBacktest('09', '10'); // Оптимизация на сентябрьских данных и тестирование на октябрьских данных
// Функция для вычисления скользящей средней (SMA)
function calculateSMA(candles, period) {
if (candles.length < period) return 0;
const sum = candles.slice(-period).reduce((acc, candle) => acc + candle.close, 0);
return sum / period;
}</code>При этом криптовалюты мне не подходили, Grademark почему-то не смог установить, а Fugle Backtest не приглянулся.
В Python есть несколько популярных библиотек для бэктестинга торговых стратегий, рассчитанных на разные уровни сложности и типы активов. Вот найденные варианты:
Backtesting.py github.com/kernc/backtesting.py
Легкая, интуитивно понятная библиотека для векторизованного бэктестинга, включающая популярные индикаторы и метрики.
❌ 4 года не обновлялась.
Backtrader github.com/mementum/backtrader
Одна из самых популярных и многофункциональных библиотек для бэктестинга. Поддерживает несколько активов, таймфреймов, индикаторов и оптимизацию стратегий.
PyAlgoTrade github.com/gbeced/pyalgotrade
Простая библиотека бэктестинга со встроенной поддержкой технических индикаторов и создания базовой стратегии.
❌ Этот репозиторий был заархивирован владельцем 13 ноября 2023 г.
Zipline github.com/quantopian/zipline
Разработанная Quantopian (теперь поддерживаемая сообществом), Zipline — это надежная библиотека бэктестинга, ориентированная на событийно-управляемое бэктестирование, используемая профессионалами.
❌ 4 года не обновлялась.
QuantConnect/Lean github.com/QuantConnect/Lean
Движок с открытым исходным кодом, лежащий в основе QuantConnect; поддерживает бэктестинг и торговлю в реальном времени для нескольких классов активов.
VectorBT github.com/polakowo/vectorbt
Разработан для быстрого векторизованного бэктестинга и анализа стратегий непосредственно на Pandas DataFrames.
Fastquant github.com/enzoampil/fastquant
Удобная библиотека бэктестинга, разработанная для быстрого тестирования с минимальной настройкой, вдохновленная Prophet от Facebook.
❌ 3 года не обновлялась.
MibianLib github.com/yassinemaaroufi/MibianLib
Фокусируется на ценообразовании и волатильности опционов, а не на полном бэктестинге, но полезен для стратегий, связанных с опционами.
❌ 11 лет не обновлялась.
Сначала выбрал использовать Backtesting.py, потому что она упоминалась на многих сайтах, но уже на первоначальном этапе использования стали вылазит проблемы. Ошибка возникла из-за несоответствия в том, как новые версии pandas обрабатывают метод get_loc(). Аргумент method='nearest' больше не поддерживается в последних версиях pandas. Эта проблема связана с тем, как библиотека Backtesting.py взаимодействует с новыми версиями pandas, в частности, при повторной выборке данных для построения графиков. А новой версии Backtesting.py, которая решает эту проблему и поддерживает последние изменения API pandas просто нет.
Следующий в списке был Backtrader — с ним и продолжил работать.

Хотя считается что торговая стратегия необязательно должна быть «человекочитаемой» — это вполне может быть результат обучения алгоритма, основанного на интеллектуальных технологиях (нейросети, машинное обучение и т.п.), но я решил начать с простого.
Мои условия:
Моя торговая стратегия основана на пересечении скользящих средних двух разных таймфреймов со скользящим стоп-лоссом для продажи.
Условие покупки представляет собой комбинацию двух пересечений скользящих средних:

Требуя выполнения обоих этих условий, гарантирую что акция будет иметь бычий импульс как на коротких, так и на длинных таймфреймах перед входом в позицию. Такое выравнивание двух таймфреймов помогает избегать покупок во время временного шума или незначительных колебаний на более коротком таймфрейме, отфильтровывая менее стабильные движения.
Условие продажи: трейлинг стоп, который предназначен для защиты прибыли и ограничения риска падения. Как работает лучше всего показано на картинке:

Моя, описанная выше стратегия для двух таймфреймов на нескольких бумагах, выглядит в библиотеке backtrader на Python следующим образом:
strategy0_ma_5min_hourly.py:
<code>import sys
sys.stdout.reconfigure(encoding='utf-8')
import backtrader as bt
# Стратегия скользящие средние на двух разных временных интервалах
class MovingAveragesOnDifferentTimeIntervalsStrategy(bt.Strategy):
params = (
('ma_period_5min', 30), # Период для скользящей средней на 5-минутках
('ma_period_hourly', 45), # Период для скользящей средней на часовом интервале
('trailing_stop', 0.03) # Процент для трейлинг-стопа
)
def __init__(self):
print(f"\nРасчет для параметров: {self.params.ma_period_5min} / {self.params.ma_period_hourly} / {self.params.trailing_stop}")
# Создаем списки для хранения индикаторов по каждому инструменту
self.ma_5min = {}
self.ma_hourly = {}
# Для каждого инструмента добавляем скользящие средние по разным интервалам
for i, data in enumerate(self.datas):
if i % 2 == 0: # Четные индексы - 5-минутные данные
ticker = data._name.replace('_5min', '')
self.ma_5min[ticker] = bt.indicators.SimpleMovingAverage(data.close, period=self.params.ma_period_5min)
else: # Нечетные индексы - часовые данные
ticker = data._name.replace('_hourly', '')
self.ma_hourly[ticker] = bt.indicators.SimpleMovingAverage(data.close, period=self.params.ma_period_hourly)
# Переменные для отслеживания максимальной цены после покупки по каждому инструменту
self.buy_price = {}
self.max_price = {}
self.order = {} # Словарь для отслеживания ордеров по каждому инструменту
def next(self):
# Для каждого инструмента проверяем условия покупки и продажи
for i in range(0, len(self.datas), 2): # Проходим по 5-минутным данным
ticker = self.datas[i]._name.replace('_5min', '')
data_5min = self.datas[i]
data_hourly = self.datas[i + 1]
# Проверяем, есть ли открытый ордер для этого инструмента
if ticker in self.order and self.order[ticker]:
continue # Пропускаем, если есть открытый ордер
# Проверяем условия покупки:
# цена на 5 мин таймфрейме выше скользящей средней на 5 мин + часовая цена тоже выше часовой скользящей средней
if not self.getposition(data_5min): # Открываем сделку только если нет открытой позиции
if data_5min.close[0] > self.ma_5min[ticker][0] and data_hourly.close[0] > self.ma_hourly[ticker][0]:
self.order[ticker] = self.buy(data=data_5min)
self.buy_price[ticker] = data_5min.close[0]
self.max_price[ticker] = self.buy_price[ticker]
# Получаем текущий тикер и дату покупки
buy_date = data_5min.datetime.date(0)
buy_time = data_5min.datetime.time(0)
print(f"{buy_date} в {buy_time}: покупка за {self.buy_price[ticker]} для {ticker}")
# Если уже есть открытая позиция
elif self.getposition(data_5min):
current_price = data_5min.close[0]
# Обновляем максимальную цену, если текущая выше
if current_price > self.max_price[ticker]:
self.max_price[ticker] = current_price
# Рассчитываем уровень стоп-лосса
stop_loss_level = self.max_price[ticker] * (1 - self.params.trailing_stop)
# Проверяем условие для продажи по трейлинг-стопу
if current_price < stop_loss_level:
self.order[ticker] = self.sell(data=data_5min)
sell_date = data_5min.datetime.date(0)
sell_time = data_5min.datetime.time(0)
print(f"{sell_date} в {sell_time}: продажа за {current_price} для {ticker}")
# Обрабатываем уведомления по ордерам
def notify_order(self, order):
ticker = order.data._name.replace('_5min', '')
if order.status in [order.Completed, order.Canceled, order.Margin]:
self.order[ticker] = None # Очищаем ордер после завершения</code>Сделал переключатель одиночный тест или оптимизация: singleTest / optimization для основного файла запуска: SingleTestOrOptimization = "optimization"
Основной файл запуска main.py:
<code>import sys
import time
sys.stdout.reconfigure(encoding='utf-8')
from datetime import datetime
from src.data_loader import load_data_for_ticker, load_ticker_mapping
import pandas as pd
import backtrader as bt
import backtrader.analyzers as btanalyzers
from src.strategy0_ma_5min_hourly import MovingAveragesOnDifferentTimeIntervalsStrategy
# отобразить имена всех столбцов в большом фреймворке данных pandas
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 1000)
# Начало времени
start_time = time.perf_counter()
# Путь к JSON файлу с сопоставлениями
mapping_file = "./data/+mappings.json"
# Загрузка сопоставлений тикеров
ticker_mapping = load_ticker_mapping(mapping_file)
# Промежуточное время выполнения
total_end_time = time.perf_counter()
elapsed_time = total_end_time - start_time
print(f"Промежуточное время выполнения: {elapsed_time:.4f} секунд.")
current_time = datetime.now().strftime("%Y-%m-%d %H-%M") # Генерируем текущее время в формате 'yyyy-mm-dd HH-mm'
# Следующая часть кода запускается только если это основной модуль
if __name__ == '__main__': # Исправление для работы с multiprocessing
# Создаем объект Cerebro
cerebro = bt.Cerebro(optreturn=False)
# Получаем количество бумаг в ticker_mapping.items()
num_securities = len(ticker_mapping.items())
# Рассчитываем процент капитала на одну бумагу
percent_per_security = 100 / num_securities
print(f"Процент капитала на одну бумагу: {percent_per_security:.2f}%")
# Условия капитала
cerebro.broker.set_cash(100000) # Устанавливаем стартовый капитал
cerebro.broker.setcommission(commission=0.005) # Комиссия 0.5%
cerebro.addsizer(bt.sizers.PercentSizer, percents=percent_per_security) # Настраиваем размер позиций как процент от капитала
# Для каждого инструмента добавляем оба временных интервала
for uid, ticker in ticker_mapping.items():
print(f"Загружаем данные для {ticker}")
# Загрузка данных с таймфреймами 5 минут и час
data_5min, data_hourly = load_data_for_ticker(ticker)
# Пропуск, если данные не были загружены
if data_5min is None or data_hourly is None:
continue
# Добавляем 5-минутные данные в Cerebro
data_5min_bt = bt.feeds.PandasData(dataname=data_5min, timeframe=bt.TimeFrame.Minutes, compression=5)
cerebro.adddata(data_5min_bt, name=f"{ticker}_5min")
# Добавляем часовые данные в Cerebro
data_hourly_bt = bt.feeds.PandasData(dataname=data_hourly, timeframe=bt.TimeFrame.Minutes, compression=60)
# Совмещаем графики 5 минут и часа на одном виде
data_hourly_bt.plotinfo.plotmaster = data_5min_bt # Связываем графики
data_hourly_bt.plotinfo.sameaxis = True # Отображаем на той же оси
cerebro.adddata(data_hourly_bt, name=f"{ticker}_hourly")
# Переключатель одиночный тест или оптимизация
SingleTestOrOptimization = "optimization" # singleTest / optimization
if SingleTestOrOptimization == "singleTest":
print(f"{current_time} Проводим одиночный тест стратегии.")
# Добавляем стратегию для одичного теста MovingAveragesOnDifferentTimeIntervalsStrategy
cerebro.addstrategy(MovingAveragesOnDifferentTimeIntervalsStrategy,
ma_period_5min = 30, # Период для скользящей средней на 5-минутках
ma_period_hourly = 45, # Период для скользящей средней на часовом интервале
trailing_stop = 0.03) # Процент для трейлинг-стопа
# Writer только для одиночного теста для вывода результатов в CSV-файл
cerebro.addwriter(bt.WriterFile, csv=True, out=f"./results/{current_time}_log.csv")
# Добавляем анализаторы
cerebro.addanalyzer(btanalyzers.TradeAnalyzer, _name='trade_analyzer')
cerebro.addanalyzer(btanalyzers.DrawDown, _name="drawdown")
cerebro.addanalyzer(btanalyzers.Returns, _name="returns")
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name='sharpe_ratio', timeframe=bt.TimeFrame.Days, riskfreerate=10)
cerebro.addanalyzer(btanalyzers.SQN, _name='sqn')
cerebro.addanalyzer(btanalyzers.PyFolio, _name='PyFolio')
# Запуск тестирования
results = cerebro.run(maxcpus=1) # Ограничение одним ядром для избежания многопроцессорности
# Выводим результаты анализа одиночного теста
print(f"\nОкончательная стоимость портфеля: {cerebro.broker.getvalue()}")
returnsAnalyzer = results[0].analyzers.returns.get_analysis()
print(f"Годовая/нормализованная доходность: {returnsAnalyzer['rnorm100']}%")
drawdownAnalyzer = results[0].analyzers.drawdown.get_analysis()
print(f"Максимальное значение просадки: {drawdownAnalyzer['max']['drawdown']}%")
trade_analyzer = results[0].analyzers.trade_analyzer.get_analysis()
print(f"Всего сделок: {trade_analyzer.total.closed} шт.")
print(f"Выигрышные сделки: {trade_analyzer.won.total} шт.")
print(f"Убыточные сделки: {trade_analyzer.lost.total} шт.")
sharpe_ratio = results[0].analyzers.sharpe_ratio.get_analysis().get('sharperatio')
print(f"Коэффициент Шарпа: {sharpe_ratio}")
sqnAnalyzer = results[0].analyzers.sqn.get_analysis().get('sqn')
print(f"Мера доходности с поправкой на риск: {sqnAnalyzer}")
# Время выполнения
total_end_time = time.perf_counter()
elapsed_time = (total_end_time - start_time) / 60
print(f"\nВремя выполнения: {elapsed_time:.4f} минут.")
# Построение графика для одиночного теста
cerebro.plot()
else:
print(f"{current_time} Проводим оптимизацию статегии.")
# Оптимизация стратегии start_date = 2024-10_MovingAveragesOnDifferentTimeIntervalsStrategy
cerebro.optstrategy(MovingAveragesOnDifferentTimeIntervalsStrategy,
ma_period_5min=range(10, 61, 5), # Диапазон для 5-минутной скользящей средней
ma_period_hourly=range(15, 61, 2), # Диапазон для часовой скользящей средней
trailing_stop=[0.03]) # Разные проценты для трейлинг-стопа 0.03, 0.05, 0.07
print(f"\nКоличество варинатов оптимизации: {(( (61-10)/10 * (61-15))/2 )}\n")
# Добавляем анализаторы
cerebro.addanalyzer(btanalyzers.TradeAnalyzer, _name='trade_analyzer')
cerebro.addanalyzer(btanalyzers.DrawDown, _name="drawdown")
cerebro.addanalyzer(btanalyzers.Returns, _name="returns")
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name='sharpe_ratio', timeframe=bt.TimeFrame.Days, riskfreerate=10)
cerebro.addanalyzer(btanalyzers.SQN, _name='sqn')
# Запуск тестирования
results = cerebro.run(maxcpus=1) # Ограничение одним ядром для избежания многопроцессорности для оптимизации
# Выводим результаты оптимизации
par_list = [ [
# MovingAveragesOnDifferentTimeIntervalsStrategy
x[0].params.ma_period_5min,
x[0].params.ma_period_hourly,
x[0].params.trailing_stop,
x[0].analyzers.trade_analyzer.get_analysis().pnl.net.total,
x[0].analyzers.returns.get_analysis()['rnorm100'],
x[0].analyzers.drawdown.get_analysis()['max']['drawdown'],
x[0].analyzers.trade_analyzer.get_analysis().total.closed,
x[0].analyzers.trade_analyzer.get_analysis().won.total,
x[0].analyzers.trade_analyzer.get_analysis().lost.total,
x[0].analyzers.sharpe_ratio.get_analysis()['sharperatio'],
x[0].analyzers.sqn.get_analysis().get('sqn')
] for x in results]
# MovingAveragesOnDifferentTimeIntervalsStrategy
par_df = pd.DataFrame(par_list, columns = ['ma_period_5min', 'ma_period_hourly', 'trailing_stop', 'pnl net', 'return', 'drawdown', 'total closed', 'won total', 'lost total', 'sharpe', 'sqn'])
# Формируем имя файла с текущей датой и временем
filename = f"./results/{current_time}_optimization.csv"
# Сохраняем DataFrame в CSV файл с динамическим именем
par_df.to_csv(filename, index=False)
print(f"\n\nРезультаты оптимизации:\n{par_df}")
# Время выполнения
total_end_time = time.perf_counter()
elapsed_time = (total_end_time - start_time) / 60
print(f"\nВремя выполнения: {elapsed_time:.4f} минут.")
# Общее время выполнения
total_end_time = time.perf_counter()
elapsed_time = (total_end_time - start_time) / 60
print(f"\nОбщее время выполнения: {elapsed_time:.4f} минут.")</code>В данные загрузил котировки за октябрь 2024:
Время выполнения оптимизации для таких параметров составило 74 минуты:
<code># Оптимизация стратегии start_date = 2024-10_MovingAveragesOnDifferentTimeIntervalsStrategy
cerebro.optstrategy(MovingAveragesOnDifferentTimeIntervalsStrategy,
ma_period_5min=range(10, 61, 5), # Диапазон для 5-минутной скользящей средней
ma_period_hourly=range(15, 61, 2), # Диапазон для часовой скользящей средней
trailing_stop=[0.03]) # Разные проценты для трейлинг-стопа 0.03, 0.05, 0.07</code>
Для того чтобы визуально представить результаты оптимизации написал модуль, который строит трехмерный график.
Модуль 3dchart.py:
<code>import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from matplotlib.widgets import Slider
from datetime import datetime
# Чтение данных из CSV файла
data = pd.read_csv('./results/2024-11-12 16-12_optimization_2024-10_MovingAveragesOnDifferentTimeIntervalsStrategy.csv')
parameter1 = 'ma_period_5min'
parameter2 = 'ma_period_hourly'
# Извлечение необходимых колонок для построения графика
x = data[parameter1] # по оси X
y = data[parameter2] # по оси Y
z = data['pnl net'] # по оси Z (PNL net)
# Создание 3D-графика
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Построение поверхности с использованием триангуляции
surf = ax.plot_trisurf(x, y, z, cmap='viridis', edgecolor='none')
# Подписи к осям
ax.set_xlabel(parameter1)
ax.set_ylabel(parameter2)
ax.set_zlabel('PNL Net')
# Заголовок графика
current_time = datetime.now().strftime("%Y-%m-%d %H:%M") # Генерируем текущее время в формате 'yyyy-mm-dd HH:mm'
ax.set_title(f"3D Optimization Chart, {current_time}")
# Добавление плоскости, которая будет двигаться вдоль оси Z
# Начальное значение плоскости по оси Z
z_plane = np.mean(z)
# Плоскость - запоминаем ее как отдельный объект
x_plane = np.array([[min(x), max(x)], [min(x), max(x)]])
y_plane = np.array([[min(y), min(y)], [max(y), max(y)]])
z_plane_values = np.array([[z_plane, z_plane], [z_plane, z_plane]])
# Отображение плоскости
plane = ax.plot_surface(x_plane, y_plane, z_plane_values, color='red', alpha=0.5)
# Создание слайдера для управления позицией плоскости по оси Z
ax_slider = plt.axes([0.25, 0.02, 0.50, 0.03], facecolor='lightgoldenrodyellow')
z_slider = Slider(ax_slider, 'Z Plane', min(z), max(z), valinit=z_plane)
# Функция обновления положения плоскости при перемещении слайдера
def update(val):
new_z_plane = z_slider.val
z_plane_values[:] = new_z_plane # Обновляем значения Z для плоскости
ax.collections[-1].remove() # Удаляем старую плоскость
ax.plot_surface(x_plane, y_plane, z_plane_values, color='red', alpha=0.5) # Рисуем новую плоскость
fig.canvas.draw_idle() # Обновляем график
# Привязка слайдера к функции обновления
z_slider.on_changed(update)
# Отображение графика
plt.show()</code>Результат оптимизации в виде графика:

Цифры по шкале Z показывают лишь степень убытков в рублях. Они со знаком минус.
Вы можете сами полностью повторить мой опыт потому что код загружен на GitHub:https://github.com/empenoso/SilverFir-TradingBot_backtesting
Тем не менее:
Я хочу использовать подход скользящего окна — когда данные разбиваются на более мелкие последовательные периоды например по месяцам, за которым следует период тестирования вне этой выборки. Например, оптимизация идёт на месячных данных, а тестировать уже на следующем месяце. То есть происходит сдвиг вперед: после каждого периода тестирования окно «скользит» вперед на указанный интервал, и процесс повторяется. Таким образом, каждый сегмент данных используется как для обучения, так и для тестирования с течением времени, но никогда одновременно. Это помогает проверить, что стратегия работает стабильно в меняющихся рыночных условиях.
Также планирую использовать Technical Analysis of STOCKS & COMMODITIES для поиска новых идей. Их советы трейдерам доступны в открытом доступе.
А ещё планирую использовать ChatGPT, отправляя запросы вроде:
Действуй как опытный издатель. Отобрази 10 ведущих авторов в области алгоритмической торговли на рынке Америки. Для каждого автора перечисли три самые популярные книги, включая сведения о книге (дату публикации, издателя и ISBN), и предоставь русские переводы для каждого названия книги.

и дальше после ответа:
Действуй как опытный пользователь библиотеки backtrader на Python.Хочу использовать торговую стратегию из книги Yves Hilpisch «Python for Finance: Mastering Data-Driven Finance» для тестов.
Добавляй все комментарии на русском языке, продолжай со мной общение на английском.

И дальше подобные промты.
Несмотря на то, что первоначальный выбор стратегии на двух разных таймфреймах и сразу для 15 активов был не самый удачный — впереди ещё очень большое поле исследований и тестов.
Автор:Михаил Шардин
18 ноября 2024 г.
На мой взгляд использование готовых библиотек позволяет сосредоточиться на сути, а не на реализации базовых функций, что делает разработку быстрее, надежнее и эффективнее.
Я несколько лет был вынужден писать под чужие платформы. 2 разные. Много мучений доставляют скрытые ошибки в чужих прогах, непонятные и неприятные ограничения, медленная работа. При том, что собственно тестирование — процесс с точки зрения программиста очень простой и вполне реализуемый самостоятельно. Ну, кроме, возможно, тяжелых библиотек типа дип леарнинг. Которые есть и стандартно подключаются.
smart-lab.ru/blog/tradesignals/1062006.php
smart-lab.ru/my/KonstantinChaschegorov/
Ну если так глубоко копать то
О́дин — верховный бог, отец предводитель асов
А́сы -основная группа богов.
ru.wikipedia.org/wiki/Один
ru.wikipedia.org/wiki/Асы
Один предстаёт как бог войны и победы, поэзии шаманских ритуалов. Он описывается как обладающий великой мудростью, тайными знаниями и поэтическим искусством, покровительствующий воинам и военной аристократии.
Если Рюрик происходил из прибалтийских племен, то мог иметь верования, схожие со славянскими. Впрочем, он мог почитать и скандинавских богов, таких как Один и Тор.
readovka.news/news/91933
Так что 1С это практически сердце Руси
Ass к мифическим асам не имеет никакого отношения.
Ass (англ слово) — в переводе на русский — жопа
также есть варианты перевода как:
— задница
— зад
— осел
— ишак
— глупец
Это громоздкий монстр, пожирающий ресурсы