├── .gitignore ├── js ├── start.py ├── btc.json ├── package.json ├── md5.js └── marketmaker.js ├── py ├── start.py ├── btc.json └── marketmaker.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules/ 4 | dist/ 5 | dist-test/ 6 | dist-prod/ 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | *.iml 11 | apikey.json 12 | 13 | -------------------------------------------------------------------------------- /js/start.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import time 4 | mod = sys.argv[1] 5 | while True : 6 | localtime = time.asctime(time.localtime(time.time())) 7 | print localtime 8 | os.system('node grid.js '+mod) 9 | print 'start node 5s later' 10 | time.sleep(5) 11 | -------------------------------------------------------------------------------- /py/start.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import time 4 | mod = sys.argv[1] 5 | while True : 6 | localtime = time.asctime(time.localtime(time.time())) 7 | print (localtime) 8 | os.system('node maketmaker.py '+mod) 9 | print ('start python 5s later') 10 | time.sleep(5) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # marketmarker 2 | 3 | > Using grid trading strategy for market maker. 4 | 5 | #### Run Setup 6 | 7 | > node.js 8 | in js/ directory: 9 | 10 | ``` bash 11 | # install dependencies 12 | npm install 13 | 14 | # run 15 | npm run dev 16 | 17 | ``` 18 | > python 19 | in py/ directory: 20 | 21 | ``` bash 22 | # test 23 | python maketmaker.py 24 | 25 | # run 26 | python start.py 27 | 28 | ``` 29 | 30 | ### Disclaimer 31 | This code DOES NOT give investment advice. On top of that there might be bugs in the code - This code DOES NOT come with ANY warranty. 32 | -------------------------------------------------------------------------------- /js/btc.json: -------------------------------------------------------------------------------- 1 | { 2 | "URL" : "wss://real.okex.com:10441/websocket", 3 | "initCounter" : 1.01, 4 | "baseInfo" : [ 5 | { 6 | "currency" : "btc" 7 | }, 8 | { 9 | "currency" : "bch", 10 | "initPrice" : 0.1642, 11 | "lowLimit" : 0.1, 12 | "hightLimit" : 0.9, 13 | "gap" : 2, 14 | "rate" : 1.01, 15 | "initBase" : 10, 16 | "tradeAmount" : 1, 17 | "orderLength" : 3 18 | }, 19 | { 20 | "currency" : "eth", 21 | "initPrice" : 0.089, 22 | "lowLimit" : 0.01, 23 | "hightLimit" : 0.2, 24 | "gap" : 2, 25 | "rate" : 1.01, 26 | "initBase" : 10, 27 | "tradeAmount" : 1, 28 | "orderLength" : 3 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /py/btc.json: -------------------------------------------------------------------------------- 1 | { 2 | "URL" : "wss://real.okex.com:10441/websocket", 3 | "initCounter" : 1.01, 4 | "baseInfo" : [ 5 | { 6 | "currency" : "btc" 7 | }, 8 | { 9 | "currency" : "bch", 10 | "initPrice" : 0.1642, 11 | "lowLimit" : 0.1, 12 | "hightLimit" : 0.9, 13 | "gap" : 2, 14 | "rate" : 1.01, 15 | "initBase" : 10, 16 | "tradeAmount" : 1, 17 | "orderLength" : 3 18 | }, 19 | { 20 | "currency" : "eth", 21 | "initPrice" : 0.089, 22 | "lowLimit" : 0.01, 23 | "hightLimit" : 0.2, 24 | "gap" : 2, 25 | "rate" : 1.01, 26 | "initBase" : 10, 27 | "tradeAmount" : 1, 28 | "orderLength" : 3 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marketmarker", 3 | "version": "1.0.0", 4 | "description": "grid market maker", 5 | "main": "marketmarker.js", 6 | "scripts": { 7 | "dev": "node marketmarker.js" 8 | }, 9 | "dependencies": { 10 | "request": "^2.83.0", 11 | "ws": "^4.0.0" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/bilibilihuangyifan/marketmarker.git" 16 | }, 17 | "author": "bilibilihuangyifan", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/bilibilihuangyifan/marketmarker/issues" 21 | }, 22 | "homepage": "https://github.com/bilibilihuangyifan/marketmarker#readme" 23 | } 24 | -------------------------------------------------------------------------------- /js/md5.js: -------------------------------------------------------------------------------- 1 | 2 | var hex_chr = "0123456789ABCDEF"; 3 | function rhex(num) { 4 | str = ""; 5 | for(var j = 0; j <= 3; j++) 6 | str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) + 7 | hex_chr.charAt((num >> (j * 8)) & 0x0F); 8 | return str; 9 | } 10 | function str2blks_MD5(str) { 11 | nblk = ((str.length + 8) >> 6) + 1; 12 | blks = new Array(nblk * 16); 13 | for(var i = 0; i < nblk * 16; i++) blks[i] = 0; 14 | for(i = 0; i < str.length; i++) 15 | blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8); 16 | blks[i >> 2] |= 0x80 << ((i % 4) * 8); 17 | blks[nblk * 16 - 2] = str.length * 8; 18 | return blks; 19 | } 20 | function add(x, y) { 21 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 22 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 23 | return (msw << 16) | (lsw & 0xFFFF); 24 | } 25 | function rol(num, cnt) { 26 | return (num << cnt) | (num >>> (32 - cnt)); 27 | } 28 | function cmn(q, a, b, x, s, t) { 29 | return add(rol(add(add(a, q), add(x, t)), s), b); 30 | } 31 | function ff(a, b, c, d, x, s, t) { 32 | return cmn((b & c) | ((~b) & d), a, b, x, s, t); 33 | } 34 | function gg(a, b, c, d, x, s, t) { 35 | return cmn((b & d) | (c & (~d)), a, b, x, s, t); 36 | } 37 | function hh(a, b, c, d, x, s, t) { 38 | return cmn(b ^ c ^ d, a, b, x, s, t); 39 | } 40 | function ii(a, b, c, d, x, s, t) { 41 | return cmn(c ^ (b | (~d)), a, b, x, s, t); 42 | } 43 | function MD5(str) { 44 | x = str2blks_MD5(str); 45 | var a = 1732584193; 46 | var b = -271733879; 47 | var c = -1732584194; 48 | var d = 271733878; 49 | for(i = 0; i < x.length; i += 16) { 50 | var olda = a; 51 | var oldb = b; 52 | var oldc = c; 53 | var oldd = d; 54 | a = ff(a, b, c, d, x[i+ 0], 7 , -680876936); 55 | d = ff(d, a, b, c, x[i+ 1], 12, -389564586); 56 | c = ff(c, d, a, b, x[i+ 2], 17, 606105819); 57 | b = ff(b, c, d, a, x[i+ 3], 22, -1044525330); 58 | a = ff(a, b, c, d, x[i+ 4], 7 , -176418897); 59 | d = ff(d, a, b, c, x[i+ 5], 12, 1200080426); 60 | c = ff(c, d, a, b, x[i+ 6], 17, -1473231341); 61 | b = ff(b, c, d, a, x[i+ 7], 22, -45705983); 62 | a = ff(a, b, c, d, x[i+ 8], 7 , 1770035416); 63 | d = ff(d, a, b, c, x[i+ 9], 12, -1958414417); 64 | c = ff(c, d, a, b, x[i+10], 17, -42063); 65 | b = ff(b, c, d, a, x[i+11], 22, -1990404162); 66 | a = ff(a, b, c, d, x[i+12], 7 , 1804603682); 67 | d = ff(d, a, b, c, x[i+13], 12, -40341101); 68 | c = ff(c, d, a, b, x[i+14], 17, -1502002290); 69 | b = ff(b, c, d, a, x[i+15], 22, 1236535329); 70 | a = gg(a, b, c, d, x[i+ 1], 5 , -165796510); 71 | d = gg(d, a, b, c, x[i+ 6], 9 , -1069501632); 72 | c = gg(c, d, a, b, x[i+11], 14, 643717713); 73 | b = gg(b, c, d, a, x[i+ 0], 20, -373897302); 74 | a = gg(a, b, c, d, x[i+ 5], 5 , -701558691); 75 | d = gg(d, a, b, c, x[i+10], 9 , 38016083); 76 | c = gg(c, d, a, b, x[i+15], 14, -660478335); 77 | b = gg(b, c, d, a, x[i+ 4], 20, -405537848); 78 | a = gg(a, b, c, d, x[i+ 9], 5 , 568446438); 79 | d = gg(d, a, b, c, x[i+14], 9 , -1019803690); 80 | c = gg(c, d, a, b, x[i+ 3], 14, -187363961); 81 | b = gg(b, c, d, a, x[i+ 8], 20, 1163531501); 82 | a = gg(a, b, c, d, x[i+13], 5 , -1444681467); 83 | d = gg(d, a, b, c, x[i+ 2], 9 , -51403784); 84 | c = gg(c, d, a, b, x[i+ 7], 14, 1735328473); 85 | b = gg(b, c, d, a, x[i+12], 20, -1926607734); 86 | a = hh(a, b, c, d, x[i+ 5], 4 , -378558); 87 | d = hh(d, a, b, c, x[i+ 8], 11, -2022574463); 88 | c = hh(c, d, a, b, x[i+11], 16, 1839030562); 89 | b = hh(b, c, d, a, x[i+14], 23, -35309556); 90 | a = hh(a, b, c, d, x[i+ 1], 4 , -1530992060); 91 | d = hh(d, a, b, c, x[i+ 4], 11, 1272893353); 92 | c = hh(c, d, a, b, x[i+ 7], 16, -155497632); 93 | b = hh(b, c, d, a, x[i+10], 23, -1094730640); 94 | a = hh(a, b, c, d, x[i+13], 4 , 681279174); 95 | d = hh(d, a, b, c, x[i+ 0], 11, -358537222); 96 | c = hh(c, d, a, b, x[i+ 3], 16, -722521979); 97 | b = hh(b, c, d, a, x[i+ 6], 23, 76029189); 98 | a = hh(a, b, c, d, x[i+ 9], 4 , -640364487); 99 | d = hh(d, a, b, c, x[i+12], 11, -421815835); 100 | c = hh(c, d, a, b, x[i+15], 16, 530742520); 101 | b = hh(b, c, d, a, x[i+ 2], 23, -995338651); 102 | a = ii(a, b, c, d, x[i+ 0], 6 , -198630844); 103 | d = ii(d, a, b, c, x[i+ 7], 10, 1126891415); 104 | c = ii(c, d, a, b, x[i+14], 15, -1416354905); 105 | b = ii(b, c, d, a, x[i+ 5], 21, -57434055); 106 | a = ii(a, b, c, d, x[i+12], 6 , 1700485571); 107 | d = ii(d, a, b, c, x[i+ 3], 10, -1894986606); 108 | c = ii(c, d, a, b, x[i+10], 15, -1051523); 109 | b = ii(b, c, d, a, x[i+ 1], 21, -2054922799); 110 | a = ii(a, b, c, d, x[i+ 8], 6 , 1873313359); 111 | d = ii(d, a, b, c, x[i+15], 10, -30611744); 112 | c = ii(c, d, a, b, x[i+ 6], 15, -1560198380); 113 | b = ii(b, c, d, a, x[i+13], 21, 1309151649); 114 | a = ii(a, b, c, d, x[i+ 4], 6 , -145523070); 115 | d = ii(d, a, b, c, x[i+11], 10, -1120210379); 116 | c = ii(c, d, a, b, x[i+ 2], 15, 718787259); 117 | b = ii(b, c, d, a, x[i+ 9], 21, -343485551); 118 | a = add(a, olda); 119 | b = add(b, oldb); 120 | c = add(c, oldc); 121 | d = add(d, oldd); 122 | } 123 | return rhex(a) + rhex(b) + rhex(c) + rhex(d); 124 | } 125 | module.exports = { 126 | MD5: MD5 127 | }; 128 | -------------------------------------------------------------------------------- /py/marketmaker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # encoding: utf-8 4 | import ccxt 5 | import json 6 | import time 7 | from numpy import NaN 8 | import math 9 | import sys 10 | from pprint import pprint 11 | 12 | Info = json.load(open(sys.argv[1])) 13 | 14 | initCounter = Info['initCounter'] 15 | baseInfo = Info['baseInfo'] 16 | Names = [info['currency'] for info in baseInfo] 17 | 18 | marketLength = len(baseInfo) 19 | Balances = [0.0 for i in range(marketLength)] 20 | buyOrders = [[] for i in range(marketLength)] 21 | sellOrders = [[] for i in range(marketLength)] 22 | 23 | #初始化apikey,secretkey,url 24 | config = open('.apikey','r') 25 | lines = config.readlines() 26 | apikey = lines[0].strip() 27 | secretkey = lines[1].strip() 28 | config.close() 29 | 30 | exchange = ccxt.huobipro({ 31 | 'apiKey': apikey, 32 | 'secret': secretkey 33 | }) 34 | 35 | flagShow = True 36 | def checkMyOrders(index, orders, targetOrders, Type): 37 | temp = [order for order in targetOrders] 38 | uselessOrdersID = [] 39 | #print(orders) 40 | for order in orders: 41 | #print(order) 42 | seq = order[0] 43 | myPrice = order[1] 44 | myIOUAmount = order[2] 45 | flagUseless = True 46 | for target in temp: 47 | price = target[0] 48 | amount = target[1] 49 | if (abs(myPrice/price - 1) < 0.000001) and (abs(myIOUAmount/amount - 1) < 0.1) : 50 | temp.remove(target) 51 | flagUseless = False 52 | break 53 | if flagUseless: 54 | #cancelOrder(index, seq) 55 | #try: 56 | print (u'##Cancel order') 57 | print (exchange.cancelOrder(id = str(seq), symbol = Names[index]+'/'+Names[0])) 58 | print (u'##Cancel order') 59 | #except: 60 | # print (u'##Cancel order') 61 | # continue 62 | for target in temp: 63 | orderprice = target[0] 64 | orderamount = target[1] 65 | #createMarketOrder(index, Type, price, amount) 66 | print (Names[index]+'/'+Names[0], Type, str(orderprice), str(orderamount)) 67 | try: 68 | print (exchange.createOrder(symbol=Names[index]+'/'+Names[0],side=Type,type='limit',amount=orderamount,price=orderprice)) 69 | except: 70 | continue 71 | 72 | while True: 73 | time.sleep(5) 74 | #################1. Fetch balance################### 75 | print (u'#################1. Fetch balance###################') 76 | try: 77 | res = exchange.fetchBalance() 78 | funds = res 79 | except Exception as ex: 80 | print (u'##Fail to fetch balance' + str (ex)) 81 | continue 82 | try: 83 | for i in range(marketLength): 84 | Balances[i] = float(funds['free'][Names[i]])+float(funds['used'][Names[i]]) 85 | print(Names[i] + ': ' + str(Balances[i])) 86 | except Exception as ex: 87 | print (u'##Fail to fetch free or used balance' + str(ex)) 88 | continue 89 | #################2. Fetch open order################### 90 | print (u'#################2. Fetch open order###################') 91 | flagsuc = True 92 | for i in range(1, marketLength): 93 | buyOrders[i] = [] 94 | sellOrders[i] = [] 95 | try: 96 | res = exchange.fetchOpenOrders(symbol = Names[i]+'/'+Names[0]) 97 | orders = res 98 | #pprint(res) 99 | except: 100 | flagsuc = False 101 | break 102 | for order in orders: 103 | info = [order['id'], order['price'], order['amount']] 104 | #print (info) 105 | if order['side'] == 'buy': 106 | buyOrders[i].append(info) 107 | elif order['side'] == 'sell': 108 | sellOrders[i].append(info) 109 | print('##' + Names[i], '\n Buy orders: ', buyOrders[i], '; Sell orders: ', sellOrders[i]) 110 | if not flagsuc: 111 | continue 112 | #print (Balances) 113 | #################3. Analyse################### 114 | print(u'#################3. Analyse###################') 115 | diff = 0 116 | for t in range(1, marketLength): 117 | initPrice = baseInfo[t]['initPrice'] 118 | lowLimit = baseInfo[t]['lowLimit'] 119 | highLimit = baseInfo[t]['highLimit'] 120 | gap = baseInfo[t]['gap'] 121 | rate = baseInfo[t]['rate'] 122 | initBase = baseInfo[t]['initBase'] 123 | tradeAmount = baseInfo[t]['tradeAmount'] 124 | orderLength = baseInfo[t]['orderLength'] 125 | minAmount = baseInfo[t]['minAmount'] 126 | 127 | initbuy = - gap 128 | initsell = gap 129 | balanceState = (initBase - Balances[t])/tradeAmount 130 | 131 | balanceStateBuy = balanceState 132 | buyDecimal = balanceStateBuy - math.floor(balanceStateBuy) 133 | 134 | if (buyDecimal < 0.1) : 135 | buyDecimal = buyDecimal + 1 136 | balanceStateBuy = math.floor(balanceStateBuy) 137 | else: 138 | balanceStateBuy = math.ceil(balanceStateBuy) 139 | buyAmount = buyDecimal * tradeAmount 140 | buyPower = initbuy + balanceStateBuy 141 | buyPrice = initPrice * math.pow(rate, buyPower) 142 | 143 | balanceStateSell = balanceState 144 | sellDecimal = math.ceil(balanceStateSell) - balanceStateSell 145 | if (sellDecimal < 0.1): 146 | sellDecimal = sellDecimal + 1 147 | balanceStateSell = math.ceil(balanceStateSell) 148 | else: 149 | balanceStateSell = math.floor(balanceStateSell) 150 | sellAmount = sellDecimal * tradeAmount 151 | sellPower = initsell + balanceStateSell 152 | sellPrice = initPrice * math.pow(rate, sellPower) 153 | diff = diff + (-balanceState)*tradeAmount*buyPrice 154 | 155 | #if (orderLength == 0): continue 156 | buyTarget = [] 157 | sellTarget = [] 158 | for i in range(orderLength): 159 | if i == 0: 160 | buyprice = round(buyPrice,6) 161 | buyamount = round(buyAmount,8) - 0.00000001 162 | sellprice = round(sellPrice,6) 163 | sellamount = round(sellAmount,8) - 0.00000001 164 | else: 165 | buyprice = round(buyPrice * math.pow(rate, -i), 6) 166 | buyamount = round(tradeAmount,3) 167 | sellprice = round(sellPrice * math.pow(rate, i), 6) 168 | sellamount = round(tradeAmount,3) 169 | 170 | if buyamount >= minAmount : 171 | buyTarget.append([buyprice, buyamount]) 172 | if sellamount >= minAmount : 173 | sellTarget.append([sellprice, sellamount]) 174 | print (Names[t],'Target\n',buyTarget,'\n',sellTarget) 175 | 176 | #if (Balances[t] < tradeAmount * orderLength): 177 | if (Balances[t] < sellAmount): 178 | if flagShow: 179 | print('not enough '+Names[t]+' to create sell orders') 180 | elif highLimit < sellprice: 181 | if flagShow: 182 | print(Names[t]+' is too high') 183 | else: 184 | checkMyOrders(t, sellOrders[t], sellTarget, 'sell') 185 | 186 | #if (Balances[0] < tradeAmount * orderLength * buyPrice): 187 | if (Balances[0] < tradeAmount * buyPrice): 188 | print('not enough '+Names[t]+' to create buy orders') 189 | elif lowLimit > buyprice: 190 | if flagShow: 191 | print(Names[t]+' is too low') 192 | else: 193 | checkMyOrders(t, buyOrders[t], buyTarget, 'buy') 194 | 195 | if flagShow: 196 | print ('current Balances:',Balances) 197 | print ('you should have ',initCounter+diff,Names[0]) 198 | flagShow = False 199 | -------------------------------------------------------------------------------- /js/marketmaker.js: -------------------------------------------------------------------------------- 1 | 2 | var myArgs = process.argv.slice(2); 3 | var config=require('./' + (myArgs[0] || 'btc.json')); 4 | var MD5 = require('./md5.js'); 5 | var API_KEY = require('./apikey.json'); 6 | var api_key = API_KEY.apikey; 7 | var secret_key = API_KEY.secretkey; 8 | var URL = config.URL; 9 | var initCounter = config.initCounter; 10 | var baseInfo = config.baseInfo; 11 | 12 | var gatewaysLength = baseInfo.length ; 13 | var currencys = new Array(gatewaysLength); 14 | var Lines = new Array(gatewaysLength); 15 | var Frees = new Array(gatewaysLength); 16 | var flagShowOrders = new Array(gatewaysLength); 17 | 18 | var sellOrders = new Array(gatewaysLength); 19 | var buyOrders = new Array(gatewaysLength); 20 | 21 | for(var i=0; i highLimit) { console.log('impossible price!!'); continue; } 284 | diff = diff + (balanceState)*tradeAmount*sellPrice; 285 | 286 | if (flagShowOrders[t]){ 287 | console.log('****************** orders in ',currencys[t]+'/'+currencys[0],'market *******************'); 288 | for (var id= 0; id