Last active
December 1, 2025 03:18
-
-
Save robcarver17/ea197e15492e75ef345f496fd18454fb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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