Исходный код
Мартингейл — это стратегия управления капиталом, при которой размер ставки удваивается после каждого проигрыша, чтобы компенсировать все предыдущие потери при первой же победе. Несмотря на кажущуюся простоту, эта стратегия сопряжена с чрезвычайно высоким риском разорения, особенно на финансовых рынках, где серии проигрышей могут быть длинными, а капитал ограничен.
Представленный ниже Python-скрипт позволяет провести бэктестинг Мартингейл-стратегии на исторических данных. Он симулирует последовательность сделок, отслеживает изменение капитала и, что критически важно, рассчитывает вероятность разорения (Risk of Ruin, RoR) с использованием метода Монте-Карло. Это дает более реалистичную оценку рисков, чем просто теоретические формулы, поскольку учитывает наблюдаемые характеристики доходности на исторических данных.
Для демонстрации работы скрипта используются сгенерированные случайные исторические данные. В реальных условиях вы должны будете загрузить свои собственные данные (например, OHLCV из CSV-файла).
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
# --- Конфигурация для бэктеста ---
# Для демонстрации генерируем фиктивные данные.
# В реальном сценарии вы бы загружали данные из CSV.
# data_path = 'your_historical_data.csv'
# Если используете реальные данные, убедитесь, что есть столбец 'Close'.
# Параметры стратегии
INITIAL_CAPITAL = 10000.0
INITIAL_BET_SIZE = 10.0
MARTINGALE_MULTIPLIER = 2.0 # Во сколько раз увеличивать ставку после проигрыша
MAX_CONSECUTIVE_LOSSES = 7 # Максимальное количество последовательных проигрышей до сброса ставки
PROFIT_TARGET_PCT = 0.01 # 1% прибыли от размера ставки при выигрыше
STOP_LOSS_PCT = 0.01 # 1% убытка от размера ставки при проигрыше
# Параметры симуляции Risk of Ruin (RoR)
NUM_ROR_SIMULATIONS = 10000
MAX_TRADES_PER_ROR_SIM = 1000 # Максимальное количество сделок в одной симуляции RoR
# --- Загрузка данных (генерация фиктивных данных для демонстрации) ---
def generate_dummy_data(num_days=252, start_price=100):
"""Генерирует фиктивные дневные цены закрытия."""
np.random.seed(42)
returns = np.random.normal(0.0005, 0.01, num_days) # Среднее 0.05%, Ст.откл. 1%
prices = start_price * np.exp(np.cumsum(returns))
df = pd.DataFrame({'Close': prices})
return df
# --- Функция бэктестинга Мартингейл-стратегии ---
def run_martingale_backtest(
data: pd.DataFrame,
initial_capital: float,
initial_bet_size: float,
martingale_multiplier: float,
max_consecutive_losses: int,
profit_target_pct: float,
stop_loss_pct: float
):
"""
Выполняет бэктестинг Мартингейл-стратегии на исторических данных.
Результаты сделок определяются простым движением цены:
если цена закрытия следующего дня > цены закрытия текущего дня, это выигрыш;
в противном случае - проигрыш. Это упрощенная 'стратегия'.
"""
capital = initial_capital
current_bet = initial_bet_size
consecutive_losses = 0
equity_curve = [capital]
trade_outcomes = [] # True для выигрыша, False для проигрыша
trade_profits = []
# Вычисляем дневные доходности для симуляции результатов сделок.
# Предполагаем, что сделка открывается по цене Close[i] и закрывается по Close[i+1].
data['Daily_Return'] = data['Close'].pct_change().shift(-1)
# Итерируем по данным для симуляции сделок
# Итерируем до len(data) - 1, так как нам нужна доходность следующего дня.
for i in range(len(data) - 1):
if capital <= 0:
print(f"Капитал исчерпан на точке данных {i}. Разорение!")
break
daily_return = data['Daily_Return'].iloc[i]
if pd.isna(daily_return):
continue # Пропускаем, если нет доходности следующего дня
is_win = daily_return > 0
if is_win:
# Прибыль составляет процент от текущей ставки
profit = current_bet * profit_target_pct
capital += profit
consecutive_losses = 0
current_bet = initial_bet_size # Сброс ставки после выигрыша
trade_outcomes.append(True)
trade_profits.append(profit)
else:
# Убыток составляет процент от текущей ставки
loss = current_bet * stop_loss_pct
capital -= loss
consecutive_losses += 1
trade_outcomes.append(False)
trade_profits.append(-loss)
if consecutive_losses >= max_consecutive_losses:
# Остановка или сброс стратегии после максимального количества последовательных проигрышей
current_bet = initial_bet_size # Сброс ставки
consecutive_losses = 0
else:
current_bet *= martingale_multiplier # Удвоение ставки для следующей сделки
# Важно: убедитесь, что размер ставки не превышает доступный капитал
# или разумный лимит, чтобы предотвратить немедленное разорение при большом убытке.
if current_bet > capital * 0.8: # Пример: не ставить более 80% капитала
current_bet = capital * 0.8
if current_bet < initial_bet_size: # Убедитесь, что не опускается ниже начальной ставки
current_bet = initial_bet_size
equity_curve.append(capital)
final_capital = capital
total_trades = len(trade_outcomes)
total_wins = sum(trade_outcomes)
total_losses = total_trades - total_wins
win_rate_observed = total_wins / total_trades if total_trades > 0 else 0
# Расчет максимальной просадки
equity_series = pd.Series(equity_curve)
peak = equity_series.expanding(min_periods=1).max()
drawdown = (equity_series - peak) / peak
max_drawdown = drawdown.min() if not drawdown.empty else 0
# Расчет средних значений выигрышей/проигрышей для симуляции RoR
avg_win_amount = np.mean([p for p in trade_profits if p > 0]) if any(p > 0 for p in trade_profits) else 0
avg_loss_amount = np.mean([abs(p) for p in trade_profits if p < 0]) if any(p < 0 for p in trade_profits) else 0
print("\n--- Результаты бэктеста ---")
print(f"Начальный капитал: ${initial_capital:,.2f}")
print(f"Конечный капитал: ${final_capital:,.2f}")
print(f"Всего сделок: {total_trades}")
print(f"Всего выигрышей: {total_wins}")
print(f"Всего проигрышей: {total_losses}")
print(f"Наблюдаемый процент выигрышей: {win_rate_observed:.2%}")
print(f"Максимальная просадка: {max_drawdown:.2%}")
print(f"Разорение: {'Да' if final_capital <= 0 else 'Нет'}")
return {
'equity_curve': equity_curve,
'final_capital': final_capital,
'total_trades': total_trades,
'total_wins': total_wins,
'total_losses': total_losses,
'win_rate_observed': win_rate_observed,
'max_drawdown': max_drawdown,
'ruined': final_capital <= 0,
'avg_win_amount': avg_win_amount,
'avg_loss_amount': avg_loss_amount
}
# --- Функция симуляции Risk of Ruin (RoR) ---
def simulate_martingale_ror(
initial_capital: float,
initial_bet_size: float,
martingale_multiplier: float,
trade_signal_win_rate: float, # Используйте наблюдаемый процент выигрышей из бэктеста или предполагаемый
avg_win_amount: float,
avg_loss_amount: float,
max_consecutive_losses: int,
num_simulations: int,
max_trades_per_sim: int
):
"""
Оценивает Risk of Ruin для Мартингейл-стратегии с использованием симуляции Монте-Карло.
Результаты сделок основаны на заданном проценте выигрышей и средних суммах выигрышей/проигрышей,
а не напрямую на исторических движениях цен.
"""
ruin_count = 0
for _ in range(num_simulations):
capital = initial_capital
current_bet = initial_bet_size
consecutive_losses = 0
for trade_num in range(max_trades_per_sim):
if capital <= 0:
ruin_count += 1
break
is_win = random.random() < trade_signal_win_rate
if is_win:
capital += avg_win_amount # Используем среднюю сумму выигрыша
consecutive_losses = 0
current_bet = initial_bet_size
else:
capital -= avg_loss_amount # Используем среднюю сумму проигрыша
consecutive_losses += 1
if consecutive_losses >= max_consecutive_losses:
current_bet = initial_bet_size # Сброс ставки
consecutive_losses = 0
else:
current_bet *= martingale_multiplier
# Убедитесь, что ставка не превышает доступный капитал или разумный лимит
if current_bet > capital * 0.5: # Пример: не ставить более 50% капитала
current_bet = capital * 0.5
if current_bet < initial_bet_size: # Убедитесь, что не опускается ниже начальной ставки
current_bet = initial_bet_size
if capital <= 0: # Проверяем еще раз в конце симуляции, если не было выхода раньше
ruin_count += 1
ror = ruin_count / num_simulations
return ror
# --- Основное выполнение ---
if __name__ == "__main__":
# 1. Загрузка или генерация данных
# Для реальных данных:
# try:
# historical_data = pd.read_csv(data_path)
# historical_data['Close'] = pd.to_numeric(historical_data['Close'], errors='coerce')
# historical_data.dropna(subset=['Close'], inplace=True)
# except FileNotFoundError:
# print(f"Ошибка: Файл данных не найден по пути {data_path}. Генерируем фиктивные данные.")
# historical_data = generate_dummy_data()
# else:
# print(f"Данные успешно загружены из {data_path}.")
# Используем фиктивные данные для демонстрации
historical_data = generate_dummy_data(num_days=500)
print("Сгенерированы фиктивные исторические данные.")
# 2. Запуск бэктеста Мартингейл-стратегии
backtest_results = run_martingale_backtest(
data=historical_data.copy(), # Передаем копию, чтобы избежать изменения исходного DataFrame
initial_capital=INITIAL_CAPITAL,
initial_bet_size=INITIAL_BET_SIZE,
martingale_multiplier=MARTINGALE_MULTIPLIER,
max_consecutive_losses=MAX_CONSECUTIVE_LOSSES,
profit_target_pct=PROFIT_TARGET_PCT,
stop_loss_pct=STOP_LOSS_PCT
)
# 3. Построение графика кривой капитала
plt.figure(figsize=(12, 6))
plt.plot(backtest_results['equity_curve'], label='Кривая капитала')
plt.title('Кривая капитала Мартингейл-стратегии')
plt.xlabel('Номер сделки')
plt.ylabel('Капитал ($)')
plt.grid(True)
plt.axhline(y=INITIAL_CAPITAL, color='r', linestyle='--', label='Начальный капитал')
plt.legend()
plt.show()
# 4. Расчет Risk of Ruin (RoR)
# Используем наблюдаемый процент выигрышей и средние суммы выигрышей/проигрышей из бэктеста для симуляции RoR.
# Если бэктест сам по себе привел к разорению или было слишком мало сделок, используем начальные предположения.
if backtest_results['ruined'] or backtest_results['total_trades'] == 0:
print("\nВнимание: Бэктест привел к разорению или не имел сделок. Расчет RoR может быть менее надежным на основе наблюдаемой статистики.")
print("Используем начальные предположения для параметров симуляции RoR.")
# Возвращаемся к начальным предположениям для симуляции RoR, если бэктест был проблематичным
ror_win_rate = 0.5 # Распространенное предположение для простых стратегий
ror_avg_win = INITIAL_BET_SIZE * PROFIT_TARGET_PCT
ror_avg_loss = INITIAL_BET_SIZE * STOP_LOSS_PCT
else:
ror_win_rate = backtest_results['win_rate_observed']
ror_avg_win = backtest_results['avg_win_amount']
ror_avg_loss = backtest_results['avg_loss_amount']
risk_of_ruin = simulate_martingale_ror(
initial_capital=INITIAL_CAPITAL,
initial_bet_size=INITIAL_BET_SIZE,
martingale_multiplier=MARTINGALE_MULTIPLIER,
trade_signal_win_rate=ror_win_rate,
avg_win_amount=ror_avg_win,
avg_loss_amount=ror_avg_loss,
max_consecutive_losses=MAX_CONSECUTIVE_LOSSES,
num_simulations=NUM_ROR_SIMULATIONS,
max_trades_per_sim=MAX_TRADES_PER_ROR_SIM
)
print(f"\n--- Результаты симуляции Risk of Ruin (RoR) ({NUM_ROR_SIMULATIONS} запусков) ---")
print(f"Оценочный Risk of Ruin: {risk_of_ruin:.2%}")
Разбор параметров
INITIAL_CAPITAL: Начальный капитал, с которым начинается бэктест и симуляция.INITIAL_BET_SIZE: Размер первой ставки в Мартингейл-стратегии.MARTINGALE_MULTIPLIER: Коэффициент, на который увеличивается ставка после каждого проигрыша (например, 2.0 для удвоения).MAX_CONSECUTIVE_LOSSES: Максимальное количество последовательных проигрышей, после которого стратегия сбрасывает ставку доINITIAL_BET_SIZE. Это мера для ограничения экспоненциального роста ставок.PROFIT_TARGET_PCT: Процент от текущей ставки, который составляет прибыль при выигрыше.STOP_LOSS_PCT: Процент от текущей ставки, который составляет убыток при проигрыше.NUM_ROR_SIMULATIONS:: Количество симуляций Монте-Карло для расчета Risk of Ruin. Чем больше, тем точнее оценка.MAX_TRADES_PER_ROR_SIM: Максимальное количество сделок в каждой отдельной симуляции Risk of Ruin.data: Объектpandas.DataFrame, содержащий исторические данные. Обязательно должен быть столбец'Close'с ценами закрытия.trade_signal_win_rate: Вероятность выигрыша одной сделки, используемая в симуляции RoR. Обычно это наблюдаемый процент выигрышей из бэктеста.avg_win_amount: Средняя сумма выигрыша за сделку, используемая в симуляции RoR.avg_loss_amount: Средняя сумма проигрыша за сделку, используемая в симуляции RoR.
Как запустить
Для запуска скрипта выполните следующие шаги:
-
Сохраните код: Сохраните приведенный выше код в файл с расширением
.py, например,martingale_backtester.py. -
Подготовьте данные (опционально): Если вы хотите использовать свои исторические данные вместо сгенерированных фиктивных, подготовьте CSV-файл с колонкой
'Close'(цена закрытия). Раскомментируйте и настройте секцию загрузки данных в блокеif __name__ == "__main__":, указав путь к вашему файлу в переменнойdata_path. -
Настройте параметры: Измените значения глобальных переменных в начале скрипта (
INITIAL_CAPITAL,INITIAL_BET_SIZE,MARTINGALE_MULTIPLIERи т.д.) в соответствии с вашими предпочтениями и условиями тестирования. -
Запустите скрипт: Откройте терминал или командную строку, перейдите в директорию, где сохранен файл, и выполните команду:
python martingale_backtester.py -
Проанализируйте результаты: Скрипт выведет в консоль результаты бэктеста (конечный капитал, процент выигрышей, максимальную просадку) и оценочный Risk of Ruin. Также будет построен график кривой капитала.
Дополнительные рекомендации:
-
Для более глубокого анализа распределения доходностей ваших исторических данных, что может быть полезно для уточнения параметров стратегии, обратитесь к статье Распределение доходностей и тест Шапиро-Уилка на Python.
-
Если вы хотите разработать более сложные сигналы для входа/выхода, которые могли бы улучшить процент выигрышей, рассмотрите методы, описанные в статье Поиск ценовых паттернов с помощью Dynamic Time Warping (DTW) в Pandas.
-
Понимание того, является ли рынок трендовым или флэтовым, может существенно повлиять на эффективность Мартингейла. Для этого может быть полезен Расчет показателя Херста (Hurst Exponent) для BTC/USDT на Python.




