├── README.md ├── config.js ├── config.py ├── gridbot.js ├── gridbot.py ├── gridbot_part1.html ├── gridbot_part1.js ├── gridbot_part2.html ├── gridbot_part2.js ├── gridbot_part5.html ├── gridbot_part5.js ├── gridbot_websocket_client.py ├── gridbot_websocket_server.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # gridbot-websockets 2 | build a gridbot using real-time market data over websockets 3 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | API_KEY: "yourapikey", 3 | SECRET_KEY: "yoursecretkey", 4 | SYMBOL: "SOL/USD", 5 | POSITION_SIZE: 0.01, 6 | NUM_BUY_GRID_LINES: 5, 7 | NUM_SELL_GRID_LINES: 5, 8 | GRID_SIZE: 0.25, 9 | CHECK_ORDERS_FREQUENCY: 1, 10 | CLOSED_ORDER_STATUS: 'closed' 11 | }; -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | API_KEY = "yourapikey" 2 | SECRET_KEY = "yoursecretkey" 3 | 4 | SYMBOL = "ETH/USD" 5 | POSITION_SIZE = 0.001 6 | 7 | # gridbot settings 8 | NUM_BUY_GRID_LINES = 5 9 | NUM_SELL_GRID_LINES = 5 10 | GRID_SIZE = 2 11 | 12 | CHECK_ORDERS_FREQUENCY = 1 13 | CLOSED_ORDER_STATUS = 'closed' 14 | -------------------------------------------------------------------------------- /gridbot.js: -------------------------------------------------------------------------------- 1 | var config = require('./config'); 2 | var ccxt = require('ccxt'); 3 | 4 | (async function () { 5 | var exchange = new ccxt.ftxus({ 6 | 'apiKey': config.API_KEY, 7 | 'secret': config.SECRET_KEY 8 | }); 9 | var ticker = await exchange.fetchTicker(config.SYMBOL); 10 | 11 | var buyOrders = []; 12 | var sellOrders = []; 13 | 14 | //var initialBuyOrder = exchange.createMarketBuyOrder(config.SYMBOL, config.POSITION_SIZE * config.NUM_SELL_GRID_LINES); 15 | 16 | for (var i = 1; i <= config.NUM_BUY_GRID_LINES; ++i) { 17 | var price = ticker['bid'] - (config.GRID_SIZE * i); 18 | console.log(`submitting market limit buy order at ${price}`); 19 | var order = await exchange.createLimitBuyOrder(config.SYMBOL, config.POSITION_SIZE, price); 20 | buyOrders.push(order['info']); 21 | } 22 | 23 | for (var i = 1; i <= config.NUM_SELL_GRID_LINES; ++i) { 24 | var price = ticker['bid'] + (config.GRID_SIZE * i); 25 | console.log(`submitting market limit sell order at ${price}`); 26 | var order = await exchange.createLimitSellOrder(config.SYMBOL, config.POSITION_SIZE, price); 27 | sellOrders.push(order['info']); 28 | } 29 | 30 | while (true) { 31 | var closedOrderIds = []; 32 | 33 | for (var buyOrder of buyOrders) { 34 | console.log(`checking buy order ${buyOrder['id']}`); 35 | 36 | try { 37 | order = await exchange.fetchOrder(buyOrder['id']); 38 | } catch (error) { 39 | console.log("request failed: ", error); 40 | } 41 | 42 | var orderInfo = order['info']; 43 | 44 | if (orderInfo['status'] == config.CLOSED_ORDER_STATUS) { 45 | closedOrderIds.push(orderInfo['id']); 46 | console.log(`buy order executed at ${orderInfo['price']}`); 47 | var newSellPrice = parseFloat(orderInfo['price']) + config.GRID_SIZE; 48 | console.log(`creating new limit sell order at ${newSellPrice}`); 49 | var newSellOrder = await exchange.createLimitSellOrder(config.SYMBOL, config.POSITION_SIZE, newSellPrice); 50 | sellOrders.push(newSellOrder); 51 | } 52 | 53 | await new Promise(resolve => setTimeout(resolve, config.CHECK_ORDERS_FREQUENCY)); 54 | } 55 | 56 | for (var sellOrder of sellOrders) { 57 | console.log(`checking sell order ${sellOrder['id']}`); 58 | 59 | try { 60 | order = await exchange.fetchOrder(sellOrder['id']); 61 | } catch (error) { 62 | console.log("request failed: ", error); 63 | } 64 | 65 | var orderInfo = order['info']; 66 | 67 | if (orderInfo['status'] == config.CLOSED_ORDER_STATUS) { 68 | closedOrderIds.push(orderInfo['id']); 69 | console.log(`sell order executed at ${orderInfo['price']}`); 70 | var newBuyPrice = parseFloat(orderInfo['price']) - config.GRID_SIZE; 71 | console.log(`creating new limit buy order at ${newBuyPrice}`); 72 | var newBuyOrder = await exchange.createLimitBuyOrder(config.SYMBOL, config.POSITION_SIZE, newBuyPrice); 73 | buyOrders.push(newBuyOrder); 74 | } 75 | 76 | await new Promise(resolve => setTimeout(resolve, config.CHECK_ORDERS_FREQUENCY)); 77 | } 78 | 79 | closedOrderIds.forEach(closedOrderId => { 80 | buyOrders = buyOrders.filter(buyOrder => buyOrder['id'] != closedOrderId); 81 | sellOrders = sellOrders.filter(sellOrder => sellOrder['id'] != closedOrderId); 82 | }); 83 | 84 | if (sellOrders.length == 0) { 85 | console.log("nothing left to sell, exiting"); 86 | process.exit(1); 87 | } 88 | } 89 | })(); -------------------------------------------------------------------------------- /gridbot.py: -------------------------------------------------------------------------------- 1 | import ccxt, config, time, sys 2 | 3 | exchange = ccxt.ftxus({ 4 | 'apiKey': config.API_KEY, 5 | 'secret': config.SECRET_KEY 6 | }) 7 | 8 | ticker = exchange.fetch_ticker(config.SYMBOL) 9 | 10 | buy_orders = [] 11 | sell_orders = [] 12 | 13 | # initial_buy_order = exchange.create_market_buy_order(config.SYMBOL, config.POSITION_SIZE * config.NUM_SELL_GRID_LINES) 14 | 15 | for i in range(config.NUM_BUY_GRID_LINES): 16 | price = ticker['bid'] - (config.GRID_SIZE * (i+1)) 17 | print("submitting market limit buy order at {}".format(price)) 18 | order = exchange.create_limit_buy_order(config.SYMBOL, config.POSITION_SIZE, price) 19 | buy_orders.append(order['info']) 20 | 21 | for i in range(config.NUM_SELL_GRID_LINES): 22 | price = ticker['bid'] + (config.GRID_SIZE * (i+1)) 23 | print("submitting market limit sell order at {}".format(price)) 24 | order = exchange.create_limit_sell_order(config.SYMBOL, config.POSITION_SIZE, price) 25 | sell_orders.append(order['info']) 26 | 27 | while True: 28 | closed_order_ids = [] 29 | 30 | for buy_order in buy_orders: 31 | print("checking buy order {}".format(buy_order['id'])) 32 | try: 33 | order = exchange.fetch_order(buy_order['id']) 34 | except Exception as e: 35 | print("request failed, retrying") 36 | continue 37 | 38 | order_info = order['info'] 39 | 40 | if order_info['status'] == config.CLOSED_ORDER_STATUS: 41 | closed_order_ids.append(order_info['id']) 42 | print("buy order executed at {}".format(order_info['price'])) 43 | new_sell_price = float(order_info['price']) + config.GRID_SIZE 44 | print("creating new limit sell order at {}".format(new_sell_price)) 45 | new_sell_order = exchange.create_limit_sell_order(config.SYMBOL, config.POSITION_SIZE, new_sell_price) 46 | sell_orders.append(new_sell_order) 47 | 48 | time.sleep(config.CHECK_ORDERS_FREQUENCY) 49 | 50 | for sell_order in sell_orders: 51 | print("checking sell order {}".format(sell_order['id'])) 52 | try: 53 | order = exchange.fetch_order(sell_order['id']) 54 | except Exception as e: 55 | print("request failed, retrying") 56 | continue 57 | 58 | order_info = order['info'] 59 | 60 | if order_info['status'] == config.CLOSED_ORDER_STATUS: 61 | closed_order_ids.append(order_info['id']) 62 | print("sell order executed at {}".format(order_info['price'])) 63 | new_buy_price = float(order_info['price']) - config.GRID_SIZE 64 | print("creating new limit buy order at {}".format(new_buy_price)) 65 | new_buy_order = exchange.create_limit_buy_order(config.SYMBOL, config.POSITION_SIZE, new_buy_price) 66 | buy_orders.append(new_buy_order) 67 | 68 | time.sleep(config.CHECK_ORDERS_FREQUENCY) 69 | 70 | for order_id in closed_order_ids: 71 | buy_orders = [buy_order for buy_order in buy_orders if buy_order['id'] != order_id] 72 | sell_orders = [sell_order for sell_order in sell_orders if sell_order['id'] != order_id] 73 | 74 | if len(sell_orders) == 0: 75 | sys.exit("stopping bot, nothing left to sell") -------------------------------------------------------------------------------- /gridbot_part1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | gridbot demo 4 | 8 | 9 | 10 | 11 |

quotes

12 |
13 |

trades

14 |
15 |

bars

16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /gridbot_part1.js: -------------------------------------------------------------------------------- 1 | 2 | const url = "wss://stream.data.alpaca.markets/v1beta1/crypto"; 3 | const socket = new WebSocket(url); 4 | 5 | const auth = {"action": "auth", "key": "yourapikey", "secret": "yourapisecret"} 6 | 7 | const subscribe = {"action":"subscribe", "trades":["ETHUSD"], "quotes":["ETHUSD"], "bars":["ETHUSD"]} 8 | 9 | const quotesElement = document.getElementById('quotes'); 10 | const tradesElement = document.getElementById('trades'); 11 | 12 | socket.onmessage = function(event) { 13 | const data = JSON.parse(event.data); 14 | const message = data[0]['msg']; 15 | 16 | if (message == 'connected') { 17 | console.log('do authentication'); 18 | socket.send(JSON.stringify(auth)); 19 | } 20 | 21 | if (message == 'authenticated') { 22 | socket.send(JSON.stringify(subscribe)); 23 | } 24 | 25 | for (var key in data) { 26 | 27 | const type = data[key].T; 28 | 29 | if (type == 'q') { 30 | console.log('got a quote'); 31 | console.log(data[key]); 32 | 33 | const quoteElement = document.createElement('div'); 34 | quoteElement.className = 'quote'; 35 | quoteElement.innerHTML = `${data[key].t} ${data[key].bp} ${data[key].ap}`; 36 | quotesElement.appendChild(quoteElement); 37 | 38 | var elements = document.getElementsByClassName('quote'); 39 | if (elements.length > 10) { 40 | quotesElement.removeChild(elements[0]); 41 | } 42 | } 43 | 44 | if (type == 't') { 45 | console.log('got a trade'); 46 | console.log(data[key]); 47 | 48 | const tradeElement = document.createElement('div'); 49 | tradeElement.className = 'trade'; 50 | tradeElement.innerHTML = `${data[key].t} ${data[key].p} ${data[key].s}`; 51 | tradesElement.appendChild(tradeElement); 52 | 53 | var elements = document.getElementsByClassName('trade'); 54 | if (elements.length > 10) { 55 | tradesElement.removeChild(elements[0]); 56 | } 57 | } 58 | 59 | if (type == 'b') { 60 | console.log('got a new bar'); 61 | console.log(data[key]); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /gridbot_part2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | gridbot demo 4 | 11 | 12 | 13 | 14 | 15 |
16 | 22 | 23 |
24 |

chart

25 |
26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /gridbot_part2.js: -------------------------------------------------------------------------------- 1 | 2 | const url = "wss://stream.data.alpaca.markets/v1beta1/crypto"; 3 | const socket = new WebSocket(url); 4 | 5 | const API_KEY = 'yourapikey'; 6 | const SECRET_KEY = 'yoursecretkey'; 7 | 8 | const auth = {"action": "auth", "key": API_KEY, "secret": SECRET_KEY}; 9 | const subscribe = {"action":"subscribe", "trades":["ETHUSD"], "quotes":["ETHUSD"], "bars":["ETHUSD"]} 10 | 11 | const quotesElement = document.getElementById('quotes'); 12 | const tradesElement = document.getElementById('trades'); 13 | 14 | let currentBar = {}; 15 | let trades = []; 16 | 17 | var chart = LightweightCharts.createChart(document.getElementById('chart'), { 18 | width: 700, 19 | height: 700, 20 | layout: { 21 | backgroundColor: '#000000', 22 | textColor: '#ffffff', 23 | }, 24 | grid: { 25 | vertLines: { 26 | color: '#404040', 27 | }, 28 | horzLines: { 29 | color: '#404040', 30 | }, 31 | }, 32 | crosshair: { 33 | mode: LightweightCharts.CrosshairMode.Normal, 34 | }, 35 | priceScale: { 36 | borderColor: '#cccccc', 37 | }, 38 | timeScale: { 39 | borderColor: '#cccccc', 40 | timeVisible: true, 41 | }, 42 | }); 43 | 44 | var candleSeries = chart.addCandlestickSeries(); 45 | 46 | var start = new Date(Date.now() - (7200 * 1000)).toISOString(); 47 | 48 | console.log(start); 49 | 50 | var bars_url = 'https://data.alpaca.markets/v1beta1/crypto/ETHUSD/bars?exchanges=CBSE&timeframe=1Min&start=' + start; 51 | 52 | fetch(bars_url, { 53 | headers: { 54 | 'APCA-API-KEY-ID': API_KEY, 55 | 'APCA-API-SECRET-KEY': SECRET_KEY 56 | } 57 | }).then((r) => r.json()) 58 | .then((response) => { 59 | console.log(response); 60 | 61 | const data = response.bars.map(bar => ( 62 | { 63 | open: bar.o, 64 | high: bar.h, 65 | low: bar.l, 66 | close: bar.c, 67 | time: Date.parse(bar.t) / 1000 68 | } 69 | )); 70 | 71 | currentBar = data[data.length-1]; 72 | 73 | console.log(data); 74 | 75 | candleSeries.setData(data); 76 | 77 | }) 78 | 79 | 80 | socket.onmessage = function(event) { 81 | const data = JSON.parse(event.data); 82 | const message = data[0]['msg']; 83 | 84 | if (message == 'connected') { 85 | console.log('do authentication'); 86 | socket.send(JSON.stringify(auth)); 87 | } 88 | 89 | if (message == 'authenticated') { 90 | socket.send(JSON.stringify(subscribe)); 91 | } 92 | 93 | for (var key in data) { 94 | 95 | const type = data[key].T; 96 | 97 | if (type == 'q') { 98 | //console.log('got a quote'); 99 | //console.log(data[key]); 100 | 101 | const quoteElement = document.createElement('div'); 102 | quoteElement.className = 'quote'; 103 | quoteElement.innerHTML = `${data[key].t} ${data[key].bp} ${data[key].ap}`; 104 | quotesElement.appendChild(quoteElement); 105 | 106 | var elements = document.getElementsByClassName('quote'); 107 | if (elements.length > 10) { 108 | quotesElement.removeChild(elements[0]); 109 | } 110 | } 111 | 112 | if (type == 't') { 113 | //console.log('got a trade'); 114 | //console.log(data[key]); 115 | 116 | const tradeElement = document.createElement('div'); 117 | tradeElement.className = 'trade'; 118 | tradeElement.innerHTML = `${data[key].t} ${data[key].p} ${data[key].s}`; 119 | tradesElement.appendChild(tradeElement); 120 | 121 | var elements = document.getElementsByClassName('trade'); 122 | if (elements.length > 10) { 123 | tradesElement.removeChild(elements[0]); 124 | } 125 | 126 | trades.push(data[key].p); 127 | 128 | var open = trades[0]; 129 | var high = Math.max(...trades); 130 | var low = Math.min(...trades); 131 | var close = trades[trades.length - 1]; 132 | 133 | console.log(open, high, low, close); 134 | 135 | candleSeries.update({ 136 | time: currentBar.time + 60, 137 | open: open, 138 | high: high, 139 | low: low, 140 | close: close 141 | }) 142 | } 143 | 144 | if (type == 'b' && data[key].x == 'CBSE') { 145 | console.log('got a new bar'); 146 | console.log(data[key]); 147 | 148 | var bar = data[key]; 149 | var timestamp = new Date(bar.t).getTime() / 1000; 150 | 151 | currentBar = { 152 | time: timestamp, 153 | open: bar.o, 154 | high: bar.h, 155 | low: bar.l, 156 | close: bar.c 157 | } 158 | 159 | candleSeries.update(currentBar); 160 | 161 | trades = []; 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /gridbot_part5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | gridbot demo 4 | 15 | 16 | 17 | 18 | 19 |
20 | 26 | 27 |
28 |

quotes

29 |
30 |

trades

31 |
32 |
33 | 34 |
35 |

ethereum

36 |
37 |
38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /gridbot_part5.js: -------------------------------------------------------------------------------- 1 | const API_KEY = 'yourkey'; 2 | const SECRET_KEY = 'yoursecret'; 3 | 4 | const url = "wss://stream.data.alpaca.markets/v1beta1/crypto"; 5 | const socket = new WebSocket(url); 6 | 7 | const auth = {"action": "auth", "key": API_KEY, "secret": SECRET_KEY}; 8 | const subscribe = {"action":"subscribe", "trades":["ETHUSD"], "quotes":["ETHUSD"], "bars":["ETHUSD"]} 9 | 10 | const quotesElement = document.getElementById('quotes'); 11 | const tradesElement = document.getElementById('trades'); 12 | const openOrdersElement = document.getElementById('open_orders'); 13 | const closedOrdersElement = document.getElementById('closed_orders'); 14 | 15 | let currentBar = {}; 16 | let trades = []; 17 | 18 | var chart = LightweightCharts.createChart(document.getElementById('chart'), { 19 | width: 500, 20 | height: 665, 21 | layout: { 22 | backgroundColor: '#000000', 23 | textColor: '#ffffff', 24 | }, 25 | grid: { 26 | vertLines: { 27 | color: '#404040', 28 | }, 29 | horzLines: { 30 | color: '#404040', 31 | }, 32 | }, 33 | crosshair: { 34 | mode: LightweightCharts.CrosshairMode.Normal, 35 | }, 36 | priceScale: { 37 | borderColor: '#cccccc', 38 | }, 39 | timeScale: { 40 | borderColor: '#cccccc', 41 | timeVisible: true, 42 | }, 43 | }); 44 | 45 | var candleSeries = chart.addCandlestickSeries(); 46 | 47 | var start = new Date(Date.now() - (7200 * 1000)).toISOString(); 48 | 49 | console.log(start); 50 | 51 | var bars_url = 'https://data.alpaca.markets/v1beta1/crypto/ETHUSD/bars?exchanges=CBSE&timeframe=1Min&start=' + start; 52 | 53 | fetch(bars_url, { 54 | headers: { 55 | 'APCA-API-KEY-ID': API_KEY, 56 | 'APCA-API-SECRET-KEY': SECRET_KEY 57 | } 58 | }).then((r) => r.json()) 59 | .then((response) => { 60 | console.log(response); 61 | 62 | const data = response.bars.map(bar => ( 63 | { 64 | open: bar.o, 65 | high: bar.h, 66 | low: bar.l, 67 | close: bar.c, 68 | time: Date.parse(bar.t) / 1000 69 | } 70 | )); 71 | 72 | currentBar = data[data.length-1]; 73 | 74 | console.log(data); 75 | 76 | candleSeries.setData(data); 77 | 78 | }) 79 | 80 | socket.onmessage = function(event) { 81 | const data = JSON.parse(event.data); 82 | const message = data[0]['msg']; 83 | 84 | if (message == 'connected') { 85 | console.log('do authentication'); 86 | socket.send(JSON.stringify(auth)); 87 | } 88 | 89 | if (message == 'authenticated') { 90 | socket.send(JSON.stringify(subscribe)); 91 | } 92 | 93 | for (var key in data) { 94 | 95 | const type = data[key].T; 96 | 97 | if (type == 'q') { 98 | //console.log('got a quote'); 99 | //console.log(data[key]); 100 | 101 | const quoteElement = document.createElement('div'); 102 | quoteElement.className = 'quote'; 103 | quoteElement.innerHTML = `${data[key].t} ${data[key].bp} ${data[key].ap}`; 104 | quotesElement.appendChild(quoteElement); 105 | 106 | var elements = document.getElementsByClassName('quote'); 107 | if (elements.length > 5) { 108 | quotesElement.removeChild(elements[0]); 109 | } 110 | } 111 | 112 | if (type == 't') { 113 | //console.log('got a trade'); 114 | //console.log(data[key]); 115 | 116 | const tradeElement = document.createElement('div'); 117 | tradeElement.className = 'trade'; 118 | tradeElement.innerHTML = `${data[key].t} ${data[key].p} ${data[key].s}`; 119 | tradesElement.appendChild(tradeElement); 120 | 121 | var elements = document.getElementsByClassName('trade'); 122 | if (elements.length > 5) { 123 | tradesElement.removeChild(elements[0]); 124 | } 125 | 126 | trades.push(data[key].p); 127 | 128 | var open = trades[0]; 129 | var high = Math.max(...trades); 130 | var low = Math.min(...trades); 131 | var close = trades[trades.length - 1]; 132 | 133 | //console.log(open, high, low, close); 134 | 135 | candleSeries.update({ 136 | time: currentBar.time + 60, 137 | open: open, 138 | high: high, 139 | low: low, 140 | close: close 141 | }) 142 | } 143 | 144 | if (type == 'b' && data[key].x == 'CBSE') { 145 | //console.log('got a new bar'); 146 | //console.log(data[key]); 147 | 148 | var bar = data[key]; 149 | var timestamp = new Date(bar.t).getTime() / 1000; 150 | 151 | currentBar = { 152 | time: timestamp, 153 | open: bar.o, 154 | high: bar.h, 155 | low: bar.l, 156 | close: bar.c 157 | } 158 | 159 | candleSeries.update(currentBar); 160 | 161 | trades = []; 162 | } 163 | } 164 | } 165 | 166 | var priceLines = []; 167 | 168 | // connect to websocket server 169 | const orderSocket = new WebSocket("ws://localhost:9001"); 170 | 171 | orderSocket.onopen = function(data) { 172 | //orderSocket.send('ui'); 173 | } 174 | 175 | orderSocket.onmessage = function(event) { 176 | console.log('received message from server'); 177 | console.log(event.data); 178 | try { 179 | // clear pricelines and open orders 180 | priceLines.forEach((priceLine) => { 181 | candleSeries.removePriceLine(priceLine); 182 | }); 183 | openOrdersElement.innerHTML = ''; 184 | 185 | // add pricelines and open orders to UI 186 | let orderData = JSON.parse(event.data); 187 | 188 | orderData.forEach((order) => { 189 | console.log(order); 190 | // create an HTML div element for the order if one doesn't exist 191 | var orderElement = document.getElementById(order.id); 192 | if (!orderElement) { 193 | orderElement = document.createElement('div'); 194 | orderElement.innerHTML = `
${order.side} ${order.price} ${order.status}
`; 195 | } 196 | 197 | if (order.status == 'closed') { 198 | closedOrdersElement.appendChild(orderElement); 199 | } else { 200 | openOrdersElement.appendChild(orderElement); 201 | 202 | // create priceline for the chart for open orders 203 | var priceLine = { 204 | price: order.price, 205 | color: order.side == 'buy' ? '#00ff00' : '#ff0000', 206 | lineWidth: 2, 207 | lineStyle: LightweightCharts.LineStyle.Solid, 208 | axisLabelVisible: true, 209 | title: order.side 210 | }; 211 | console.log(priceLine); 212 | var line = candleSeries.createPriceLine(priceLine); 213 | priceLines.push(line); 214 | } 215 | }); 216 | } catch (error) { 217 | console.log(error); 218 | } 219 | } -------------------------------------------------------------------------------- /gridbot_websocket_client.py: -------------------------------------------------------------------------------- 1 | import ccxt, config, time, sys 2 | import websocket, json 3 | 4 | ws = websocket.WebSocket() 5 | ws.connect("ws://localhost:9001") 6 | 7 | exchange = ccxt.ftxus({ 8 | 'apiKey': config.API_KEY, 9 | 'secret': config.SECRET_KEY 10 | }) 11 | 12 | ticker = exchange.fetch_ticker(config.SYMBOL) 13 | 14 | buy_orders = [] 15 | sell_orders = [] 16 | 17 | # initial_buy_order = exchange.create_market_buy_order(config.SYMBOL, config.POSITION_SIZE * config.NUM_SELL_GRID_LINES) 18 | 19 | for i in range(config.NUM_BUY_GRID_LINES): 20 | price = ticker['bid'] - (config.GRID_SIZE * (i+1)) 21 | print("submitting market limit buy order at {}".format(price)) 22 | order = exchange.create_limit_buy_order(config.SYMBOL, config.POSITION_SIZE, price) 23 | buy_orders.append(order['info']) 24 | 25 | for i in range(config.NUM_SELL_GRID_LINES): 26 | price = ticker['bid'] + (config.GRID_SIZE * (i+1)) 27 | print("submitting market limit sell order at {}".format(price)) 28 | order = exchange.create_limit_sell_order(config.SYMBOL, config.POSITION_SIZE, price) 29 | sell_orders.append(order['info']) 30 | 31 | 32 | closed_orders = [] 33 | 34 | while True: 35 | # concatenate 3 order lists and send as jsonified string 36 | ws.send(json.dumps(buy_orders + sell_orders + closed_orders)) 37 | 38 | closed_order_ids = [] 39 | 40 | for buy_order in buy_orders: 41 | print("checking buy order {}".format(buy_order['id'])) 42 | try: 43 | order = exchange.fetch_order(buy_order['id']) 44 | except Exception as e: 45 | print("request failed, retrying") 46 | continue 47 | 48 | order_info = order['info'] 49 | 50 | if order_info['status'] == config.CLOSED_ORDER_STATUS: 51 | closed_orders.append(order_info) 52 | closed_order_ids.append(order_info['id']) 53 | print("buy order executed at {}".format(order_info['price'])) 54 | new_sell_price = float(order_info['price']) + config.GRID_SIZE 55 | print("creating new limit sell order at {}".format(new_sell_price)) 56 | new_sell_order = exchange.create_limit_sell_order(config.SYMBOL, config.POSITION_SIZE, new_sell_price) 57 | sell_orders.append(new_sell_order) 58 | 59 | time.sleep(config.CHECK_ORDERS_FREQUENCY) 60 | 61 | for sell_order in sell_orders: 62 | print("checking sell order {}".format(sell_order['id'])) 63 | try: 64 | order = exchange.fetch_order(sell_order['id']) 65 | except Exception as e: 66 | print("request failed, retrying") 67 | continue 68 | 69 | order_info = order['info'] 70 | 71 | if order_info['status'] == config.CLOSED_ORDER_STATUS: 72 | closed_orders.append(order_info) 73 | closed_order_ids.append(order_info['id']) 74 | print("sell order executed at {}".format(order_info['price'])) 75 | new_buy_price = float(order_info['price']) - config.GRID_SIZE 76 | print("creating new limit buy order at {}".format(new_buy_price)) 77 | new_buy_order = exchange.create_limit_buy_order(config.SYMBOL, config.POSITION_SIZE, new_buy_price) 78 | buy_orders.append(new_buy_order) 79 | 80 | time.sleep(config.CHECK_ORDERS_FREQUENCY) 81 | 82 | for order_id in closed_order_ids: 83 | buy_orders = [buy_order for buy_order in buy_orders if buy_order['id'] != order_id] 84 | sell_orders = [sell_order for sell_order in sell_orders if sell_order['id'] != order_id] 85 | 86 | if len(sell_orders) == 0: 87 | sys.exit("stopping bot, nothing left to sell") -------------------------------------------------------------------------------- /gridbot_websocket_server.py: -------------------------------------------------------------------------------- 1 | from websocket_server import WebsocketServer 2 | 3 | # Called for every client connecting (after handshake) 4 | def new_client(client, server): 5 | print("New client connected and was given id %d" % client['id']) 6 | server.send_message_to_all("Hey all, a new client has joined us") 7 | 8 | # Called when a client sends a message 9 | def message_received(client, server, message): 10 | print("Client(%d) said: %s" % (client['id'], message)) 11 | server.send_message_to_all(message) 12 | 13 | 14 | PORT = 9001 15 | server = WebsocketServer(port = PORT) 16 | server.set_fn_new_client(new_client) 17 | server.set_fn_message_received(message_received) 18 | server.run_forever() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ccxt 2 | websocket-client 3 | websocket-server --------------------------------------------------------------------------------