├── .gitignore ├── __pycache__ ├── rules.cpython-38.pyc ├── secret.cpython-38.pyc └── stream_data.cpython-38.pyc ├── caclulate_risk.txt ├── grid_bot.py ├── legacy_run.py ├── legacy_trade.log ├── rules.py ├── run.py ├── secret.py ├── start.py ├── stream_data.py └── trade.log /.gitignore: -------------------------------------------------------------------------------- 1 | secret.py -------------------------------------------------------------------------------- /__pycache__/rules.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CraigMariani/GridTradingBot/138583ae306c7f6b4eccd584f1b8740f28c7faa7/__pycache__/rules.cpython-38.pyc -------------------------------------------------------------------------------- /__pycache__/secret.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CraigMariani/GridTradingBot/138583ae306c7f6b4eccd584f1b8740f28c7faa7/__pycache__/secret.cpython-38.pyc -------------------------------------------------------------------------------- /__pycache__/stream_data.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CraigMariani/GridTradingBot/138583ae306c7f6b4eccd584f1b8740f28c7faa7/__pycache__/stream_data.cpython-38.pyc -------------------------------------------------------------------------------- /caclulate_risk.txt: -------------------------------------------------------------------------------- 1 | Risk = ticks at risk * tick value * position size 2 | 3 | Risk should be no more than 10 percent of the account 4 | Say we have $1000 and we risk $100 5 | 6 | Use the exmple below to calculate position size 7 | Position size is how we manage our risk 8 | We have five grid lines with a stop loss below the 5th line 9 | This means we have a stop loss at this percentage 10 | 11 | 100 = 600 * 1 * position_size 12 | 13 | total position size = 0.166 14 | 15 | -------------------------------------------------------------------------------- /grid_bot.py: -------------------------------------------------------------------------------- 1 | import alpaca_trade_api as tradeapi 2 | from stream_data import Stream_Data as stream 3 | from secret import Secret 4 | from rules import Rules 5 | 6 | 7 | ''' 8 | Algorithm 9 | Inital Set Up 10 | -Buy at the current price 11 | -Set the grid lines (5 above, 5 below) 12 | -Set stop loss (exit all positions at a loss, this will be below the 5th lowest level) 13 | -Set take profit (exit all position at a profit, this will be above the 5th highest level) 14 | 15 | Running 16 | -price hits the grid lines above it will sell one fifth of the current position 17 | -price hits the grid lines below it will buy one fifth of the current position 18 | -hit stop loss we exit all positions at a loss 19 | -hit take profit we exit all positions at a profit 20 | -After tp/sl is hit we go back to initial set up 21 | 22 | 23 | Example Output: 24 | {'S': 'BTCUSD', 25 | 'T': 'b', 26 | 'c': 29466.03, 27 | 'h': 29474.04, 28 | 'l': 29464.84, 29 | 'n': 149, 30 | 'o': 29472.95, 31 | 't': '2022-05-22T01:59:00Z', 32 | 'v': 2.15212065, 33 | 'vw': 29468.8181816825, 34 | 'x': 'CBSE'} 35 | ''' 36 | 37 | 38 | 39 | class Bot: 40 | 41 | def __init__(self): 42 | api_key = Secret.paper_api_key 43 | secret_key = Secret.paper_secret_key 44 | alp_base_url = 'https://paper-api.alpaca.markets' 45 | api = tradeapi.REST(api_key, secret_key, alp_base_url, api_version='v2') 46 | 47 | self.data_url = 'https://data.alpaca.markets/v1beta1/crypto' 48 | self.header = { 49 | 'APCA-API-KEY-ID' : Secret.paper_api_key, 50 | 'APCA-API-SECRET-KEY' : Secret.paper_secret_key} 51 | 52 | self.api_key = api_key 53 | self.secret_key = secret_key 54 | self.api = api 55 | 56 | def current_profit_loss(self): 57 | api = self.api 58 | account = api.get_account() 59 | 60 | # Check our current balance vs. our balance at the last market close 61 | balance_change = float(account.equity) - float(account.last_equity) 62 | print(f'Today\'s portfolio balance change: ${balance_change}') 63 | 64 | def start_bot(self): 65 | api = self.api 66 | s = stream() 67 | bars = s.bar_data() 68 | 69 | 70 | for _ in range(3): 71 | print(next(bars)) 72 | 73 | first_close = next(bars)['c'] 74 | r = Rules(first_close) 75 | 76 | account = api.get_account() 77 | available_funds = int(round(float(account.buying_power), 2)) 78 | print(available_funds) 79 | # position size is the total amount of our account that we are risking 80 | position_size = r.calculate_position_size(account_size=available_funds) 81 | print('position_size {}'.format(position_size)) 82 | 83 | # the amount of the position we buy or sell off at each line 84 | partial_position = position_size / 5 85 | 86 | # inital buy order 87 | print('First Purchase of {} at {}'.format(position_size, first_close)) 88 | api.submit_order( 89 | symbol='ETHUSD', 90 | side='buy', 91 | type='market', 92 | qty=position_size, 93 | ) 94 | # print('order executed') 95 | # set up grid lines 96 | stop_loss, take_profit, buy_lines, sell_lines = r.calculate_grid_lines() 97 | 98 | for bar in bars: 99 | close = bar['c'] 100 | 101 | if close in buy_lines: 102 | api.submit_order( 103 | symbol='ETHUSD', 104 | side='buy', 105 | type='market', 106 | qty=partial_position, 107 | ) 108 | 109 | if close in sell_lines: 110 | api.submit_order( 111 | symbol='ETHUSD', 112 | side='sell', 113 | type='market', 114 | qty=partial_position, 115 | ) 116 | 117 | if take_profit == close: 118 | print('exit all positions at profit') 119 | api.close_all_positions() 120 | b.current_profit_loss() 121 | b.start_bot() 122 | 123 | if stop_loss == close: 124 | print('exit all positions at a loss') 125 | api.close_all_positions() 126 | b.current_profit_loss() 127 | b.start_bot() 128 | 129 | 130 | if __name__ == '__main__': 131 | b = Bot() 132 | b.start_bot() -------------------------------------------------------------------------------- /legacy_run.py: -------------------------------------------------------------------------------- 1 | ''' 2 | For cron job this will be used every time we are entering trades 3 | 4 | 5 | crontab -e 6 | select 1 (nano) 7 | 0 */1 * * * (run every hour) 8 | 9 | ''' 10 | import alpaca_trade_api as tradeapi 11 | from stream_data import Stream_Data as stream 12 | from secret import Secret 13 | from rules import Rules 14 | from subprocess import call 15 | import pytz 16 | from datetime import datetime 17 | from datetime import timezone 18 | from datetime import timedelta 19 | 20 | class Run: 21 | def __init__(self): 22 | api_key = Secret.paper_api_key 23 | secret_key = Secret.paper_secret_key 24 | alp_base_url = 'https://paper-api.alpaca.markets' 25 | api = tradeapi.REST(api_key, secret_key, alp_base_url, api_version='v2') 26 | 27 | self.data_url = 'https://data.alpaca.markets/v1beta1/crypto' 28 | self.header = { 29 | 'APCA-API-KEY-ID' : Secret.paper_api_key, 30 | 'APCA-API-SECRET-KEY' : Secret.paper_secret_key} 31 | 32 | self.api_key = api_key 33 | self.secret_key = secret_key 34 | self.api = api 35 | 36 | def current_profit_loss(self): 37 | api = self.api 38 | account = api.get_account() 39 | 40 | # Check our current balance vs. our balance at the last market close 41 | balance_change = float(account.equity) - float(account.last_equity) 42 | print(f'Today\'s portfolio balance change: ${balance_change}') 43 | 44 | def main(self): 45 | s = stream() 46 | 47 | api = self.api 48 | bars = s.bar_data() 49 | for _ in range(3): 50 | print(next(bars)) 51 | 52 | first_close = next(bars)['c'] 53 | r = Rules(first_close) 54 | stop_loss, take_profit, buy_lines, sell_lines = r.calculate_grid_lines() 55 | account = api.get_account() 56 | available_funds = int(round(float(account.buying_power), 2)) 57 | print(available_funds) 58 | # position size is the total amount of our account that we are risking 59 | position_size = r.calculate_position_size(account_size=available_funds) 60 | print('position_size {}'.format(position_size)) 61 | 62 | # the amount of the position we buy or sell off at each line 63 | partial_position = position_size / 5 64 | 65 | # get the current time in PST 66 | PST_tz = timezone(timedelta(hours=-8)) 67 | 68 | for bar in bars: 69 | close = bar['c'] 70 | print('close: {}'.format(close)) 71 | print('bar: {}'.format(bar)) 72 | print('buy_lines {}'.format(buy_lines)) 73 | print('sell_lines {}'.format(sell_lines)) 74 | 75 | 76 | # problem: I suspect price is moving and the grid lines are not detecting the price change 77 | # solution: confirm theory by checking trade log after running script 78 | # we might need to see where the buy and sell lines are in relation to closing price 79 | # right now the lines are more focused if the price hits it, price may never be the exact same 80 | # as those lines, use a greater/less than or equal to in order to catch these movements 81 | if close in buy_lines: 82 | print('Long ETHUSD {} : {}'.format(partial_position, datetime.now(PST_tz))) 83 | api.submit_order( 84 | symbol='ETHUSD', 85 | side='buy', 86 | type='market', 87 | qty=partial_position, 88 | ) 89 | 90 | if close in sell_lines: 91 | print('Short ETHUSD {} : {}'.format(partial_position, datetime.now(PST_tz))) 92 | api.submit_order( 93 | symbol='ETHUSD', 94 | side='sell', 95 | type='market', 96 | qty=partial_position, 97 | ) 98 | 99 | if take_profit == close: 100 | print('exit all positions at profit') 101 | api.close_all_positions() 102 | ru.current_profit_loss() 103 | call(["python3", "start.py"]) 104 | 105 | if stop_loss == close: 106 | print('exit all positions at a loss') 107 | api.close_all_positions() 108 | ru.current_profit_loss() 109 | call(["python3", "start.py"]) 110 | print('No Purchase {}'.format(datetime.now(PST_tz))) 111 | 112 | 113 | 114 | if __name__ == '__main__': 115 | ru = Run() 116 | ru.main() 117 | -------------------------------------------------------------------------------- /rules.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Define 3 | -number of grid lines 4 | -grid size (space between lines) 5 | -stop loss (5x position size) 6 | -take profits (5x position size) 7 | -position size 8 | ''' 9 | 10 | class Rules: 11 | 12 | def __init__(self, current_price) -> None: 13 | self.line_count = 5 14 | self.grid_space = 50 15 | self.current_price = current_price 16 | 17 | def calculate_grid_lines(self): 18 | line_count = self.line_count 19 | grid_space = self.grid_space 20 | price = self.current_price 21 | 22 | stop_loss = price - ((line_count + 1) * grid_space) 23 | take_profit = price + ((line_count + 1) * grid_space) 24 | buy_lines = [] 25 | sell_lines = [] 26 | for i in range(line_count): 27 | buy_lines.append(price - ((i * grid_space))) 28 | sell_lines.append(price + ((i * grid_space))) 29 | 30 | print('sl {}'.format(stop_loss)) 31 | print('tp {}'.format(take_profit)) 32 | print('bl {}'.format(buy_lines)) 33 | print('sl {}'.format(sell_lines)) 34 | 35 | return stop_loss, take_profit, buy_lines, sell_lines 36 | 37 | 38 | # we want to only risk no more than 10 percent of our account 39 | # when the stoploss is triggered we should only lose 10 percent 40 | # write the code so it does this 41 | def calculate_position_size(self, account_size): 42 | line_count = self.line_count 43 | grid_space = self.grid_space 44 | 45 | risk = account_size * .1 46 | tick_value = 1 47 | ticks_at_risk = (line_count + 1) * grid_space 48 | position_size = risk / (ticks_at_risk * tick_value) 49 | position_size = round(position_size, 2) 50 | return position_size -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | ''' 2 | For cron job this will be used every time we are entering trades 3 | 4 | 5 | crontab -e 6 | select 1 (nano) 7 | 0 */1 * * * (run every hour) 8 | 9 | ''' 10 | import alpaca_trade_api as tradeapi 11 | from stream_data import Stream_Data as stream 12 | from secret import Secret 13 | from rules import Rules 14 | from subprocess import call 15 | import pytz 16 | from datetime import datetime 17 | from datetime import timezone 18 | from datetime import timedelta 19 | 20 | class Run: 21 | def __init__(self): 22 | api_key = Secret.paper_api_key 23 | secret_key = Secret.paper_secret_key 24 | alp_base_url = 'https://paper-api.alpaca.markets' 25 | api = tradeapi.REST(api_key, secret_key, alp_base_url, api_version='v2') 26 | 27 | self.data_url = 'https://data.alpaca.markets/v1beta1/crypto' 28 | self.header = { 29 | 'APCA-API-KEY-ID' : Secret.paper_api_key, 30 | 'APCA-API-SECRET-KEY' : Secret.paper_secret_key} 31 | 32 | self.api_key = api_key 33 | self.secret_key = secret_key 34 | self.api = api 35 | 36 | def current_profit_loss(self): 37 | api = self.api 38 | account = api.get_account() 39 | 40 | # Check our current balance vs. our balance at the last market close 41 | balance_change = float(account.equity) - float(account.last_equity) 42 | print(f'Today\'s portfolio balance change: ${balance_change}') 43 | 44 | def main(self): 45 | s = stream() 46 | 47 | api = self.api 48 | bars = s.bar_data() 49 | for _ in range(3): 50 | print(next(bars)) 51 | 52 | first_close = next(bars)['c'] 53 | r = Rules(first_close) 54 | stop_loss, take_profit, buy_lines, sell_lines = r.calculate_grid_lines() 55 | account = api.get_account() 56 | available_funds = int(round(float(account.buying_power), 2)) 57 | print(available_funds) 58 | # position size is the total amount of our account that we are risking 59 | position_size = r.calculate_position_size(account_size=available_funds) 60 | print('position_size {}'.format(position_size)) 61 | 62 | # the amount of the position we buy or sell off at each line 63 | partial_position = position_size / 5 64 | 65 | # get the current time in PST 66 | PST_tz = timezone(timedelta(hours=-8)) 67 | 68 | current_position = [] 69 | 70 | for bar in bars: 71 | close = bar['c'] 72 | print('close: {}'.format(close)) 73 | # print('bar: {}'.format(bar)) 74 | print('buy_lines {}'.format(buy_lines)) 75 | print('sell_lines {}'.format(sell_lines)) 76 | 77 | 78 | # problem: I suspect price is moving and the grid lines are not detecting the price change 79 | # solution: confirm theory by checking trade log after running script 80 | # we might need to see where the buy and sell lines are in relation to closing price 81 | # right now the lines are more focused if the price hits it, price may never be the exact same 82 | # as those lines, use a greater/less than or equal to in order to catch these movements 83 | for i in range(1, len(buy_lines)): 84 | # price is in between these buy lines we buy 85 | if close <= buy_lines[i-1] and close >= buy_lines[i]: 86 | if buy_lines[i-1] in current_position and buy_lines[i] in current_position: 87 | print('already bought') 88 | break 89 | current_position.clear() 90 | current_position.append(buy_lines[i]) 91 | current_position.append(buy_lines[i-1]) 92 | 93 | print('Long ETHUSD {} : {}'.format(partial_position, datetime.now(PST_tz))) 94 | api.submit_order( 95 | symbol='ETHUSD', 96 | side='buy', 97 | type='market', 98 | qty=partial_position, 99 | ) 100 | for i in range(1, len(sell_lines)): 101 | # price is in between these sell lines we sell 102 | if close >= sell_lines[i-1] and close <= sell_lines[i]: 103 | if sell_lines[i-1] in current_position and sell_lines[i]: 104 | print('already sold') 105 | break 106 | current_position.clear() 107 | current_position.append(sell_lines[i]) 108 | current_position.append(sell_lines[i-1]) 109 | 110 | print('Short ETHUSD {} : {}'.format(partial_position, datetime.now(PST_tz))) 111 | api.submit_order( 112 | symbol='ETHUSD', 113 | side='sell', 114 | type='market', 115 | qty=partial_position, 116 | ) 117 | 118 | if close >= take_profit: 119 | print('exit all positions at profit') 120 | api.close_all_positions() 121 | ru.current_profit_loss() 122 | call(["python3", "start.py"]) 123 | 124 | if close <= stop_loss: 125 | print('exit all positions at a loss') 126 | api.close_all_positions() 127 | ru.current_profit_loss() 128 | call(["python3", "start.py"]) 129 | print('No Purchase {}'.format(datetime.now(PST_tz))) 130 | 131 | 132 | 133 | if __name__ == '__main__': 134 | ru = Run() 135 | ru.main() 136 | -------------------------------------------------------------------------------- /secret.py: -------------------------------------------------------------------------------- 1 | # holds our API keys 2 | class Secret: 3 | paper_api_key = '' 4 | paper_secret_key = '' 5 | -------------------------------------------------------------------------------- /start.py: -------------------------------------------------------------------------------- 1 | ''' 2 | For initial position of trading bot, run this at the start 3 | ''' 4 | 5 | 6 | from secret import Secret 7 | import alpaca_trade_api as tradeapi 8 | from stream_data import Stream_Data as stream 9 | from rules import Rules 10 | 11 | class Start: 12 | def __init__(self): 13 | api_key = Secret.paper_api_key 14 | secret_key = Secret.paper_secret_key 15 | alp_base_url = 'https://paper-api.alpaca.markets' 16 | api = tradeapi.REST(api_key, secret_key, alp_base_url, api_version='v2') 17 | 18 | self.data_url = 'https://data.alpaca.markets/v1beta1/crypto' 19 | self.header = { 20 | 'APCA-API-KEY-ID' : Secret.paper_api_key, 21 | 'APCA-API-SECRET-KEY' : Secret.paper_secret_key} 22 | 23 | self.api_key = api_key 24 | self.secret_key = secret_key 25 | self.api = api 26 | 27 | def current_profit_loss(self): 28 | api = self.api 29 | account = api.get_account() 30 | 31 | # Check our current balance vs. our balance at the last market close 32 | balance_change = float(account.equity) - float(account.last_equity) 33 | print(f'Today\'s portfolio balance change: ${balance_change}') 34 | 35 | def start_bot(self): 36 | api = self.api 37 | s = stream() 38 | bars = s.bar_data() 39 | 40 | 41 | 42 | for _ in range(3): 43 | print(next(bars)) 44 | 45 | first_close = next(bars)['c'] 46 | r = Rules(first_close) 47 | 48 | account = api.get_account() 49 | available_funds = int(round(float(account.buying_power), 2)) 50 | print(available_funds) 51 | # position size is the total amount of our account that we are risking 52 | position_size = r.calculate_position_size(account_size=available_funds) 53 | print('position_size {}'.format(position_size)) 54 | 55 | # inital buy order 56 | print('First Purchase of {} at {}'.format(position_size, first_close)) 57 | 58 | api.submit_order( 59 | symbol='ETHUSD', 60 | side='buy', 61 | type='market', 62 | qty=position_size, 63 | ) 64 | 65 | 66 | if __name__ == '__main__': 67 | s = Start() 68 | s.start_bot() -------------------------------------------------------------------------------- /stream_data.py: -------------------------------------------------------------------------------- 1 | from websocket import create_connection 2 | import simplejson as json 3 | from secret import Secret 4 | 5 | 6 | class Stream_Data: 7 | 8 | def __init__(self) -> None: 9 | self.url = 'wss://stream.data.alpaca.markets/v1beta1/crypto?exchanges=CBSE' 10 | 11 | def bar_data(self): 12 | 13 | 14 | ws = create_connection(self.url) 15 | # print(json.loads(ws.recv())) # print the connection message 16 | 17 | auth_message = {"action":"auth","key": Secret.paper_api_key, "secret": Secret.paper_secret_key} 18 | ws.send(json.dumps(auth_message)) 19 | 20 | 21 | subscription = {"action":"subscribe","bars":["ETHUSD"]} 22 | ws.send(json.dumps(subscription)) 23 | # print(json.loads(ws.recv())) # print the authentication message 24 | 25 | while True: 26 | data = json.loads(ws.recv()) 27 | 28 | # return a generator that we will loop through 29 | yield data[0] 30 | # print(data) 31 | 32 | s = Stream_Data() 33 | data = s.bar_data 34 | print(data) --------------------------------------------------------------------------------