Skip to content

Instantly share code, notes, and snippets.

@robcarver17
Last active December 1, 2025 03:18
Show Gist options
  • Select an option

  • Save robcarver17/ea197e15492e75ef345f496fd18454fb to your computer and use it in GitHub Desktop.

Select an option

Save robcarver17/ea197e15492e75ef345f496fd18454fb to your computer and use it in GitHub Desktop.
from typing import List, Dict
import matplotlib
import numpy as np
import pandas as pd
import random
## Change these values to your favourite prop firm challenge
#Firm number one
PROFIT_THRESHOLD = 0.2
DAILY_DD_LIMIT=-0.02
MIN_TRADING_DAYS = 30
COST_PER_MONTH = 300.0
RESET_FEE = 0
ACTIVATION_FEE = 0.0
YEARS_TO_GIVE_UP_AFTER = 1
MAX_DD = -1 ## not used
"""
# Firm number two
PROFIT_THRESHOLD = 0.06
DAILY_DD_LIMIT=-1 # not used
MIN_TRADING_DAYS = 0 ## not used
COST_PER_MONTH = 100.0
YEARS_TO_GIVE_UP_AFTER = 1
ACTIVATION_FEE = 130.0
RESET_FEE = 80.0
MAX_DD = -0.04
"""
## dont mess with these numbers
BUS_DAYS_IN_YEAR = 256
MONTHS_IN_YEAR = 12
BUS_DAYS_IN_MONTH = 22
DAYS_TO_GIVE_UP_AFTER = YEARS_TO_GIVE_UP_AFTER*BUS_DAYS_IN_YEAR
def generate_many_random_sequences_and_get_times(annual_SR: float, annual_vol: float,
bootstraps:int
):
annual_mean = annual_SR*annual_vol
daily_vol = annual_vol/16
daily_mean = annual_mean/256
list_of_costs = [cost_to_pass_test_with_resets(daily_mean=daily_mean, daily_vol=daily_vol) for __ in range(bootstraps)]
return list_of_costs
class TradeRecord:
def reset(self):
self.list_of_trades = []
def over_max_dd(self, max_dd):
return self.drawdown()<max_dd
def drawdown(self):
return list(drawdown(self.one_plus()).values)[-1]
def add_day(self, return_for_day: float):
list_of_trades = self.list_of_trades
list_of_trades.append(return_for_day)
self.list_of_trades = list_of_trades
self.add_day_to_last_cost()
def is_it_time_to_charge_cost(self, charge_every_n_days: int):
days_stored = self.days_since_last_cost_charged
if days_stored>=charge_every_n_days:
self.reset_days_since_last_cost()
return True
return False
def passed(self, profit_threshold: float, min_days: int):
return self.got_profits(profit_threshold) and self.got_days(min_days)
def got_profits(self, profit_threshold: float):
return self.total_profits()>profit_threshold
def total_profits(self):
cumprod = self.one_plus().cumprod()
return cumprod[len(cumprod)-1] - 1
def one_plus(self):
return pd.Series([1+daily_return for daily_return in self.list_of_trades])
def got_days(self, min_days: int):
return len(self.list_of_trades)>min_days
def reset_days_since_last_cost(self):
self.days_since_last_cost_charged = 0
def add_day_to_last_cost(self):
stored_days = self.days_since_last_cost_charged
stored_days+=1
self.days_since_last_cost_charged = stored_days
@property
def days_since_last_cost_charged(self):
stored_days = getattr(self, "_days_stored", 0)
return stored_days
@days_since_last_cost_charged.setter
def days_since_last_cost_charged(self, days: int):
self._days_stored = days
@property
def list_of_trades(self) -> List[float]:
return getattr(self, "_list_of_trades", [])
@list_of_trades.setter
def list_of_trades(self, list_of_trades: List[float]):
self._list_of_trades = list_of_trades
def cost_to_pass_test_with_resets(daily_mean: float, daily_vol: float):
cost = ACTIVATION_FEE + COST_PER_MONTH ## pay in advance
passed = False
trade_record = TradeRecord()
total_days_in_attempt = 0
while not passed:
total_days_in_attempt+=1
if total_days_in_attempt>DAYS_TO_GIVE_UP_AFTER:
break
day_return = random.gauss(daily_mean, daily_vol)
trade_record.add_day(day_return)
if day_return<DAILY_DD_LIMIT or trade_record.over_max_dd(MAX_DD):
trade_record.reset()
cost+=RESET_FEE
continue
time_to_charge = trade_record.is_it_time_to_charge_cost(BUS_DAYS_IN_MONTH)
if time_to_charge:
cost+=COST_PER_MONTH
if trade_record.passed(PROFIT_THRESHOLD, MIN_TRADING_DAYS):
passed = True
break
return dict(passed=passed, cost=cost)
def probability_of_passing(seqs: List[Dict]):
passed = [1 for seq in seqs if seq['passed']]
pass_count = len(passed)
prob_pass = pass_count / len(seqs)
return prob_pass
def drawdown(x: pd.Series):
maxx = x.expanding(min_periods=1).max()
return x - maxx
BOOSTRAPS = 500
list_of_SR = [.5, .75, 1, 1.25, 1.5, 2.0, 2.5, 3.0]
list_of_vol = list(np.arange(start=0.01, stop=1.0, step=0.01))
prob_pass_dict = {}
cost_dict = {}
for annual_SR in list_of_SR:
prob_pass_dict[annual_SR] = {}
cost_dict[annual_SR] = {}
for annual_vol in list_of_vol:
seqs = generate_many_random_sequences_and_get_times(annual_SR=annual_SR, annual_vol=annual_vol, bootstraps=BOOSTRAPS)
costs = [seq['cost'] for seq in seqs]
median_cost = -pd.Series(costs).median()
prob_pass_dict[annual_SR][annual_vol] = probability_of_passing(seqs)
cost_dict[annual_SR][annual_vol] = median_cost
prob_pass_df = pd.DataFrame(prob_pass_dict)
cost_df = pd.DataFrame(cost_dict)
prob_pass_df.plot()
matplotlib.pyplot.show(block=True)
cost_df.plot()
matplotlib.pyplot.show(block=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment