app.backbone.utils.montecarlo_utils

  1import numpy as np
  2import pandas as pd
  3
  4def max_drawdown(equity_curve):
  5    running_max = np.maximum.accumulate(equity_curve)
  6    drawdown = (equity_curve - running_max) / running_max
  7    return np.min(drawdown) * 100  # Convertir el drawdown a porcentaje
  8
  9
 10def montecarlo_statistics_simulation(
 11    trade_history,
 12    equity_curve,
 13    n_simulations,
 14    initial_equity,
 15    threshold_ruin=0.85,
 16    return_raw_curves=False,
 17    percentiles=None,
 18):
 19
 20    # Renombro las columnas
 21
 22    trade_history = trade_history.rename(columns={"ExitTime": "Date"})
 23    trade_history = trade_history[["Date", "NetPnL"]]
 24
 25    equity_curve = (
 26        equity_curve.reset_index()
 27        .rename(columns={"index": "Date"})[["Date", "Equity"]]
 28        .sort_values(by="Date")
 29    )
 30
 31    trade_history["Date"] = pd.to_datetime(trade_history["Date"])
 32    equity_curve["Date"] = pd.to_datetime(equity_curve["Date"])
 33
 34    # joineo los dfs por fechas
 35
 36    full_df = pd.merge(equity_curve, trade_history, on="Date", how="left")
 37
 38    full_df = full_df[~full_df["NetPnL"].isna()]
 39
 40    # Porcentaje de ganancia
 41
 42    full_df["pct"] = full_df["NetPnL"] / full_df["Equity"].shift(1)
 43
 44    # Parámetros iniciales
 45
 46    n_steps = len(trade_history)
 47    mean_return = full_df["pct"].mean()
 48    std_return = full_df["pct"].std()
 49
 50    drawdowns_pct = []  # Lista para almacenar los drawdowns en porcentaje
 51    final_returns_pct = []  # Lista para almacenar los retornos finales en porcentaje
 52    ruin_count = 0  # Contador de simulaciones que alcanzan la ruina
 53    ruin_threshold = (
 54        initial_equity * threshold_ruin
 55    )  # Umbral de ruina en términos de equidad
 56
 57    # Simulaciones de Montecarlo
 58
 59    for _ in range(n_simulations):
 60        # Generar retornos aleatorios con media y desviación estándar de los históricos
 61
 62        random_returns = mean_return + std_return * np.random.standard_t(15, size=n_steps)
 63
 64        # Calcular la curva de equidad acumulada
 65
 66        synthetic_equity_curve = initial_equity * np.cumprod(1 + random_returns)
 67
 68        # Calcular drawdown y almacenarlo en porcentaje
 69
 70        dd_pct = max_drawdown(synthetic_equity_curve)
 71        drawdowns_pct.append(dd_pct)
 72
 73        # Calcular el retorno acumulado porcentual y almacenarlo
 74
 75        final_return_pct = (
 76            synthetic_equity_curve[-1] / initial_equity - 1
 77        ) * 100  # Retorno final en porcentaje
 78        final_returns_pct.append(final_return_pct)
 79
 80        # Verificar si la equidad cae por debajo del umbral de ruina en algún punto
 81
 82        if np.any(synthetic_equity_curve <= ruin_threshold):
 83            ruin_count += 1
 84    # Crear un DataFrame separado para los drawdowns y los retornos acumulados en porcentaje
 85
 86    df_drawdowns = pd.DataFrame({"Drawdown (%)": drawdowns_pct})
 87    df_final_returns_pct = pd.DataFrame({"Final Return (%)": final_returns_pct})
 88
 89    # Calcular las estadísticas usando df.describe() para cada DataFrame
 90
 91    if not percentiles:
 92        drawdown_stats = df_drawdowns.describe()
 93        return_stats = df_final_returns_pct.describe()
 94    else:
 95        drawdown_stats = df_drawdowns.describe(percentiles=percentiles)
 96        return_stats = df_final_returns_pct.describe(percentiles=percentiles)
 97    # Calcular el riesgo de ruina
 98
 99    risk_of_ruin = ruin_count / n_simulations
100
101    # Agregar el riesgo de ruina a las estadísticas de drawdown
102
103    drawdown_stats.loc["Risk of Ruin"] = risk_of_ruin
104
105    # Combinar las métricas de drawdowns y retornos porcentuales
106
107    combined_stats = pd.concat([drawdown_stats, return_stats], axis=1)
108    if return_raw_curves:
109        return combined_stats, df_drawdowns, df_final_returns_pct
110    
111    return combined_stats
112
113def monte_carlo_simulation_v2(
114    equity_curve,
115    trade_history,
116    n_simulations,
117    initial_equity,
118    threshold_ruin,
119    return_raw_curves,
120    percentiles=[0.1, 0.25, 0.5, 0.75, 0.9]
121):
122    """
123    Simulación de Monte Carlo para un sistema de trading con distribución basada en probabilidades de trades.
124
125    Args:
126        df (pd.DataFrame): DataFrame con columnas 'NetPnL' (Profit and Loss) y 'Type' ('long' o 'short').
127        equity_start (float): Valor inicial del equity.
128        num_simulations (int): Número de simulaciones a realizar.
129        threshold (float): Umbral para calcular el riesgo de ruina.
130
131    Returns:
132        dict: Resultados estadísticos de las simulaciones, incluyendo drawdowns y retornos.
133    """
134    # Filtrar trades por tipo y resultados
135    trade_history.ReturnPct = trade_history.ReturnPct / 100
136    
137    long_trades = trade_history[trade_history['Size'] > 0]
138    short_trades = trade_history[trade_history['Size'] < 0]
139    
140    long_winning_trades = long_trades[long_trades['NetPnL'] > 0]
141    short_winning_trades = short_trades[short_trades['NetPnL'] > 0]
142    long_losing_trades = long_trades[long_trades['NetPnL'] <= 0]
143    short_losing_trades = short_trades[short_trades['NetPnL'] <= 0]
144
145    # Calcular estadísticas para trades
146    prob_trade = len(trade_history) / len(equity_curve)  # Probabilidad de realizar un trade
147    prob_long = len(long_trades) / len(trade_history) if len(trade_history) > 0 else 0
148    prob_short = len(short_trades) / len(trade_history) if len(trade_history) > 0 else 0
149    prob_long_winner = len(long_winning_trades) / len(long_trades) if len(long_trades) > 0 else 0
150    prob_short_winner = len(short_winning_trades) / len(short_trades) if len(short_trades) > 0 else 0
151    
152    long_win_mean, long_win_std = long_winning_trades['ReturnPct'].mean(), long_winning_trades['ReturnPct'].std()
153    long_loss_mean, long_loss_std = long_losing_trades['ReturnPct'].mean(), long_losing_trades['ReturnPct'].std()
154    short_win_mean, short_win_std = short_winning_trades['ReturnPct'].mean(), short_winning_trades['ReturnPct'].std()
155    short_loss_mean, short_loss_std = short_losing_trades['ReturnPct'].mean(), short_losing_trades['ReturnPct'].std()
156
157    # Inicializar resultados
158    equity_curves = []
159    drawdowns = []
160    returns = []
161
162    ruin_count = 0
163    for _ in range(n_simulations):
164        equity = [initial_equity]  # Curva de equity inicial
165
166        for _ in range(len(equity_curve)):
167            # Decidir si se realiza un trade
168            if np.random.rand() < prob_trade:
169                # Decidir si es long o short
170                if np.random.rand() < prob_long:
171                    # Decidir si el long es ganador o perdedor
172                    if np.random.rand() < prob_long_winner:
173                        trade = np.random.normal(long_win_mean, long_win_std)
174                    else:
175                        trade = np.random.normal(long_loss_mean, long_loss_std)
176                else:
177                    # Decidir si el short es ganador o perdedor
178                    if np.random.rand() < prob_short_winner:
179                        trade = np.random.normal(short_win_mean, short_win_std)
180                    else:
181                        trade = np.random.normal(short_loss_mean, short_loss_std)
182            else:
183                trade = 0  # No se realiza trade
184
185            # Actualizar la curva de equity
186            equity.append(equity[-1] +  equity[-1] * trade)
187
188        # Calcular drawdown
189        peak = np.maximum.accumulate(equity)
190        dd = (equity - peak) / peak * 100 # Drawdown en porcentaje
191
192        # Calcular retorno final
193        ret = ((equity[-1] - initial_equity) / initial_equity) * 100  # Retorno en porcentaje
194        
195        if np.any(np.array(equity) <= initial_equity * threshold_ruin):
196            ruin_count += 1
197
198        # Guardar resultados
199        equity_curves.append(equity)
200        drawdowns.append(dd.min())  # Máximo drawdown
201        returns.append(ret)
202
203    df_drawdowns = pd.DataFrame({"Drawdown (%)": drawdowns})
204    df_final_returns_pct = pd.DataFrame({"Final Return (%)": returns})
205
206    # Calcular las estadísticas usando df.describe() para cada DataFrame
207
208    if not percentiles:
209        drawdown_stats = df_drawdowns.describe()
210        return_stats = df_final_returns_pct.describe()
211    else:
212        drawdown_stats = df_drawdowns.describe(percentiles=percentiles)
213        return_stats = df_final_returns_pct.describe(percentiles=percentiles)
214
215    # Calcular el riesgo de ruina
216
217    risk_of_ruin = (ruin_count / n_simulations) * 100
218    drawdown_stats.loc["Risk of Ruin"] = risk_of_ruin
219
220    # Combinar las métricas de drawdowns y retornos porcentuales
221
222    combined_stats = pd.concat([drawdown_stats, return_stats], axis=1)
223    if return_raw_curves:
224        return combined_stats, df_drawdowns, df_final_returns_pct
225    
226    return combined_stats
def max_drawdown(equity_curve):
5def max_drawdown(equity_curve):
6    running_max = np.maximum.accumulate(equity_curve)
7    drawdown = (equity_curve - running_max) / running_max
8    return np.min(drawdown) * 100  # Convertir el drawdown a porcentaje
def montecarlo_statistics_simulation( trade_history, equity_curve, n_simulations, initial_equity, threshold_ruin=0.85, return_raw_curves=False, percentiles=None):
 11def montecarlo_statistics_simulation(
 12    trade_history,
 13    equity_curve,
 14    n_simulations,
 15    initial_equity,
 16    threshold_ruin=0.85,
 17    return_raw_curves=False,
 18    percentiles=None,
 19):
 20
 21    # Renombro las columnas
 22
 23    trade_history = trade_history.rename(columns={"ExitTime": "Date"})
 24    trade_history = trade_history[["Date", "NetPnL"]]
 25
 26    equity_curve = (
 27        equity_curve.reset_index()
 28        .rename(columns={"index": "Date"})[["Date", "Equity"]]
 29        .sort_values(by="Date")
 30    )
 31
 32    trade_history["Date"] = pd.to_datetime(trade_history["Date"])
 33    equity_curve["Date"] = pd.to_datetime(equity_curve["Date"])
 34
 35    # joineo los dfs por fechas
 36
 37    full_df = pd.merge(equity_curve, trade_history, on="Date", how="left")
 38
 39    full_df = full_df[~full_df["NetPnL"].isna()]
 40
 41    # Porcentaje de ganancia
 42
 43    full_df["pct"] = full_df["NetPnL"] / full_df["Equity"].shift(1)
 44
 45    # Parámetros iniciales
 46
 47    n_steps = len(trade_history)
 48    mean_return = full_df["pct"].mean()
 49    std_return = full_df["pct"].std()
 50
 51    drawdowns_pct = []  # Lista para almacenar los drawdowns en porcentaje
 52    final_returns_pct = []  # Lista para almacenar los retornos finales en porcentaje
 53    ruin_count = 0  # Contador de simulaciones que alcanzan la ruina
 54    ruin_threshold = (
 55        initial_equity * threshold_ruin
 56    )  # Umbral de ruina en términos de equidad
 57
 58    # Simulaciones de Montecarlo
 59
 60    for _ in range(n_simulations):
 61        # Generar retornos aleatorios con media y desviación estándar de los históricos
 62
 63        random_returns = mean_return + std_return * np.random.standard_t(15, size=n_steps)
 64
 65        # Calcular la curva de equidad acumulada
 66
 67        synthetic_equity_curve = initial_equity * np.cumprod(1 + random_returns)
 68
 69        # Calcular drawdown y almacenarlo en porcentaje
 70
 71        dd_pct = max_drawdown(synthetic_equity_curve)
 72        drawdowns_pct.append(dd_pct)
 73
 74        # Calcular el retorno acumulado porcentual y almacenarlo
 75
 76        final_return_pct = (
 77            synthetic_equity_curve[-1] / initial_equity - 1
 78        ) * 100  # Retorno final en porcentaje
 79        final_returns_pct.append(final_return_pct)
 80
 81        # Verificar si la equidad cae por debajo del umbral de ruina en algún punto
 82
 83        if np.any(synthetic_equity_curve <= ruin_threshold):
 84            ruin_count += 1
 85    # Crear un DataFrame separado para los drawdowns y los retornos acumulados en porcentaje
 86
 87    df_drawdowns = pd.DataFrame({"Drawdown (%)": drawdowns_pct})
 88    df_final_returns_pct = pd.DataFrame({"Final Return (%)": final_returns_pct})
 89
 90    # Calcular las estadísticas usando df.describe() para cada DataFrame
 91
 92    if not percentiles:
 93        drawdown_stats = df_drawdowns.describe()
 94        return_stats = df_final_returns_pct.describe()
 95    else:
 96        drawdown_stats = df_drawdowns.describe(percentiles=percentiles)
 97        return_stats = df_final_returns_pct.describe(percentiles=percentiles)
 98    # Calcular el riesgo de ruina
 99
100    risk_of_ruin = ruin_count / n_simulations
101
102    # Agregar el riesgo de ruina a las estadísticas de drawdown
103
104    drawdown_stats.loc["Risk of Ruin"] = risk_of_ruin
105
106    # Combinar las métricas de drawdowns y retornos porcentuales
107
108    combined_stats = pd.concat([drawdown_stats, return_stats], axis=1)
109    if return_raw_curves:
110        return combined_stats, df_drawdowns, df_final_returns_pct
111    
112    return combined_stats
def monte_carlo_simulation_v2( equity_curve, trade_history, n_simulations, initial_equity, threshold_ruin, return_raw_curves, percentiles=[0.1, 0.25, 0.5, 0.75, 0.9]):
114def monte_carlo_simulation_v2(
115    equity_curve,
116    trade_history,
117    n_simulations,
118    initial_equity,
119    threshold_ruin,
120    return_raw_curves,
121    percentiles=[0.1, 0.25, 0.5, 0.75, 0.9]
122):
123    """
124    Simulación de Monte Carlo para un sistema de trading con distribución basada en probabilidades de trades.
125
126    Args:
127        df (pd.DataFrame): DataFrame con columnas 'NetPnL' (Profit and Loss) y 'Type' ('long' o 'short').
128        equity_start (float): Valor inicial del equity.
129        num_simulations (int): Número de simulaciones a realizar.
130        threshold (float): Umbral para calcular el riesgo de ruina.
131
132    Returns:
133        dict: Resultados estadísticos de las simulaciones, incluyendo drawdowns y retornos.
134    """
135    # Filtrar trades por tipo y resultados
136    trade_history.ReturnPct = trade_history.ReturnPct / 100
137    
138    long_trades = trade_history[trade_history['Size'] > 0]
139    short_trades = trade_history[trade_history['Size'] < 0]
140    
141    long_winning_trades = long_trades[long_trades['NetPnL'] > 0]
142    short_winning_trades = short_trades[short_trades['NetPnL'] > 0]
143    long_losing_trades = long_trades[long_trades['NetPnL'] <= 0]
144    short_losing_trades = short_trades[short_trades['NetPnL'] <= 0]
145
146    # Calcular estadísticas para trades
147    prob_trade = len(trade_history) / len(equity_curve)  # Probabilidad de realizar un trade
148    prob_long = len(long_trades) / len(trade_history) if len(trade_history) > 0 else 0
149    prob_short = len(short_trades) / len(trade_history) if len(trade_history) > 0 else 0
150    prob_long_winner = len(long_winning_trades) / len(long_trades) if len(long_trades) > 0 else 0
151    prob_short_winner = len(short_winning_trades) / len(short_trades) if len(short_trades) > 0 else 0
152    
153    long_win_mean, long_win_std = long_winning_trades['ReturnPct'].mean(), long_winning_trades['ReturnPct'].std()
154    long_loss_mean, long_loss_std = long_losing_trades['ReturnPct'].mean(), long_losing_trades['ReturnPct'].std()
155    short_win_mean, short_win_std = short_winning_trades['ReturnPct'].mean(), short_winning_trades['ReturnPct'].std()
156    short_loss_mean, short_loss_std = short_losing_trades['ReturnPct'].mean(), short_losing_trades['ReturnPct'].std()
157
158    # Inicializar resultados
159    equity_curves = []
160    drawdowns = []
161    returns = []
162
163    ruin_count = 0
164    for _ in range(n_simulations):
165        equity = [initial_equity]  # Curva de equity inicial
166
167        for _ in range(len(equity_curve)):
168            # Decidir si se realiza un trade
169            if np.random.rand() < prob_trade:
170                # Decidir si es long o short
171                if np.random.rand() < prob_long:
172                    # Decidir si el long es ganador o perdedor
173                    if np.random.rand() < prob_long_winner:
174                        trade = np.random.normal(long_win_mean, long_win_std)
175                    else:
176                        trade = np.random.normal(long_loss_mean, long_loss_std)
177                else:
178                    # Decidir si el short es ganador o perdedor
179                    if np.random.rand() < prob_short_winner:
180                        trade = np.random.normal(short_win_mean, short_win_std)
181                    else:
182                        trade = np.random.normal(short_loss_mean, short_loss_std)
183            else:
184                trade = 0  # No se realiza trade
185
186            # Actualizar la curva de equity
187            equity.append(equity[-1] +  equity[-1] * trade)
188
189        # Calcular drawdown
190        peak = np.maximum.accumulate(equity)
191        dd = (equity - peak) / peak * 100 # Drawdown en porcentaje
192
193        # Calcular retorno final
194        ret = ((equity[-1] - initial_equity) / initial_equity) * 100  # Retorno en porcentaje
195        
196        if np.any(np.array(equity) <= initial_equity * threshold_ruin):
197            ruin_count += 1
198
199        # Guardar resultados
200        equity_curves.append(equity)
201        drawdowns.append(dd.min())  # Máximo drawdown
202        returns.append(ret)
203
204    df_drawdowns = pd.DataFrame({"Drawdown (%)": drawdowns})
205    df_final_returns_pct = pd.DataFrame({"Final Return (%)": returns})
206
207    # Calcular las estadísticas usando df.describe() para cada DataFrame
208
209    if not percentiles:
210        drawdown_stats = df_drawdowns.describe()
211        return_stats = df_final_returns_pct.describe()
212    else:
213        drawdown_stats = df_drawdowns.describe(percentiles=percentiles)
214        return_stats = df_final_returns_pct.describe(percentiles=percentiles)
215
216    # Calcular el riesgo de ruina
217
218    risk_of_ruin = (ruin_count / n_simulations) * 100
219    drawdown_stats.loc["Risk of Ruin"] = risk_of_ruin
220
221    # Combinar las métricas de drawdowns y retornos porcentuales
222
223    combined_stats = pd.concat([drawdown_stats, return_stats], axis=1)
224    if return_raw_curves:
225        return combined_stats, df_drawdowns, df_final_returns_pct
226    
227    return combined_stats

Simulación de Monte Carlo para un sistema de trading con distribución basada en probabilidades de trades.

Args: df (pd.DataFrame): DataFrame con columnas 'NetPnL' (Profit and Loss) y 'Type' ('long' o 'short'). equity_start (float): Valor inicial del equity. num_simulations (int): Número de simulaciones a realizar. threshold (float): Umbral para calcular el riesgo de ruina.

Returns: dict: Resultados estadísticos de las simulaciones, incluyendo drawdowns y retornos.