├── .gitignore ├── MarketMaker.py ├── MarketMakerAbsorption.py ├── MarketMakerEphemeral.py ├── MarketMakerFutures.py ├── MarketMakerPrudent.py ├── MarketMakerSpread.py ├── README.md ├── SFDMaker.py ├── bforder.py ├── config └── config_default.json ├── cryptowatch.py ├── get-pip.py └── src └── bforder.py /.gitignore: -------------------------------------------------------------------------------- 1 | # directory 2 | tmp 3 | __pycache__ 4 | 5 | # user data 6 | *.json 7 | *.log 8 | *.csv 9 | *.log.* 10 | *.png 11 | 12 | # default config 13 | !*_default.json 14 | !*_default.csv 15 | 16 | !.gitkeep 17 | !api_permission.png 18 | -------------------------------------------------------------------------------- /MarketMaker.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | class MarketMaker: 4 | pass 5 | def __init__(self): 6 | #config.jsonの読み込み 7 | f = open('config/config.json', 'r', encoding="utf-8") 8 | config = json.load(f) 9 | 10 | 11 | #!/usr/bin/python3 12 | # coding: utf-8 13 | 14 | import datetime 15 | import time 16 | import json 17 | import ccxt 18 | import requests 19 | import bforder 20 | import cryptowatch 21 | #import talib as ta 22 | import numpy as np 23 | import pandas as pd 24 | 25 | #configの読み込み 26 | f = open('config/config.json', 'r', encoding="utf-8") 27 | config = json.load(f) 28 | 29 | order = bforder.BFOrder(); 30 | 31 | cryptowatch = cryptowatch.CryptoWatch() 32 | 33 | bitflyer = ccxt.bitflyer({ 34 | 'apiKey': config["key"], 35 | 'secret': config["secret"], 36 | }) 37 | 38 | # 取引する通貨、シンボルを設定 39 | COIN = 'BTC' 40 | PAIR = 'BTC/JPY' 41 | 42 | # プロダクトコードの指定 43 | PRODUCT = config["product_code"] 44 | 45 | # ロット(単位はBTC) 46 | LOT = config["lotSize"]; 47 | 48 | CANDLETERM = config["candleTerm"]; 49 | 50 | # 最小注文数(取引所の仕様に応じて設定) 51 | AMOUNT_MIN = 0.001 52 | 53 | # スプレッド閾値 54 | SPREAD_ENTRY = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらエントリー 55 | SPREAD_CANCEL = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を下回ったら指値更新を停止 56 | 57 | # 数量X(この数量よりも下に指値をおく) 58 | AMOUNT_THRU = 1 59 | AMOUNT_ASKBID = 0.5 60 | 61 | # 実効Ask/BidからDELTA離れた位置に指値をおく 62 | DELTA = 30 63 | 64 | INVDELTA = -20 65 | 66 | # 買い気配、売り気配に応じて主観的ファンダメンタル価格をずらす 67 | OFFSET = 2 68 | 69 | ABSOFFSET = 100 70 | 71 | PERTURB = 40 72 | 73 | spread = 0 74 | 75 | vixFlag = 0 76 | 77 | callback = 'stay'; 78 | 79 | 80 | #------------------------------------------------------------------------------# 81 | #log設定 82 | import logging 83 | logger = logging.getLogger('LoggingTest') 84 | logger.setLevel(10) 85 | fh = logging.FileHandler('log_mm_bf_' + datetime.datetime.now().strftime('%Y%m%d') + '_' + datetime.datetime.now().strftime('%H%M%S') + '.log') 86 | logger.addHandler(fh) 87 | sh = logging.StreamHandler() 88 | logger.addHandler(sh) 89 | formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") 90 | fh.setFormatter(formatter) 91 | sh.setFormatter(formatter) 92 | 93 | #------------------------------------------------------------------------------# 94 | 95 | # JPY残高を参照する関数 96 | def get_asset(): 97 | 98 | while True: 99 | try: 100 | value = bitflyer.fetch_balance(params = { "product_code" : PRODUCT }) 101 | break 102 | except Exception as e: 103 | logger.info(e) 104 | time.sleep(1) 105 | return value 106 | 107 | # JPY証拠金を参照する関数 108 | def get_colla(): 109 | 110 | while True: 111 | try: 112 | value = bitflyer.privateGetGetcollateral() 113 | break 114 | except Exception as e: 115 | logger.info(e) 116 | time.sleep(1) 117 | return value 118 | 119 | # 板情報から実効Ask/Bid(=指値を入れる基準値)を計算する関数 120 | def get_effective_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 121 | 122 | while True: 123 | try: 124 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 125 | break 126 | except Exception as e: 127 | logger.info(e) 128 | time.sleep(2) 129 | 130 | i = 0 131 | s = 0 132 | while s <= size_thru: 133 | if value['bids'][i][0] == rate_bid: 134 | s += value['bids'][i][1] - size_bid 135 | else: 136 | s += value['bids'][i][1] 137 | i += 1 138 | 139 | j = 0 140 | t = 0 141 | while t <= size_thru: 142 | if value['asks'][j][0] == rate_ask: 143 | t += value['asks'][j][1] - size_ask 144 | else: 145 | t += value['asks'][j][1] 146 | j += 1 147 | 148 | time.sleep(0.5) 149 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 150 | 151 | # 成行注文する関数 152 | def market(side, size): 153 | 154 | while True: 155 | try: 156 | value = bitflyer.create_order(PAIR, type = 'market', side = side, amount = size,params = { "product_code" : PRODUCT }) 157 | break 158 | except Exception as e: 159 | logger.info(e) 160 | time.sleep(2) 161 | #場当たり的な対処 162 | size = LOT; 163 | 164 | time.sleep(0.5) 165 | return value 166 | 167 | # 指値注文する関数 168 | def limit(side, size, price): 169 | 170 | while True: 171 | try: 172 | value = bitflyer.create_order(PAIR, type = 'limit', side = side, amount = size, price = price, params = { "product_code" : PRODUCT }) 173 | break 174 | except Exception as e: 175 | logger.info(e) 176 | time.sleep(2) 177 | #場当たり的な対処 178 | size = LOT; 179 | 180 | time.sleep(0.5) 181 | return value 182 | 183 | # 注文をキャンセルする関数 184 | def cancel(id): 185 | 186 | try: 187 | value = bitflyer.cancelOrder(symbol = PAIR, id = id) 188 | except Exception as e: 189 | logger.info(e) 190 | 191 | # 指値が約定していた(=キャンセルが通らなかった)場合、 192 | # 注文情報を更新(約定済み)して返す 193 | value = get_status(id) 194 | 195 | time.sleep(0.5) 196 | return value 197 | 198 | # 指定した注文idのステータスを参照する関数 199 | def get_status(id): 200 | 201 | 202 | while True: 203 | try: 204 | value = bitflyer.private_get_getchildorders(params = {'product_code': PRODUCT, 'child_order_acceptance_id': id})[0] 205 | break 206 | except Exception as e: 207 | logger.info(e) 208 | time.sleep(2) 209 | 210 | # APIで受け取った値を読み換える 211 | if value['child_order_state'] == 'ACTIVE': 212 | status = 'open' 213 | elif value['child_order_state'] == 'COMPLETED': 214 | status = 'closed' 215 | else: 216 | status = value['child_order_state'] 217 | 218 | # 未約定量を計算する 219 | remaining = float(value['size']) - float(value['executed_size']) 220 | 221 | time.sleep(0.1) 222 | return {'id': value['child_order_acceptance_id'], 'status': status, 'filled': value['executed_size'], 'remaining': remaining, 'amount': value['size'], 'price': value['price']} 223 | 224 | 225 | def fromListToDF(candleStick): 226 | """ 227 | Listのローソク足をpandasデータフレームへ. 228 | """ 229 | date = [price[0] for price in candleStick] 230 | priceOpen = [int(price[1]) for price in candleStick] 231 | priceHigh = [int(price[2]) for price in candleStick] 232 | priceLow = [int(price[3]) for price in candleStick] 233 | priceClose = [int(price[4]) for price in candleStick] 234 | volume = [int(price[5]) for price in candleStick] 235 | date_datetime = map(datetime.datetime.fromtimestamp, date) 236 | dti = pd.DatetimeIndex(date_datetime) 237 | df_candleStick = pd.DataFrame({"open" : priceOpen, "high" : priceHigh, "low": priceLow, "close" : priceClose, "volume" : volume}, index=dti) 238 | return df_candleStick 239 | 240 | def processCandleStick(candleStick, timeScale): 241 | """ 242 | 1分足データから各時間軸のデータを作成.timeScaleには5T(5分),H(1時間)などの文字列を入れる 243 | """ 244 | df_candleStick = fromListToDF(candleStick) 245 | processed_candleStick = df_candleStick.resample(timeScale).agg({'open': 'first','high': 'max','low': 'min','close': 'last',"volume" : "sum"}) 246 | processed_candleStick = processed_candleStick.dropna() 247 | return processed_candleStick 248 | 249 | def zscore(x, axis = None): 250 | xmean = x.mean(axis=axis, keepdims=True) 251 | xstd = np.std(x, axis=axis, keepdims=True) 252 | zscore = (x-xmean)/xstd 253 | return zscore 254 | 255 | #rciのdの計算 256 | def dofrci(itv,src): 257 | from scipy.stats import rankdata 258 | sum = 0.0 259 | for i in range(itv, 0, -1): 260 | date_rank = itv - i + 1 261 | price_rank = (itv - rankdata(src)[i-1] + 1) 262 | sum = sum + pow( (date_rank - price_rank) ,2) 263 | #pprint("hiduke = {}, price={}, juni={}, goukei={}".format(date_rank, src[i-1], price_rank, sum) ) 264 | 265 | return sum 266 | 267 | #rciの計算 268 | def calc_rci(src, term): 269 | 270 | listnull = [None] 271 | itv = term 272 | rcinull = listnull * itv 273 | rci_tmp = [ (1.0 - 6.0 * dofrci(itv,src[i-itv:i]) / (itv * itv * itv - itv)) * 100.0 for i in range(itv,len(src))] 274 | rci = rcinull + rci_tmp 275 | 276 | return rci 277 | 278 | def vixfix(close, low): 279 | prd = 22 280 | bbl = 20 281 | mult = 2.0 282 | lb = 50 283 | ph = 0.85 284 | pl = 1.01 285 | 286 | wvf = (pd.Series(close).rolling(prd, 1).max() - low) / pd.Series(close).rolling(prd, 1).max() * 100 287 | 288 | sDev = mult * pd.Series(wvf).rolling(bbl, 1).std() 289 | midLine = pd.Series(wvf).rolling(bbl, 1).mean() 290 | 291 | lowerBand = midLine - sDev 292 | upperBand = midLine + sDev 293 | rangeHigh = pd.Series(wvf).rolling(lb, 1).max() * ph 294 | rangeLow = pd.Series(wvf).rolling(lb, 1).min() * pl 295 | 296 | #緑が点灯しているときはエントリーしない 297 | if wvf[len(wvf)-1] > rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] > upperBand[len(wvf)-1]: 298 | return 'buy' 299 | #print("VIX: 緑") 300 | elif wvf[len(wvf)-2] > rangeHigh[len(wvf)-2] or wvf[len(wvf)-2] > upperBand[len(wvf)-2]: 301 | if wvf[len(wvf)-1] < rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] < upperBand[len(wvf)-1]: 302 | #print('VIX: 緑からグレー') 303 | 1+1 304 | #return 'buy' 305 | #赤が点灯しているときはエントリーしない 306 | elif wvf[len(wvf)-1] < rangeLow[len(wvf)-1] or wvf[len(wvf)-1] < lowerBand[len(wvf)-1]: 307 | return 'sell' 308 | #print("VIX: 赤") 309 | elif wvf[len(wvf)-2] < rangeLow[len(wvf)-2] or wvf[len(wvf)-2] < lowerBand[len(wvf)-2]: 310 | if wvf[len(wvf)-1] > rangeLow[len(wvf)-1] or wvf[len(wvf)-1] > lowerBand[len(wvf)-1]: 311 | #print('VIX: 赤からグレー') 312 | 1+1 313 | #return 'sell' 314 | else: 315 | pass 316 | #print("VIX: グレー") 317 | 318 | return 'stay' 319 | 320 | 321 | #------------------------------------------------------------------------------# 322 | 323 | # 未約定量が存在することを示すフラグ 324 | remaining_ask_flag = 0 325 | remaining_bid_flag = 0 326 | 327 | # 指値の有無を示す変数 328 | pos = 'none' 329 | 330 | #------------------------------------------------------------------------------# 331 | 332 | logger.info('--------TradeStart--------') 333 | logger.info('BOT TYPE : MarketMaker @ bitFlyer') 334 | logger.info('SYMBOL : {0}'.format(PAIR)) 335 | logger.info('LOT : {0} {1}'.format(LOT, COIN)) 336 | logger.info('SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)) 337 | logger.info('SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100)) 338 | 339 | # 残高取得 340 | asset = float(get_asset()['info'][0]['amount']) 341 | colla = float(get_colla()['collateral']) 342 | logger.info('--------------------------') 343 | logger.info('ASSET : {0}'.format(int(asset))) 344 | logger.info('COLLATERAL : {0}'.format(int(colla))) 345 | logger.info('TOTAL : {0}'.format(int(asset + colla))) 346 | 347 | #初期化 348 | trade_ask = {"status":'closed'} 349 | trade_bid = {"status":'closed'} 350 | 351 | # メインループ 352 | while True: 353 | 354 | 355 | try: 356 | if "H" in CANDLETERM: 357 | candleStick = cryptowatch.getCandlestick(480, "3600") 358 | elif "30T" in CANDLETERM: 359 | candleStick = cryptowatch.getCandlestick(100, "1800") 360 | elif "15T" in CANDLETERM: 361 | candleStick = cryptowatch.getCandlestick(100, "900") 362 | elif "5T" in CANDLETERM: 363 | candleStick = cryptowatch.getCandlestick(100, "300") 364 | elif "3T" in CANDLETERM: 365 | candleStick = cryptowatch.getCandlestick(100, "180") 366 | else: 367 | candleStick = cryptowatch.getCandlestick(480, "60") 368 | except: 369 | logging.error("Unknown error happend when you requested candleStick") 370 | 371 | if CANDLETERM == None: 372 | df_candleStick = fromListToDF(candleStick) 373 | else: 374 | df_candleStick = processCandleStick(candleStick,CANDLETERM) 375 | 376 | #MACDの計算 377 | #try: 378 | #macd, macdsignal, macdhist = ta.MACD(np.array(df_candleStick["close"][:], dtype='f8'), fastperiod=12, slowperiod=26, signalperiod=9); 379 | #except: 380 | #pass; 381 | 382 | #dmacdhist = np.gradient(macdhist) 383 | #Normdmacdhist = zscore(dmacdhist[~np.isnan(dmacdhist)]) 384 | #absNormdmacdhist = np.abs(Normdmacdhist); 385 | 386 | #3期間RCIの計算 387 | rcirangetermNine = calc_rci(df_candleStick["close"][:],3); 388 | logger.info('rcirangetermNine:%s ', rcirangetermNine[-1]); 389 | 390 | # 未約定量の繰越がなければリセット 391 | if remaining_ask_flag == 0: 392 | remaining_ask = 0 393 | if remaining_bid_flag == 0: 394 | remaining_bid = 0 395 | 396 | #VIX戦略 397 | LOW_PRICE = 3 398 | CLOSE_PRICE = 4 399 | PERIOD = 60 # どの時間足で運用するか(例: 5分足 => 60秒*5 =「300」,1分足 => 60秒 =「60」を入力) 400 | 401 | nowvix = str(int(datetime.datetime.now().timestamp())) 402 | resvix = requests.get('https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=' + str(PERIOD) + '&after=' + str(int(nowvix)-PERIOD*100) + '&before=' + nowvix) 403 | ohlcvvix = resvix.json() 404 | ohlcvRvix = list(map(list, zip(*ohlcvvix['result'][str(PERIOD)]))) 405 | lowvix = np.array(ohlcvRvix[LOW_PRICE]) 406 | closevix = np.array(ohlcvRvix[CLOSE_PRICE]) 407 | callback = vixfix(closevix, lowvix) 408 | 409 | 410 | if callback == 'buy': 411 | #Buy 412 | vixFlag = 1 413 | elif callback == 'sell': 414 | #Sell 415 | vixFlag = 2 416 | else: 417 | #Stay 418 | vixFlag = 0; 419 | 420 | #VIXflagのログ 421 | logger.info('vixflag:%s ', vixFlag); 422 | 423 | # フラグリセット 424 | remaining_ask_flag = 0 425 | remaining_bid_flag = 0 426 | 427 | #positionを取得(指値だけだとバグるので修正取得) 428 | side , size = order.getmypos(); 429 | 430 | if size == 0 and side =="": 431 | pos = 'none'; 432 | trade_ask['status'] = 'closed'; 433 | trade_bid['status'] = 'closed'; 434 | else : 435 | pos = 'entry'; 436 | if side == "SELL": 437 | trade_ask['status'] = 'open'; 438 | if side == "BUY": 439 | trade_bid['status'] = 'open'; 440 | 441 | # 自分の指値が存在しないとき実行する 442 | if pos == 'none' or pos == 'entry': 443 | 444 | try: 445 | # 一つ前のspread 446 | previousspread = spread; 447 | # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する 448 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 449 | ask = float(tick['ask']) 450 | bid = float(tick['bid']) 451 | # 実効スプレッドを計算する 452 | spread = (ask - bid) / bid 453 | 454 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 455 | # askとbidを再計算する 456 | ask = float(tick['ask']) 457 | bid = float(tick['bid']) 458 | 459 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 460 | 461 | if int((ask + bid)/2) > int(ticker["last"]): 462 | trend = "buy" 463 | else: 464 | trend = "sell" 465 | 466 | except: 467 | pass; 468 | 469 | try: 470 | 471 | # 実効スプレッドが閾値を超えた場合に実行する 472 | if spread > SPREAD_ENTRY: 473 | 474 | # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える 475 | amount_int_ask = LOT + remaining_bid 476 | amount_int_bid = LOT + remaining_ask 477 | 478 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 479 | # askとbidを再計算する 480 | ask = float(tick['ask']) 481 | bid = float(tick['bid']) 482 | 483 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 484 | lastprice9 = int(ticker["last"]) 485 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 486 | lastprice8 = int(ticker["last"]) 487 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 488 | lastprice7 = int(ticker["last"]) 489 | 490 | 491 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 492 | lastprice6 = int(ticker["last"]) 493 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 494 | lastprice5 = int(ticker["last"]) 495 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 496 | lastprice4 = int(ticker["last"]) 497 | 498 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 499 | lastprice3 = int(ticker["last"]) 500 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 501 | lastprice2 = int(ticker["last"]) 502 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 503 | lastprice1 = int(ticker["last"]) 504 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 505 | lastprice0 = int(ticker["last"]) 506 | 507 | lastprice = int((lastprice9 + lastprice8 + lastprice7 + lastprice6 + lastprice5 + lastprice4 + lastprice3 + lastprice2 + lastprice1 + lastprice0)/10) 508 | 509 | bidaskmiddleprice = int(((ticker["bid"]) + (ticker["ask"]))/2) 510 | 511 | lastminusbid = int(bid) - int(ticker["last"]); 512 | askminuslast = int(ticker["last"]) - int(ask); 513 | logger.info('Last - Bid:%s ', lastminusbid); 514 | logger.info('Ask - Last:%s ', askminuslast); 515 | 516 | 517 | #実効Ask/Bidからdelta離れた位置に指値を入れる 518 | if rcirangetermNine[-1] > -85 and rcirangetermNine[-1] < 85 and trend == "buy" and vixFlag == 0 and size < 0.3: 519 | trade_bid = limit('buy', amount_int_bid, (ticker["bid"])) 520 | time.sleep(0.2) 521 | order.cancelAllOrderFutures(); 522 | 523 | elif rcirangetermNine[-1] > -85 and rcirangetermNine[-1] < 85 and trend == "sell" and vixFlag == 0 and size < 0.3: 524 | trade_ask = limit('sell', amount_int_ask, (ticker["ask"])) 525 | time.sleep(0.2) 526 | order.cancelAllOrderFutures(); 527 | 528 | elif rcirangetermNine[-1] < -85 and side == "SELL": 529 | trade_bid = limit('buy', size, (ticker["bid"])) 530 | time.sleep(1) 531 | order.cancelAllOrderFutures(); 532 | 533 | elif rcirangetermNine[-1] > 85 and side == "BUY": 534 | trade_ask = limit('sell', size, (ticker["ask"])) 535 | time.sleep(1) 536 | order.cancelAllOrderFutures(); 537 | 538 | logger.info('--------------------------') 539 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 540 | 541 | #logger.info('Normdmacdhist:%s ', Normdmacdhist[-1]); 542 | logger.info('Offset:%s ', int((spread * 10000) / 100) * OFFSET); 543 | logger.info('ABSOffset:%s ', int((spread * 10000) / 100) * ABSOFFSET); 544 | logger.info('trend:%s ', trend); 545 | 546 | logger.info('--------------------------') 547 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 548 | 549 | trade_ask['status'] = 'open' 550 | trade_bid['status'] = 'open' 551 | pos = 'entry' 552 | 553 | logger.info('--------------------------') 554 | logger.info('entry') 555 | 556 | time.sleep(1) 557 | except: 558 | pass; 559 | 560 | # 自分の指値が存在するとき実行する 561 | if pos == 'entry': 562 | 563 | try: 564 | orders = bitflyer.fetch_orders( 565 | symbol = PAIR, 566 | params = { "product_code" : PRODUCT}) 567 | 568 | openorders = bitflyer.fetch_open_orders( 569 | symbol = PAIR, 570 | params = { "product_code" : PRODUCT}) 571 | 572 | trade_ask['status'] = "closed"; 573 | trade_bid['status'] = "closed"; 574 | 575 | for o in openorders: 576 | if o["side"] == "sell": 577 | trade_ask['status'] = "open"; 578 | elif o["side"] == "buy": 579 | trade_bid['status'] = "open"; 580 | else: 581 | trade_ask['status'] = "closed"; 582 | trade_bid['status'] = "closed"; 583 | 584 | #最新の注文のidを取得する 585 | for o in orders: 586 | 587 | if o["side"] == "sell": 588 | trade_ask['id'] = orders[-1]["id"]; 589 | # 注文ステータス取得 590 | if trade_ask['status'] != 'closed': 591 | trade_ask = get_status(trade_ask['id']) 592 | break; 593 | for o in orders: 594 | if o["side"] == "buy": 595 | trade_bid['id'] = orders[-1]["id"]; 596 | # 注文ステータス取得 597 | if trade_bid['status'] != 'closed': 598 | trade_bid = get_status(trade_bid['id']) 599 | break; 600 | 601 | # 板情報を取得、実効Ask/Bid(指値を入れる基準値)を決定する 602 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 603 | ask = float(tick['ask']) 604 | bid = float(tick['bid']) 605 | spread = (ask - bid) / bid 606 | 607 | 608 | logger.info('--------------------------') 609 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 610 | logger.info('ask status:{0}, price:{1}'.format(trade_ask['status'], trade_ask['price'])) 611 | logger.info('bid status:{0}, price:{1}'.format(trade_bid['status'], trade_bid['price'])) 612 | except: 613 | pass; 614 | 615 | 616 | try: 617 | # Ask未約定量が最小注文量を下回るとき実行 618 | if trade_ask['status'] == 'open' and trade_ask['remaining'] <= AMOUNT_MIN: 619 | 620 | # 注文をキャンセル 621 | order.cancelAllOrder(); 622 | 623 | # ステータスをCLOSEDに書き換える 624 | trade_ask['status'] = 'closed' 625 | 626 | # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる 627 | remaining_ask = float(trade_ask['remaining']) 628 | remaining_ask_flag = 1 629 | 630 | logger.info('--------------------------') 631 | logger.info('ask almost filled.') 632 | 633 | # Bid未約定量が最小注文量を下回るとき実行 634 | if trade_bid['status'] == 'open' and trade_bid['remaining'] <= AMOUNT_MIN: 635 | 636 | # 注文をキャンセル 637 | order.cancelAllOrder(); 638 | 639 | # ステータスをCLOSEDに書き換える 640 | trade_bid['status'] = 'closed' 641 | 642 | # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる 643 | remaining_bid = float(trade_bid['remaining']) 644 | remaining_bid_flag = 1 645 | 646 | logger.info('--------------------------') 647 | logger.info('bid almost filled.') 648 | 649 | #スプレッドが閾値以上のときに実行する 650 | if spread > SPREAD_CANCEL: 651 | 652 | # Ask指値が最良位置に存在しないとき、指値を更新する 653 | if trade_ask['status'] == 'open': 654 | if trade_ask['price'] != ask: 655 | 656 | # 指値を一旦キャンセル 657 | order.cancelAllOrder(); 658 | 659 | # 注文数が最小注文数より大きいとき、指値を更新する 660 | if trade_ask['remaining'] >= AMOUNT_MIN: 661 | trade_ask = limit('sell', trade_ask['remaining'], ask) 662 | trade_ask['status'] = 'open' 663 | # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする 664 | elif AMOUNT_MIN > trade_ask['remaining'] > 0: 665 | trade_ask['status'] = 'closed' 666 | remaining_ask = float(trade_ask['remaining']) 667 | remaining_ask_flag = 1 668 | 669 | # 注文数が最小注文数より小さく0のとき、CLOSEDとする 670 | else: 671 | trade_ask['status'] = 'closed' 672 | 673 | # Bid指値が最良位置に存在しないとき、指値を更新する 674 | if trade_bid['status'] == 'open': 675 | if trade_bid['price'] != bid: 676 | 677 | # 指値を一旦キャンセル 678 | order.cancelAllOrder(); 679 | 680 | # 注文数が最小注文数より大きいとき、指値を更新する 681 | if trade_bid['remaining'] >= AMOUNT_MIN: 682 | trade_bid = limit('buy', trade_bid['remaining'], bid) 683 | trade_bid['status'] = 'open' 684 | # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする 685 | elif AMOUNT_MIN > trade_bid['remaining'] > 0: 686 | trade_bid['status'] = 'closed' 687 | remaining_bid = float(trade_bid['remaining']) 688 | remaining_bid_flag = 1 689 | # 注文数が最小注文数より小さく0のとき、CLOSEDとする 690 | else: 691 | trade_bid['status'] = 'closed' 692 | 693 | #おそうじする 694 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 695 | # askとbidを再計算する 696 | ask = float(tick['ask']) 697 | bid = float(tick['bid']) 698 | 699 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRPRODUCT }) 700 | 701 | if int((ask + bid)/2) > int(ticker["last"]): 702 | trend = "buy" 703 | else: 704 | trend = "sell" 705 | 706 | #positionを取得(指値だけだとバグるので修正取得) 707 | side , size = order.getmypos(); 708 | 709 | if side == "SELL" and trend == 'buy' and trade_bid['status'] == "closed": 710 | amount_int_bid = LOT + remaining_ask 711 | trade_bid = limit('buy', size, bid + DELTA + int((spread * 10000) / 100) * OFFSET) 712 | trade_bid['status'] = 'open' 713 | if side == "BUY" and trend == 'sell' and trade_ask['status'] == "closed": 714 | amount_int_ask = LOT + remaining_bid 715 | trade_ask = limit('sell', size, ask - DELTA - int((spread * 10000) / 100) * OFFSET) 716 | trade_ask['status'] = 'open' 717 | except: 718 | pass; 719 | 720 | # Ask/Bid両方の指値が約定したとき、1サイクル終了、最初の処理に戻る 721 | try: 722 | if trade_ask['status'] == 'closed' and trade_bid['status'] == 'closed': 723 | pos = 'none' 724 | 725 | logger.info('--------------------------') 726 | logger.info('completed.') 727 | except: 728 | pass; 729 | 730 | 731 | 732 | -------------------------------------------------------------------------------- /MarketMakerAbsorption.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | class MarketMaker: 4 | pass 5 | def __init__(self): 6 | #config.jsonの読み込み 7 | f = open('config/config.json', 'r', encoding="utf-8") 8 | config = json.load(f) 9 | 10 | 11 | #!/usr/bin/python3 12 | # coding: utf-8 13 | 14 | import datetime 15 | import time 16 | import json 17 | import ccxt 18 | import requests 19 | import bforder 20 | import cryptowatch 21 | #import talib as ta 22 | import numpy as np 23 | import pandas as pd 24 | 25 | #configの読み込み 26 | f = open('config/config.json', 'r', encoding="utf-8") 27 | config = json.load(f) 28 | 29 | order = bforder.BFOrder(); 30 | 31 | cryptowatch = cryptowatch.CryptoWatch() 32 | 33 | bitflyer = ccxt.bitflyer({ 34 | 'apiKey': config["key"], 35 | 'secret': config["secret"], 36 | }) 37 | 38 | # 取引する通貨、シンボルを設定 39 | COIN = 'BTC' 40 | PAIR = 'BTC/JPY' 41 | 42 | # プロダクトコードの指定 43 | PRODUCT = config["product_code"] 44 | 45 | # ロット(単位はBTC) 46 | LOT = config["lotSize"]; 47 | 48 | CANDLETERM = config["candleTerm"]; 49 | 50 | # 最小注文数(取引所の仕様に応じて設定) 51 | AMOUNT_MIN = 0.001 52 | 53 | # スプレッド閾値 54 | SPREAD_ENTRY = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらエントリー 55 | SPREAD_CANCEL = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を下回ったら指値更新を停止 56 | 57 | # 数量X(この数量よりも下に指値をおく) 58 | AMOUNT_THRU = 3 59 | AMOUNT_ASKBID = 0.5 60 | AMOUNT_EVIL_ASKBID = 9 61 | 62 | # 実効Ask/BidからDELTA離れた位置に指値をおく 63 | DELTA = 30 64 | 65 | INVDELTA = -20 66 | 67 | # 買い気配、売り気配に応じて主観的ファンダメンタル価格をずらす 68 | OFFSET = 2 69 | 70 | ABSOFFSET = 100 71 | 72 | PERTURB = 40 73 | 74 | spread = 0 75 | 76 | vixFlag = 0 77 | 78 | callback = 'stay'; 79 | 80 | signedsize = 0; 81 | 82 | minLOT = 0.01 83 | 84 | 85 | #------------------------------------------------------------------------------# 86 | #log設定 87 | import logging 88 | logger = logging.getLogger('LoggingTest') 89 | logger.setLevel(10) 90 | fh = logging.FileHandler('log_mm_bf_' + datetime.datetime.now().strftime('%Y%m%d') + '_' + datetime.datetime.now().strftime('%H%M%S') + '.log') 91 | logger.addHandler(fh) 92 | sh = logging.StreamHandler() 93 | logger.addHandler(sh) 94 | formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") 95 | fh.setFormatter(formatter) 96 | sh.setFormatter(formatter) 97 | 98 | #------------------------------------------------------------------------------# 99 | 100 | # JPY残高を参照する関数 101 | def get_asset(): 102 | 103 | while True: 104 | try: 105 | value = bitflyer.fetch_balance(params = { "product_code" : PRODUCT }) 106 | break 107 | except Exception as e: 108 | logger.info(e) 109 | time.sleep(1) 110 | return value 111 | 112 | # JPY証拠金を参照する関数 113 | def get_colla(): 114 | 115 | while True: 116 | try: 117 | value = bitflyer.privateGetGetcollateral() 118 | break 119 | except Exception as e: 120 | logger.info(e) 121 | time.sleep(1) 122 | return value 123 | 124 | # 板情報から実効Ask/Bid(=指値を入れる基準値)を計算する関数 125 | def get_effective_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 126 | 127 | while True: 128 | try: 129 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 130 | break 131 | except Exception as e: 132 | logger.info(e) 133 | time.sleep(2) 134 | 135 | i = 0 136 | s = 0 137 | while s <= size_thru: 138 | if value['bids'][i][0] == rate_bid: 139 | s += value['bids'][i][1] - size_bid 140 | else: 141 | s += value['bids'][i][1] 142 | i += 1 143 | 144 | j = 0 145 | t = 0 146 | while t <= size_thru: 147 | if value['asks'][j][0] == rate_ask: 148 | t += value['asks'][j][1] - size_ask 149 | else: 150 | t += value['asks'][j][1] 151 | j += 1 152 | 153 | time.sleep(0.5) 154 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 155 | 156 | # 板情報から実効Ask/Bid(=指値を入れる基準値)を計算する関数 157 | def get_threshold_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 158 | 159 | while True: 160 | try: 161 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 162 | break 163 | except Exception as e: 164 | logger.info(e) 165 | time.sleep(2) 166 | 167 | i = 0 168 | s = 0 169 | while s <= size_thru: 170 | if value['bids'][i][0] == rate_bid: 171 | s = value['bids'][i][1] 172 | else: 173 | s = value['bids'][i][1] 174 | i += 1 175 | 176 | j = 0 177 | t = 0 178 | while t <= size_thru: 179 | if value['asks'][j][0] == rate_ask: 180 | t = value['asks'][j][1] 181 | else: 182 | t = value['asks'][j][1] 183 | j += 1 184 | time.sleep(0.5) 185 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 186 | 187 | # 成行注文する関数 188 | def market(side, size): 189 | 190 | while True: 191 | try: 192 | value = bitflyer.create_order(PAIR, type = 'market', side = side, amount = size,params = { "product_code" : PRODUCT }) 193 | break 194 | except Exception as e: 195 | logger.info(e) 196 | time.sleep(2) 197 | #場当たり的な対処 198 | size = minLOT; 199 | 200 | time.sleep(0.5) 201 | return value 202 | 203 | # 指値注文する関数 204 | def limit(side, size, price): 205 | 206 | while True: 207 | try: 208 | value = bitflyer.create_order(PAIR, type = 'limit', side = side, amount = size, price = price, params = { "product_code" : PRODUCT }) 209 | break 210 | except Exception as e: 211 | logger.info(e) 212 | time.sleep(2) 213 | #場当たり的な対処 214 | size = minLOT; 215 | 216 | time.sleep(0.5) 217 | return value 218 | 219 | # 注文をキャンセルする関数 220 | def cancel(id): 221 | 222 | try: 223 | value = bitflyer.cancelOrder(symbol = PAIR, id = id) 224 | except Exception as e: 225 | logger.info(e) 226 | 227 | # 指値が約定していた(=キャンセルが通らなかった)場合、 228 | # 注文情報を更新(約定済み)して返す 229 | value = get_status(id) 230 | 231 | time.sleep(0.5) 232 | return value 233 | 234 | # 指定した注文idのステータスを参照する関数 235 | def get_status(id): 236 | 237 | 238 | while True: 239 | try: 240 | value = bitflyer.private_get_getchildorders(params = {'product_code': PRODUCT, 'child_order_acceptance_id': id})[0] 241 | break 242 | except Exception as e: 243 | logger.info(e) 244 | time.sleep(2) 245 | 246 | # APIで受け取った値を読み換える 247 | if value['child_order_state'] == 'ACTIVE': 248 | status = 'open' 249 | elif value['child_order_state'] == 'COMPLETED': 250 | status = 'closed' 251 | else: 252 | status = value['child_order_state'] 253 | 254 | # 未約定量を計算する 255 | remaining = float(value['size']) - float(value['executed_size']) 256 | 257 | time.sleep(0.1) 258 | return {'id': value['child_order_acceptance_id'], 'status': status, 'filled': value['executed_size'], 'remaining': remaining, 'amount': value['size'], 'price': value['price']} 259 | 260 | 261 | def fromListToDF(candleStick): 262 | """ 263 | Listのローソク足をpandasデータフレームへ. 264 | """ 265 | date = [price[0] for price in candleStick] 266 | priceOpen = [int(price[1]) for price in candleStick] 267 | priceHigh = [int(price[2]) for price in candleStick] 268 | priceLow = [int(price[3]) for price in candleStick] 269 | priceClose = [int(price[4]) for price in candleStick] 270 | volume = [int(price[5]) for price in candleStick] 271 | date_datetime = map(datetime.datetime.fromtimestamp, date) 272 | dti = pd.DatetimeIndex(date_datetime) 273 | df_candleStick = pd.DataFrame({"open" : priceOpen, "high" : priceHigh, "low": priceLow, "close" : priceClose, "volume" : volume}, index=dti) 274 | return df_candleStick 275 | 276 | def processCandleStick(candleStick, timeScale): 277 | """ 278 | 1分足データから各時間軸のデータを作成.timeScaleには5T(5分),H(1時間)などの文字列を入れる 279 | """ 280 | df_candleStick = fromListToDF(candleStick) 281 | processed_candleStick = df_candleStick.resample(timeScale).agg({'open': 'first','high': 'max','low': 'min','close': 'last',"volume" : "sum"}) 282 | processed_candleStick = processed_candleStick.dropna() 283 | return processed_candleStick 284 | 285 | def zscore(x, axis = None): 286 | xmean = x.mean(axis=axis, keepdims=True) 287 | xstd = np.std(x, axis=axis, keepdims=True) 288 | zscore = (x-xmean)/xstd 289 | return zscore 290 | 291 | #rciのdの計算 292 | def dofrci(itv,src): 293 | from scipy.stats import rankdata 294 | sum = 0.0 295 | for i in range(itv, 0, -1): 296 | date_rank = itv - i + 1 297 | price_rank = (itv - rankdata(src)[i-1] + 1) 298 | sum = sum + pow( (date_rank - price_rank) ,2) 299 | #pprint("hiduke = {}, price={}, juni={}, goukei={}".format(date_rank, src[i-1], price_rank, sum) ) 300 | 301 | return sum 302 | 303 | #rciの計算 304 | def calc_rci(src, term): 305 | 306 | listnull = [None] 307 | itv = term 308 | rcinull = listnull * itv 309 | rci_tmp = [ (1.0 - 6.0 * dofrci(itv,src[i-itv:i]) / (itv * itv * itv - itv)) * 100.0 for i in range(itv,len(src))] 310 | rci = rcinull + rci_tmp 311 | 312 | return rci 313 | 314 | def vixfix(close, low): 315 | prd = 22 316 | bbl = 20 317 | mult = 2.0 318 | lb = 50 319 | ph = 0.85 320 | pl = 1.01 321 | 322 | wvf = (pd.Series(close).rolling(prd, 1).max() - low) / pd.Series(close).rolling(prd, 1).max() * 100 323 | 324 | sDev = mult * pd.Series(wvf).rolling(bbl, 1).std() 325 | midLine = pd.Series(wvf).rolling(bbl, 1).mean() 326 | 327 | lowerBand = midLine - sDev 328 | upperBand = midLine + sDev 329 | rangeHigh = pd.Series(wvf).rolling(lb, 1).max() * ph 330 | rangeLow = pd.Series(wvf).rolling(lb, 1).min() * pl 331 | 332 | #緑が点灯しているときはエントリーしない 333 | if wvf[len(wvf)-1] > rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] > upperBand[len(wvf)-1]: 334 | return 'buy' 335 | #print("VIX: 緑") 336 | elif wvf[len(wvf)-2] > rangeHigh[len(wvf)-2] or wvf[len(wvf)-2] > upperBand[len(wvf)-2]: 337 | if wvf[len(wvf)-1] < rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] < upperBand[len(wvf)-1]: 338 | #print('VIX: 緑からグレー') 339 | 1+1 340 | #return 'buy' 341 | #赤が点灯しているときはエントリーしない 342 | elif wvf[len(wvf)-1] < rangeLow[len(wvf)-1] or wvf[len(wvf)-1] < lowerBand[len(wvf)-1]: 343 | return 'sell' 344 | #print("VIX: 赤") 345 | elif wvf[len(wvf)-2] < rangeLow[len(wvf)-2] or wvf[len(wvf)-2] < lowerBand[len(wvf)-2]: 346 | if wvf[len(wvf)-1] > rangeLow[len(wvf)-1] or wvf[len(wvf)-1] > lowerBand[len(wvf)-1]: 347 | #print('VIX: 赤からグレー') 348 | 1+1 349 | #return 'sell' 350 | else: 351 | pass 352 | #print("VIX: グレー") 353 | 354 | return 'stay' 355 | 356 | 357 | #------------------------------------------------------------------------------# 358 | 359 | # 未約定量が存在することを示すフラグ 360 | remaining_ask_flag = 0 361 | remaining_bid_flag = 0 362 | 363 | # 指値の有無を示す変数 364 | pos = 'none' 365 | 366 | #------------------------------------------------------------------------------# 367 | 368 | logger.info('--------TradeStart--------') 369 | logger.info('BOT TYPE : MarketMaker @ bitFlyer') 370 | logger.info('SYMBOL : {0}'.format(PAIR)) 371 | logger.info('LOT : {0} {1}'.format(LOT, COIN)) 372 | logger.info('SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)) 373 | logger.info('SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100)) 374 | 375 | # 残高取得 376 | asset = float(get_asset()['info'][0]['amount']) 377 | colla = float(get_colla()['collateral']) 378 | logger.info('--------------------------') 379 | logger.info('ASSET : {0}'.format(int(asset))) 380 | logger.info('COLLATERAL : {0}'.format(int(colla))) 381 | logger.info('TOTAL : {0}'.format(int(asset + colla))) 382 | 383 | #初期化 384 | trade_ask = {"status":'closed'} 385 | trade_bid = {"status":'closed'} 386 | 387 | # メインループ 388 | while True: 389 | 390 | 391 | try: 392 | if "H" in CANDLETERM: 393 | candleStick = cryptowatch.getCandlestick(480, "3600") 394 | elif "30T" in CANDLETERM: 395 | candleStick = cryptowatch.getCandlestick(100, "1800") 396 | elif "15T" in CANDLETERM: 397 | candleStick = cryptowatch.getCandlestick(100, "900") 398 | elif "5T" in CANDLETERM: 399 | candleStick = cryptowatch.getCandlestick(100, "300") 400 | elif "3T" in CANDLETERM: 401 | candleStick = cryptowatch.getCandlestick(100, "180") 402 | else: 403 | candleStick = cryptowatch.getCandlestick(480, "60") 404 | except: 405 | logging.error("Unknown error happend when you requested candleStick") 406 | 407 | if CANDLETERM == None: 408 | df_candleStick = fromListToDF(candleStick) 409 | else: 410 | df_candleStick = processCandleStick(candleStick,CANDLETERM) 411 | 412 | #3期間RCIの計算 413 | rcirangetermNine = calc_rci(df_candleStick["close"][:],9); 414 | logger.info('rcirangetermNine:%s ', rcirangetermNine[-1]); 415 | 416 | # 未約定量の繰越がなければリセット 417 | if remaining_ask_flag == 0: 418 | remaining_ask = 0 419 | if remaining_bid_flag == 0: 420 | remaining_bid = 0 421 | 422 | #VIX戦略 423 | LOW_PRICE = 3 424 | CLOSE_PRICE = 4 425 | PERIOD = 60 # どの時間足で運用するか(例: 5分足 => 60秒*5 =「300」,1分足 => 60秒 =「60」を入力) 426 | 427 | nowvix = str(int(datetime.datetime.now().timestamp())) 428 | resvix = requests.get('https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=' + str(PERIOD) + '&after=' + str(int(nowvix)-PERIOD*100) + '&before=' + nowvix) 429 | ohlcvvix = resvix.json() 430 | ohlcvRvix = list(map(list, zip(*ohlcvvix['result'][str(PERIOD)]))) 431 | lowvix = np.array(ohlcvRvix[LOW_PRICE]) 432 | closevix = np.array(ohlcvRvix[CLOSE_PRICE]) 433 | callback = vixfix(closevix, lowvix) 434 | 435 | 436 | if callback == 'buy': 437 | #Buy 438 | vixFlag = 1 439 | elif callback == 'sell': 440 | #Sell 441 | vixFlag = 2 442 | else: 443 | #Stay 444 | vixFlag = 0; 445 | 446 | #VIXflagのログ 447 | logger.info('vixflag:%s ', vixFlag); 448 | 449 | # フラグリセット 450 | remaining_ask_flag = 0 451 | remaining_bid_flag = 0 452 | 453 | #positionを取得(指値だけだとバグるので修正取得) 454 | side , size = order.getmypos(); 455 | 456 | if side == "SELL": 457 | signedsize = -size; 458 | if side == "BUY": 459 | signedsize = size; 460 | 461 | if size == 0 and side =="": 462 | pos = 'none'; 463 | trade_ask['status'] = 'closed'; 464 | trade_bid['status'] = 'closed'; 465 | else : 466 | pos = 'entry'; 467 | if side == "SELL": 468 | trade_ask['status'] = 'open'; 469 | if side == "BUY": 470 | trade_bid['status'] = 'open'; 471 | 472 | # 自分の指値が存在しないとき実行する 473 | if pos == 'none' or pos == 'entry': 474 | 475 | try: 476 | # 一つ前のspread 477 | previousspread = spread; 478 | # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する 479 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 480 | ask = float(tick['ask']) 481 | bid = float(tick['bid']) 482 | # 実効スプレッドを計算する 483 | spread = (ask - bid) / bid 484 | 485 | tick = get_threshold_tick(size_thru=AMOUNT_EVIL_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 486 | # askとbidを再計算する 487 | ask = float(tick['ask']) 488 | bid = float(tick['bid']) 489 | 490 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 491 | 492 | if int((ask - 1000)) < int(ticker["last"]): 493 | trend = "buy" 494 | if int((bid + 1000)) > int(ticker["last"]): 495 | trend = "stay" 496 | elif int((bid + 1000)) > int(ticker["last"]): 497 | trend = "sell" 498 | if int((ask - 1000)) < int(ticker["last"]): 499 | trend = "stay" 500 | else: 501 | trend = "stay" 502 | 503 | except: 504 | pass; 505 | 506 | try: 507 | 508 | # 実効スプレッドが閾値を超えた場合に実行する 509 | if spread > SPREAD_ENTRY: 510 | 511 | # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える 512 | amount_int_ask = LOT + remaining_bid 513 | amount_int_bid = LOT + remaining_ask 514 | 515 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 516 | # askとbidを再計算する 517 | ask = float(tick['ask']) 518 | bid = float(tick['bid']) 519 | 520 | #実効Ask/Bidからdelta離れた位置に指値を入れる 521 | if rcirangetermNine[-1] < 85 and trend == "buy" and vixFlag == 0 and signedsize < 0.3: 522 | trade_bid = limit('buy', amount_int_bid, (ticker["bid"])) 523 | time.sleep(1) 524 | order.cancelAllOrder(); 525 | 526 | elif rcirangetermNine[-1] > -85 and trend == "sell" and vixFlag == 0 and signedsize > -0.3: 527 | trade_ask = limit('sell', amount_int_ask, (ticker["ask"])) 528 | time.sleep(1) 529 | order.cancelAllOrder(); 530 | 531 | elif side == "SELL": 532 | if rcirangetermNine[-1] < -85 or rcirangetermNine[-1] > 85 : 533 | trade_bid = market('buy', size) 534 | time.sleep(1) 535 | order.cancelAllOrder(); 536 | 537 | elif side == "BUY": 538 | if rcirangetermNine[-1] < -85 or rcirangetermNine[-1] > 85 : 539 | trade_ask = market('sell', size) 540 | time.sleep(1) 541 | order.cancelAllOrder(); 542 | 543 | elif side == "SELL": 544 | if trend == "buy" or "stay": 545 | trade_bid = market('buy', size) 546 | time.sleep(1) 547 | order.cancelAllOrder(); 548 | 549 | elif side == "BUY": 550 | if trend == "sell" or "stay": 551 | trade_ask = market('sell', size) 552 | time.sleep(1) 553 | order.cancelAllOrder(); 554 | 555 | logger.info('--------------------------') 556 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 557 | 558 | #logger.info('Normdmacdhist:%s ', Normdmacdhist[-1]); 559 | logger.info('Offset:%s ', int((spread * 10000) / 100) * OFFSET); 560 | logger.info('ABSOffset:%s ', int((spread * 10000) / 100) * ABSOFFSET); 561 | logger.info('trend:%s ', trend); 562 | 563 | logger.info('--------------------------') 564 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 565 | 566 | trade_ask['status'] = 'open' 567 | trade_bid['status'] = 'open' 568 | pos = 'entry' 569 | 570 | logger.info('--------------------------') 571 | logger.info('entry') 572 | 573 | time.sleep(1) 574 | except: 575 | pass; 576 | 577 | 578 | -------------------------------------------------------------------------------- /MarketMakerEphemeral.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | class MarketMaker: 4 | pass 5 | def __init__(self): 6 | #config.jsonの読み込み 7 | f = open('config/config.json', 'r', encoding="utf-8") 8 | config = json.load(f) 9 | 10 | 11 | #!/usr/bin/python3 12 | # coding: utf-8 13 | 14 | import datetime 15 | import time 16 | import json 17 | import ccxt 18 | import requests 19 | import bforder 20 | import cryptowatch 21 | #import talib as ta 22 | import numpy as np 23 | import pandas as pd 24 | 25 | #configの読み込み 26 | f = open('config/config.json', 'r', encoding="utf-8") 27 | config = json.load(f) 28 | 29 | order = bforder.BFOrder(); 30 | 31 | cryptowatch = cryptowatch.CryptoWatch() 32 | 33 | bitflyer = ccxt.bitflyer({ 34 | 'apiKey': config["key"], 35 | 'secret': config["secret"], 36 | }) 37 | 38 | # 取引する通貨、シンボルを設定 39 | COIN = 'BTC' 40 | PAIR = 'BTC/JPY' 41 | 42 | # プロダクトコードの指定 43 | PRODUCT = config["product_code"] 44 | 45 | # ロット(単位はBTC) 46 | LOT = config["lotSize"]; 47 | 48 | CANDLETERM = config["candleTerm"]; 49 | 50 | # 最小注文数(取引所の仕様に応じて設定) 51 | AMOUNT_MIN = 0.001 52 | 53 | # スプレッド閾値 54 | SPREAD_ENTRY = 0.0001 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらエントリー 55 | SPREAD_CANCEL = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を下回ったら指値更新を停止 56 | SPREAD_CLOSE = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらクローズ 57 | 58 | 59 | # 数量X(この数量よりも下に指値をおく) 60 | AMOUNT_THRU = 30 61 | AMOUNT_ASKBID = 0.01 62 | 63 | # 実効Ask/BidからDELTA離れた位置に指値をおく 64 | DELTA = 30 65 | 66 | INVDELTA = -20 67 | 68 | # 買い気配、売り気配に応じて主観的ファンダメンタル価格をずらす 69 | OFFSET = 2 70 | 71 | ABSOFFSET = 100 72 | 73 | PERTURB = 40 74 | 75 | spread = 0 76 | 77 | vixFlag = 0 78 | 79 | callback = 'stay'; 80 | 81 | signedsize = 0; 82 | 83 | minLOT = 0.01 84 | 85 | 86 | #------------------------------------------------------------------------------# 87 | #log設定 88 | import logging 89 | logger = logging.getLogger('LoggingTest') 90 | logger.setLevel(10) 91 | fh = logging.FileHandler('log_mm_bf_' + datetime.datetime.now().strftime('%Y%m%d') + '_' + datetime.datetime.now().strftime('%H%M%S') + '.log') 92 | logger.addHandler(fh) 93 | sh = logging.StreamHandler() 94 | logger.addHandler(sh) 95 | formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") 96 | fh.setFormatter(formatter) 97 | sh.setFormatter(formatter) 98 | 99 | #------------------------------------------------------------------------------# 100 | 101 | # JPY残高を参照する関数 102 | def get_asset(): 103 | 104 | while True: 105 | try: 106 | value = bitflyer.fetch_balance(params = { "product_code" : PRODUCT }) 107 | break 108 | except Exception as e: 109 | logger.info(e) 110 | time.sleep(1) 111 | return value 112 | 113 | # JPY証拠金を参照する関数 114 | def get_colla(): 115 | 116 | while True: 117 | try: 118 | value = bitflyer.privateGetGetcollateral() 119 | break 120 | except Exception as e: 121 | logger.info(e) 122 | time.sleep(1) 123 | return value 124 | 125 | # 板情報から実効Ask/Bid(=指値を入れる基準値)を計算する関数 126 | def get_effective_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 127 | 128 | while True: 129 | try: 130 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 131 | break 132 | except Exception as e: 133 | logger.info(e) 134 | time.sleep(2) 135 | 136 | i = 0 137 | s = 0 138 | while s <= size_thru: 139 | if value['bids'][i][0] == rate_bid: 140 | s += value['bids'][i][1] - size_bid 141 | else: 142 | s += value['bids'][i][1] 143 | i += 1 144 | 145 | j = 0 146 | t = 0 147 | while t <= size_thru: 148 | if value['asks'][j][0] == rate_ask: 149 | t += value['asks'][j][1] - size_ask 150 | else: 151 | t += value['asks'][j][1] 152 | j += 1 153 | 154 | time.sleep(0.2) 155 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 156 | 157 | # 成行注文する関数 158 | def market(side, size): 159 | 160 | while True: 161 | try: 162 | value = bitflyer.create_order(PAIR, type = 'market', side = side, amount = size,params = { "product_code" : PRODUCT }) 163 | break 164 | except Exception as e: 165 | logger.info(e) 166 | time.sleep(1) 167 | #場当たり的な対処 168 | size = minLOT; 169 | 170 | time.sleep(0.2) 171 | return value 172 | 173 | # 指値注文する関数 174 | def limit(side, size, price): 175 | 176 | while True: 177 | try: 178 | value = bitflyer.create_order(PAIR, type = 'limit', side = side, amount = size, price = price, params = { "product_code" : PRODUCT }) 179 | break 180 | except Exception as e: 181 | logger.info(e) 182 | time.sleep(1) 183 | #場当たり的な対処 184 | size = minLOT; 185 | 186 | time.sleep(0.2) 187 | return value 188 | 189 | # 注文をキャンセルする関数 190 | def cancel(id): 191 | 192 | try: 193 | value = bitflyer.cancelOrder(symbol = PAIR, id = id) 194 | except Exception as e: 195 | logger.info(e) 196 | 197 | # 指値が約定していた(=キャンセルが通らなかった)場合、 198 | # 注文情報を更新(約定済み)して返す 199 | value = get_status(id) 200 | 201 | time.sleep(0.5) 202 | return value 203 | 204 | # 指定した注文idのステータスを参照する関数 205 | def get_status(id): 206 | 207 | 208 | while True: 209 | try: 210 | value = bitflyer.private_get_getchildorders(params = {'product_code': PRODUCT, 'child_order_acceptance_id': id})[0] 211 | break 212 | except Exception as e: 213 | logger.info(e) 214 | time.sleep(2) 215 | 216 | # APIで受け取った値を読み換える 217 | if value['child_order_state'] == 'ACTIVE': 218 | status = 'open' 219 | elif value['child_order_state'] == 'COMPLETED': 220 | status = 'closed' 221 | else: 222 | status = value['child_order_state'] 223 | 224 | # 未約定量を計算する 225 | remaining = float(value['size']) - float(value['executed_size']) 226 | 227 | time.sleep(0.1) 228 | return {'id': value['child_order_acceptance_id'], 'status': status, 'filled': value['executed_size'], 'remaining': remaining, 'amount': value['size'], 'price': value['price']} 229 | 230 | 231 | def fromListToDF(candleStick): 232 | """ 233 | Listのローソク足をpandasデータフレームへ. 234 | """ 235 | date = [price[0] for price in candleStick] 236 | priceOpen = [int(price[1]) for price in candleStick] 237 | priceHigh = [int(price[2]) for price in candleStick] 238 | priceLow = [int(price[3]) for price in candleStick] 239 | priceClose = [int(price[4]) for price in candleStick] 240 | volume = [int(price[5]) for price in candleStick] 241 | date_datetime = map(datetime.datetime.fromtimestamp, date) 242 | dti = pd.DatetimeIndex(date_datetime) 243 | df_candleStick = pd.DataFrame({"open" : priceOpen, "high" : priceHigh, "low": priceLow, "close" : priceClose, "volume" : volume}, index=dti) 244 | return df_candleStick 245 | 246 | def processCandleStick(candleStick, timeScale): 247 | """ 248 | 1分足データから各時間軸のデータを作成.timeScaleには5T(5分),H(1時間)などの文字列を入れる 249 | """ 250 | df_candleStick = fromListToDF(candleStick) 251 | processed_candleStick = df_candleStick.resample(timeScale).agg({'open': 'first','high': 'max','low': 'min','close': 'last',"volume" : "sum"}) 252 | processed_candleStick = processed_candleStick.dropna() 253 | return processed_candleStick 254 | 255 | def zscore(x, axis = None): 256 | xmean = x.mean(axis=axis, keepdims=True) 257 | xstd = np.std(x, axis=axis, keepdims=True) 258 | zscore = (x-xmean)/xstd 259 | return zscore 260 | 261 | #rciのdの計算 262 | def dofrci(itv,src): 263 | from scipy.stats import rankdata 264 | sum = 0.0 265 | for i in range(itv, 0, -1): 266 | date_rank = itv - i + 1 267 | price_rank = (itv - rankdata(src)[i-1] + 1) 268 | sum = sum + pow( (date_rank - price_rank) ,2) 269 | #pprint("hiduke = {}, price={}, juni={}, goukei={}".format(date_rank, src[i-1], price_rank, sum) ) 270 | 271 | return sum 272 | 273 | #rciの計算 274 | def calc_rci(src, term): 275 | 276 | listnull = [None] 277 | itv = term 278 | rcinull = listnull * itv 279 | rci_tmp = [ (1.0 - 6.0 * dofrci(itv,src[i-itv:i]) / (itv * itv * itv - itv)) * 100.0 for i in range(itv,len(src))] 280 | rci = rcinull + rci_tmp 281 | 282 | return rci 283 | 284 | def vixfix(close, low): 285 | prd = 22 286 | bbl = 20 287 | mult = 2.0 288 | lb = 50 289 | ph = 0.85 290 | pl = 1.01 291 | 292 | wvf = (pd.Series(close).rolling(prd, 1).max() - low) / pd.Series(close).rolling(prd, 1).max() * 100 293 | 294 | sDev = mult * pd.Series(wvf).rolling(bbl, 1).std() 295 | midLine = pd.Series(wvf).rolling(bbl, 1).mean() 296 | 297 | lowerBand = midLine - sDev 298 | upperBand = midLine + sDev 299 | rangeHigh = pd.Series(wvf).rolling(lb, 1).max() * ph 300 | rangeLow = pd.Series(wvf).rolling(lb, 1).min() * pl 301 | 302 | #緑が点灯しているときはエントリーしない 303 | if wvf[len(wvf)-1] > rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] > upperBand[len(wvf)-1]: 304 | return 'buy' 305 | #print("VIX: 緑") 306 | elif wvf[len(wvf)-2] > rangeHigh[len(wvf)-2] or wvf[len(wvf)-2] > upperBand[len(wvf)-2]: 307 | if wvf[len(wvf)-1] < rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] < upperBand[len(wvf)-1]: 308 | #print('VIX: 緑からグレー') 309 | 1+1 310 | #return 'buy' 311 | #赤が点灯しているときはエントリーしない 312 | elif wvf[len(wvf)-1] < rangeLow[len(wvf)-1] or wvf[len(wvf)-1] < lowerBand[len(wvf)-1]: 313 | return 'sell' 314 | #print("VIX: 赤") 315 | elif wvf[len(wvf)-2] < rangeLow[len(wvf)-2] or wvf[len(wvf)-2] < lowerBand[len(wvf)-2]: 316 | if wvf[len(wvf)-1] > rangeLow[len(wvf)-1] or wvf[len(wvf)-1] > lowerBand[len(wvf)-1]: 317 | #print('VIX: 赤からグレー') 318 | 1+1 319 | #return 'sell' 320 | else: 321 | pass 322 | #print("VIX: グレー") 323 | 324 | return 'stay' 325 | 326 | 327 | #------------------------------------------------------------------------------# 328 | 329 | # 未約定量が存在することを示すフラグ 330 | remaining_ask_flag = 0 331 | remaining_bid_flag = 0 332 | 333 | # 指値の有無を示す変数 334 | pos = 'none' 335 | 336 | #------------------------------------------------------------------------------# 337 | 338 | logger.info('--------TradeStart--------') 339 | logger.info('BOT TYPE : MarketMaker @ bitFlyer') 340 | logger.info('SYMBOL : {0}'.format(PAIR)) 341 | logger.info('LOT : {0} {1}'.format(LOT, COIN)) 342 | logger.info('SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)) 343 | logger.info('SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100)) 344 | 345 | # 残高取得 346 | asset = float(get_asset()['info'][0]['amount']) 347 | colla = float(get_colla()['collateral']) 348 | logger.info('--------------------------') 349 | logger.info('ASSET : {0}'.format(int(asset))) 350 | logger.info('COLLATERAL : {0}'.format(int(colla))) 351 | logger.info('TOTAL : {0}'.format(int(asset + colla))) 352 | 353 | #初期化 354 | trade_ask = {"status":'closed'} 355 | trade_bid = {"status":'closed'} 356 | 357 | # メインループ 358 | while True: 359 | 360 | try: 361 | if "H" in CANDLETERM: 362 | candleStick = cryptowatch.getCandlestick(480, "3600") 363 | elif "30T" in CANDLETERM: 364 | candleStick = cryptowatch.getCandlestick(100, "1800") 365 | elif "15T" in CANDLETERM: 366 | candleStick = cryptowatch.getCandlestick(100, "900") 367 | elif "5T" in CANDLETERM: 368 | candleStick = cryptowatch.getCandlestick(100, "300") 369 | elif "3T" in CANDLETERM: 370 | candleStick = cryptowatch.getCandlestick(100, "180") 371 | else: 372 | candleStick = cryptowatch.getCandlestick(480, "60") 373 | except: 374 | logging.error("Unknown error happend when you requested candleStick") 375 | 376 | if CANDLETERM == None: 377 | df_candleStick = fromListToDF(candleStick) 378 | else: 379 | df_candleStick = processCandleStick(candleStick,CANDLETERM) 380 | 381 | #3期間RCIの計算 382 | rcirangetermNine = calc_rci(df_candleStick["close"][:],3); 383 | logger.info('rcirangetermNine:%s ', rcirangetermNine[-1]); 384 | 385 | # 未約定量の繰越がなければリセット 386 | if remaining_ask_flag == 0: 387 | remaining_ask = 0 388 | if remaining_bid_flag == 0: 389 | remaining_bid = 0 390 | 391 | #VIX戦略 392 | LOW_PRICE = 3 393 | CLOSE_PRICE = 4 394 | PERIOD = 60 # どの時間足で運用するか(例: 5分足 => 60秒*5 =「300」,1分足 => 60秒 =「60」を入力) 395 | 396 | nowvix = str(int(datetime.datetime.now().timestamp())) 397 | resvix = requests.get('https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=' + str(PERIOD) + '&after=' + str(int(nowvix)-PERIOD*100) + '&before=' + nowvix) 398 | ohlcvvix = resvix.json() 399 | ohlcvRvix = list(map(list, zip(*ohlcvvix['result'][str(PERIOD)]))) 400 | lowvix = np.array(ohlcvRvix[LOW_PRICE]) 401 | closevix = np.array(ohlcvRvix[CLOSE_PRICE]) 402 | callback = vixfix(closevix, lowvix) 403 | 404 | 405 | if callback == 'buy': 406 | #Buy 407 | vixFlag = 0 408 | elif callback == 'sell': 409 | #Sell 410 | vixFlag = 0 411 | else: 412 | #Stay 413 | vixFlag = 0; 414 | 415 | #VIXflagのログ 416 | logger.info('vixflag:%s ', vixFlag); 417 | 418 | # フラグリセット 419 | remaining_ask_flag = 0 420 | remaining_bid_flag = 0 421 | 422 | #positionを取得(指値だけだとバグるので修正取得) 423 | side , size = order.getmypos(); 424 | 425 | if side == "SELL": 426 | signedsize = -size; 427 | if side == "BUY": 428 | signedsize = size; 429 | if side == "": 430 | signedsize = size; 431 | 432 | if size == 0 and side =="": 433 | pos = 'none'; 434 | trade_ask['status'] = 'closed'; 435 | trade_bid['status'] = 'closed'; 436 | else : 437 | pos = 'entry'; 438 | if side == "SELL": 439 | trade_ask['status'] = 'open'; 440 | if side == "BUY": 441 | trade_bid['status'] = 'open'; 442 | 443 | # 自分の指値が存在しないとき実行する 444 | if pos == 'none' or pos == 'entry': 445 | 446 | try: 447 | # 一つ前のspread 448 | previousspread = spread; 449 | # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する 450 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 451 | ask = float(tick['ask']) 452 | bid = float(tick['bid']) 453 | # 実効スプレッドを計算する 454 | spread = (ask - bid) / bid 455 | 456 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 457 | # askとbidを再計算する 458 | ask = float(tick['ask']) 459 | bid = float(tick['bid']) 460 | 461 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 462 | lastprice9 = int(ticker["last"]) 463 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 464 | lastprice8 = int(ticker["last"]) 465 | 466 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 467 | lastprice7 = int(ticker["last"]) 468 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 469 | lastprice6 = int(ticker["last"]) 470 | 471 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 472 | lastprice5 = int(ticker["last"]) 473 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 474 | lastprice4 = int(ticker["last"]) 475 | 476 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 477 | lastprice3 = int(ticker["last"]) 478 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 479 | lastprice2 = int(ticker["last"]) 480 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 481 | lastprice1 = int(ticker["last"]) 482 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 483 | lastprice0 = int(ticker["last"]) 484 | 485 | lastprice = int((lastprice9 + lastprice8 + lastprice7 + lastprice6 + lastprice5 + lastprice4 + lastprice3 + lastprice2 + lastprice1 + lastprice0)/10) 486 | 487 | 488 | if int((ask + bid)/2) > int(lastprice): 489 | trend = "sell" 490 | else: 491 | trend = "buy" 492 | except: 493 | pass; 494 | 495 | try: 496 | 497 | # 実効スプレッドが閾値を超えた場合に実行する 498 | if spread > SPREAD_ENTRY: 499 | 500 | # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える 501 | amount_int_ask = LOT + remaining_bid 502 | amount_int_bid = LOT + remaining_ask 503 | 504 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 505 | # askとbidを再計算する 506 | ask = float(tick['ask']) 507 | bid = float(tick['bid']) 508 | 509 | #実効Ask/Bidからdelta離れた位置に指値を入れる 510 | if rcirangetermNine[-1] < 85 and trend == "buy" and vixFlag == 0 and size < 0.3: 511 | trade_bid = limit('buy', amount_int_bid, (ticker["bid"])) 512 | time.sleep(0.2) 513 | order.cancelAllOrder(); 514 | 515 | elif rcirangetermNine[-1] > -85 and trend == "sell" and vixFlag == 0 and size < 0.3: 516 | trade_ask = limit('sell', amount_int_ask, (ticker["ask"])) 517 | time.sleep(0.2) 518 | order.cancelAllOrder(); 519 | 520 | # 実効スプレッドが閾値を超えた場合に実行する 521 | if spread > SPREAD_CLOSE: 522 | 523 | side , size = order.getmypos(); 524 | 525 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 526 | # askとbidを再計算する 527 | ask = float(tick['ask']) 528 | bid = float(tick['bid']) 529 | 530 | if side == "SELL": 531 | if trend == "buy": 532 | trade_bid = limit('buy', round(size,15),(ticker["ask"])) 533 | time.sleep(0.2) 534 | order.cancelAllOrder(); 535 | 536 | elif side == "BUY": 537 | if trend == "sell": 538 | trade_ask = limit('sell', round(size,15),(ticker["bid"])) 539 | time.sleep(0.2) 540 | order.cancelAllOrder(); 541 | 542 | elif side == "SELL": 543 | if rcirangetermNine[-1] < -85 or rcirangetermNine[-1] > 85 : 544 | trade_bid = market('buy', size) 545 | time.sleep(3) 546 | order.cancelAllOrder(); 547 | 548 | elif side == "BUY": 549 | if rcirangetermNine[-1] < -85 or rcirangetermNine[-1] > 85 : 550 | trade_ask = market('sell', size) 551 | time.sleep(3) 552 | order.cancelAllOrder(); 553 | 554 | logger.info('--------------------------') 555 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 556 | 557 | #logger.info('Normdmacdhist:%s ', Normdmacdhist[-1]); 558 | logger.info('Offset:%s ', int((spread * 10000) / 100) * OFFSET); 559 | logger.info('ABSOffset:%s ', int((spread * 10000) / 100) * ABSOFFSET); 560 | logger.info('trend:%s ', trend); 561 | logger.info('signedsize:%s ', signedsize); 562 | 563 | logger.info('--------------------------') 564 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 565 | 566 | trade_ask['status'] = 'open' 567 | trade_bid['status'] = 'open' 568 | pos = 'entry' 569 | 570 | logger.info('--------------------------') 571 | logger.info('entry') 572 | 573 | except: 574 | pass; 575 | 576 | 577 | -------------------------------------------------------------------------------- /MarketMakerFutures.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | class MarketMaker: 4 | pass 5 | def __init__(self): 6 | #config.jsonの読み込み 7 | f = open('config/config.json', 'r', encoding="utf-8") 8 | config = json.load(f) 9 | 10 | 11 | #!/usr/bin/python3 12 | # coding: utf-8 13 | 14 | import datetime 15 | import time 16 | import json 17 | import ccxt 18 | import requests 19 | import bforder 20 | import cryptowatch 21 | #import talib as ta 22 | import numpy as np 23 | import pandas as pd 24 | 25 | #configの読み込み 26 | f = open('config/config.json', 'r', encoding="utf-8") 27 | config = json.load(f) 28 | 29 | order = bforder.BFOrder(); 30 | 31 | cryptowatch = cryptowatch.CryptoWatch() 32 | 33 | bitflyer = ccxt.bitflyer({ 34 | 'apiKey': config["key"], 35 | 'secret': config["secret"], 36 | }) 37 | 38 | # 取引する通貨、シンボルを設定 39 | COIN = 'BTC' 40 | PAIR = 'BTCJPY28SEP2018' 41 | 42 | # プロダクトコードの指定 43 | PRODUCT = config["product_code"] 44 | 45 | # ロット(単位はBTC) 46 | LOT = config["lotSize"]; 47 | 48 | CANDLETERM = config["candleTerm"]; 49 | 50 | # 最小注文数(取引所の仕様に応じて設定) 51 | AMOUNT_MIN = 0.001 52 | 53 | # スプレッド閾値 54 | SPREAD_ENTRY = 0.0001 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらエントリー 55 | SPREAD_CANCEL = 0.0001 # 実効スプレッド(100%=1,1%=0.01)がこの値を下回ったら指値更新を停止 56 | SPREAD_CLEAN = 0.0002 57 | 58 | # 数量X(この数量よりも下に指値をおく) 59 | AMOUNT_THRU = 1 60 | AMOUNT_ASKBID = 0.5 61 | 62 | # 実効Ask/BidからDELTA離れた位置に指値をおく 63 | DELTA = 30 64 | 65 | INVDELTA = -20 66 | 67 | # 買い気配、売り気配に応じて主観的ファンダメンタル価格をずらす 68 | OFFSET = 2 69 | 70 | ABSOFFSET = 100 71 | 72 | PERTURB = 40 73 | 74 | spread = 0 75 | 76 | vixFlag = 0 77 | 78 | callback = 'stay'; 79 | 80 | 81 | #------------------------------------------------------------------------------# 82 | #log設定 83 | import logging 84 | logger = logging.getLogger('LoggingTest') 85 | logger.setLevel(10) 86 | fh = logging.FileHandler('log_mm_bf_' + datetime.datetime.now().strftime('%Y%m%d') + '_' + datetime.datetime.now().strftime('%H%M%S') + '.log') 87 | logger.addHandler(fh) 88 | sh = logging.StreamHandler() 89 | logger.addHandler(sh) 90 | formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") 91 | fh.setFormatter(formatter) 92 | sh.setFormatter(formatter) 93 | 94 | #------------------------------------------------------------------------------# 95 | 96 | # JPY残高を参照する関数 97 | def get_asset(): 98 | 99 | while True: 100 | try: 101 | value = bitflyer.fetch_balance(params = { "product_code" : PRODUCT }) 102 | break 103 | except Exception as e: 104 | logger.info(e) 105 | time.sleep(1) 106 | return value 107 | 108 | # JPY証拠金を参照する関数 109 | def get_colla(): 110 | 111 | while True: 112 | try: 113 | value = bitflyer.privateGetGetcollateral() 114 | break 115 | except Exception as e: 116 | logger.info(e) 117 | time.sleep(1) 118 | return value 119 | 120 | # 板情報から実効Ask/Bid(=指値を入れる基準値)を計算する関数 121 | def get_effective_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 122 | 123 | while True: 124 | try: 125 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 126 | break 127 | except Exception as e: 128 | logger.info(e) 129 | time.sleep(2) 130 | 131 | i = 0 132 | s = 0 133 | while s <= size_thru: 134 | if value['bids'][i][0] == rate_bid: 135 | s += value['bids'][i][1] - size_bid 136 | else: 137 | s += value['bids'][i][1] 138 | i += 1 139 | 140 | j = 0 141 | t = 0 142 | while t <= size_thru: 143 | if value['asks'][j][0] == rate_ask: 144 | t += value['asks'][j][1] - size_ask 145 | else: 146 | t += value['asks'][j][1] 147 | j += 1 148 | 149 | time.sleep(0.5) 150 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 151 | 152 | # 成行注文する関数 153 | def market(side, size): 154 | 155 | while True: 156 | try: 157 | value = bitflyer.create_order(PAIR, type = 'market', side = side, amount = size,params = { "product_code" : PRODUCT }) 158 | break 159 | except Exception as e: 160 | logger.info(e) 161 | time.sleep(2) 162 | #場当たり的な対処 163 | size = LOT; 164 | 165 | time.sleep(0.5) 166 | return value 167 | 168 | # 指値注文する関数 169 | def limit(side, size, price): 170 | 171 | while True: 172 | try: 173 | value = bitflyer.create_order(PAIR, type = 'limit', side = side, amount = size, price = price, params = { "product_code" : PRODUCT }) 174 | break 175 | except Exception as e: 176 | logger.info(e) 177 | time.sleep(2) 178 | #場当たり的な対処 179 | size = LOT; 180 | 181 | time.sleep(0.5) 182 | return value 183 | 184 | # 注文をキャンセルする関数 185 | def cancel(id): 186 | 187 | try: 188 | value = bitflyer.cancelOrder(symbol = PAIR, id = id) 189 | except Exception as e: 190 | logger.info(e) 191 | 192 | # 指値が約定していた(=キャンセルが通らなかった)場合、 193 | # 注文情報を更新(約定済み)して返す 194 | value = get_status(id) 195 | 196 | time.sleep(0.5) 197 | return value 198 | 199 | # 指定した注文idのステータスを参照する関数 200 | def get_status(id): 201 | 202 | 203 | while True: 204 | try: 205 | value = bitflyer.private_get_getchildorders(params = {'product_code': PRODUCT, 'child_order_acceptance_id': id})[0] 206 | break 207 | except Exception as e: 208 | logger.info(e) 209 | time.sleep(2) 210 | 211 | # APIで受け取った値を読み換える 212 | if value['child_order_state'] == 'ACTIVE': 213 | status = 'open' 214 | elif value['child_order_state'] == 'COMPLETED': 215 | status = 'closed' 216 | else: 217 | status = value['child_order_state'] 218 | 219 | # 未約定量を計算する 220 | remaining = float(value['size']) - float(value['executed_size']) 221 | 222 | time.sleep(0.1) 223 | return {'id': value['child_order_acceptance_id'], 'status': status, 'filled': value['executed_size'], 'remaining': remaining, 'amount': value['size'], 'price': value['price']} 224 | 225 | 226 | def fromListToDF(candleStick): 227 | """ 228 | Listのローソク足をpandasデータフレームへ. 229 | """ 230 | date = [price[0] for price in candleStick] 231 | priceOpen = [int(price[1]) for price in candleStick] 232 | priceHigh = [int(price[2]) for price in candleStick] 233 | priceLow = [int(price[3]) for price in candleStick] 234 | priceClose = [int(price[4]) for price in candleStick] 235 | volume = [int(price[5]) for price in candleStick] 236 | date_datetime = map(datetime.datetime.fromtimestamp, date) 237 | dti = pd.DatetimeIndex(date_datetime) 238 | df_candleStick = pd.DataFrame({"open" : priceOpen, "high" : priceHigh, "low": priceLow, "close" : priceClose, "volume" : volume}, index=dti) 239 | return df_candleStick 240 | 241 | def processCandleStick(candleStick, timeScale): 242 | """ 243 | 1分足データから各時間軸のデータを作成.timeScaleには5T(5分),H(1時間)などの文字列を入れる 244 | """ 245 | df_candleStick = fromListToDF(candleStick) 246 | processed_candleStick = df_candleStick.resample(timeScale).agg({'open': 'first','high': 'max','low': 'min','close': 'last',"volume" : "sum"}) 247 | processed_candleStick = processed_candleStick.dropna() 248 | return processed_candleStick 249 | 250 | def zscore(x, axis = None): 251 | xmean = x.mean(axis=axis, keepdims=True) 252 | xstd = np.std(x, axis=axis, keepdims=True) 253 | zscore = (x-xmean)/xstd 254 | return zscore 255 | 256 | #rciのdの計算 257 | def dofrci(itv,src): 258 | from scipy.stats import rankdata 259 | sum = 0.0 260 | for i in range(itv, 0, -1): 261 | date_rank = itv - i + 1 262 | price_rank = (itv - rankdata(src)[i-1] + 1) 263 | sum = sum + pow( (date_rank - price_rank) ,2) 264 | #pprint("hiduke = {}, price={}, juni={}, goukei={}".format(date_rank, src[i-1], price_rank, sum) ) 265 | 266 | return sum 267 | 268 | #rciの計算 269 | def calc_rci(src, term): 270 | 271 | listnull = [None] 272 | itv = term 273 | rcinull = listnull * itv 274 | rci_tmp = [ (1.0 - 6.0 * dofrci(itv,src[i-itv:i]) / (itv * itv * itv - itv)) * 100.0 for i in range(itv,len(src))] 275 | rci = rcinull + rci_tmp 276 | 277 | return rci 278 | 279 | def vixfix(close, low): 280 | prd = 22 281 | bbl = 20 282 | mult = 2.0 283 | lb = 50 284 | ph = 0.85 285 | pl = 1.01 286 | 287 | wvf = (pd.Series(close).rolling(prd, 1).max() - low) / pd.Series(close).rolling(prd, 1).max() * 100 288 | 289 | sDev = mult * pd.Series(wvf).rolling(bbl, 1).std() 290 | midLine = pd.Series(wvf).rolling(bbl, 1).mean() 291 | 292 | lowerBand = midLine - sDev 293 | upperBand = midLine + sDev 294 | rangeHigh = pd.Series(wvf).rolling(lb, 1).max() * ph 295 | rangeLow = pd.Series(wvf).rolling(lb, 1).min() * pl 296 | 297 | #緑が点灯しているときはエントリーしない 298 | if wvf[len(wvf)-1] > rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] > upperBand[len(wvf)-1]: 299 | return 'buy' 300 | #print("VIX: 緑") 301 | elif wvf[len(wvf)-2] > rangeHigh[len(wvf)-2] or wvf[len(wvf)-2] > upperBand[len(wvf)-2]: 302 | if wvf[len(wvf)-1] < rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] < upperBand[len(wvf)-1]: 303 | #print('VIX: 緑からグレー') 304 | 1+1 305 | #return 'buy' 306 | #赤が点灯しているときはエントリーしない 307 | elif wvf[len(wvf)-1] < rangeLow[len(wvf)-1] or wvf[len(wvf)-1] < lowerBand[len(wvf)-1]: 308 | return 'sell' 309 | #print("VIX: 赤") 310 | elif wvf[len(wvf)-2] < rangeLow[len(wvf)-2] or wvf[len(wvf)-2] < lowerBand[len(wvf)-2]: 311 | if wvf[len(wvf)-1] > rangeLow[len(wvf)-1] or wvf[len(wvf)-1] > lowerBand[len(wvf)-1]: 312 | #print('VIX: 赤からグレー') 313 | 1+1 314 | #return 'sell' 315 | else: 316 | pass 317 | #print("VIX: グレー") 318 | 319 | return 'stay' 320 | 321 | 322 | #------------------------------------------------------------------------------# 323 | 324 | # 未約定量が存在することを示すフラグ 325 | remaining_ask_flag = 0 326 | remaining_bid_flag = 0 327 | 328 | # 指値の有無を示す変数 329 | pos = 'none' 330 | 331 | #------------------------------------------------------------------------------# 332 | 333 | logger.info('--------TradeStart--------') 334 | logger.info('BOT TYPE : MarketMaker @ bitFlyer') 335 | logger.info('SYMBOL : {0}'.format(PAIR)) 336 | logger.info('LOT : {0} {1}'.format(LOT, COIN)) 337 | logger.info('SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)) 338 | logger.info('SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100)) 339 | 340 | # 残高取得 341 | asset = float(get_asset()['info'][0]['amount']) 342 | colla = float(get_colla()['collateral']) 343 | logger.info('--------------------------') 344 | logger.info('ASSET : {0}'.format(int(asset))) 345 | logger.info('COLLATERAL : {0}'.format(int(colla))) 346 | logger.info('TOTAL : {0}'.format(int(asset + colla))) 347 | 348 | #初期化 349 | trade_ask = {"status":'closed'} 350 | trade_bid = {"status":'closed'} 351 | 352 | # メインループ 353 | while True: 354 | 355 | 356 | try: 357 | if "H" in CANDLETERM: 358 | candleStick = cryptowatch.getCandlestick(480, "3600") 359 | elif "30T" in CANDLETERM: 360 | candleStick = cryptowatch.getCandlestick(100, "1800") 361 | elif "15T" in CANDLETERM: 362 | candleStick = cryptowatch.getCandlestick(100, "900") 363 | elif "5T" in CANDLETERM: 364 | candleStick = cryptowatch.getCandlestick(100, "300") 365 | elif "3T" in CANDLETERM: 366 | candleStick = cryptowatch.getCandlestick(100, "180") 367 | else: 368 | candleStick = cryptowatch.getCandlestick(480, "60") 369 | except: 370 | logging.error("Unknown error happend when you requested candleStick") 371 | 372 | if CANDLETERM == None: 373 | df_candleStick = fromListToDF(candleStick) 374 | else: 375 | df_candleStick = processCandleStick(candleStick,CANDLETERM) 376 | 377 | #MACDの計算 378 | #try: 379 | #macd, macdsignal, macdhist = ta.MACD(np.array(df_candleStick["close"][:], dtype='f8'), fastperiod=12, slowperiod=26, signalperiod=9); 380 | #except: 381 | #pass; 382 | 383 | #dmacdhist = np.gradient(macdhist) 384 | #Normdmacdhist = zscore(dmacdhist[~np.isnan(dmacdhist)]) 385 | #absNormdmacdhist = np.abs(Normdmacdhist); 386 | 387 | #9期間RCIの計算 388 | rcirangetermNine = calc_rci(df_candleStick["close"][:],4); 389 | logger.info('rcirangetermNine:%s ', rcirangetermNine[-1]); 390 | 391 | # 未約定量の繰越がなければリセット 392 | if remaining_ask_flag == 0: 393 | remaining_ask = 0 394 | if remaining_bid_flag == 0: 395 | remaining_bid = 0 396 | 397 | 398 | #VIX戦略 399 | LOW_PRICE = 3 400 | CLOSE_PRICE = 4 401 | PERIOD = 60 # どの時間足で運用するか(例: 5分足 => 60秒*5 =「300」,1分足 => 60秒 =「60」を入力) 402 | 403 | nowvix = str(int(datetime.datetime.now().timestamp())) 404 | resvix = requests.get('https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=' + str(PERIOD) + '&after=' + str(int(nowvix)-PERIOD*100) + '&before=' + nowvix) 405 | ohlcvvix = resvix.json() 406 | ohlcvRvix = list(map(list, zip(*ohlcvvix['result'][str(PERIOD)]))) 407 | lowvix = np.array(ohlcvRvix[LOW_PRICE]) 408 | closevix = np.array(ohlcvRvix[CLOSE_PRICE]) 409 | callback = vixfix(closevix, lowvix) 410 | 411 | 412 | if callback == 'buy': 413 | #Buy 414 | vixFlag = 1 415 | elif callback == 'sell': 416 | #Sell 417 | vixFlag = 2 418 | else: 419 | #Stay 420 | vixFlag = 0; 421 | 422 | #VIXflagのログ 423 | logger.info('vixflag:%s ', vixFlag); 424 | 425 | # フラグリセット 426 | remaining_ask_flag = 0 427 | remaining_bid_flag = 0 428 | 429 | #positionを取得(指値だけだとバグるので修正取得) 430 | side , size = order.getmypos(); 431 | 432 | 433 | 434 | if size == 0 and side =="": 435 | pos = 'none'; 436 | trade_ask['status'] = 'closed'; 437 | trade_bid['status'] = 'closed'; 438 | else : 439 | pos = 'entry'; 440 | if side == "SELL": 441 | trade_ask['status'] = 'open'; 442 | if side == "BUY": 443 | trade_bid['status'] = 'open'; 444 | 445 | # 自分の指値が存在しないとき実行する 446 | if pos == 'none' or pos == 'entry': 447 | 448 | try: 449 | # 一つ前のspread 450 | previousspread = spread; 451 | # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する 452 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 453 | ask = float(tick['ask']) 454 | bid = float(tick['bid']) 455 | # 実効スプレッドを計算する 456 | spread = (ask - bid) / bid 457 | 458 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 459 | # askとbidを再計算する 460 | ask = float(tick['ask']) 461 | bid = float(tick['bid']) 462 | 463 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 464 | 465 | if int((ask + bid)/2) > int(ticker["last"]): 466 | trend = "buy" 467 | else: 468 | trend = "sell" 469 | 470 | except: 471 | pass; 472 | 473 | try: 474 | 475 | # 実効スプレッドが閾値を超えた場合に実行しない 476 | if spread > SPREAD_ENTRY: 477 | 478 | # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える 479 | amount_int_ask = LOT + remaining_bid 480 | amount_int_bid = LOT + remaining_ask 481 | 482 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 483 | # askとbidを再計算する 484 | ask = float(tick['ask']) 485 | bid = float(tick['bid']) 486 | 487 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 488 | lastprice9 = int(ticker["last"]) 489 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 490 | lastprice8 = int(ticker["last"]) 491 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 492 | lastprice7 = int(ticker["last"]) 493 | 494 | 495 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 496 | lastprice6 = int(ticker["last"]) 497 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 498 | lastprice5 = int(ticker["last"]) 499 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 500 | lastprice4 = int(ticker["last"]) 501 | 502 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 503 | lastprice3 = int(ticker["last"]) 504 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 505 | lastprice2 = int(ticker["last"]) 506 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 507 | lastprice1 = int(ticker["last"]) 508 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 509 | lastprice0 = int(ticker["last"]) 510 | 511 | lastprice = int((lastprice9 + lastprice8 + lastprice7 + lastprice6 + lastprice5 + lastprice4 + lastprice3 + lastprice2 + lastprice1 + lastprice0)/10) 512 | 513 | bidaskmiddleprice = int(((ticker["bid"]) + (ticker["ask"]))/2) 514 | 515 | lastminusbid = int(bid) - int(ticker["last"]); 516 | askminuslast = int(ticker["last"]) - int(ask); 517 | logger.info('Last - Bid:%s ', lastminusbid); 518 | logger.info('Ask - Last:%s ', askminuslast); 519 | 520 | 521 | #実効Ask/Bidからdelta離れた位置に指値を入れる 522 | if rcirangetermNine[-1] > -85 and rcirangetermNine[-1] < 85 and trend == "buy" and vixFlag == 0 and size < 0.3: 523 | #trade_ask = limit('sell', amount_int_ask, ask - DELTA + int((spread * 10000) / 100) * ABSOFFSET) 524 | #trade_bid = limit('buy', amount_int_bid, bid + DELTA + int((spread * 10000) / 100) * ABSOFFSET) 525 | #trade_ask = limit('sell', amount_int_ask, int((ask + bid)/2) + PERTURB) 526 | #trade_bid = limit('buy', amount_int_bid, int((ask + bid)/2) - PERTURB) 527 | #trade_ask = limit('sell', amount_int_ask, (ticker["ask"])) 528 | trade_bid = limit('buy', amount_int_bid, (ticker["bid"])) 529 | time.sleep(0.2) 530 | order.cancelAllOrderFutures(); 531 | 532 | 533 | elif rcirangetermNine[-1] > -85 and rcirangetermNine[-1] < 85 and trend == "sell" and vixFlag == 0 and size < 0.3: 534 | #trade_ask = limit('sell', amount_int_ask, ask - DELTA - int((spread * 10000) / 100) * ABSOFFSET) 535 | #trade_bid = limit('buy', amount_int_bid, bid + DELTA - int((spread * 10000) / 100) * ABSOFFSET) 536 | #trade_ask = limit('sell', amount_int_ask, int((ask + bid)/2) + PERTURB) 537 | #trade_bid = limit('buy', amount_int_bid, int((ask + bid)/2) - PERTURB) 538 | trade_ask = limit('sell', amount_int_ask, (ticker["ask"])) 539 | #trade_bid = limit('buy', amount_int_bid, (ticker["bid"])) 540 | time.sleep(0.2) 541 | order.cancelAllOrderFutures(); 542 | 543 | #実効Ask/Bidからdelta離れた位置に指値を入れる 544 | if rcirangetermNine[-1] > -85 and rcirangetermNine[-1] < 85 and trend == "buy" and side == "SELL" and vixFlag == 0 and spread > SPREAD_CLEAN: 545 | #trade_ask = limit('sell', amount_int_ask, ask - DELTA + int((spread * 10000) / 100) * ABSOFFSET) 546 | #trade_bid = limit('buy', amount_int_bid, bid + DELTA + int((spread * 10000) / 100) * ABSOFFSET) 547 | #trade_ask = limit('sell', amount_int_ask, int((ask + bid)/2) + PERTURB) 548 | #trade_bid = limit('buy', amount_int_bid, int((ask + bid)/2) - PERTURB) 549 | #trade_ask = limit('sell', amount_int_ask, (ticker["ask"])) 550 | trade_bid = limit('buy', size, (ticker["bid"])) 551 | time.sleep(0.2) 552 | order.cancelAllOrderFutures(); 553 | 554 | 555 | elif rcirangetermNine[-1] > -85 and rcirangetermNine[-1] < 85 and trend == "sell" and side == "BUY" and vixFlag == 0 and spread > SPREAD_CLEAN: 556 | #trade_ask = limit('sell', amount_int_ask, ask - DELTA - int((spread * 10000) / 100) * ABSOFFSET) 557 | #trade_bid = limit('buy', amount_int_bid, bid + DELTA - int((spread * 10000) / 100) * ABSOFFSET) 558 | #trade_ask = limit('sell', amount_int_ask, int((ask + bid)/2) + PERTURB) 559 | #trade_bid = limit('buy', amount_int_bid, int((ask + bid)/2) - PERTURB) 560 | trade_ask = limit('sell', size, (ticker["ask"])) 561 | #trade_bid = limit('buy', amount_int_bid, (ticker["bid"])) 562 | time.sleep(0.2) 563 | order.cancelAllOrderFutures(); 564 | 565 | 566 | 567 | logger.info('--------------------------') 568 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 569 | 570 | #logger.info('Normdmacdhist:%s ', Normdmacdhist[-1]); 571 | logger.info('Offset:%s ', int((spread * 10000) / 100) * OFFSET); 572 | logger.info('ABSOffset:%s ', int((spread * 10000) / 100) * ABSOFFSET); 573 | logger.info('trend:%s ', trend); 574 | 575 | logger.info('--------------------------') 576 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 577 | 578 | trade_ask['status'] = 'open' 579 | trade_bid['status'] = 'open' 580 | pos = 'entry' 581 | 582 | logger.info('--------------------------') 583 | logger.info('entry') 584 | 585 | except: 586 | pass; 587 | 588 | # 自分の指値が存在するとき実行する 589 | if pos == 'entry' and False: 590 | 591 | try: 592 | orders = bitflyer.fetch_orders( 593 | symbol = PAIR, 594 | params = { "product_code" : PRODUCT}) 595 | 596 | openorders = bitflyer.fetch_open_orders( 597 | symbol = PAIR, 598 | params = { "product_code" : PRODUCT}) 599 | 600 | trade_ask['status'] = "closed"; 601 | trade_bid['status'] = "closed"; 602 | 603 | for o in openorders: 604 | if o["side"] == "sell": 605 | trade_ask['status'] = "open"; 606 | elif o["side"] == "buy": 607 | trade_bid['status'] = "open"; 608 | else: 609 | trade_ask['status'] = "closed"; 610 | trade_bid['status'] = "closed"; 611 | 612 | #最新の注文のidを取得する 613 | for o in orders: 614 | 615 | if o["side"] == "sell": 616 | trade_ask['id'] = orders[-1]["id"]; 617 | # 注文ステータス取得 618 | if trade_ask['status'] != 'closed': 619 | trade_ask = get_status(trade_ask['id']) 620 | break; 621 | for o in orders: 622 | if o["side"] == "buy": 623 | trade_bid['id'] = orders[-1]["id"]; 624 | # 注文ステータス取得 625 | if trade_bid['status'] != 'closed': 626 | trade_bid = get_status(trade_bid['id']) 627 | break; 628 | 629 | # 板情報を取得、実効Ask/Bid(指値を入れる基準値)を決定する 630 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 631 | ask = float(tick['ask']) 632 | bid = float(tick['bid']) 633 | spread = (ask - bid) / bid 634 | 635 | 636 | logger.info('--------------------------') 637 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 638 | logger.info('ask status:{0}, price:{1}'.format(trade_ask['status'], trade_ask['price'])) 639 | logger.info('bid status:{0}, price:{1}'.format(trade_bid['status'], trade_bid['price'])) 640 | except: 641 | pass; 642 | 643 | 644 | try: 645 | # Ask未約定量が最小注文量を下回るとき実行 646 | if trade_ask['status'] == 'open' and trade_ask['remaining'] <= AMOUNT_MIN: 647 | 648 | # 注文をキャンセル 649 | order.cancelAllOrder(); 650 | 651 | # ステータスをCLOSEDに書き換える 652 | trade_ask['status'] = 'closed' 653 | 654 | # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる 655 | remaining_ask = float(trade_ask['remaining']) 656 | remaining_ask_flag = 1 657 | 658 | logger.info('--------------------------') 659 | logger.info('ask almost filled.') 660 | 661 | # Bid未約定量が最小注文量を下回るとき実行 662 | if trade_bid['status'] == 'open' and trade_bid['remaining'] <= AMOUNT_MIN: 663 | 664 | # 注文をキャンセル 665 | order.cancelAllOrder(); 666 | 667 | # ステータスをCLOSEDに書き換える 668 | trade_bid['status'] = 'closed' 669 | 670 | # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる 671 | remaining_bid = float(trade_bid['remaining']) 672 | remaining_bid_flag = 1 673 | 674 | logger.info('--------------------------') 675 | logger.info('bid almost filled.') 676 | 677 | #スプレッドが閾値以上のときに実行する 678 | if spread > SPREAD_CANCEL: 679 | 680 | # Ask指値が最良位置に存在しないとき、指値を更新する 681 | if trade_ask['status'] == 'open': 682 | if trade_ask['price'] != ask: 683 | 684 | # 指値を一旦キャンセル 685 | order.cancelAllOrder(); 686 | 687 | # 注文数が最小注文数より大きいとき、指値を更新する 688 | if trade_ask['remaining'] >= AMOUNT_MIN: 689 | trade_ask = limit('sell', trade_ask['remaining'], ask) 690 | trade_ask['status'] = 'open' 691 | # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする 692 | elif AMOUNT_MIN > trade_ask['remaining'] > 0: 693 | trade_ask['status'] = 'closed' 694 | remaining_ask = float(trade_ask['remaining']) 695 | remaining_ask_flag = 1 696 | 697 | # 注文数が最小注文数より小さく0のとき、CLOSEDとする 698 | else: 699 | trade_ask['status'] = 'closed' 700 | 701 | # Bid指値が最良位置に存在しないとき、指値を更新する 702 | if trade_bid['status'] == 'open': 703 | if trade_bid['price'] != bid: 704 | 705 | # 指値を一旦キャンセル 706 | order.cancelAllOrder(); 707 | 708 | # 注文数が最小注文数より大きいとき、指値を更新する 709 | if trade_bid['remaining'] >= AMOUNT_MIN: 710 | trade_bid = limit('buy', trade_bid['remaining'], bid) 711 | trade_bid['status'] = 'open' 712 | # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする 713 | elif AMOUNT_MIN > trade_bid['remaining'] > 0: 714 | trade_bid['status'] = 'closed' 715 | remaining_bid = float(trade_bid['remaining']) 716 | remaining_bid_flag = 1 717 | # 注文数が最小注文数より小さく0のとき、CLOSEDとする 718 | else: 719 | trade_bid['status'] = 'closed' 720 | 721 | #おそうじする 722 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 723 | # askとbidを再計算する 724 | ask = float(tick['ask']) 725 | bid = float(tick['bid']) 726 | 727 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 728 | 729 | if int((ask + bid)/2) > int(ticker["last"]): 730 | trend = "buy" 731 | else: 732 | trend = "sell" 733 | 734 | #positionを取得(指値だけだとバグるので修正取得) 735 | side , size = order.getmypos(); 736 | 737 | if side == "SELL" and trend == 'buy' and trade_bid['status'] == "closed" and False: 738 | amount_int_bid = LOT + remaining_ask 739 | trade_bid = limit('buy', size, bid + DELTA + int((spread * 10000) / 100) * OFFSET) 740 | trade_bid['status'] = 'open' 741 | if side == "BUY" and trend == 'sell' and trade_ask['status'] == "closed" and False: 742 | amount_int_ask = LOT + remaining_bid 743 | trade_ask = limit('sell', size, ask - DELTA - int((spread * 10000) / 100) * OFFSET) 744 | trade_ask['status'] = 'open' 745 | except: 746 | pass; 747 | 748 | # Ask/Bid両方の指値が約定したとき、1サイクル終了、最初の処理に戻る 749 | try: 750 | if trade_ask['status'] == 'closed' and trade_bid['status'] == 'closed': 751 | pos = 'none' 752 | 753 | logger.info('--------------------------') 754 | logger.info('completed.') 755 | except: 756 | pass; 757 | 758 | 759 | -------------------------------------------------------------------------------- /MarketMakerPrudent.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | class MarketMaker: 4 | pass 5 | def __init__(self): 6 | #config.jsonの読み込み 7 | f = open('config/config.json', 'r', encoding="utf-8") 8 | config = json.load(f) 9 | 10 | 11 | #!/usr/bin/python3 12 | # coding: utf-8 13 | 14 | import datetime 15 | import time 16 | import json 17 | import ccxt 18 | import requests 19 | import bforder 20 | import cryptowatch 21 | #import talib as ta 22 | import numpy as np 23 | import pandas as pd 24 | 25 | #configの読み込み 26 | f = open('config/config.json', 'r', encoding="utf-8") 27 | config = json.load(f) 28 | 29 | order = bforder.BFOrder(); 30 | 31 | cryptowatch = cryptowatch.CryptoWatch() 32 | 33 | bitflyer = ccxt.bitflyer({ 34 | 'apiKey': config["key"], 35 | 'secret': config["secret"], 36 | }) 37 | 38 | # 取引する通貨、シンボルを設定 39 | COIN = 'BTC' 40 | PAIR = 'BTC/JPY' 41 | 42 | # プロダクトコードの指定 43 | PRODUCT = config["product_code"] 44 | 45 | # ロット(単位はBTC) 46 | LOT = config["lotSize"]; 47 | 48 | CANDLETERM = config["candleTerm"]; 49 | 50 | # 最小注文数(取引所の仕様に応じて設定) 51 | AMOUNT_MIN = 0.001 52 | 53 | # スプレッド閾値 54 | SPREAD_ENTRY = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらエントリー 55 | SPREAD_CANCEL = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を下回ったら指値更新を停止 56 | 57 | # 数量X(この数量よりも下に指値をおく) 58 | AMOUNT_THRU = 1 59 | AMOUNT_ASKBID = 0.5 60 | AMOUNT_EVIL_ASKBID = 11 61 | 62 | # 実効Ask/BidからDELTA離れた位置に指値をおく 63 | DELTA = 30 64 | 65 | INVDELTA = -20 66 | 67 | # 買い気配、売り気配に応じて主観的ファンダメンタル価格をずらす 68 | OFFSET = 2 69 | 70 | ABSOFFSET = 100 71 | 72 | PERTURB = 40 73 | 74 | spread = 0 75 | 76 | vixFlag = 0 77 | 78 | callback = 'stay'; 79 | 80 | signedsize = 0; 81 | 82 | minLOT = 0.01 83 | 84 | 85 | #------------------------------------------------------------------------------# 86 | #log設定 87 | import logging 88 | logger = logging.getLogger('LoggingTest') 89 | logger.setLevel(10) 90 | fh = logging.FileHandler('log_mm_bf_' + datetime.datetime.now().strftime('%Y%m%d') + '_' + datetime.datetime.now().strftime('%H%M%S') + '.log') 91 | logger.addHandler(fh) 92 | sh = logging.StreamHandler() 93 | logger.addHandler(sh) 94 | formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") 95 | fh.setFormatter(formatter) 96 | sh.setFormatter(formatter) 97 | 98 | #------------------------------------------------------------------------------# 99 | 100 | # JPY残高を参照する関数 101 | def get_asset(): 102 | 103 | while True: 104 | try: 105 | value = bitflyer.fetch_balance(params = { "product_code" : PRODUCT }) 106 | break 107 | except Exception as e: 108 | logger.info(e) 109 | time.sleep(1) 110 | return value 111 | 112 | # JPY証拠金を参照する関数 113 | def get_colla(): 114 | 115 | while True: 116 | try: 117 | value = bitflyer.privateGetGetcollateral() 118 | break 119 | except Exception as e: 120 | logger.info(e) 121 | time.sleep(1) 122 | return value 123 | 124 | # 板情報から実効Ask/Bid(=指値を入れる基準値)を計算する関数 125 | def get_effective_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 126 | 127 | while True: 128 | try: 129 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 130 | break 131 | except Exception as e: 132 | logger.info(e) 133 | time.sleep(2) 134 | 135 | i = 0 136 | s = 0 137 | while s <= size_thru: 138 | if value['bids'][i][0] == rate_bid: 139 | s += value['bids'][i][1] - size_bid 140 | else: 141 | s += value['bids'][i][1] 142 | i += 1 143 | 144 | j = 0 145 | t = 0 146 | while t <= size_thru: 147 | if value['asks'][j][0] == rate_ask: 148 | t += value['asks'][j][1] - size_ask 149 | else: 150 | t += value['asks'][j][1] 151 | j += 1 152 | 153 | time.sleep(0.5) 154 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 155 | 156 | # 板情報からある一定の閾値を越えるAsk/Bid(=指値を入れる基準値)の位置を計算する関数 157 | def get_threshold_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 158 | 159 | while True: 160 | try: 161 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 162 | break 163 | except Exception as e: 164 | logger.info(e) 165 | time.sleep(2) 166 | 167 | i = 0 168 | s = 0 169 | while s <= size_thru: 170 | if value['bids'][i][0] == rate_bid: 171 | s = value['bids'][i][1] 172 | else: 173 | s = value['bids'][i][1] 174 | i += 1 175 | 176 | j = 0 177 | t = 0 178 | while t <= size_thru: 179 | if value['asks'][j][0] == rate_ask: 180 | t = value['asks'][j][1] 181 | else: 182 | t = value['asks'][j][1] 183 | j += 1 184 | time.sleep(0.5) 185 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 186 | 187 | # 成行注文する関数 188 | def market(side, size): 189 | 190 | while True: 191 | try: 192 | value = bitflyer.create_order(PAIR, type = 'market', side = side, amount = size,params = { "product_code" : PRODUCT }) 193 | break 194 | except Exception as e: 195 | logger.info(e) 196 | time.sleep(2) 197 | #場当たり的な対処 198 | size = minLOT; 199 | 200 | time.sleep(0.5) 201 | return value 202 | 203 | # 指値注文する関数 204 | def limit(side, size, price): 205 | 206 | while True: 207 | try: 208 | value = bitflyer.create_order(PAIR, type = 'limit', side = side, amount = size, price = price, params = { "product_code" : PRODUCT }) 209 | break 210 | except Exception as e: 211 | logger.info(e) 212 | time.sleep(2) 213 | #場当たり的な対処 214 | size = minLOT; 215 | 216 | time.sleep(0.5) 217 | return value 218 | 219 | # 注文をキャンセルする関数 220 | def cancel(id): 221 | 222 | try: 223 | value = bitflyer.cancelOrder(symbol = PAIR, id = id) 224 | except Exception as e: 225 | logger.info(e) 226 | 227 | # 指値が約定していた(=キャンセルが通らなかった)場合、 228 | # 注文情報を更新(約定済み)して返す 229 | value = get_status(id) 230 | 231 | time.sleep(0.5) 232 | return value 233 | 234 | # 指定した注文idのステータスを参照する関数 235 | def get_status(id): 236 | 237 | 238 | while True: 239 | try: 240 | value = bitflyer.private_get_getchildorders(params = {'product_code': PRODUCT, 'child_order_acceptance_id': id})[0] 241 | break 242 | except Exception as e: 243 | logger.info(e) 244 | time.sleep(2) 245 | 246 | # APIで受け取った値を読み換える 247 | if value['child_order_state'] == 'ACTIVE': 248 | status = 'open' 249 | elif value['child_order_state'] == 'COMPLETED': 250 | status = 'closed' 251 | else: 252 | status = value['child_order_state'] 253 | 254 | # 未約定量を計算する 255 | remaining = float(value['size']) - float(value['executed_size']) 256 | 257 | time.sleep(0.1) 258 | return {'id': value['child_order_acceptance_id'], 'status': status, 'filled': value['executed_size'], 'remaining': remaining, 'amount': value['size'], 'price': value['price']} 259 | 260 | 261 | def fromListToDF(candleStick): 262 | """ 263 | Listのローソク足をpandasデータフレームへ. 264 | """ 265 | date = [price[0] for price in candleStick] 266 | priceOpen = [int(price[1]) for price in candleStick] 267 | priceHigh = [int(price[2]) for price in candleStick] 268 | priceLow = [int(price[3]) for price in candleStick] 269 | priceClose = [int(price[4]) for price in candleStick] 270 | volume = [int(price[5]) for price in candleStick] 271 | date_datetime = map(datetime.datetime.fromtimestamp, date) 272 | dti = pd.DatetimeIndex(date_datetime) 273 | df_candleStick = pd.DataFrame({"open" : priceOpen, "high" : priceHigh, "low": priceLow, "close" : priceClose, "volume" : volume}, index=dti) 274 | return df_candleStick 275 | 276 | def processCandleStick(candleStick, timeScale): 277 | """ 278 | 1分足データから各時間軸のデータを作成.timeScaleには5T(5分),H(1時間)などの文字列を入れる 279 | """ 280 | df_candleStick = fromListToDF(candleStick) 281 | processed_candleStick = df_candleStick.resample(timeScale).agg({'open': 'first','high': 'max','low': 'min','close': 'last',"volume" : "sum"}) 282 | processed_candleStick = processed_candleStick.dropna() 283 | return processed_candleStick 284 | 285 | def zscore(x, axis = None): 286 | xmean = x.mean(axis=axis, keepdims=True) 287 | xstd = np.std(x, axis=axis, keepdims=True) 288 | zscore = (x-xmean)/xstd 289 | return zscore 290 | 291 | #rciのdの計算 292 | def dofrci(itv,src): 293 | from scipy.stats import rankdata 294 | sum = 0.0 295 | for i in range(itv, 0, -1): 296 | date_rank = itv - i + 1 297 | price_rank = (itv - rankdata(src)[i-1] + 1) 298 | sum = sum + pow( (date_rank - price_rank) ,2) 299 | #pprint("hiduke = {}, price={}, juni={}, goukei={}".format(date_rank, src[i-1], price_rank, sum) ) 300 | 301 | return sum 302 | 303 | #rciの計算 304 | def calc_rci(src, term): 305 | 306 | listnull = [None] 307 | itv = term 308 | rcinull = listnull * itv 309 | rci_tmp = [ (1.0 - 6.0 * dofrci(itv,src[i-itv:i]) / (itv * itv * itv - itv)) * 100.0 for i in range(itv,len(src))] 310 | rci = rcinull + rci_tmp 311 | 312 | return rci 313 | 314 | def vixfix(close, low): 315 | prd = 22 316 | bbl = 20 317 | mult = 2.0 318 | lb = 50 319 | ph = 0.85 320 | pl = 1.01 321 | 322 | wvf = (pd.Series(close).rolling(prd, 1).max() - low) / pd.Series(close).rolling(prd, 1).max() * 100 323 | 324 | sDev = mult * pd.Series(wvf).rolling(bbl, 1).std() 325 | midLine = pd.Series(wvf).rolling(bbl, 1).mean() 326 | 327 | lowerBand = midLine - sDev 328 | upperBand = midLine + sDev 329 | rangeHigh = pd.Series(wvf).rolling(lb, 1).max() * ph 330 | rangeLow = pd.Series(wvf).rolling(lb, 1).min() * pl 331 | 332 | #緑が点灯しているときはエントリーしない 333 | if wvf[len(wvf)-1] > rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] > upperBand[len(wvf)-1]: 334 | return 'buy' 335 | #print("VIX: 緑") 336 | elif wvf[len(wvf)-2] > rangeHigh[len(wvf)-2] or wvf[len(wvf)-2] > upperBand[len(wvf)-2]: 337 | if wvf[len(wvf)-1] < rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] < upperBand[len(wvf)-1]: 338 | #print('VIX: 緑からグレー') 339 | 1+1 340 | #return 'buy' 341 | #赤が点灯しているときはエントリーしない 342 | elif wvf[len(wvf)-1] < rangeLow[len(wvf)-1] or wvf[len(wvf)-1] < lowerBand[len(wvf)-1]: 343 | return 'sell' 344 | #print("VIX: 赤") 345 | elif wvf[len(wvf)-2] < rangeLow[len(wvf)-2] or wvf[len(wvf)-2] < lowerBand[len(wvf)-2]: 346 | if wvf[len(wvf)-1] > rangeLow[len(wvf)-1] or wvf[len(wvf)-1] > lowerBand[len(wvf)-1]: 347 | #print('VIX: 赤からグレー') 348 | 1+1 349 | #return 'sell' 350 | else: 351 | pass 352 | #print("VIX: グレー") 353 | 354 | return 'stay' 355 | 356 | 357 | #------------------------------------------------------------------------------# 358 | 359 | # 未約定量が存在することを示すフラグ 360 | remaining_ask_flag = 0 361 | remaining_bid_flag = 0 362 | 363 | # 指値の有無を示す変数 364 | pos = 'none' 365 | 366 | #------------------------------------------------------------------------------# 367 | 368 | logger.info('--------TradeStart--------') 369 | logger.info('BOT TYPE : MarketMaker @ bitFlyer') 370 | logger.info('SYMBOL : {0}'.format(PAIR)) 371 | logger.info('LOT : {0} {1}'.format(LOT, COIN)) 372 | logger.info('SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)) 373 | logger.info('SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100)) 374 | 375 | # 残高取得 376 | asset = float(get_asset()['info'][0]['amount']) 377 | colla = float(get_colla()['collateral']) 378 | logger.info('--------------------------') 379 | logger.info('ASSET : {0}'.format(int(asset))) 380 | logger.info('COLLATERAL : {0}'.format(int(colla))) 381 | logger.info('TOTAL : {0}'.format(int(asset + colla))) 382 | 383 | #初期化 384 | trade_ask = {"status":'closed'} 385 | trade_bid = {"status":'closed'} 386 | 387 | # メインループ 388 | while True: 389 | 390 | 391 | try: 392 | if "H" in CANDLETERM: 393 | candleStick = cryptowatch.getCandlestick(480, "3600") 394 | elif "30T" in CANDLETERM: 395 | candleStick = cryptowatch.getCandlestick(100, "1800") 396 | elif "15T" in CANDLETERM: 397 | candleStick = cryptowatch.getCandlestick(100, "900") 398 | elif "5T" in CANDLETERM: 399 | candleStick = cryptowatch.getCandlestick(100, "300") 400 | elif "3T" in CANDLETERM: 401 | candleStick = cryptowatch.getCandlestick(100, "180") 402 | else: 403 | candleStick = cryptowatch.getCandlestick(480, "60") 404 | except: 405 | logging.error("Unknown error happend when you requested candleStick") 406 | 407 | if CANDLETERM == None: 408 | df_candleStick = fromListToDF(candleStick) 409 | else: 410 | df_candleStick = processCandleStick(candleStick,CANDLETERM) 411 | 412 | #3期間RCIの計算 413 | rcirangetermNine = calc_rci(df_candleStick["close"][:],3); 414 | logger.info('rcirangetermNine:%s ', rcirangetermNine[-1]); 415 | 416 | # 未約定量の繰越がなければリセット 417 | if remaining_ask_flag == 0: 418 | remaining_ask = 0 419 | if remaining_bid_flag == 0: 420 | remaining_bid = 0 421 | 422 | #VIX戦略 423 | LOW_PRICE = 3 424 | CLOSE_PRICE = 4 425 | PERIOD = 60 # どの時間足で運用するか(例: 5分足 => 60秒*5 =「300」,1分足 => 60秒 =「60」を入力) 426 | 427 | nowvix = str(int(datetime.datetime.now().timestamp())) 428 | resvix = requests.get('https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=' + str(PERIOD) + '&after=' + str(int(nowvix)-PERIOD*100) + '&before=' + nowvix) 429 | ohlcvvix = resvix.json() 430 | ohlcvRvix = list(map(list, zip(*ohlcvvix['result'][str(PERIOD)]))) 431 | lowvix = np.array(ohlcvRvix[LOW_PRICE]) 432 | closevix = np.array(ohlcvRvix[CLOSE_PRICE]) 433 | callback = vixfix(closevix, lowvix) 434 | 435 | 436 | if callback == 'buy': 437 | #Buy 438 | vixFlag = 1 439 | elif callback == 'sell': 440 | #Sell 441 | vixFlag = 2 442 | else: 443 | #Stay 444 | vixFlag = 0; 445 | 446 | #VIXflagのログ 447 | logger.info('vixflag:%s ', vixFlag); 448 | 449 | # フラグリセット 450 | remaining_ask_flag = 0 451 | remaining_bid_flag = 0 452 | 453 | #positionを取得(指値だけだとバグるので修正取得) 454 | side , size = order.getmypos(); 455 | 456 | if side == "SELL": 457 | signedsize = -size; 458 | if side == "BUY": 459 | signedsize = size; 460 | 461 | if size == 0 and side =="": 462 | pos = 'none'; 463 | trade_ask['status'] = 'closed'; 464 | trade_bid['status'] = 'closed'; 465 | else : 466 | pos = 'entry'; 467 | if side == "SELL": 468 | trade_ask['status'] = 'open'; 469 | if side == "BUY": 470 | trade_bid['status'] = 'open'; 471 | 472 | # 自分の指値が存在しないとき実行する 473 | if pos == 'none' or pos == 'entry': 474 | 475 | try: 476 | # 一つ前のspread 477 | previousspread = spread; 478 | # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する 479 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 480 | ask = float(tick['ask']) 481 | bid = float(tick['bid']) 482 | # 実効スプレッドを計算する 483 | spread = (ask - bid) / bid 484 | 485 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 486 | # askとbidを再計算する 487 | ask = float(tick['ask']) 488 | bid = float(tick['bid']) 489 | 490 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 491 | 492 | if int((ask + bid)/2) > int(ticker["last"]): 493 | trendeph = "sell" 494 | else: 495 | trendeph = "buy" 496 | 497 | 498 | tick = get_threshold_tick(size_thru=AMOUNT_EVIL_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 499 | # askとbidを再計算する 500 | ask = float(tick['ask']) 501 | bid = float(tick['bid']) 502 | 503 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 504 | 505 | if int((ask - 1500)) < int(ticker["last"]): 506 | trendthres = "buy" 507 | if int((bid + 1500)) > int(ticker["last"]): 508 | trendthres = "stay" 509 | elif int((bid + 1500)) > int(ticker["last"]): 510 | trendthres = "sell" 511 | if int((ask - 1500)) < int(ticker["last"]): 512 | trendthres = "stay" 513 | else: 514 | trendthres = "stay" 515 | 516 | if trendeph == "sell" and trendthres == "sell": 517 | trend = "sell" 518 | elif trendeph == "buy" and trendthres == "buy": 519 | trend = "buy" 520 | else: 521 | trend = "stay" 522 | 523 | except: 524 | pass; 525 | 526 | try: 527 | 528 | # 実効スプレッドが閾値を超えた場合に実行する 529 | if spread > SPREAD_ENTRY: 530 | 531 | # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える 532 | amount_int_ask = LOT + remaining_bid 533 | amount_int_bid = LOT + remaining_ask 534 | 535 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 536 | # askとbidを再計算する 537 | ask = float(tick['ask']) 538 | bid = float(tick['bid']) 539 | 540 | #実効Ask/Bidからdelta離れた位置に指値を入れる 541 | if rcirangetermNine[-1] < 85 and trend == "buy" and vixFlag == 0 and size < 0.3: 542 | trade_bid = limit('buy', amount_int_bid, (ticker["bid"])) 543 | time.sleep(0.2) 544 | order.cancelAllOrder(); 545 | 546 | elif rcirangetermNine[-1] > -85 and trend == "sell" and vixFlag == 0 and size < 0.3: 547 | trade_ask = limit('sell', amount_int_ask, (ticker["ask"])) 548 | time.sleep(0.2) 549 | order.cancelAllOrder(); 550 | 551 | #elif side == "SELL": 552 | # if rcirangetermNine[-1] < -85 or rcirangetermNine[-1] > 85 : 553 | # trade_bid = market('buy', size) 554 | # time.sleep(1) 555 | # order.cancelAllOrder(); 556 | 557 | #elif side == "BUY": 558 | # if rcirangetermNine[-1] < -85 or rcirangetermNine[-1] > 85 : 559 | # trade_ask = market('sell', size) 560 | # time.sleep(3) 561 | # order.cancelAllOrder(); 562 | 563 | elif side == "SELL": 564 | if trend == "buy": 565 | trade_bid = market('buy', size) 566 | time.sleep(3) 567 | order.cancelAllOrder(); 568 | 569 | elif side == "BUY": 570 | if trend == "buy": 571 | trade_ask = market('sell', size) 572 | time.sleep(3) 573 | order.cancelAllOrder(); 574 | 575 | logger.info('--------------------------') 576 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 577 | 578 | #logger.info('Normdmacdhist:%s ', Normdmacdhist[-1]); 579 | logger.info('Offset:%s ', int((spread * 10000) / 100) * OFFSET); 580 | logger.info('ABSOffset:%s ', int((spread * 10000) / 100) * ABSOFFSET); 581 | logger.info('trend:%s ', trend); 582 | logger.info('trendeph:%s ', trendeph); 583 | logger.info('trendthres:%s ', trendthres); 584 | 585 | logger.info('--------------------------') 586 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 587 | 588 | trade_ask['status'] = 'open' 589 | trade_bid['status'] = 'open' 590 | pos = 'entry' 591 | 592 | logger.info('--------------------------') 593 | logger.info('entry') 594 | 595 | time.sleep(1) 596 | except: 597 | pass; 598 | 599 | 600 | -------------------------------------------------------------------------------- /MarketMakerSpread.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | class MarketMaker: 4 | pass 5 | def __init__(self): 6 | #config.jsonの読み込み 7 | f = open('config/config.json', 'r', encoding="utf-8") 8 | config = json.load(f) 9 | 10 | 11 | #!/usr/bin/python3 12 | # coding: utf-8 13 | 14 | import datetime 15 | import time 16 | import json 17 | import ccxt 18 | import requests 19 | import bforder 20 | import cryptowatch 21 | #import talib as ta 22 | import numpy as np 23 | import pandas as pd 24 | import pybitflyer 25 | 26 | api = pybitflyer.API() 27 | 28 | #configの読み込み 29 | f = open('config/config.json', 'r', encoding="utf-8") 30 | config = json.load(f) 31 | 32 | order = bforder.BFOrder(); 33 | 34 | cryptowatch = cryptowatch.CryptoWatch() 35 | 36 | bitflyer = ccxt.bitflyer({ 37 | 'apiKey': config["key"], 38 | 'secret': config["secret"], 39 | }) 40 | 41 | # 取引する通貨、シンボルを設定 42 | COIN = 'BTC' 43 | PAIR = 'BTC/JPY' 44 | 45 | # プロダクトコードの指定 46 | PRODUCT = config["product_code"] 47 | 48 | # ロット(単位はBTC) 49 | LOT = config["lotSize"]; 50 | 51 | CANDLETERM = config["candleTerm"]; 52 | 53 | # 最小注文数(取引所の仕様に応じて設定) 54 | AMOUNT_MIN = 0.001 55 | 56 | # スプレッド閾値 57 | SPREAD_ENTRY = 0.0010 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらエントリー 58 | SPREAD_CANCEL = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を下回ったら指値更新を停止 59 | SPREAD_CLOSE = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらクローズ 60 | 61 | 62 | # 数量X(この数量よりも下に指値をおく) 63 | AMOUNT_THRU = 30 64 | AMOUNT_ASKBID = 0.01 65 | AMOUNT_SPREAD = 1 66 | 67 | # 実効Ask/BidからDELTA離れた位置に指値をおく 68 | DELTA = 30 69 | 70 | INVDELTA = -20 71 | 72 | # 買い気配、売り気配に応じて主観的ファンダメンタル価格をずらす 73 | OFFSET = 2 74 | 75 | ABSOFFSET = 100 76 | 77 | PERTURB = 40 78 | 79 | spread = 0 80 | 81 | vixFlag = 0 82 | 83 | callback = 'stay'; 84 | 85 | signedsize = 0; 86 | 87 | minLOT = 0.01 88 | 89 | 90 | #------------------------------------------------------------------------------# 91 | #log設定 92 | import logging 93 | logger = logging.getLogger('LoggingTest') 94 | logger.setLevel(10) 95 | fh = logging.FileHandler('log_mm_bf_' + datetime.datetime.now().strftime('%Y%m%d') + '_' + datetime.datetime.now().strftime('%H%M%S') + '.log') 96 | logger.addHandler(fh) 97 | sh = logging.StreamHandler() 98 | logger.addHandler(sh) 99 | formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") 100 | fh.setFormatter(formatter) 101 | sh.setFormatter(formatter) 102 | 103 | #------------------------------------------------------------------------------# 104 | 105 | # JPY残高を参照する関数 106 | def get_asset(): 107 | 108 | while True: 109 | try: 110 | value = bitflyer.fetch_balance(params = { "product_code" : PRODUCT }) 111 | break 112 | except Exception as e: 113 | logger.info(e) 114 | time.sleep(1) 115 | return value 116 | 117 | # JPY証拠金を参照する関数 118 | def get_colla(): 119 | 120 | while True: 121 | try: 122 | value = bitflyer.privateGetGetcollateral() 123 | break 124 | except Exception as e: 125 | logger.info(e) 126 | time.sleep(1) 127 | return value 128 | 129 | # 板情報から実効Ask/Bid(=指値を入れる基準値)を計算する関数 130 | def get_effective_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 131 | 132 | while True: 133 | try: 134 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 135 | break 136 | except Exception as e: 137 | logger.info(e) 138 | time.sleep(2) 139 | 140 | i = 0 141 | s = 0 142 | while s <= size_thru: 143 | if value['bids'][i][0] == rate_bid: 144 | s += value['bids'][i][1] - size_bid 145 | else: 146 | s += value['bids'][i][1] 147 | i += 1 148 | 149 | j = 0 150 | t = 0 151 | while t <= size_thru: 152 | if value['asks'][j][0] == rate_ask: 153 | t += value['asks'][j][1] - size_ask 154 | else: 155 | t += value['asks'][j][1] 156 | j += 1 157 | 158 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 159 | 160 | # 成行注文する関数 161 | def market(side, size): 162 | 163 | while True: 164 | try: 165 | value = bitflyer.create_order(PAIR, type = 'market', side = side, amount = size,params = { "product_code" : PRODUCT }) 166 | break 167 | except Exception as e: 168 | logger.info(e) 169 | time.sleep(1) 170 | #場当たり的な対処 171 | size = minLOT; 172 | 173 | time.sleep(0.2) 174 | return value 175 | 176 | # 指値注文する関数 177 | def limit(side, size, price): 178 | 179 | while True: 180 | try: 181 | value = bitflyer.create_order(PAIR, type = 'limit', side = side, amount = size, price = price, params = { "product_code" : PRODUCT }) 182 | break 183 | except Exception as e: 184 | logger.info(e) 185 | time.sleep(1) 186 | #場当たり的な対処 187 | size = minLOT; 188 | return value 189 | 190 | # 注文をキャンセルする関数 191 | def cancel(id): 192 | 193 | try: 194 | value = bitflyer.cancelOrder(symbol = PAIR, id = id) 195 | except Exception as e: 196 | logger.info(e) 197 | 198 | # 指値が約定していた(=キャンセルが通らなかった)場合、 199 | # 注文情報を更新(約定済み)して返す 200 | value = get_status(id) 201 | 202 | time.sleep(0.5) 203 | return value 204 | 205 | # 指定した注文idのステータスを参照する関数 206 | def get_status(id): 207 | 208 | 209 | while True: 210 | try: 211 | value = bitflyer.private_get_getchildorders(params = {'product_code': PRODUCT, 'child_order_acceptance_id': id})[0] 212 | break 213 | except Exception as e: 214 | logger.info(e) 215 | time.sleep(2) 216 | 217 | # APIで受け取った値を読み換える 218 | if value['child_order_state'] == 'ACTIVE': 219 | status = 'open' 220 | elif value['child_order_state'] == 'COMPLETED': 221 | status = 'closed' 222 | else: 223 | status = value['child_order_state'] 224 | 225 | # 未約定量を計算する 226 | remaining = float(value['size']) - float(value['executed_size']) 227 | 228 | time.sleep(0.1) 229 | return {'id': value['child_order_acceptance_id'], 'status': status, 'filled': value['executed_size'], 'remaining': remaining, 'amount': value['size'], 'price': value['price']} 230 | 231 | 232 | def fromListToDF(candleStick): 233 | """ 234 | Listのローソク足をpandasデータフレームへ. 235 | """ 236 | date = [price[0] for price in candleStick] 237 | priceOpen = [int(price[1]) for price in candleStick] 238 | priceHigh = [int(price[2]) for price in candleStick] 239 | priceLow = [int(price[3]) for price in candleStick] 240 | priceClose = [int(price[4]) for price in candleStick] 241 | volume = [int(price[5]) for price in candleStick] 242 | date_datetime = map(datetime.datetime.fromtimestamp, date) 243 | dti = pd.DatetimeIndex(date_datetime) 244 | df_candleStick = pd.DataFrame({"open" : priceOpen, "high" : priceHigh, "low": priceLow, "close" : priceClose, "volume" : volume}, index=dti) 245 | return df_candleStick 246 | 247 | def processCandleStick(candleStick, timeScale): 248 | """ 249 | 1分足データから各時間軸のデータを作成.timeScaleには5T(5分),H(1時間)などの文字列を入れる 250 | """ 251 | df_candleStick = fromListToDF(candleStick) 252 | processed_candleStick = df_candleStick.resample(timeScale).agg({'open': 'first','high': 'max','low': 'min','close': 'last',"volume" : "sum"}) 253 | processed_candleStick = processed_candleStick.dropna() 254 | return processed_candleStick 255 | 256 | def zscore(x, axis = None): 257 | xmean = x.mean(axis=axis, keepdims=True) 258 | xstd = np.std(x, axis=axis, keepdims=True) 259 | zscore = (x-xmean)/xstd 260 | return zscore 261 | 262 | #rciのdの計算 263 | def dofrci(itv,src): 264 | from scipy.stats import rankdata 265 | sum = 0.0 266 | for i in range(itv, 0, -1): 267 | date_rank = itv - i + 1 268 | price_rank = (itv - rankdata(src)[i-1] + 1) 269 | sum = sum + pow( (date_rank - price_rank) ,2) 270 | #pprint("hiduke = {}, price={}, juni={}, goukei={}".format(date_rank, src[i-1], price_rank, sum) ) 271 | 272 | return sum 273 | 274 | #rciの計算 275 | def calc_rci(src, term): 276 | 277 | listnull = [None] 278 | itv = term 279 | rcinull = listnull * itv 280 | rci_tmp = [ (1.0 - 6.0 * dofrci(itv,src[i-itv:i]) / (itv * itv * itv - itv)) * 100.0 for i in range(itv,len(src))] 281 | rci = rcinull + rci_tmp 282 | 283 | return rci 284 | 285 | def vixfix(close, low): 286 | prd = 22 287 | bbl = 20 288 | mult = 2.0 289 | lb = 50 290 | ph = 0.85 291 | pl = 1.01 292 | 293 | wvf = (pd.Series(close).rolling(prd, 1).max() - low) / pd.Series(close).rolling(prd, 1).max() * 100 294 | 295 | sDev = mult * pd.Series(wvf).rolling(bbl, 1).std() 296 | midLine = pd.Series(wvf).rolling(bbl, 1).mean() 297 | 298 | lowerBand = midLine - sDev 299 | upperBand = midLine + sDev 300 | rangeHigh = pd.Series(wvf).rolling(lb, 1).max() * ph 301 | rangeLow = pd.Series(wvf).rolling(lb, 1).min() * pl 302 | 303 | #緑が点灯しているときはエントリーしない 304 | if wvf[len(wvf)-1] > rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] > upperBand[len(wvf)-1]: 305 | return 'buy' 306 | #print("VIX: 緑") 307 | elif wvf[len(wvf)-2] > rangeHigh[len(wvf)-2] or wvf[len(wvf)-2] > upperBand[len(wvf)-2]: 308 | if wvf[len(wvf)-1] < rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] < upperBand[len(wvf)-1]: 309 | #print('VIX: 緑からグレー') 310 | 1+1 311 | #return 'buy' 312 | #赤が点灯しているときはエントリーしない 313 | elif wvf[len(wvf)-1] < rangeLow[len(wvf)-1] or wvf[len(wvf)-1] < lowerBand[len(wvf)-1]: 314 | return 'sell' 315 | #print("VIX: 赤") 316 | elif wvf[len(wvf)-2] < rangeLow[len(wvf)-2] or wvf[len(wvf)-2] < lowerBand[len(wvf)-2]: 317 | if wvf[len(wvf)-1] > rangeLow[len(wvf)-1] or wvf[len(wvf)-1] > lowerBand[len(wvf)-1]: 318 | #print('VIX: 赤からグレー') 319 | 1+1 320 | #return 'sell' 321 | else: 322 | pass 323 | #print("VIX: グレー") 324 | 325 | return 'stay' 326 | 327 | 328 | #------------------------------------------------------------------------------# 329 | 330 | # 未約定量が存在することを示すフラグ 331 | remaining_ask_flag = 0 332 | remaining_bid_flag = 0 333 | 334 | # 指値の有無を示す変数 335 | pos = 'none' 336 | 337 | #------------------------------------------------------------------------------# 338 | 339 | logger.info('--------TradeStart--------') 340 | logger.info('BOT TYPE : MarketMaker @ bitFlyer') 341 | logger.info('SYMBOL : {0}'.format(PAIR)) 342 | logger.info('LOT : {0} {1}'.format(LOT, COIN)) 343 | logger.info('SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)) 344 | logger.info('SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100)) 345 | 346 | # 残高取得 347 | asset = float(get_asset()['info'][0]['amount']) 348 | colla = float(get_colla()['collateral']) 349 | logger.info('--------------------------') 350 | logger.info('ASSET : {0}'.format(int(asset))) 351 | logger.info('COLLATERAL : {0}'.format(int(colla))) 352 | logger.info('TOTAL : {0}'.format(int(asset + colla))) 353 | 354 | #初期化 355 | trade_ask = {"status":'closed'} 356 | trade_bid = {"status":'closed'} 357 | 358 | # メインループ 359 | while True: 360 | 361 | try: 362 | if "H" in CANDLETERM: 363 | candleStick = cryptowatch.getCandlestick(480, "3600") 364 | elif "30T" in CANDLETERM: 365 | candleStick = cryptowatch.getCandlestick(100, "1800") 366 | elif "15T" in CANDLETERM: 367 | candleStick = cryptowatch.getCandlestick(100, "900") 368 | elif "5T" in CANDLETERM: 369 | candleStick = cryptowatch.getCandlestick(100, "300") 370 | elif "3T" in CANDLETERM: 371 | candleStick = cryptowatch.getCandlestick(100, "180") 372 | else: 373 | candleStick = cryptowatch.getCandlestick(480, "60") 374 | except: 375 | logging.error("Unknown error happend when you requested candleStick") 376 | 377 | if CANDLETERM == None: 378 | df_candleStick = fromListToDF(candleStick) 379 | else: 380 | df_candleStick = processCandleStick(candleStick,CANDLETERM) 381 | 382 | # 未約定量の繰越がなければリセット 383 | if remaining_ask_flag == 0: 384 | remaining_ask = 0 385 | if remaining_bid_flag == 0: 386 | remaining_bid = 0 387 | 388 | # フラグリセット 389 | remaining_ask_flag = 0 390 | remaining_bid_flag = 0 391 | 392 | #positionを取得(指値だけだとバグるので修正取得) 393 | side , size = order.getmypos(); 394 | 395 | if side == "SELL": 396 | signedsize = -size; 397 | if side == "BUY": 398 | signedsize = size; 399 | if side == "": 400 | signedsize = size; 401 | 402 | if size == 0 and side =="": 403 | pos = 'none'; 404 | trade_ask['status'] = 'closed'; 405 | trade_bid['status'] = 'closed'; 406 | else : 407 | pos = 'entry'; 408 | if side == "SELL": 409 | trade_ask['status'] = 'open'; 410 | if side == "BUY": 411 | trade_bid['status'] = 'open'; 412 | 413 | # 自分の指値が存在しないとき実行する 414 | if pos == 'none' or pos == 'entry': 415 | 416 | try: 417 | # 一つ前のspread 418 | previousspread = spread; 419 | # 板情報を取得、実効ask/bid(指値を入れる基準値)を決定する 420 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 421 | ask = float(tick['ask']) 422 | bid = float(tick['bid']) 423 | # 実効スプレッドを計算する 424 | spread = (ask - bid) / bid 425 | 426 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 427 | # askとbidを再計算する 428 | ask = float(tick['ask']) 429 | bid = float(tick['bid']) 430 | 431 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : PRODUCT }) 432 | lastprice = int(ticker["last"]) 433 | 434 | if int((ask + bid)/2) > int(lastprice): 435 | trend = "sell" 436 | else: 437 | trend = "buy" 438 | except: 439 | pass; 440 | 441 | try: 442 | 443 | # 実効スプレッドが閾値を超えた場合に実行する 444 | if spread > SPREAD_ENTRY: 445 | 446 | # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える 447 | amount_int_ask = LOT + remaining_bid 448 | amount_int_bid = LOT + remaining_ask 449 | 450 | tick = get_effective_tick(size_thru=AMOUNT_SPREAD, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 451 | # askとbidを再計算する 452 | ask = float(tick['ask']) 453 | bid = float(tick['bid']) 454 | 455 | #実効Ask/Bidからdelta離れた位置に指値を入れる 456 | if trend == "buy" and size < 0.3: 457 | trade_bid = limit('buy', amount_int_bid, (bid)) 458 | time.sleep(0.2) 459 | order.cancelAllOrder(); 460 | 461 | elif trend == "sell" and size < 0.3: 462 | trade_ask = limit('sell', amount_int_ask, (ask)) 463 | time.sleep(0.2) 464 | order.cancelAllOrder(); 465 | # 実効スプレッドが閾値を超えた場合に実行する 466 | if spread > SPREAD_CLOSE: 467 | 468 | side , size = order.getmypos(); 469 | res = order.getcollateral(); 470 | pnl = res["open_position_pnl"]; 471 | 472 | order.cancelAllOrder(); 473 | time.sleep(0.5) 474 | 475 | tick = get_effective_tick(size_thru=AMOUNT_SPREAD, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 476 | # askとbidを再計算する 477 | ask = float(tick['ask']) 478 | bid = float(tick['bid']) 479 | 480 | if side == "SELL" and int(pnl) > 3 : 481 | trade_bid = limit('buy', round(size,15),(tick["ask"])) 482 | 483 | elif side == "BUY" and int(pnl) > 3 : 484 | trade_ask = limit('sell', round(size,15),(tick["bid"])) 485 | 486 | if side == "SELL" and trend == "buy": 487 | trade_bid = limit('buy', round(size,15),(tick["ask"])) 488 | 489 | elif side == "BUY"and trend == "sell": 490 | trade_ask = limit('sell', round(size,15),(tick["bid"])) 491 | 492 | logger.info('--------------------------') 493 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 494 | 495 | #logger.info('Normdmacdhist:%s ', Normdmacdhist[-1]); 496 | logger.info('Offset:%s ', int((spread * 10000) / 100) * OFFSET); 497 | logger.info('ABSOffset:%s ', int((spread * 10000) / 100) * ABSOFFSET); 498 | logger.info('trend:%s ', trend); 499 | logger.info('signedsize:%s ', signedsize); 500 | logger.info('pnl:%s ', int(pnl)); 501 | 502 | logger.info('--------------------------') 503 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 504 | 505 | trade_ask['status'] = 'open' 506 | trade_bid['status'] = 'open' 507 | pos = 'entry' 508 | 509 | logger.info('--------------------------') 510 | logger.info('entry') 511 | 512 | except: 513 | pass; 514 | 515 | 516 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarketMaker 2 | Market Maker Strategy Bot. 3 | -------------------------------------------------------------------------------- /SFDMaker.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | class MarketMaker: 4 | pass 5 | def __init__(self): 6 | #config.jsonの読み込み 7 | f = open('config/config.json', 'r', encoding="utf-8") 8 | config = json.load(f) 9 | 10 | 11 | #!/usr/bin/python3 12 | # coding: utf-8 13 | 14 | import datetime 15 | import time 16 | import json 17 | import ccxt 18 | import requests 19 | import bforder 20 | import cryptowatch 21 | #import talib as ta 22 | import numpy as np 23 | import pandas as pd 24 | 25 | #configの読み込み 26 | f = open('config/config.json', 'r', encoding="utf-8") 27 | config = json.load(f) 28 | 29 | order = bforder.BFOrder(); 30 | 31 | cryptowatch = cryptowatch.CryptoWatch() 32 | 33 | bitflyer = ccxt.bitflyer({ 34 | 'apiKey': config["key"], 35 | 'secret': config["secret"], 36 | }) 37 | 38 | # 取引する通貨、シンボルを設定 39 | COIN = 'BTC' 40 | PAIR = 'BTC/JPY' 41 | 42 | # プロダクトコードの指定 43 | PRODUCT = config["product_code"] 44 | 45 | # ロット(単位はBTC) 46 | LOT = config["lotSize"]; 47 | 48 | CANDLETERM = config["candleTerm"]; 49 | 50 | # 最小注文数(取引所の仕様に応じて設定) 51 | AMOUNT_MIN = 0.001 52 | 53 | # スプレッド閾値 54 | SPREAD_ENTRY = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を上回ったらエントリー 55 | SPREAD_CANCEL = 0.0000 # 実効スプレッド(100%=1,1%=0.01)がこの値を下回ったら指値更新を停止 56 | 57 | # 数量X(この数量よりも下に指値をおく) 58 | AMOUNT_THRU = 1 59 | AMOUNT_ASKBID = 0.5 60 | 61 | # 実効Ask/BidからDELTA離れた位置に指値をおく 62 | DELTA = 30 63 | 64 | INVDELTA = -20 65 | 66 | # 買い気配、売り気配に応じて主観的ファンダメンタル価格をずらす 67 | OFFSET = 2 68 | 69 | ABSOFFSET = 100 70 | 71 | PERTURB = 40 72 | 73 | spread = 0 74 | 75 | vixFlag = 0 76 | 77 | callback = 'stay'; 78 | 79 | sfdflag = False; 80 | 81 | canSellflag = True; 82 | 83 | canBuyflag = True; 84 | 85 | 86 | #------------------------------------------------------------------------------# 87 | #log設定 88 | import logging 89 | logger = logging.getLogger('LoggingTest') 90 | logger.setLevel(10) 91 | fh = logging.FileHandler('log_mm_bf_' + datetime.datetime.now().strftime('%Y%m%d') + '_' + datetime.datetime.now().strftime('%H%M%S') + '.log') 92 | logger.addHandler(fh) 93 | sh = logging.StreamHandler() 94 | logger.addHandler(sh) 95 | formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") 96 | fh.setFormatter(formatter) 97 | sh.setFormatter(formatter) 98 | 99 | #------------------------------------------------------------------------------# 100 | 101 | # JPY残高を参照する関数 102 | def get_asset(): 103 | 104 | while True: 105 | try: 106 | value = bitflyer.fetch_balance(params = { "product_code" : PRODUCT }) 107 | break 108 | except Exception as e: 109 | logger.info(e) 110 | time.sleep(1) 111 | return value 112 | 113 | # JPY証拠金を参照する関数 114 | def get_colla(): 115 | 116 | while True: 117 | try: 118 | value = bitflyer.privateGetGetcollateral() 119 | break 120 | except Exception as e: 121 | logger.info(e) 122 | time.sleep(1) 123 | return value 124 | 125 | # 板情報から実効Ask/Bid(=指値を入れる基準値)を計算する関数 126 | def get_effective_tick(size_thru, rate_ask, size_ask, rate_bid, size_bid): 127 | 128 | while True: 129 | try: 130 | value = bitflyer.fetchOrderBook(PAIR,params = { "product_code" : PRODUCT }) 131 | break 132 | except Exception as e: 133 | logger.info(e) 134 | time.sleep(2) 135 | 136 | i = 0 137 | s = 0 138 | while s <= size_thru: 139 | if value['bids'][i][0] == rate_bid: 140 | s += value['bids'][i][1] - size_bid 141 | else: 142 | s += value['bids'][i][1] 143 | i += 1 144 | 145 | j = 0 146 | t = 0 147 | while t <= size_thru: 148 | if value['asks'][j][0] == rate_ask: 149 | t += value['asks'][j][1] - size_ask 150 | else: 151 | t += value['asks'][j][1] 152 | j += 1 153 | 154 | time.sleep(0.5) 155 | return {'bid': value['bids'][i-1][0], 'ask': value['asks'][j-1][0]} 156 | 157 | # 成行注文する関数 158 | def market(side, size): 159 | 160 | while True: 161 | try: 162 | value = bitflyer.create_order(PAIR, type = 'market', side = side, amount = size,params = { "product_code" : PRODUCT }) 163 | break 164 | except Exception as e: 165 | logger.info(e) 166 | time.sleep(2) 167 | #場当たり的な対処 168 | size = LOT; 169 | 170 | time.sleep(0.5) 171 | return value 172 | 173 | # 指値注文する関数 174 | def limit(side, size, price): 175 | 176 | while True: 177 | try: 178 | value = bitflyer.create_order(PAIR, type = 'limit', side = side, amount = size, price = price, params = { "product_code" : PRODUCT }) 179 | break 180 | except Exception as e: 181 | logger.info(e) 182 | time.sleep(2) 183 | #場当たり的な対処 184 | size = LOT; 185 | 186 | time.sleep(0.5) 187 | return value 188 | 189 | # 注文をキャンセルする関数 190 | def cancel(id): 191 | 192 | try: 193 | value = bitflyer.cancelOrder(symbol = PAIR, id = id) 194 | except Exception as e: 195 | logger.info(e) 196 | 197 | # 指値が約定していた(=キャンセルが通らなかった)場合、 198 | # 注文情報を更新(約定済み)して返す 199 | value = get_status(id) 200 | 201 | time.sleep(0.5) 202 | return value 203 | 204 | # 指定した注文idのステータスを参照する関数 205 | def get_status(id): 206 | 207 | 208 | while True: 209 | try: 210 | value = bitflyer.private_get_getchildorders(params = {'product_code': PRODUCT, 'child_order_acceptance_id': id})[0] 211 | break 212 | except Exception as e: 213 | logger.info(e) 214 | time.sleep(2) 215 | 216 | # APIで受け取った値を読み換える 217 | if value['child_order_state'] == 'ACTIVE': 218 | status = 'open' 219 | elif value['child_order_state'] == 'COMPLETED': 220 | status = 'closed' 221 | else: 222 | status = value['child_order_state'] 223 | 224 | # 未約定量を計算する 225 | remaining = float(value['size']) - float(value['executed_size']) 226 | 227 | time.sleep(0.1) 228 | return {'id': value['child_order_acceptance_id'], 'status': status, 'filled': value['executed_size'], 'remaining': remaining, 'amount': value['size'], 'price': value['price']} 229 | 230 | def fromListToDF(candleStick): 231 | """ 232 | Listのローソク足をpandasデータフレームへ. 233 | """ 234 | date = [price[0] for price in candleStick] 235 | priceOpen = [int(price[1]) for price in candleStick] 236 | priceHigh = [int(price[2]) for price in candleStick] 237 | priceLow = [int(price[3]) for price in candleStick] 238 | priceClose = [int(price[4]) for price in candleStick] 239 | volume = [int(price[5]) for price in candleStick] 240 | date_datetime = map(datetime.datetime.fromtimestamp, date) 241 | dti = pd.DatetimeIndex(date_datetime) 242 | df_candleStick = pd.DataFrame({"open" : priceOpen, "high" : priceHigh, "low": priceLow, "close" : priceClose, "volume" : volume}, index=dti) 243 | return df_candleStick 244 | 245 | def processCandleStick(candleStick, timeScale): 246 | """ 247 | 1分足データから各時間軸のデータを作成.timeScaleには5T(5分),H(1時間)などの文字列を入れる 248 | """ 249 | df_candleStick = fromListToDF(candleStick) 250 | processed_candleStick = df_candleStick.resample(timeScale).agg({'open': 'first','high': 'max','low': 'min','close': 'last',"volume" : "sum"}) 251 | processed_candleStick = processed_candleStick.dropna() 252 | return processed_candleStick 253 | 254 | def zscore(x, axis = None): 255 | xmean = x.mean(axis=axis, keepdims=True) 256 | xstd = np.std(x, axis=axis, keepdims=True) 257 | zscore = (x-xmean)/xstd 258 | return zscore 259 | 260 | #rciのdの計算 261 | def dofrci(itv,src): 262 | from scipy.stats import rankdata 263 | sum = 0.0 264 | for i in range(itv, 0, -1): 265 | date_rank = itv - i + 1 266 | price_rank = (itv - rankdata(src)[i-1] + 1) 267 | sum = sum + pow( (date_rank - price_rank) ,2) 268 | #pprint("hiduke = {}, price={}, juni={}, goukei={}".format(date_rank, src[i-1], price_rank, sum) ) 269 | 270 | return sum 271 | 272 | #rciの計算 273 | def calc_rci(src, term): 274 | 275 | listnull = [None] 276 | itv = term 277 | rcinull = listnull * itv 278 | rci_tmp = [ (1.0 - 6.0 * dofrci(itv,src[i-itv:i]) / (itv * itv * itv - itv)) * 100.0 for i in range(itv,len(src))] 279 | rci = rcinull + rci_tmp 280 | 281 | return rci 282 | 283 | def vixfix(close, low): 284 | prd = 22 285 | bbl = 20 286 | mult = 2.0 287 | lb = 50 288 | ph = 0.85 289 | pl = 1.01 290 | 291 | wvf = (pd.Series(close).rolling(prd, 1).max() - low) / pd.Series(close).rolling(prd, 1).max() * 100 292 | 293 | sDev = mult * pd.Series(wvf).rolling(bbl, 1).std() 294 | midLine = pd.Series(wvf).rolling(bbl, 1).mean() 295 | 296 | lowerBand = midLine - sDev 297 | upperBand = midLine + sDev 298 | rangeHigh = pd.Series(wvf).rolling(lb, 1).max() * ph 299 | rangeLow = pd.Series(wvf).rolling(lb, 1).min() * pl 300 | 301 | #緑が点灯しているときはエントリーしない 302 | if wvf[len(wvf)-1] > rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] > upperBand[len(wvf)-1]: 303 | return 'buy' 304 | #print("VIX: 緑") 305 | elif wvf[len(wvf)-2] > rangeHigh[len(wvf)-2] or wvf[len(wvf)-2] > upperBand[len(wvf)-2]: 306 | if wvf[len(wvf)-1] < rangeHigh[len(wvf)-1] or wvf[len(wvf)-1] < upperBand[len(wvf)-1]: 307 | #print('VIX: 緑からグレー') 308 | 1+1 309 | #return 'buy' 310 | #赤が点灯しているときはエントリーしない 311 | elif wvf[len(wvf)-1] < rangeLow[len(wvf)-1] or wvf[len(wvf)-1] < lowerBand[len(wvf)-1]: 312 | return 'sell' 313 | #print("VIX: 赤") 314 | elif wvf[len(wvf)-2] < rangeLow[len(wvf)-2] or wvf[len(wvf)-2] < lowerBand[len(wvf)-2]: 315 | if wvf[len(wvf)-1] > rangeLow[len(wvf)-1] or wvf[len(wvf)-1] > lowerBand[len(wvf)-1]: 316 | #print('VIX: 赤からグレー') 317 | 1+1 318 | #return 'sell' 319 | else: 320 | pass 321 | #print("VIX: グレー") 322 | 323 | return 'stay' 324 | 325 | #------------------------------------------------------------------------------# 326 | 327 | # 未約定量が存在することを示すフラグ 328 | remaining_ask_flag = 0 329 | remaining_bid_flag = 0 330 | 331 | # 指値の有無を示す変数 332 | pos = 'none' 333 | 334 | #------------------------------------------------------------------------------# 335 | 336 | logger.info('--------TradeStart--------') 337 | logger.info('BOT TYPE : MarketMaker @ bitFlyer') 338 | logger.info('SYMBOL : {0}'.format(PAIR)) 339 | logger.info('LOT : {0} {1}'.format(LOT, COIN)) 340 | logger.info('SPREAD ENTRY : {0} %'.format(SPREAD_ENTRY * 100)) 341 | logger.info('SPREAD CANCEL : {0} %'.format(SPREAD_CANCEL * 100)) 342 | 343 | # 残高取得 344 | asset = float(get_asset()['info'][0]['amount']) 345 | colla = float(get_colla()['collateral']) 346 | logger.info('--------------------------') 347 | logger.info('ASSET : {0}'.format(int(asset))) 348 | logger.info('COLLATERAL : {0}'.format(int(colla))) 349 | logger.info('TOTAL : {0}'.format(int(asset + colla))) 350 | 351 | #初期化 352 | trade_ask = {"status":'closed'} 353 | trade_bid = {"status":'closed'} 354 | 355 | # メインループ 356 | while True: 357 | 358 | try: 359 | if "H" in CANDLETERM: 360 | candleStick = cryptowatch.getCandlestick(480, "3600") 361 | elif "30T" in CANDLETERM: 362 | candleStick = cryptowatch.getCandlestick(100, "1800") 363 | elif "15T" in CANDLETERM: 364 | candleStick = cryptowatch.getCandlestick(100, "900") 365 | elif "5T" in CANDLETERM: 366 | candleStick = cryptowatch.getCandlestick(100, "300") 367 | elif "3T" in CANDLETERM: 368 | candleStick = cryptowatch.getCandlestick(100, "180") 369 | else: 370 | candleStick = cryptowatch.getCandlestick(480, "60") 371 | except: 372 | logging.error("Unknown error happend when you requested candleStick") 373 | 374 | if CANDLETERM == None: 375 | df_candleStick = fromListToDF(candleStick) 376 | else: 377 | df_candleStick = processCandleStick(candleStick,CANDLETERM) 378 | 379 | # 未約定量の繰越がなければリセット 380 | if remaining_ask_flag == 0: 381 | remaining_ask = 0 382 | if remaining_bid_flag == 0: 383 | remaining_bid = 0 384 | 385 | # フラグリセット 386 | remaining_ask_flag = 0 387 | remaining_bid_flag = 0 388 | sfdflag = False; 389 | 390 | #positionを取得(指値だけだとバグるので修正取得) 391 | side , size = order.getmypos(); 392 | 393 | if size == 0 and side =="": 394 | pos = 'none'; 395 | trade_ask['status'] = 'closed'; 396 | trade_bid['status'] = 'closed'; 397 | else : 398 | pos = 'entry'; 399 | if side == "SELL": 400 | trade_ask['status'] = 'open'; 401 | if side == "BUY": 402 | trade_bid['status'] = 'open'; 403 | 404 | if size >= 0.3 and side =="SELL": 405 | canSellflag = False; 406 | canBuyflag = True; 407 | elif size >= 0.3 and side =="BUY": 408 | canSellflag = True; 409 | canBuyflag = False; 410 | # 自分の指値が存在しないとき実行する 411 | if pos == 'none' or pos == 'entry': 412 | 413 | try: 414 | 415 | #SFDの計算 416 | tickerbtcfx = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : "FX_BTC_JPY" }) 417 | tickerbtc = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : "BTC_JPY" }) 418 | 419 | spot = round(tickerbtc["last"]) 420 | fx = round(tickerbtcfx["last"]) 421 | diff = round((fx-spot)/spot * 100,6); 422 | 423 | if(diff <= 5.05 and diff >= 4.95): 424 | sfdflag = True; 425 | logger.info("SPOT: " + str(spot) + "/FX: " + str(fx) + "/DIFF: " + str(diff)+ '%') 426 | 427 | except: 428 | pass; 429 | 430 | try: 431 | 432 | # 実効スプレッドが閾値を超えた場合に実行しない 433 | if spread >= SPREAD_ENTRY: 434 | 435 | # 前回のサイクルにて未約定量が存在すれば今回の注文数に加える 436 | amount_int_ask = LOT + remaining_bid 437 | amount_int_bid = LOT + remaining_ask 438 | 439 | #SFD時の計算 440 | if sfdflag == True: 441 | 442 | #tickerを再計算 443 | tickerbtcfx = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : "FX_BTC_JPY" }) 444 | tickerbtc = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : "BTC_JPY" }) 445 | 446 | spot = tickerbtc["last"] 447 | fx = tickerbtcfx["last"] 448 | diff = round((fx-spot)/spot * 100,6); 449 | 450 | try: 451 | if diff >= 5.00001 and canSellflag == True: 452 | trade_ask = limit('sell', amount_int_bid, (tickerbtcfx["ask"])) 453 | elif diff <= 4.99 and canBuyflag == True: 454 | trade_bid = limit('buy', amount_int_bid, (tickerbtcfx["bid"])) 455 | logger.info("--------------") 456 | logger.info("SPOT: " + str(spot) + "/FX: " + str(fx) + "/DIFF: " + str(diff)+ '%') 457 | logger.info("--------------") 458 | time.sleep(0.2) 459 | # 注文をキャンセル 460 | order.cancelAllOrder(); 461 | 462 | except: 463 | pass 464 | 465 | #tickerを再計算 466 | tickerbtcfx = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : "FX_BTC_JPY" }) 467 | tickerbtc = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : "BTC_JPY" }) 468 | 469 | spot = tickerbtc["last"] 470 | fx = tickerbtcfx["last"] 471 | diff = round((fx-spot)/spot * 100,6); 472 | 473 | try: 474 | if diff >= 5.0001 and canSellflag == True and side == "BUY": 475 | trade_ask = limit('sell', size, (tickerbtcfx["ask"])) 476 | elif diff <= 4.98 and canBuyflag == True and side == "SELL": 477 | trade_bid = limit('buy', size, (tickerbtcfx["bid"])) 478 | logger.info("--------------") 479 | logger.info("SPOT: " + str(spot) + "/FX: " + str(fx) + "/DIFF: " + str(diff)+ '%') 480 | logger.info("--------------") 481 | time.sleep(0.2) 482 | # 注文をキャンセル 483 | order.cancelAllOrder(); 484 | 485 | except: 486 | pass 487 | 488 | logger.info('--------------------------') 489 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 490 | 491 | #logger.info('Normdmacdhist:%s ', Normdmacdhist[-1]); 492 | logger.info('Offset:%s ', int((spread * 10000) / 100) * OFFSET); 493 | logger.info('ABSOffset:%s ', int((spread * 10000) / 100) * ABSOFFSET); 494 | logger.info('trend:%s ', trend); 495 | 496 | logger.info('--------------------------') 497 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 498 | 499 | trade_ask['status'] = 'open' 500 | trade_bid['status'] = 'open' 501 | pos = 'entry' 502 | 503 | logger.info('--------------------------') 504 | logger.info('entry') 505 | 506 | except: 507 | pass; 508 | 509 | # 自分の指値が存在するとき実行する 510 | if pos == 'entry' and False: 511 | 512 | try: 513 | orders = bitflyer.fetch_orders( 514 | symbol = PAIR, 515 | params = { "product_code" : PRODUCT}) 516 | 517 | openorders = bitflyer.fetch_open_orders( 518 | symbol = PAIR, 519 | params = { "product_code" : PRODUCT}) 520 | 521 | trade_ask['status'] = "closed"; 522 | trade_bid['status'] = "closed"; 523 | 524 | for o in openorders: 525 | if o["side"] == "sell": 526 | trade_ask['status'] = "open"; 527 | elif o["side"] == "buy": 528 | trade_bid['status'] = "open"; 529 | else: 530 | trade_ask['status'] = "closed"; 531 | trade_bid['status'] = "closed"; 532 | 533 | #最新の注文のidを取得する 534 | for o in orders: 535 | 536 | if o["side"] == "sell": 537 | trade_ask['id'] = orders[-1]["id"]; 538 | # 注文ステータス取得 539 | if trade_ask['status'] != 'closed': 540 | trade_ask = get_status(trade_ask['id']) 541 | break; 542 | for o in orders: 543 | if o["side"] == "buy": 544 | trade_bid['id'] = orders[-1]["id"]; 545 | # 注文ステータス取得 546 | if trade_bid['status'] != 'closed': 547 | trade_bid = get_status(trade_bid['id']) 548 | break; 549 | 550 | # 板情報を取得、実効Ask/Bid(指値を入れる基準値)を決定する 551 | tick = get_effective_tick(size_thru=AMOUNT_THRU, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 552 | ask = float(tick['ask']) 553 | bid = float(tick['bid']) 554 | spread = (ask - bid) / bid 555 | 556 | 557 | logger.info('--------------------------') 558 | logger.info('ask:{0}, bid:{1}, spread:{2}%'.format(int(ask * 100) / 100, int(bid * 100) / 100, int(spread * 10000) / 100)) 559 | logger.info('ask status:{0}, price:{1}'.format(trade_ask['status'], trade_ask['price'])) 560 | logger.info('bid status:{0}, price:{1}'.format(trade_bid['status'], trade_bid['price'])) 561 | except: 562 | pass; 563 | 564 | 565 | try: 566 | # Ask未約定量が最小注文量を下回るとき実行 567 | if trade_ask['status'] == 'open' and trade_ask['remaining'] <= AMOUNT_MIN: 568 | 569 | # 注文をキャンセル 570 | order.cancelAllOrder(); 571 | 572 | # ステータスをCLOSEDに書き換える 573 | trade_ask['status'] = 'closed' 574 | 575 | # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる 576 | remaining_ask = float(trade_ask['remaining']) 577 | remaining_ask_flag = 1 578 | 579 | logger.info('--------------------------') 580 | logger.info('ask almost filled.') 581 | 582 | # Bid未約定量が最小注文量を下回るとき実行 583 | if trade_bid['status'] == 'open' and trade_bid['remaining'] <= AMOUNT_MIN: 584 | 585 | # 注文をキャンセル 586 | order.cancelAllOrder(); 587 | 588 | # ステータスをCLOSEDに書き換える 589 | trade_bid['status'] = 'closed' 590 | 591 | # 未約定量を記録、次サイクルで未約定量を加えるフラグを立てる 592 | remaining_bid = float(trade_bid['remaining']) 593 | remaining_bid_flag = 1 594 | 595 | logger.info('--------------------------') 596 | logger.info('bid almost filled.') 597 | 598 | #スプレッドが閾値以上のときに実行する 599 | if spread > SPREAD_CANCEL: 600 | 601 | # Ask指値が最良位置に存在しないとき、指値を更新する 602 | if trade_ask['status'] == 'open': 603 | if trade_ask['price'] != ask: 604 | 605 | # 指値を一旦キャンセル 606 | order.cancelAllOrder(); 607 | 608 | # 注文数が最小注文数より大きいとき、指値を更新する 609 | if trade_ask['remaining'] >= AMOUNT_MIN: 610 | trade_ask = limit('sell', trade_ask['remaining'], ask) 611 | trade_ask['status'] = 'open' 612 | # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする 613 | elif AMOUNT_MIN > trade_ask['remaining'] > 0: 614 | trade_ask['status'] = 'closed' 615 | remaining_ask = float(trade_ask['remaining']) 616 | remaining_ask_flag = 1 617 | 618 | # 注文数が最小注文数より小さく0のとき、CLOSEDとする 619 | else: 620 | trade_ask['status'] = 'closed' 621 | 622 | # Bid指値が最良位置に存在しないとき、指値を更新する 623 | if trade_bid['status'] == 'open': 624 | if trade_bid['price'] != bid: 625 | 626 | # 指値を一旦キャンセル 627 | order.cancelAllOrder(); 628 | 629 | # 注文数が最小注文数より大きいとき、指値を更新する 630 | if trade_bid['remaining'] >= AMOUNT_MIN: 631 | trade_bid = limit('buy', trade_bid['remaining'], bid) 632 | trade_bid['status'] = 'open' 633 | # 注文数が最小注文数より小さく0でないとき、未約定量を記録してCLOSEDとする 634 | elif AMOUNT_MIN > trade_bid['remaining'] > 0: 635 | trade_bid['status'] = 'closed' 636 | remaining_bid = float(trade_bid['remaining']) 637 | remaining_bid_flag = 1 638 | # 注文数が最小注文数より小さく0のとき、CLOSEDとする 639 | else: 640 | trade_bid['status'] = 'closed' 641 | 642 | #おそうじする 643 | tick = get_effective_tick(size_thru=AMOUNT_ASKBID, rate_ask=0, size_ask=0, rate_bid=0, size_bid=0) 644 | # askとbidを再計算する 645 | ask = float(tick['ask']) 646 | bid = float(tick['bid']) 647 | 648 | ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : "FX_BTC_JPY" }) 649 | 650 | if int((ask + bid)/2) > int(ticker["last"]): 651 | trend = "buy" 652 | else: 653 | trend = "sell" 654 | 655 | #positionを取得(指値だけだとバグるので修正取得) 656 | side , size = order.getmypos(); 657 | 658 | if side == "SELL" and trend == 'buy' and trade_bid['status'] == "closed": 659 | amount_int_bid = LOT + remaining_ask 660 | trade_bid = limit('buy', size, bid + DELTA + int((spread * 10000) / 100) * OFFSET) 661 | trade_bid['status'] = 'open' 662 | if side == "BUY" and trend == 'sell' and trade_ask['status'] == "closed": 663 | amount_int_ask = LOT + remaining_bid 664 | trade_ask = limit('sell', size, ask - DELTA - int((spread * 10000) / 100) * OFFSET) 665 | trade_ask['status'] = 'open' 666 | except: 667 | pass; 668 | 669 | # Ask/Bid両方の指値が約定したとき、1サイクル終了、最初の処理に戻る 670 | try: 671 | if trade_ask['status'] == 'closed' and trade_bid['status'] == 'closed': 672 | pos = 'none' 673 | 674 | logger.info('--------------------------') 675 | logger.info('completed.') 676 | except: 677 | pass; 678 | 679 | -------------------------------------------------------------------------------- /bforder.py: -------------------------------------------------------------------------------- 1 | import pybitflyer 2 | import json 3 | import logging 4 | import time 5 | import datetime 6 | 7 | #注文処理をまとめている 8 | class BFOrder: 9 | def __init__(self): 10 | #config.jsonの読み込み 11 | f = open('config/config.json', 'r', encoding="utf-8") 12 | config = json.load(f) 13 | self.product_code = config["product_code"] 14 | self.key = config["key"] 15 | self.secret = config["secret"] 16 | self.api = pybitflyer.API(self.key, self.secret) 17 | 18 | def limit(self, side, price, size, minute_to_expire=None): 19 | logging.info("Order: Limit. Side : {}".format(side)) 20 | response = {"status":"internalError in bforder.py"} 21 | try: 22 | response = self.api.sendchildorder(product_code=self.product_code, child_order_type="LIMIT", side=side, price=price, size=size, minute_to_expire = minute_to_expire) 23 | except: 24 | pass 25 | logging.debug(response) 26 | retry = 0 27 | while "status" in response: 28 | try: 29 | response = self.api.sendchildorder(product_code=self.product_code, child_order_type="LIMIT", side=side, price=price, size=size, minute_to_expire = minute_to_expire) 30 | except: 31 | pass 32 | retry += 1 33 | if retry > 20: 34 | logging.error(response) 35 | else: 36 | logging.debug(response) 37 | time.sleep(0.5) 38 | return response 39 | 40 | def market(self, side, size, minute_to_expire= None): 41 | logging.info("Order: Market. Side : {}".format(side)) 42 | response = {"status": "internalError in bforder.py"} 43 | try: 44 | response = self.api.sendchildorder(product_code=self.product_code, child_order_type="MARKET", side=side, size=size, minute_to_expire = minute_to_expire) 45 | except: 46 | pass 47 | logging.info(response) 48 | retry = 0 49 | while "status" in response: 50 | try: 51 | response = self.api.sendchildorder(product_code=self.product_code, child_order_type="MARKET", side=side, size=size, minute_to_expire = minute_to_expire) 52 | except: 53 | pass 54 | retry += 1 55 | if retry > 20: 56 | logging.error(response) 57 | else: 58 | logging.debug(response) 59 | time.sleep(0.5) 60 | return response 61 | 62 | def ticker(self): 63 | response = {"status": "internalError in bforder.py"} 64 | try: 65 | response = self.api.ticker(product_code=self.product_code) 66 | except: 67 | pass 68 | logging.debug(response) 69 | retry = 0 70 | while "status" in response: 71 | try: 72 | response = self.api.ticker(product_code=self.product_code) 73 | except: 74 | pass 75 | retry += 1 76 | if retry > 20: 77 | logging.error(response) 78 | else: 79 | logging.debug(response) 80 | time.sleep(0.5) 81 | return response 82 | 83 | def getexecutions(self, order_id): 84 | response = {"status": "internalError in bforder.py"} 85 | #child orderのとき 86 | try: 87 | response = self.api.getexecutions(product_code=self.product_code, child_order_acceptance_id=order_id) 88 | except: 89 | pass 90 | #parent orderのとき 91 | try: 92 | response = self.api.getexecutions(product_code=self.product_code, parent_order_acceptance_id=order_id) 93 | except: 94 | pass 95 | logging.debug(response) 96 | retry = 0 97 | while ("status" in response or not response): 98 | #child orderのとき 99 | try: 100 | response = self.api.getexecutions(product_code=self.product_code, child_order_acceptance_id=order_id) 101 | except: 102 | pass 103 | #parent orderのとき 104 | try: 105 | response = self.api.getexecutions(product_code=self.product_code, parent_order_acceptance_id=order_id) 106 | except: 107 | pass 108 | retry += 1 109 | if retry > 500: 110 | logging.error(response) 111 | else: 112 | logging.debug(response) 113 | time.sleep(0.5) 114 | return response 115 | 116 | def getparentexecutions(self, order_id): 117 | response = {"status": "internalError in bforder.py"} 118 | try: 119 | response = self.api.getexecutions(product_code=self.product_code, parent_order_acceptance_id=order_id) 120 | except: 121 | pass 122 | logging.debug(response) 123 | retry = 0 124 | while ("status" in response or not response): 125 | try: 126 | response = self.api.getexecutions(product_code=self.product_code, parent_order_acceptance_id=order_id) 127 | except: 128 | pass 129 | retry += 1 130 | if retry > 500: 131 | logging.error(response) 132 | else: 133 | logging.debug(response) 134 | time.sleep(0.5) 135 | return response 136 | 137 | def getboardstate(self): 138 | response = {"status": "internalError in bforder.py"} 139 | try: 140 | response = self.api.getboardstate(product_code=self.product_code) 141 | except: 142 | pass 143 | logging.debug(response) 144 | retry = 0 145 | while "status" in response: 146 | try: 147 | response = self.api.getboardstate(product_code=self.product_code) 148 | except: 149 | pass 150 | retry += 1 151 | if retry > 20: 152 | logging.error(response) 153 | else: 154 | logging.debug(response) 155 | time.sleep(0.5) 156 | return response 157 | 158 | def stop(self, side, size, trigger_price, minute_to_expire=None): 159 | logging.info("Order: Stop. Side : {}".format(side)) 160 | response = {"status": "internalError in bforder.py"} 161 | try: 162 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "STOP", "side": side, "size": size,"trigger_price": trigger_price, "minute_to_expire": minute_to_expire}]) 163 | except: 164 | pass 165 | logging.debug(response) 166 | retry = 0 167 | while "status" in response: 168 | try: 169 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "STOP", "side": side, "size": size,"trigger_price": trigger_price, "minute_to_expire": minute_to_expire}]) 170 | except: 171 | pass 172 | retry += 1 173 | if retry > 20: 174 | logging.error(response) 175 | else: 176 | logging.debug(response) 177 | time.sleep(0.5) 178 | return response 179 | 180 | def stop_limit(self, side, size, trigger_price, price, minute_to_expire=None): 181 | logging.info("Side : {}".format(side)) 182 | response = {"status": "internalError in bforder.py"} 183 | try: 184 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "STOP_LIMIT", "side": side, "size": size,"trigger_price": trigger_price, "price": price, "minute_to_expire": minute_to_expire}]) 185 | except: 186 | pass 187 | logging.debug(response) 188 | while "status" in response: 189 | try: 190 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "STOP_LIMIT", "side": side, "size": size,"trigger_price": trigger_price, "price": price, "minute_to_expire": minute_to_expire}]) 191 | except: 192 | pass 193 | logging.debug(response) 194 | return response 195 | 196 | def OCO(self, side, size, trigger_price, price, minute_to_expire=None): 197 | logging.info("Side : {}".format(side)) 198 | response = {"status": "internalError in bforder.py"} 199 | 0 200 | order2 = { 201 | "product_code": "FX_BTC_JPY", 202 | "condition_type": "LIMIT", 203 | "side": side, 204 | "price": price, 205 | "size": size 206 | }; 207 | order3 = { 208 | "product_code": "FX_BTC_JPY", 209 | "condition_type": "STOP", 210 | "side": side, 211 | "trigger_price": trigger_price, 212 | "size": size 213 | }; 214 | try: 215 | response = self.api.sendparentorder(order_method="OCO", parameters=[order2,order3]) 216 | except: 217 | pass 218 | logging.debug(response) 219 | while "status" in response: 220 | try: 221 | #orderの定義 222 | order2 = { 223 | "product_code": "FX_BTC_JPY", 224 | "condition_type": "LIMIT", 225 | "side": side, 226 | "price": price, 227 | "size": size 228 | }; 229 | order3 = { 230 | "product_code": "FX_BTC_JPY", 231 | "condition_type": "STOP", 232 | "side": side, 233 | "trigger_price": trigger_price, 234 | "size": size 235 | }; 236 | response = self.api.sendparentorder(order_method="OCO", parameters=[order2,order3]) 237 | except: 238 | pass 239 | logging.debug(response) 240 | return response 241 | 242 | def IFDOCO(self, side, size, trigger_price, parentprice, price, minute_to_expire = None): 243 | logging.info("Side : {}".format(side)) 244 | response = {"status": "internalError in bforder.py"} 245 | 246 | if side == "BUY": 247 | reverseside = "SELL"; 248 | else: 249 | reverseside = "BUY"; 250 | 251 | order1 = { 252 | "product_code": "FX_BTC_JPY", 253 | "condition_type": "LIMIT", 254 | "side": side, 255 | "price": parentprice, 256 | "size": size 257 | }; 258 | order2 = { 259 | "product_code": "FX_BTC_JPY", 260 | "condition_type": "LIMIT", 261 | "side": reverseside, 262 | "price": price, 263 | "size": size 264 | }; 265 | order3 = { 266 | "product_code": "FX_BTC_JPY", 267 | "condition_type": "STOP", 268 | "side": reverseside, 269 | "trigger_price": trigger_price, 270 | "size": size 271 | }; 272 | try: 273 | response = self.api.sendparentorder(order_method="IFDOCO", minute_to_expire = minute_to_expire, parameters=[order1,order2,order3]) 274 | except: 275 | pass 276 | logging.debug(response) 277 | while "status" in response: 278 | try: 279 | if side == "BUY": 280 | reverseside = "SELL"; 281 | else: 282 | reverseside = "BUY"; 283 | 284 | #orderの定義 285 | order1 = { 286 | "product_code": "FX_BTC_JPY", 287 | "condition_type": "LIMIT", 288 | "side": side, 289 | "price": parentprice, 290 | "size": size 291 | }; 292 | order2 = { 293 | "product_code": "FX_BTC_JPY", 294 | "condition_type": "LIMIT", 295 | "side": reverseside, 296 | "price": price, 297 | "size": size 298 | }; 299 | order3 = { 300 | "product_code": "FX_BTC_JPY", 301 | "condition_type": "STOP", 302 | "side": reverseside, 303 | "trigger_price": trigger_price, 304 | "size": size 305 | }; 306 | response = self.api.sendparentorder(order_method="IFDOCO", minute_to_expire = minute_to_expire, parameters=[order1,order2,order3]) 307 | except: 308 | pass 309 | logging.debug(response) 310 | return response 311 | 312 | def trailing(self, side, size, offset, minute_to_expire=None): 313 | logging.info("Side : {}".format(side)) 314 | response = {"status": "internalError in bforder.py"} 315 | try: 316 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "TRAIL", "side": side, "size": size, "offset": offset, "minute_to_expire": minute_to_expire}]) 317 | except: 318 | pass 319 | logging.debug(response) 320 | while "status" in response: 321 | try: 322 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "TRAIL", "side": side, "size": size, "offset": offset, "minute_to_expire": minute_to_expire}]) 323 | except: 324 | pass 325 | logging.debug(response) 326 | return response 327 | 328 | def getcollateral(self): 329 | response = {"status": "internalError in bforder.py"} 330 | try: 331 | response = self.api.getcollateral() 332 | except: 333 | pass 334 | logging.debug(response) 335 | while "status" in response: 336 | try: 337 | response = self.api.getcollateral() 338 | except: 339 | pass 340 | logging.info(response) 341 | time.sleep(0.5) 342 | return response 343 | 344 | def getmypos(self): 345 | side = "" 346 | size = 0 347 | response = {"status": "internalError in bforder.py"} 348 | index =0 349 | while(index < 10): 350 | try: 351 | poss = self.api.getpositions(product_code = self.product_code) 352 | 353 | #もしポジションがあれば合計値を取得 354 | if len(poss) != 0: 355 | for pos in poss: 356 | side = pos["side"] 357 | size += pos["size"] 358 | break; 359 | except: 360 | pass 361 | index += 1 362 | return side,size 363 | 364 | def getmyparentorder(self): 365 | side = "" 366 | ordersize = 0 367 | childordersize = 0 368 | response = {"status": "internalError in bforder.py"} 369 | try: 370 | orders = self.api.getparentorders(product_code = "FX_BTC_JPY") 371 | #もしポジションがあれば合計値を取得 372 | if len(orders) != 0: 373 | for order in orders: 374 | side = order["side"] 375 | id = order["parent_order_id"] 376 | ordersize += order["outstanding_size"] 377 | childordersize += self.api.getchildorders(product_code = "FX_BTC_JPY", parent_order_id = id) 378 | if order == 10: 379 | break; #10回以上前を見ても仕方がないのでやめる 380 | except: 381 | pass 382 | return side,ordersize, childordersize 383 | 384 | #すべての注文をキャンセル 385 | def cancelAllOrder(self): 386 | index =0 387 | while(index < 50): 388 | try: 389 | self.api.cancelallchildorders(product_code = "FX_BTC_JPY") 390 | break; 391 | except: 392 | pass 393 | index += 1 394 | 395 | #すべての注文をキャンセル 396 | def cancelAllOrderFutures(self): 397 | index =0 398 | while(index < 50): 399 | try: 400 | self.api.cancelallchildorders(product_code = "BTCJPY28SEP2018") 401 | break; 402 | except: 403 | pass 404 | index += 1 -------------------------------------------------------------------------------- /config/config_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "product_code" : "FX_BTC_JPY", 3 | "key" : "", 4 | "secret" : "", 5 | "line_notify_token" : "", 6 | "healthCheck" : true, 7 | "lotSize" : 0.1, 8 | "entryTerm" : 8, 9 | "closeTerm" : 8, 10 | "rangePercent" : 1.6, 11 | "rangePercentTerm" : 5, 12 | "rangeTerm" : null, 13 | "rangeTh" : null, 14 | "waitTerm" : 0, 15 | "waitTh" : 10000, 16 | "candleTerm" : "5T", 17 | "cost" : 0, 18 | "fileName" : "chart.csv", 19 | "showFigure" : true, 20 | "sendFigure" : false, 21 | "showTradeDetail" : true, 22 | "core" : null, 23 | "hyperopt" : 1000, 24 | "mlMode" : "PL", 25 | "useBlackList" : true, 26 | "keepPosition" : false, 27 | "sfdLimit" : true 28 | } 29 | -------------------------------------------------------------------------------- /cryptowatch.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import logging 4 | 5 | class CryptoWatch: 6 | def getCandlestick(self, number, period): 7 | """ 8 | number:ローソク足の数.period:ローソク足の期間(文字列で秒数を指定,Ex:1分足なら"60").cryptowatchはときどきおかしなデータ(price=0)が含まれるのでそれを除く. 9 | """ 10 | #ローソク足の時間を指定 11 | periods = [period] 12 | #クエリパラメータを指定 13 | query = {"periods":','.join(periods)} 14 | #ローソク足取得 15 | res = json.loads(requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc", params=query).text) 16 | # 残りCPU時間とコストの表示 17 | logging.info('cost:%s remaining:%s', res["allowance"]["cost"], res["allowance"]["remaining"]) 18 | # resultのなかの構造:[ 取引時間(クローズ) , 初値 , 高値 , 安値 , 終値 , 出来高 ] 19 | # ローソク足のデータを入れる配列. 20 | data = [] 21 | for i in periods: 22 | row = res["result"][i] 23 | length = len(row) 24 | for column in row[:length - (number + 1):-1]: 25 | # dataへローソク足データを追加. 26 | if column[4] != 0: 27 | column = column[0:6] 28 | data.append(column) 29 | return data[::-1] 30 | def getSpecifiedCandlestick(self, number, period): 31 | """ 32 | number:ローソク足の数.period:ローソク足の期間(文字列で秒数を指定,Ex:1分足なら"60").cryptowatchはときどきおかしなデータ(price=0)が含まれるのでそれを除く 33 | """ 34 | # ローソク足の時間を指定 35 | periods = [period] 36 | # クエリパラメータを指定 37 | query = {"periods": ','.join(periods), "after": 1} 38 | # ローソク足取得 39 | try: 40 | res = json.loads(requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc", params=query).text) 41 | # 残りCPU時間とコストの表示 42 | logging.info('cost:%s remaining:%s', res["allowance"]["cost"], res["allowance"]["remaining"]) 43 | res = res["result"] 44 | except: 45 | logging.error(res) 46 | # ローソク足のデータを入れる配列. 47 | data = [] 48 | for i in periods: 49 | row = res[i] 50 | length = len(row) 51 | for column in row[:length - (number + 1):-1]: 52 | # dataへローソク足データを追加. 53 | if column[4] != 0: 54 | column = column[0:6] 55 | data.append(column) 56 | return data[::-1] 57 | -------------------------------------------------------------------------------- /src/bforder.py: -------------------------------------------------------------------------------- 1 | import pybitflyer 2 | import json 3 | import logging 4 | import time 5 | import datetime 6 | 7 | #注文処理をまとめている 8 | class BFOrder: 9 | def __init__(self): 10 | #config.jsonの読み込み 11 | f = open('config/config.json', 'r', encoding="utf-8") 12 | config = json.load(f) 13 | self.product_code = config["product_code"] 14 | self.key = config["key"] 15 | self.secret = config["secret"] 16 | self.api = pybitflyer.API(self.key, self.secret) 17 | 18 | def limit(self, side, price, size, minute_to_expire=None): 19 | logging.info("Order: Limit. Side : {}".format(side)) 20 | response = {"status":"internalError in bforder.py"} 21 | try: 22 | response = self.api.sendchildorder(product_code=self.product_code, child_order_type="LIMIT", side=side, price=price, size=size, minute_to_expire = minute_to_expire) 23 | except: 24 | pass 25 | logging.debug(response) 26 | retry = 0 27 | while "status" in response: 28 | try: 29 | response = self.api.sendchildorder(product_code=self.product_code, child_order_type="LIMIT", side=side, price=price, size=size, minute_to_expire = minute_to_expire) 30 | except: 31 | pass 32 | retry += 1 33 | if retry > 20: 34 | logging.error(response) 35 | else: 36 | logging.debug(response) 37 | time.sleep(0.5) 38 | return response 39 | 40 | def market(self, side, size, minute_to_expire= None): 41 | logging.info("Order: Market. Side : {}".format(side)) 42 | response = {"status": "internalError in bforder.py"} 43 | try: 44 | response = self.api.sendchildorder(product_code=self.product_code, child_order_type="MARKET", side=side, size=size, minute_to_expire = minute_to_expire) 45 | except: 46 | pass 47 | logging.info(response) 48 | retry = 0 49 | while "status" in response: 50 | try: 51 | response = self.api.sendchildorder(product_code=self.product_code, child_order_type="MARKET", side=side, size=size, minute_to_expire = minute_to_expire) 52 | except: 53 | pass 54 | retry += 1 55 | if retry > 20: 56 | logging.error(response) 57 | else: 58 | logging.debug(response) 59 | time.sleep(0.5) 60 | return response 61 | 62 | def ticker(self): 63 | response = {"status": "internalError in bforder.py"} 64 | try: 65 | response = self.api.ticker(product_code=self.product_code) 66 | except: 67 | pass 68 | logging.debug(response) 69 | retry = 0 70 | while "status" in response: 71 | try: 72 | response = self.api.ticker(product_code=self.product_code) 73 | except: 74 | pass 75 | retry += 1 76 | if retry > 20: 77 | logging.error(response) 78 | else: 79 | logging.debug(response) 80 | time.sleep(0.5) 81 | return response 82 | 83 | def getexecutions(self, order_id): 84 | response = {"status": "internalError in bforder.py"} 85 | #child orderのとき 86 | try: 87 | response = self.api.getexecutions(product_code=self.product_code, child_order_acceptance_id=order_id) 88 | except: 89 | pass 90 | #parent orderのとき 91 | try: 92 | response = self.api.getexecutions(product_code=self.product_code, parent_order_acceptance_id=order_id) 93 | except: 94 | pass 95 | logging.debug(response) 96 | retry = 0 97 | while ("status" in response or not response): 98 | #child orderのとき 99 | try: 100 | response = self.api.getexecutions(product_code=self.product_code, child_order_acceptance_id=order_id) 101 | except: 102 | pass 103 | #parent orderのとき 104 | try: 105 | response = self.api.getexecutions(product_code=self.product_code, parent_order_acceptance_id=order_id) 106 | except: 107 | pass 108 | retry += 1 109 | if retry > 500: 110 | logging.error(response) 111 | else: 112 | logging.debug(response) 113 | time.sleep(0.5) 114 | return response 115 | 116 | def getparentexecutions(self, order_id): 117 | response = {"status": "internalError in bforder.py"} 118 | try: 119 | response = self.api.getexecutions(product_code=self.product_code, parent_order_acceptance_id=order_id) 120 | except: 121 | pass 122 | logging.debug(response) 123 | retry = 0 124 | while ("status" in response or not response): 125 | try: 126 | response = self.api.getexecutions(product_code=self.product_code, parent_order_acceptance_id=order_id) 127 | except: 128 | pass 129 | retry += 1 130 | if retry > 500: 131 | logging.error(response) 132 | else: 133 | logging.debug(response) 134 | time.sleep(0.5) 135 | return response 136 | 137 | def getboardstate(self): 138 | response = {"status": "internalError in bforder.py"} 139 | try: 140 | response = self.api.getboardstate(product_code=self.product_code) 141 | except: 142 | pass 143 | logging.debug(response) 144 | retry = 0 145 | while "status" in response: 146 | try: 147 | response = self.api.getboardstate(product_code=self.product_code) 148 | except: 149 | pass 150 | retry += 1 151 | if retry > 20: 152 | logging.error(response) 153 | else: 154 | logging.debug(response) 155 | time.sleep(0.5) 156 | return response 157 | 158 | def stop(self, side, size, trigger_price, minute_to_expire=None): 159 | logging.info("Order: Stop. Side : {}".format(side)) 160 | response = {"status": "internalError in bforder.py"} 161 | try: 162 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "STOP", "side": side, "size": size,"trigger_price": trigger_price, "minute_to_expire": minute_to_expire}]) 163 | except: 164 | pass 165 | logging.debug(response) 166 | retry = 0 167 | while "status" in response: 168 | try: 169 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "STOP", "side": side, "size": size,"trigger_price": trigger_price, "minute_to_expire": minute_to_expire}]) 170 | except: 171 | pass 172 | retry += 1 173 | if retry > 20: 174 | logging.error(response) 175 | else: 176 | logging.debug(response) 177 | time.sleep(0.5) 178 | return response 179 | 180 | def stop_limit(self, side, size, trigger_price, price, minute_to_expire=None): 181 | logging.info("Side : {}".format(side)) 182 | response = {"status": "internalError in bforder.py"} 183 | try: 184 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "STOP_LIMIT", "side": side, "size": size,"trigger_price": trigger_price, "price": price, "minute_to_expire": minute_to_expire}]) 185 | except: 186 | pass 187 | logging.debug(response) 188 | while "status" in response: 189 | try: 190 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "STOP_LIMIT", "side": side, "size": size,"trigger_price": trigger_price, "price": price, "minute_to_expire": minute_to_expire}]) 191 | except: 192 | pass 193 | logging.debug(response) 194 | return response 195 | 196 | def OCO(self, side, size, trigger_price, price, minute_to_expire=None): 197 | logging.info("Side : {}".format(side)) 198 | response = {"status": "internalError in bforder.py"} 199 | 0 200 | order2 = { 201 | "product_code": "FX_BTC_JPY", 202 | "condition_type": "LIMIT", 203 | "side": side, 204 | "price": price, 205 | "size": size 206 | }; 207 | order3 = { 208 | "product_code": "FX_BTC_JPY", 209 | "condition_type": "STOP", 210 | "side": side, 211 | "trigger_price": trigger_price, 212 | "size": size 213 | }; 214 | try: 215 | response = self.api.sendparentorder(order_method="OCO", parameters=[order2,order3]) 216 | except: 217 | pass 218 | logging.debug(response) 219 | while "status" in response: 220 | try: 221 | #orderの定義 222 | order2 = { 223 | "product_code": "FX_BTC_JPY", 224 | "condition_type": "LIMIT", 225 | "side": side, 226 | "price": price, 227 | "size": size 228 | }; 229 | order3 = { 230 | "product_code": "FX_BTC_JPY", 231 | "condition_type": "STOP", 232 | "side": side, 233 | "trigger_price": trigger_price, 234 | "size": size 235 | }; 236 | response = self.api.sendparentorder(order_method="OCO", parameters=[order2,order3]) 237 | except: 238 | pass 239 | logging.debug(response) 240 | return response 241 | 242 | def IFDOCO(self, side, size, trigger_price, parentprice, price, minute_to_expire = None): 243 | logging.info("Side : {}".format(side)) 244 | response = {"status": "internalError in bforder.py"} 245 | 246 | if side == "BUY": 247 | reverseside = "SELL"; 248 | else: 249 | reverseside = "BUY"; 250 | 251 | order1 = { 252 | "product_code": "FX_BTC_JPY", 253 | "condition_type": "LIMIT", 254 | "side": side, 255 | "price": parentprice, 256 | "size": size 257 | }; 258 | order2 = { 259 | "product_code": "FX_BTC_JPY", 260 | "condition_type": "LIMIT", 261 | "side": reverseside, 262 | "price": price, 263 | "size": size 264 | }; 265 | order3 = { 266 | "product_code": "FX_BTC_JPY", 267 | "condition_type": "STOP", 268 | "side": reverseside, 269 | "trigger_price": trigger_price, 270 | "size": size 271 | }; 272 | try: 273 | response = self.api.sendparentorder(order_method="IFDOCO", minute_to_expire = minute_to_expire, parameters=[order1,order2,order3]) 274 | except: 275 | pass 276 | logging.debug(response) 277 | while "status" in response: 278 | try: 279 | if side == "BUY": 280 | reverseside = "SELL"; 281 | else: 282 | reverseside = "BUY"; 283 | 284 | #orderの定義 285 | order1 = { 286 | "product_code": "FX_BTC_JPY", 287 | "condition_type": "LIMIT", 288 | "side": side, 289 | "price": parentprice, 290 | "size": size 291 | }; 292 | order2 = { 293 | "product_code": "FX_BTC_JPY", 294 | "condition_type": "LIMIT", 295 | "side": reverseside, 296 | "price": price, 297 | "size": size 298 | }; 299 | order3 = { 300 | "product_code": "FX_BTC_JPY", 301 | "condition_type": "STOP", 302 | "side": reverseside, 303 | "trigger_price": trigger_price, 304 | "size": size 305 | }; 306 | response = self.api.sendparentorder(order_method="IFDOCO", minute_to_expire = minute_to_expire, parameters=[order1,order2,order3]) 307 | except: 308 | pass 309 | logging.debug(response) 310 | return response 311 | 312 | def trailing(self, side, size, offset, minute_to_expire=None): 313 | logging.info("Side : {}".format(side)) 314 | response = {"status": "internalError in bforder.py"} 315 | try: 316 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "TRAIL", "side": side, "size": size, "offset": offset, "minute_to_expire": minute_to_expire}]) 317 | except: 318 | pass 319 | logging.debug(response) 320 | while "status" in response: 321 | try: 322 | response = self.api.sendparentorder(order_method="SIMPLE", parameters=[{"product_code": self.product_code, "condition_type": "TRAIL", "side": side, "size": size, "offset": offset, "minute_to_expire": minute_to_expire}]) 323 | except: 324 | pass 325 | logging.debug(response) 326 | return response 327 | 328 | def getcollateral(self): 329 | response = {"status": "internalError in bforder.py"} 330 | try: 331 | response = self.api.getcollateral() 332 | except: 333 | pass 334 | logging.debug(response) 335 | while "status" in response: 336 | try: 337 | response = self.api.getcollateral() 338 | except: 339 | pass 340 | logging.info(response) 341 | time.sleep(0.5) 342 | return response 343 | 344 | def getmypos(self): 345 | side = "" 346 | size = 0 347 | response = {"status": "internalError in bforder.py"} 348 | index =0 349 | while(index < 50): 350 | try: 351 | poss = self.api.getpositions(product_code = "FX_BTC_JPY") 352 | 353 | #もしポジションがあれば合計値を取得 354 | if len(poss) != 0: 355 | for pos in poss: 356 | side = pos["side"] 357 | size += pos["size"] 358 | break; 359 | except: 360 | pass 361 | time.sleep(0.5) 362 | index += 1 363 | return side,size 364 | 365 | def getmyparentorder(self): 366 | side = "" 367 | ordersize = 0 368 | childordersize = 0 369 | response = {"status": "internalError in bforder.py"} 370 | try: 371 | orders = self.api.getparentorders(product_code = "FX_BTC_JPY") 372 | #もしポジションがあれば合計値を取得 373 | if len(orders) != 0: 374 | for order in orders: 375 | side = order["side"] 376 | id = order["parent_order_id"] 377 | ordersize += order["outstanding_size"] 378 | childordersize += self.api.getchildorders(product_code = "FX_BTC_JPY", parent_order_id = id) 379 | if order == 10: 380 | break; #10回以上前を見ても仕方がないのでやめる 381 | except: 382 | pass 383 | return side,ordersize, childordersize 384 | 385 | #すべての注文をキャンセル 386 | def cancelAllOrder(self): 387 | index =0 388 | while(index < 50): 389 | try: 390 | self.api.cancelallchildorders(product_code = "FX_BTC_JPY") 391 | break; 392 | except: 393 | pass 394 | index += 1 --------------------------------------------------------------------------------