def data_stream(*args): ''' First thread to send subscription to the exchange ''' params = [str.lower(ins) + str(s) for ins in insIds for s in stream] print_(params, fileout) ws.send(json.dumps({"method": "SUBSCRIBE", "params": params, "id": 1})) t1_idx = 0 while len(endFlag) == 0: if len(SymKlns[insIds[0]]) % 5 == 0 and len( SymKlns[insIds[0]]) > t1_idx and len( SymKlns[insIds[0]]) < models[insIds[0]].pdObserve: client.keepalive_stream() t1_idx = len(SymKlns[insIds[0]])
def strategy(*args): ''' Second thread to generate signals upon the message from the exchange ''' t2_idx = {} for symbol in insIds: t2_idx[symbol] = 0 while len(SymKlns[insIds[0]]) < models[insIds[0]].pdObserve: for symbol in insIds: sym_ = SymKlns[symbol].copy() if len(sym_) > t2_idx[symbol]: if models[symbol].modelType == 'bollinger': data_ob = pd.DataFrame(sym_) model_sig = models[symbol].get_last_signal( dataObserve=data_ob) else: model_sig = None if model_sig is not None: ready = True if ready: side, positionSide, startTime = model_sig[ 'side'], model_sig[ 'positionSide'], model_sig[ '_t'] + 60 * 1000 expTime, price = startTime + 5 * 60 * 1000, round( model_sig['_p'], PRICEPRE[symbol]) # stopLoss = model_sig['atr'] takeProfit = model_sig['atr'] new_sig = Signal(symbol=symbol, side=side, size=models[symbol].orderSize, orderType='LIMIT', positionSide=positionSide, price=price, startTime=startTime, expTime=expTime, \ stopLoss=stopLoss, takeProfit=takeProfit, timeLimit=models[symbol].pdEstimate*60, timeInForce='GTC') if in_possition_( Signals[symbol], side='BOTH') or position_count( insIds, Signals, side=side ) >= portfolio.equityDist[side]: new_sig.set_expired() else: for sig in Signals[symbol]: if sig.is_waiting(): sig.set_expired() print_( '\n\tSet WAITING signal EXPIRED: \n\t' + str(sig), fileout) Signals[symbol].append(new_sig) print_('\n\tFOUND ' + str(new_sig), fileout) t2_idx[symbol] = len(sym_)
def on_message(ws, message): ''' Control the message received from ''' mess = json.loads(message) if mess['e'] == 'kline': kln = mess['k'] if kln['x'] is True: symbol = kln['s'].upper() new_kln = { '_t': int(kln['t']), '_o': float(kln['o']), '_h': float(kln['h']), '_l': float(kln['l']), '_c': float(kln['c']), '_v': float(kln['q']) } SymKlns[symbol].append(new_kln) print_( '%d. %s\t' % (len(SymKlns[symbol]), symbol) + timestr(new_kln['_t']) + '\t' + \ ''.join(['{:>3}:{:<10}'.format(k, v) for k,v in iter(new_kln.items()) if not k=='_t']), fileout)
def book_manager(*args): ''' Third thread to excecute/cancel/track the signals generated in strategy() ''' while len(endFlag) == 0 and len( SymKlns[insIds[0]]) < models[insIds[0]].pdObserve: try: time.sleep(1) for symbol in insIds: in_position = False last_signal = None for sig in Signals[symbol]: model = models[symbol] sv_time = client.timestamp() if sig.is_waiting(): ### Check for EXPIRED order here ### if sv_time > sig.expTime: sig.set_expired() print_( '\n\tSet WAITING signal EXPIRED: \n\t' + str(sig), fileout) else: last_signal = sig elif sig.is_ordered(): ### Set ACTIVE order here ### in_position = True order_update = client.query_order( symbol, sig.orderId) if order_update['status'] == 'FILLED': sig.set_active( excTime=order_update['updateTime'], excPrice=order_update['avgPrice'], excQty=order_update['executedQty']) sig.path_update(lastTime=sig.excTime, lastPrice=sig.excPrice) print_( '\n\tSet BOOKED order ACTIVE: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) ### PROBLEM 3 Insert your code to handle EXPIRED and PARTIALLY_FILLED order here ### elif sv_time > order_update[ 'updateTime'] + 60 * 1000: if order_update[ 'status'] == 'PARTIALLY_FILLED': client.cancel_order(symbol, sig.orderId) sig.set_active( excTime=order_update['updateTime'], excPrice=order_update['avgPrice'], excQty=order_update['executedQty']) sig.path_update(lastTime=sig.excTime, lastPrice=sig.excPrice) print_( '\n\tSet BOOKED order ACTIVE: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) elif sv_time > order_update[ 'updateTime'] + 2 * 60 * 1000: client.cancel_order(symbol, sig.orderId) sig.set_expired() order_update = client.query_order( symbol, sig.orderId) print_( '\n\tSet BOOKED order EXPIRED: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) elif sig.is_active(): ### Control ACTIVE position here ### in_position = True recent_trades = model.marketData.recent_trades( limit=5) for trade in recent_trades: if int(trade['time'] ) > sig.pricePath[-1]['timestamp']: sig.path_update(lastTime=trade['time'], lastPrice=trade['price']) exit_sign, pos = sig.exit_triggers() if exit_sign: print_( '\n\tFound ' + str(exit_sign) + '{}\n'.format(round(pos, 4)), fileout) cnt_order = sig.counter_order() order = client.new_order( symbol=symbol, side=cnt_order['side'], orderType='MARKET', quantity=cnt_order['amt'], positionSide=sig.positionSide ) #, timeInForce=cnt_order['TIF'], price=lim) sig.set_cnt_ordered( cntorderId=order['orderId'], cntType='MARKET', cntTime=order['updateTime']) print_( '\tPlaced COUNTER order: \n\t' + str(sig) + '\n\t' + orderstr(order), fileout) elif sig.is_cnt_ordered(): ### Set CLOSED position here ### in_position = True order_update = client.query_order( symbol, sig.cntorderId) if order_update['status'] == 'FILLED': sig.set_closed( clsTime=order_update['updateTime'], clsPrice=order_update['avgPrice']) print_( '\n\tClosed order: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) if (not in_position) and (last_signal is not None): ### Check for ENTRY and place NEW order here ### sig = last_signal if sig.orderType == 'MARKET': order = client.new_order( symbol=symbol, side=sig.side, orderType=sig.orderType, quantity=sig.get_quantity(), positionSide=sig.positionSide) sig.set_ordered(orderId=order['orderId'], orderTime=order['updateTime'], limitPrice=None) print_( '\n\tPlaced NEW order: \n\t' + str(sig) + '\n\t' + orderstr(order), fileout) elif sig.orderType == 'LIMIT': bids, asks, lim = get_possible_price( model.marketData, sig.side) if lim is not None and (lim < sig.price * 1.01 and lim > sig.price * 0.99): order = client.new_order( symbol=symbol, side=sig.side, orderType=sig.orderType, quantity=sig.get_quantity(), positionSide=sig.positionSide, timeInForce='GTC', price=lim) sig.set_ordered(orderId=order['orderId'], orderTime=order['updateTime'], limitPrice=lim) print_( '\n\tPlaced NEW order: \n\t' + str(sig) + '\n\t' + orderstr(order), fileout) except Exception: print_('\n\tClose on book_manager()', fileout) ws.close() ws.close()
def on_error(ws, error): ''' Do something when websocket has an error ''' print_(error, fileout) return
def wss_run(*args): ### threading functions def data_stream(*args): ''' First thread to send subscription to the exchange ''' params = [str.lower(ins) + str(s) for ins in insIds for s in stream] print_(params, fileout) ws.send(json.dumps({"method": "SUBSCRIBE", "params": params, "id": 1})) t1_idx = 0 while len(endFlag) == 0: if len(SymKlns[insIds[0]]) % 5 == 0 and len( SymKlns[insIds[0]]) > t1_idx and len( SymKlns[insIds[0]]) < models[insIds[0]].pdObserve: client.keepalive_stream() t1_idx = len(SymKlns[insIds[0]]) def strategy(*args): ''' Second thread to generate signals upon the message from the exchange ''' t2_idx = {} for symbol in insIds: t2_idx[symbol] = 0 while len(endFlag) == 0 and len( SymKlns[insIds[0]]) < models[insIds[0]].pdObserve: try: for symbol in insIds: sym_ = SymKlns[symbol].copy() if len(sym_) > t2_idx[symbol]: if models[symbol].modelType == 'bollinger': data_ob = pd.DataFrame(sym_) model_sig = models[symbol].get_last_signal( dataObserve=data_ob) else: model_sig = None if model_sig is not None: ready = True if ready: side, positionSide, startTime = model_sig[ 'side'], model_sig[ 'positionSide'], model_sig[ '_t'] + 60 * 1000 expTime, price = startTime + 5 * 60 * 1000, round( model_sig['_p'], PRICEPRE[symbol]) # stopLoss = model_sig['atr'] takeProfit = model_sig['atr'] new_sig = Signal(symbol=symbol, side=side, size=models[symbol].orderSize, orderType='LIMIT', positionSide=positionSide, price=price, startTime=startTime, expTime=expTime, \ stopLoss=stopLoss, takeProfit=takeProfit, timeLimit=models[symbol].pdEstimate*60, timeInForce='GTC') if in_possition_( Signals[symbol], side='BOTH') or position_count( insIds, Signals, side=side ) >= portfolio.equityDist[side]: new_sig.set_expired() else: for sig in Signals[symbol]: if sig.is_waiting(): sig.set_expired() print_( '\n\tSet WAITING signal EXPIRED: \n\t' + str(sig), fileout) Signals[symbol].append(new_sig) print_('\n\tFOUND ' + str(new_sig), fileout) t2_idx[symbol] = len(sym_) except Exception: print_('\n\tClose on strategy()', fileout) ws.close() def book_manager(*args): ''' Third thread to excecute/cancel/track the signals generated in strategy() ''' while len(endFlag) == 0 and len( SymKlns[insIds[0]]) < models[insIds[0]].pdObserve: try: time.sleep(1) for symbol in insIds: in_position = False last_signal = None for sig in Signals[symbol]: model = models[symbol] sv_time = client.timestamp() if sig.is_waiting(): ### Check for EXPIRED order here ### if sv_time > sig.expTime: sig.set_expired() print_( '\n\tSet WAITING signal EXPIRED: \n\t' + str(sig), fileout) else: last_signal = sig elif sig.is_ordered(): ### Set ACTIVE order here ### in_position = True order_update = client.query_order( symbol, sig.orderId) if order_update['status'] == 'FILLED': sig.set_active( excTime=order_update['updateTime'], excPrice=order_update['avgPrice'], excQty=order_update['executedQty']) sig.path_update(lastTime=sig.excTime, lastPrice=sig.excPrice) print_( '\n\tSet BOOKED order ACTIVE: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) ### PROBLEM 3 Insert your code to handle EXPIRED and PARTIALLY_FILLED order here ### elif sv_time > order_update[ 'updateTime'] + 60 * 1000: if order_update[ 'status'] == 'PARTIALLY_FILLED': client.cancel_order(symbol, sig.orderId) sig.set_active( excTime=order_update['updateTime'], excPrice=order_update['avgPrice'], excQty=order_update['executedQty']) sig.path_update(lastTime=sig.excTime, lastPrice=sig.excPrice) print_( '\n\tSet BOOKED order ACTIVE: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) elif sv_time > order_update[ 'updateTime'] + 2 * 60 * 1000: client.cancel_order(symbol, sig.orderId) sig.set_expired() order_update = client.query_order( symbol, sig.orderId) print_( '\n\tSet BOOKED order EXPIRED: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) elif sig.is_active(): ### Control ACTIVE position here ### in_position = True recent_trades = model.marketData.recent_trades( limit=5) for trade in recent_trades: if int(trade['time'] ) > sig.pricePath[-1]['timestamp']: sig.path_update(lastTime=trade['time'], lastPrice=trade['price']) exit_sign, pos = sig.exit_triggers() if exit_sign: print_( '\n\tFound ' + str(exit_sign) + '{}\n'.format(round(pos, 4)), fileout) cnt_order = sig.counter_order() order = client.new_order( symbol=symbol, side=cnt_order['side'], orderType='MARKET', quantity=cnt_order['amt'], positionSide=sig.positionSide ) #, timeInForce=cnt_order['TIF'], price=lim) sig.set_cnt_ordered( cntorderId=order['orderId'], cntType='MARKET', cntTime=order['updateTime']) print_( '\tPlaced COUNTER order: \n\t' + str(sig) + '\n\t' + orderstr(order), fileout) elif sig.is_cnt_ordered(): ### Set CLOSED position here ### in_position = True order_update = client.query_order( symbol, sig.cntorderId) if order_update['status'] == 'FILLED': sig.set_closed( clsTime=order_update['updateTime'], clsPrice=order_update['avgPrice']) print_( '\n\tClosed order: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) if (not in_position) and (last_signal is not None): ### Check for ENTRY and place NEW order here ### sig = last_signal if sig.orderType == 'MARKET': order = client.new_order( symbol=symbol, side=sig.side, orderType=sig.orderType, quantity=sig.get_quantity(), positionSide=sig.positionSide) sig.set_ordered(orderId=order['orderId'], orderTime=order['updateTime'], limitPrice=None) print_( '\n\tPlaced NEW order: \n\t' + str(sig) + '\n\t' + orderstr(order), fileout) elif sig.orderType == 'LIMIT': bids, asks, lim = get_possible_price( model.marketData, sig.side) if lim is not None and (lim < sig.price * 1.01 and lim > sig.price * 0.99): order = client.new_order( symbol=symbol, side=sig.side, orderType=sig.orderType, quantity=sig.get_quantity(), positionSide=sig.positionSide, timeInForce='GTC', price=lim) sig.set_ordered(orderId=order['orderId'], orderTime=order['updateTime'], limitPrice=lim) print_( '\n\tPlaced NEW order: \n\t' + str(sig) + '\n\t' + orderstr(order), fileout) except Exception: print_('\n\tClose on book_manager()', fileout) ws.close() ws.close() ### websocket functions def on_message(ws, message): ''' Control the message received from ''' mess = json.loads(message) if mess['e'] == 'kline': kln = mess['k'] if kln['x'] is True: symbol = kln['s'].upper() new_kln = { '_t': int(kln['t']), '_o': float(kln['o']), '_h': float(kln['h']), '_l': float(kln['l']), '_c': float(kln['c']), '_v': float(kln['q']) } SymKlns[symbol].append(new_kln) print_( '%d. %s\t' % (len(SymKlns[symbol]), symbol) + timestr(new_kln['_t']) + '\t' + \ ''.join(['{:>3}:{:<10}'.format(k, v) for k,v in iter(new_kln.items()) if not k=='_t']), fileout) def on_error(ws, error): ''' Do something when websocket has an error ''' print_(error, fileout) return def on_close(ws): ''' Do something when websocket closes ''' endFlag.append(1) for t in [t1, t2, t3]: t.join() return def on_open(ws, *args): ''' Start multi-threading functions ''' t1.start() t2.start() t3.start() return def position_count(insIds, signal_list, side='BOTH'): ''' Returns number of open positions ''' count = 0 for s in insIds: for sig in signal_list[s]: if sig.side == side or side == 'BOTH': if sig.is_ordered() or sig.is_active( ) or sig.is_cnt_ordered(): count += 1 return count def in_possition_(signal_list, side='BOTH'): ''' Check if there is any open positions ''' in_pos = False for sig in signal_list: if sig.side == side or side == 'BOTH': if sig.is_ordered() or sig.is_active() or sig.is_cnt_ordered(): in_pos = True break return in_pos def get_possible_price(mk_data, side): ''' Return a safe limit price available on the market ''' mk_depth = mk_data.order_book(limit=5) bids = list(float(x[0]) for x in mk_depth['bids']) asks = list(float(x[0]) for x in mk_depth['asks']) try: lim = (side == 'BUY') * (bids[0] + bids[1]) / 2 + ( side == 'SELL') * (asks[0] + asks[1]) / 2 lim = round(lim, PRICEPRE[mk_data.symbol.upper()]) except: lim = None return bids, asks, lim start_time = time.time() portfolio, client, testnet, stream, models, fileout = args insIds = portfolio.tradeIns SymKlns = {} Signals = {} for symbol in insIds: SymKlns[symbol] = [] Signals[symbol] = [] endFlag = [] t1 = threading.Thread(target=data_stream) t2 = threading.Thread(target=strategy) t3 = threading.Thread(target=book_manager) listen_key = client.get_listen_key() ws = websocket.WebSocketApp(f'{client.wss_way}{listen_key}', on_message=on_message, on_error=on_error, on_close=on_close) ws.on_open = on_open ws.run_forever() client.close_stream() print_( '\n' + barstr('Close Opening Positions', length=100, space_size=5) + '\n', fileout) ### PROBLEM 2 Insert your code to close all positions here ### ''' Place a COUNTER order at 0.1% take-profit level from the entry price ''' in_position = False for symbol in insIds: if in_possition_(Signals[symbol]): in_position = True while in_position: for symbol in insIds: model = models[symbol] for sig in Signals[symbol]: if sig.is_waiting(): sig.set_expired() print_('\n\tSet WAITING signal EXPIRED: \n\t' + str(sig), fileout) elif sig.is_ordered(): client.cancel_order(symbol, sig.orderId) sig.set_expired() order_update = client.query_order(symbol, sig.orderId) print_( '\n\tSet BOOKED order EXPIRED: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) elif sig.is_active(): cnt_order = sig.counter_order() lim = round( sig.excPrice * (1 + SIDE[sig.side] * 0.1 / 100), PRICEPRE[symbol]) order = client.new_order(symbol=symbol, side=cnt_order['side'], orderType='LIMIT', quantity=cnt_order['amt'], positionSide=sig.positionSide, timeInForce=cnt_order['TIF'], price=lim) sig.set_cnt_ordered(cntorderId=order['orderId'], cntType='LIMIT', cntTime=order['updateTime'], cntlimitPrice=lim) print_( '\tPlaced COUNTER order: \n\t' + str(sig) + '\n\t' + orderstr(order), fileout) elif sig.is_cnt_ordered(): order_update = client.query_order(symbol, sig.cntorderId) sig.set_closed(clsTime=order_update['updateTime'], clsPrice=None) print_( '\n\tClosed order: \n\t' + str(sig) + '\n\t' + orderstr(order_update), fileout) _position = False for symbol in insIds: if in_possition_(Signals[symbol]): _position = True break in_position = _position return Signals
def main(args): start_time = time.time() testnet = True filename = str(int(time.time())) if testnet: # Testnet apikey = '' ### INSERT your api key here ### scrkey = '' ### INSERT your api secret here ### else: # Binance apikey = '' scrkey = '' if testnet: fileout = "report/testnet-" + filename else: fileout = "report/" + filename insIds = ['BTCUSDT', 'ETHUSDT', 'BCHUSDT', 'BNBUSDT'] # Generate Client object client = Client(apikey, scrkey, testnet=testnet) client.change_position_mode(dualSide='true') # Generate Portfolio object portfolio = Portfolio(client, tradeIns=insIds) long, short = portfolio.equity_distribution(longPct=0.25, shortPct=0.25, currency='USDT', orderPct=0.05) portfolio.position_locks() print_('\n' + barstr('', length=100, space_size=0), fileout) print_(barstr('BINANCE TRADING', length=100, space_size=5), fileout) print_(barstr('', length=100, space_size=0) + '\n', fileout) print_('\n' + barstr('Generating Models', length=100, space_size=5) + '\n', fileout) # Generate Models object models = {} for i in range(len(portfolio.tradeIns)): symbol = portfolio.tradeIns[i] client.change_leverage(symbol, 1) _data = MarketData(testnet=testnet, symbol=symbol) model = TradingModel(symbol=symbol, testnet=testnet, modelType='bollinger', marketData=_data, pdObserve=pd_ob, pdEstimate=pd_es, orderSize=portfolio.orderSize) model.build_initial_input() only_pos = 'BOTH' if symbol in portfolio.locks['BUY']: model.add_signal_lock(slock='BUY') only_pos = 'SELL ONLY' elif symbol in portfolio.locks['SELL']: model.add_signal_lock(slock='SELL') only_pos = 'BUY ONLY' models[symbol] = model print_( '\tFinish generating model for %s - positions: %s' % (symbol, only_pos), fileout) print_( '\n' + barstr('Start Data Streaming', length=100, space_size=5) + '\n', fileout) header_print(testnet, client, portfolio, fileout) print('\n\tPre-processing Time = %f' % (time.time() - start_time)) print_('\nStream updating for {} minutes...'.format(pd_ob * min_in_candle), fileout) signals = wss_run(portfolio, client, testnet, ['@kline_1m'], models, fileout) session_summary(signals, fileout) print_('\n\tLocal Time at Close: %s ' % timestr(time.time() * 1000), fileout) print_( barstr(text='Elapsed time = {} seconds'.format( round(time.time() - start_time, 2))), fileout) print_(barstr(text="", space_size=0), fileout) os._exit(1)
def session_summary(signals, file): ''' Print summary statistics of the trading session ''' gross_profit = 0 gross_loss = 0 win, loss, inpos = 0, 0, 0 exp_signal = 0 trade_time = 0 for symbol in signals: for sig in signals[symbol]: if sig.is_expired(): exp_signal += 1 elif sig.is_closed(): if sig.clsPrice is not None: if sig.side == 'BUY': _side = 1.0 elif sig.side == 'SELL': _side = -1.0 pnl = _side * sig.get_quantity() * (sig.clsPrice - sig.excPrice) trade_time += (sig.clsTime - sig.excTime) / (60 * 1000) if pnl > 0: win += 1 gross_profit += pnl if pnl <= 0: loss += 1 gross_loss += pnl else: inpos += 1 if (win + loss) > 0: timeAv = trade_time / (win + loss) else: timeAv = 0.0 print_( '\n####################\tTrading Session Summary\t####################\n', file) print_('\n\tGross Profit: \t%1.5f' % gross_profit, file) print_('\tGross Loss: \t%1.5f' % gross_loss, file) print_('\tNet Profit: \t%1.5f' % (gross_profit + gross_loss), file) print_('\tAverage time in position: \t%1.2f' % timeAv, file) print_('\tNumber of win trades: \t%d' % win, file) print_('\tNumber of loss trades: \t%d' % loss, file) print_('\tNumber of unfinished trades: \t%d' % inpos, file) print_('\tNumber of expired signals: \t%d \n' % exp_signal, file)
def header_print(testnet, client, portfolio, file): ''' Print general information of the trading session ''' t_server, t_local = client.timestamp(), time.time() * 1000 print_('\tTestnet: %s' % str(testnet), file) print_('\tServer Time at Start: %s' % timestr(t_server), file) print_( '\tLocal Time at Start: %s, \tOffset (local-server): %d ms\n' % (timestr(t_local), (t_local - t_server)), file) print_( '\t#LONG order : %d \t#SHORT order: %d \tOrder Size : %1.2f \n' % (portfolio.equityDist['BUY'], portfolio.equityDist['SELL'], portfolio.orderSize), file) try: bal_st = pd.DataFrame(client.balance()) bal_st['updateTime'] = [timestr(b) for b in bal_st['updateTime']] print_('\nBeginning Balance Info: \n', file) print_(bal_st, file) except Exception: print_('\nFail to connect to client.balance: \n', file)