├── 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 |
17 |
quotes
18 |
19 |
trades
20 |
21 |
22 |
23 |
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 |
21 |
open orders
22 |
23 |
closed orders
24 |
25 |
26 |
27 |
28 |
quotes
29 |
30 |
trades
31 |
32 |
33 |
34 |
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
--------------------------------------------------------------------------------