├── .gitignore ├── flow.jpg ├── main.py ├── requirements.txt ├── server.py ├── websocket-simple.py └── websocket-subscribe.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .vscode -------------------------------------------------------------------------------- /flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mthri/python-trader-bot/cdf4e747258bec30a0a4afb2723f474aa8ba4f97/flow.jpg -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pandas as pd 3 | import pandas_ta as ta 4 | import ccxt 5 | 6 | 7 | # + create exchange 8 | # + if need to buy/sell 9 | # ` 10 | # exchange = ccxt.binance({ 11 | # "apiKey": API_KEY, 12 | # "secret": API_SECRET 13 | # }) 14 | # ` 15 | exchange = ccxt.binance() 16 | 17 | # get numbers of candle 18 | def get_ohlcv(symbol, interval='1m', limit=10) -> pd.DataFrame: 19 | 20 | # headers 21 | columns = ['timestamp', 'open', 'high', 'low', 'close', 'volume'] 22 | 23 | # get candles from exchange 24 | bars = exchange.fetch_ohlcv(symbol, timeframe=interval, limit=limit) 25 | 26 | # convert list of list to pandas dataframe 27 | df = pd.DataFrame(bars, columns=columns) 28 | 29 | # convert millisecond to datetime 30 | df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') 31 | 32 | return df 33 | 34 | if __name__ == '__main__': 35 | symbol = 'BTC/USDT' 36 | quantity = 1.0 37 | dataframe = get_ohlcv(symbol, limit=50, interval='1m') 38 | 39 | # calculate last row index 40 | last_row = len(dataframe.index) - 1 41 | 42 | # calculate some indicator for fun :) 43 | dataframe['rsi3'] = ta.rsi(dataframe['close'], length=3) 44 | dataframe['sma3'] = ta.sma(dataframe['close'], length=3) 45 | 46 | macd = ta.macd(dataframe['close'], fast=12, slow=26, signal=9) 47 | dataframe['signal'] = ta.supertrend(dataframe['high'], dataframe['low'], 48 | dataframe['close'], 8, 2)['SUPERTd_8_2.0'] 49 | 50 | # print(dataframe) 51 | 52 | # very simple trader bot! 53 | print('bot is started!') 54 | bought = False 55 | while True: 56 | 57 | dataframe = get_ohlcv(symbol, limit=100) 58 | last_row = len(dataframe.index) - 2 59 | 60 | dataframe['signal'] = ta.supertrend(dataframe['high'], dataframe['low'], 61 | dataframe['close'], 8, 2)['SUPERTd_8_2.0'] 62 | 63 | current_price = dataframe['close'][last_row] 64 | current_time = dataframe['timestamp'][last_row] 65 | 66 | if not bought: 67 | if dataframe['signal'][last_row] == 1: 68 | print(f'buy signal for {symbol} on {current_price} at {current_time}') 69 | # exchange.create_market_buy_order(symbol, quantity) 70 | bought = True 71 | 72 | else: 73 | if dataframe['signal'][last_row] == -1: 74 | print(f'sell signal for {symbol} on {current_price} at {current_time}') 75 | # exchange.create_market_buy_order(symbol, quantity) 76 | bought = False 77 | 78 | time.sleep(60) 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ccxt 2 | pandas-ta 3 | websocket-client -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from http.server import BaseHTTPRequestHandler, HTTPServer 2 | import json 3 | 4 | HOST = '127.0.0.1' # or localhost 5 | PORT = 8080 6 | 7 | RESTful_API = True 8 | 9 | def sell(symbol: str, amount: float) -> None: 10 | # write your sell logic here 11 | print(f'sell {symbol}, amount: {amount}') 12 | 13 | def buy(symbol: str, amount: float) -> None: 14 | # write your buy logic here 15 | print(f'buy {symbol}, amount: {amount}') 16 | 17 | 18 | # a minimal http server 19 | class TraderWebServer(BaseHTTPRequestHandler): 20 | def do_POST(self): 21 | global RESTful_API 22 | 23 | # set headers 24 | self.send_response(200) 25 | self.send_header('Content-type', 'json/application' if RESTful_API else 'text/html') 26 | self.end_headers() 27 | 28 | if RESTful_API: 29 | 30 | status = False 31 | message = None 32 | 33 | # check have body data 34 | content_len = self.headers.get('Content-Length') 35 | if content_len is not None: 36 | content_len = int(self.headers.get('Content-Length')) 37 | body = self.rfile.read(content_len) 38 | 39 | # with `body_dict_data` do anything you need!(process operation) 40 | 41 | body_dict_data = json.loads(body.decode()) 42 | 43 | # when we want open buy posstion 44 | if body_dict_data['operation'] == 'buy': 45 | status = True 46 | message = 'successfully buy' 47 | buy( 48 | symbol=body_dict_data['symbol'], 49 | amount=body_dict_data['amount'] 50 | ) 51 | 52 | # when we want open sell posstion 53 | elif body_dict_data['operation'] == 'sell': 54 | status = True 55 | message = 'successfully sell' 56 | sell( 57 | symbol=body_dict_data['symbol'], 58 | amount=body_dict_data['amount'] 59 | ) 60 | 61 | else: 62 | message = 'can\'t find operation' 63 | 64 | else: 65 | message = 'no have data' 66 | 67 | response = json.dumps({ 68 | 'status': status, 69 | 'message': message 70 | }) 71 | 72 | self.wfile.write(response.encode()) 73 | else: 74 | # HOST:PORT/{operation}/{symbol}/{amount} 75 | path = self.path.split('/') 76 | 77 | operation = path[1] 78 | symbol = path[2] 79 | amount = float(path[3]) 80 | 81 | if operation == 'buy': 82 | buy(symbol=symbol, amount=amount) 83 | self.wfile.write(b'successfully buy') 84 | elif operation == 'sell': 85 | sell(symbol=symbol, amount=amount) 86 | self.wfile.write(b'successfully sell') 87 | else: 88 | self.wfile.write(b'can\'t find operation') 89 | 90 | 91 | if __name__ == "__main__": 92 | webServer = HTTPServer((HOST, PORT), TraderWebServer) 93 | print(f"server started http://{HOST}:{PORT}") 94 | 95 | try: 96 | #HINT: also you can run this method via thread to avoid block program here 97 | webServer.serve_forever() 98 | # when press CTRL+C 99 | except KeyboardInterrupt: 100 | pass 101 | 102 | webServer.server_close() 103 | print("server stopped.") -------------------------------------------------------------------------------- /websocket-simple.py: -------------------------------------------------------------------------------- 1 | # for more details see here: https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-streams.md#klinecandlestick-streams 2 | 3 | import pandas as pd 4 | import pandas_ta as ta 5 | import ccxt 6 | from websocket import WebSocketApp 7 | 8 | symbol = 'btcusdt' 9 | interval = '1m' 10 | 11 | ''' 12 | wss: protocol 13 | stream.binance.com: host 14 | 9443: port 15 | ws/symbol@kline_interval: path 16 | ''' 17 | url =f'wss://stream.binance.com:9443/ws/{symbol}@kline_{interval}' 18 | 19 | 20 | # for more details about `websocket-client` see https://websocket-client.readthedocs.io/en/latest/examples.html 21 | def websocket_on_open(ws: WebSocketApp, *args, **kwargs): 22 | print('websocket opened') 23 | 24 | def websocket_on_close(ws: WebSocketApp, *args, **kwargs): 25 | print('websocket closed') 26 | 27 | # important!, when server send any data, you got it here (means you can process it!) 28 | def websocket_on_message(ws: WebSocketApp, message: str, *args, **kwargs): 29 | print('new message -> ', message) 30 | 31 | # create websocket object 32 | ws = WebSocketApp(url, on_open=websocket_on_open, 33 | on_close=websocket_on_close, 34 | on_message=websocket_on_message) 35 | 36 | # connect to server then run! easy peasy lemon squeezy :))) 37 | ws.run_forever() 38 | -------------------------------------------------------------------------------- /websocket-subscribe.py: -------------------------------------------------------------------------------- 1 | # for more details see here: https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-streams.md#live-subscribingunsubscribing-to-streams 2 | 3 | import json 4 | # pretty-print 5 | from pprint import pprint 6 | 7 | import pandas as pd 8 | import pandas_ta as ta 9 | import ccxt 10 | from websocket import WebSocketApp 11 | 12 | symbol = 'btcusdt' 13 | second_symbol = 'ethusdt' 14 | interval = '1m' 15 | 16 | ''' 17 | wss: protocol 18 | stream.binance.com: host 19 | 9443: port 20 | ws/symbol@kline_interval: path 21 | ''' 22 | url =f'wss://stream.binance.com:9443/ws/{symbol}@kline_{interval}' 23 | 24 | 25 | counter = 0 26 | 27 | # for more details about `websocket-client` see https://websocket-client.readthedocs.io/en/latest/examples.html 28 | def websocket_on_open(ws: WebSocketApp, *args, **kwargs): 29 | print('websocket opened') 30 | 31 | def websocket_on_close(ws: WebSocketApp, *args, **kwargs): 32 | print('websocket closed') 33 | 34 | # important!, when server send any data, you got it here (means you can process it!) 35 | def websocket_on_message(ws: WebSocketApp, message: str, *args, **kwargs): 36 | global counter 37 | 38 | json_message = json.loads(str(message)) 39 | 40 | print(f'{counter}) new message ->', end=' ') 41 | pprint(json_message) 42 | 43 | # subscribe to new symbol 44 | if counter == 5: 45 | print(f'also subscribe to {second_symbol}') 46 | payload = { 47 | 'method': 'SUBSCRIBE', 48 | 'params': [ 49 | f'{second_symbol}@kline_{interval}', 50 | ], 51 | 'id': 1 52 | } 53 | 54 | # send data to server via ws object (send data to server with WebSocket not RESTful API!) 55 | ws.send( 56 | # convert dictionary to string 57 | json.dumps(payload) 58 | ) 59 | 60 | # unsubscribe first symbol 61 | elif counter == 15: 62 | print(f'unsubscribe to {second_symbol}') 63 | payload = { 64 | 'method': 'UNSUBSCRIBE', 65 | 'params': [ 66 | f'{symbol}@kline_{interval}', 67 | ], 68 | 'id': 1 69 | } 70 | 71 | # send data to server via ws object (send data to server with WebSocket not RESTful API!) 72 | ws.send( 73 | # convert dictionary to string 74 | json.dumps(payload) 75 | ) 76 | 77 | # disconnect websocket (means kill websocket) 78 | elif counter == 20: 79 | ws.close() 80 | 81 | counter += 1 82 | 83 | # create websocket object 84 | ws = WebSocketApp(url, on_open=websocket_on_open, 85 | on_close=websocket_on_close, 86 | on_message=websocket_on_message) 87 | 88 | # connect to server then run! easy peasy lemon squeezy :))) 89 | ws.run_forever() 90 | --------------------------------------------------------------------------------