├── README.md ├── Trade.py ├── config_creator.py ├── sample_settings.ini └── tv_parser.py /README.md: -------------------------------------------------------------------------------- 1 | # TradingViewStrategyParser 2 | A simple python script to parse the trades from a TradingView strategy and determine the approprite leverage to use according to the Kelly Risk Management System 3 | 4 | ## The Kelly System 5 | To be used in conjunction with the [Kelly Criterion/Ratio](https://www.tradingview.com/script/bnpAXRtm-Kelly-Ratio/) 6 | 7 | ## Warning 8 | ### I am not a financial advisor, this is not investment advice, I'm not responsible for any money this will probably lose you. 9 | 10 | 11 | ## TradingView Strategy Settings and Trade Export 12 | The program assumes that in your strategy.entry() and strategy.close() functions the id parameter has no spaces 13 | 14 | 15 | >strategy.entry("Long", 1, when=longCondition) 16 | > 17 | >strategy.entry("Short", 0, when=shortCondition) 18 | > 19 | >strategy.close("Long", when=longClose) 20 | > 21 | >strategy.close("Short", when=shortClose) 22 | 23 | 24 | In the TradingView Strategy Tester panel click the [Export Button](https://i.imgur.com/m6oyxDH.png) as shown to copy the trade history of your strategy to the clipboard 25 | 26 | 27 | ## Settings 28 | 1. Rename the sample_settings.ini file to settings.ini and adjust your settings accordingly. 29 | 30 | 2. Create a trades.txt (or whatever you changed the settings to) file and copy the exported TradingView trade information into it 31 | 32 | ## Running 33 | Navigate to the folder and run 34 | 35 | >$ python tv_parser.py 36 | 37 | ### Sample Output 38 | 39 | >TradeInfo trade-file-path is trades.txt 40 | > 41 | >TradeInfo initial-capital is 500 42 | > 43 | >TradeInfo maker-fee is 0.16 44 | > 45 | >TradeInfo taker-fee is 0.26 46 | > 47 | >TradeInfo slippage is 0.02 48 | > 49 | >Use Limit Orders? [y/N]: n 50 | > 51 | > 52 | >Mean Returns: 1.91% 53 | > 54 | >Standard Deviation of Returns: 6.83% 55 | > 56 | > 57 | >Kelly Leverage: 4.10 58 | > 59 | >Half Kelly: 2.05 60 | > 61 | >Kelly Ratio: 21.61% 62 | > 63 | >Initial Capital: $500.00 64 | > 65 | >4:1 Leveraged Profit/Loss: $220.49 66 | > 67 | >4:1 Leveraged P/L Percent: 44.10% 68 | > 69 | > 70 | > 71 | > 72 | >Initial Capital: $500.00 73 | > 74 | >3:1 Leveraged Profit/Loss: $183.17 75 | > 76 | >3:1 Leveraged P/L Percent: 36.63% 77 | > 78 | > 79 | > 80 | > 81 | >Initial Capital: $500.00 82 | > 83 | >2:1 Leveraged Profit/Loss: $133.33 84 | > 85 | >2:1 Leveraged P/L Percent: 26.67% 86 | > 87 | > 88 | > 89 | > 90 | >Initial Capital: $500.00 91 | > 92 | >1:1 Leveraged Profit/Loss: $71.82 93 | > 94 | >1:1 Leveraged P/L Percent: 14.36% 95 | 96 | 97 | ### Disclaimer 98 | I would advise using the lowest possible leverage and slowly increase the leverage every month. Don't just increase after a few wins because chances are good that you are due for some losses. 99 | 100 | >Kelly Leverage: 8.73 101 | > 102 | >Half Kelly: 4.37 103 | > 104 | >Initial Capital: $500.00 105 | > 106 | > 107 | >Kelly Leverage: 8 108 | > 109 | >Kelly Leveraged Profit/Loss: $-290.58 110 | > 111 | >Kelly Leveraged P/L Percent: -58.12% 112 | > 113 | > 114 | > 115 | > 116 | >Half Kelly: 4 117 | > 118 | >Half Kelly Leveraged Profit/Loss: $1065.53 119 | > 120 | >Half Kelly Leveraged P/L Percent: 213.11% 121 | 122 | #### In the above example from an older version of the script you see that using the Full Kelly of 8 would have brought on large drawdown bringing the account close to ruin where as using the half Kelly we would have tripled our money using this specific strategy. 123 | 124 | 125 | #### Again I am not responsible for my program saying "Kelly Leverage: 95" and then you go and lose your house payment. 126 | 127 | ### Roadmap/Planned Features 128 | 1. Output monthly profits and losses 129 | 2. Leveraged backtesting for specific months 130 | 3. Back testing of multiple strategies with leverage 131 | 4. WebUI 132 | -------------------------------------------------------------------------------- /Trade.py: -------------------------------------------------------------------------------- 1 | class Trade: 2 | """ 3 | A class to represent a trade and holds all data related to opening and closing of an un-levered position 4 | """ 5 | def __init__(self, side, open_price, close_price): 6 | """ 7 | Constructor 8 | """ 9 | self.open_price = open_price 10 | self.close_price = close_price 11 | if side == 'Long': 12 | self.side = True 13 | else: 14 | self.side = False 15 | 16 | def get_profit_loss(self): 17 | if self.side: 18 | return (self.close_price - self.open_price) / self.open_price 19 | else: 20 | return (self.open_price - self.close_price) / self.open_price 21 | -------------------------------------------------------------------------------- /config_creator.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import os 3 | 4 | 5 | def create_config(path): 6 | """ 7 | Create a config file 8 | 9 | Ex Interpolation 10 | vars={"font": "Arial", "font_size": "100"} 11 | :param path: 12 | :return: 13 | """ 14 | config = configparser.ConfigParser() 15 | 16 | config.add_section("TradeInfo") 17 | config.set("TradeInfo", "trade-file-path", "trades.txt") 18 | config.set("TradeInfo", "initial-capital", "500") 19 | config.set("TradeInfo", "maker-fee", "0.16") 20 | config.set("TradeInfo", "taker-fee", "0.26") 21 | config.set("TradeInfo", "slippage", "0.02") 22 | 23 | with open(path, "w") as config_file: 24 | config.write(config_file) 25 | 26 | 27 | def get_config(path): 28 | """ 29 | Returns the config object 30 | :param path: 31 | :return: 32 | """ 33 | if not os.path.exists(path): 34 | create_config() 35 | 36 | config = configparser.ConfigParser() 37 | config.read(path) 38 | return config 39 | 40 | 41 | def get_setting(path, section, setting): 42 | """ 43 | Print out a setting 44 | :param path: 45 | :param section: 46 | :param setting: 47 | :return: 48 | """ 49 | config = get_config(path) 50 | value = config.get(section,setting) 51 | msg = "{section} {setting} is {value}".format( 52 | section=section, setting=setting, value=value 53 | ) 54 | 55 | print(msg) 56 | return value 57 | 58 | 59 | def update_setting(path, section, setting, value): 60 | """ 61 | Update a setting 62 | :param path: 63 | :param section: 64 | :param setting: 65 | :param value: 66 | :return: 67 | """ 68 | config = get_config(path) 69 | config.set(section, setting, value) 70 | with open(path, "w") as config_file: 71 | config.write(config_file) 72 | 73 | 74 | def delete_setting(path, section, setting): 75 | """ 76 | Delete a setting 77 | :param path: 78 | :param section: 79 | :param setting: 80 | :return: 81 | """ 82 | config = get_config(path) 83 | config.remove_option(section, setting) 84 | with open(path, "w") as config_file: 85 | config.write(config_file) 86 | 87 | 88 | if __name__ == "__main__": 89 | path = "./settings.ini" 90 | if not os.path.exists(path): 91 | create_config(path) -------------------------------------------------------------------------------- /sample_settings.ini: -------------------------------------------------------------------------------- 1 | [TradeInfo] 2 | trade-file-path = trades.txt 3 | initial-capital = 500 4 | maker-fee = 0.16 5 | taker-fee = 0.26 6 | slippage = 0.02 -------------------------------------------------------------------------------- /tv_parser.py: -------------------------------------------------------------------------------- 1 | import Trade 2 | import config_creator 3 | 4 | 5 | def parse_trades(trade_path): 6 | trade_list = [] 7 | with open(trade_path, 'r') as f: 8 | # Throw away first 36 lines 9 | for num in range(1, 37): 10 | f.readline() 11 | 12 | for line in f: 13 | open_data = line.split() 14 | 15 | side = open_data[2] 16 | 17 | if open_data[3] == 'Close': 18 | open_price = float(open_data[9]) 19 | else: 20 | open_price = float(open_data[6]) 21 | 22 | close_data = f.readline().split() 23 | 24 | if len(close_data) < 4: 25 | break 26 | 27 | if close_data[2] == 'Close': 28 | close_price = float(close_data[8]) 29 | else: 30 | close_price = float(close_data[5]) 31 | 32 | trade_list.append(Trade.Trade(side=side, open_price=open_price, close_price=close_price)) 33 | return trade_list 34 | 35 | 36 | def get_kelly_criterion(trade_list, fees_slippage): 37 | wins = [] 38 | losses = [] 39 | 40 | for t in trade_list: 41 | trade_profit_loss = t.get_profit_loss() - 2 * fees_slippage 42 | if trade_profit_loss > 0: # Profitable trade 43 | wins.append(trade_profit_loss) 44 | else: # Losing trade or break even 45 | losses.append(trade_profit_loss) 46 | 47 | number_of_wins = len(wins) 48 | number_of_losses = len(losses) 49 | probability_of_winning = number_of_wins / (number_of_wins + number_of_losses) 50 | 51 | sum_trades = 0 52 | for w in wins: 53 | sum_trades += w 54 | 55 | average_winning_trade = sum_trades / float(number_of_wins) 56 | 57 | sum_trades = 0 58 | for l in losses: 59 | sum_trades -= l 60 | 61 | average_losing_trade = sum_trades / float(number_of_losses) 62 | 63 | return probability_of_winning - ((1 - probability_of_winning) / (average_winning_trade / average_losing_trade)) 64 | 65 | 66 | def get_kelly_leverage(trade_list, fees_slippage): 67 | profit_sum = 0 68 | number_of_trades = len(trade_list) 69 | 70 | for t in trade_list: 71 | profit_sum = profit_sum + t.get_profit_loss() - (2 * fees_slippage) 72 | 73 | mean = profit_sum / number_of_trades 74 | squared_difference_sum = 0 75 | 76 | for t in trade_list: 77 | squared_difference_sum = squared_difference_sum + pow((t.get_profit_loss() - (2 * fees_slippage) - mean), 2) 78 | 79 | variance = squared_difference_sum / float(number_of_trades) 80 | 81 | print("\n\nMean Returns: " + '{0:.2f}'.format(100 * mean) + '%') 82 | print("Standard Deviation of Returns: " + '{0:.2f}'.format(100 * pow(variance, 0.5)) + '%') 83 | 84 | return mean / variance 85 | 86 | 87 | def leveraged_backtester(initial_capital, fees_slippage, leverage): 88 | trade_capital = initial_capital 89 | for i in trades: 90 | if trade_capital < 0: 91 | break 92 | order_size = trade_capital * leverage 93 | profit_loss = i.get_profit_loss() - (2 * fees_slippage) 94 | capital_gain_loss = order_size * profit_loss 95 | trade_capital = trade_capital + capital_gain_loss 96 | 97 | profit_loss_net = trade_capital - capital 98 | print("\n\nInitial Capital: $" + '{0:.2f}'.format(capital)) 99 | print(str(leverage) + ":1 Leveraged Profit/Loss: $" + '{0:.2f}'.format(profit_loss_net)) 100 | print(str(leverage) + ":1 Leveraged P/L Percent: " + '{0:.2f}'.format(100 * profit_loss_net / capital) + '%') 101 | 102 | 103 | if __name__ == "__main__": 104 | path = config_creator.get_setting("./settings.ini", 'TradeInfo', 'trade-file-path') 105 | capital = float(config_creator.get_setting("./settings.ini", 'TradeInfo', 'initial-capital')) 106 | 107 | maker_fee = config_creator.get_setting("./settings.ini", 'TradeInfo', 'maker-fee') 108 | taker_fee = config_creator.get_setting("./settings.ini", 'TradeInfo', 'taker-fee') 109 | 110 | slippage = float(config_creator.get_setting("./settings.ini", 'TradeInfo', 'slippage')) / 100 111 | 112 | use_limit_orders = input("Use Limit Orders? [y/N]: ") 113 | use_limit_orders = str.upper(use_limit_orders) 114 | if use_limit_orders[0] == 'Y': 115 | fee = float(maker_fee) / 100 116 | else: 117 | fee = float(taker_fee) / 100 118 | 119 | fees_and_slippage = fee + slippage 120 | 121 | trades = parse_trades(path) 122 | 123 | kelly_leverage = get_kelly_leverage(trades, fees_and_slippage) 124 | kelly_criterion = get_kelly_criterion(trades, fees_and_slippage) 125 | 126 | print("\n\nKelly Leverage: " + '{0:.2f}'.format(kelly_leverage)) 127 | print("Half Kelly: " + '{0:.2f}'.format(kelly_leverage / 2)) 128 | print("Kelly Ratio: " + '{0:.2f}'.format(100 * kelly_criterion) + '%') 129 | 130 | for i in range(int(kelly_leverage), 0, -1): 131 | leveraged_backtester(initial_capital=capital, fees_slippage=fees_and_slippage, leverage=i) 132 | 133 | --------------------------------------------------------------------------------