def update_spinner(): msg = 'listening %s' % next(spinner) lock.acquire() sys.stdout.write(msg) sys.stdout.flush() sys.stdout.write('\b' * len(msg)) lock.release()
def buy(ss, algo): """Open new position, insert record to DB. @ss: snapshot dict @algo: algorithm definition dict """ db, client = app.db, app.bot.client if ss['book'] is None: book = odict(client.get_orderbook_ticker(symbol=ss['pair'])) del book['symbol'] [book.update({k: np.float64(v)}) for k, v in book.items()] ss['book'] = book record = odict({ 'pair': ss['pair'], 'quote_asset': db.assets.find_one({'symbol': ss['pair']})['quoteAsset'], 'freqstr': ss['candle']['freqstr'], 'status': 'open', 'start_time': now(), 'algo': algo['name'], 'stoploss': algo['stoploss'], 'snapshots': [ss], 'stats': {}, 'details': [{ 'algo': algo['name'], 'section': 'entry', 'desc': algo_to_string(algo['name'], 'entry') }], 'orders': [ odict({ 'action': 'BUY', 'ex': 'Binance', 'time': now(), 'price': ss['book']['askPrice'], 'volume': 1.0, 'quote': TRD_AMT_MAX, 'fee': TRD_AMT_MAX * (BINANCE_PCT_FEE / 100) }) ] }) result = db.trades.insert_one(record) update_stats(record, ss) lock.acquire() print("BUY {} ({})".format(ss['pair'], algo['name'])) lock.release() return result.inserted_id
def recv_kline(msg): """Kline socket callback function. Formats raw candle data, feeds into trading queue, adds to global dataframe, and saves to list for periodic saving to DB. """ global storedata if msg['e'] != 'kline': lock.acquire() print(msg) lock.release() return k = msg['k'] candle = { "open_time": pd.to_datetime(k['t'], unit='ms', utc=True), "close_time": pd.to_datetime(k['T'], unit='ms', utc=True), "pair": k['s'], "freqstr": k['i'], "open": np.float64(k['o']), "close": np.float64(k['c']), "high": np.float64(k['h']), "low": np.float64(k['l']), "trades": k['n'], "volume": np.float64(k['v']), "buy_vol": np.float64(k['V']), "quote_volume": np.float64(k['q']), "quote_buy_vol": np.float64(k['Q']), "closed": k['x'] } if k['x'] == True: storedata.append(candle) lock.acquire() print("{}{:<7}{}{:>5}{:>12g}{}".format(colors.GRN, candle['pair'], colors.WHITE, candle['freqstr'], candle['close'], colors.ENDC)) lock.release() # Send to trade queue. q.put(candle)
def run(e_pairs, e_kill): global storedata, connkeys, ws client = app.bot.client #print("Connecting to websocket...") ws = BinanceSocketManager(client) pairs = get_pairs() connkeys += [ws.start_kline_socket(pair, recv_kline, interval=n) \ for n in TRD_FREQS for pair in pairs] lock.acquire() print("Subscribed to {} kline sockets.".format(len(connkeys))) lock.release() ws.start() #print('Connected. Press Ctrl+C to quit') tmr = Timer(name='pairs', expire='every 5 clock min utc', quiet=True) while True: if e_kill.isSet(): break if e_pairs.isSet(): update_sockets() e_pairs.clear() if tmr.remain() == 0: tmr.reset() if len(storedata) > 0: #print("websock_thread: saving new candles...") candles.bulk_save(storedata) storedata = [] time.sleep(1) close_all() print("Websock thread: Terminating...")
def snapshot(c): """Gather state of trade--candle, indicators--each tick and save to DB. """ global dfW book = None wick_slope = macd_value = amp_slope = np.nan pair, freqstr = c['pair'], c['freqstr'] buyratio = (c['buy_vol'] / c['volume']) if c['volume'] > 0 else 0.0 # MACD Indicators dfm_dict = {} df = app.bot.dfc.loc[pair, strtofreq(freqstr)] try: dfmacd, phases = macd.histo_phases(df, pair, freqstr, 100, to_bson=True) except Exception as e: lock.acquire() print('snapshot exc') print(str(e)) lock.release() if len(dfmacd) < 1: dfm_dict['bars'] = 0 else: dfm_dict = dfmacd.iloc[-1].to_dict() dfm_dict['bars'] = int(dfm_dict['bars']) macd_value = phases[-1].iloc[-1] amp_slope = phases[-1].diff().ewm(span=min(3, len(phases[-1])), min_periods=0).mean().iloc[-1] if c['closed']: # Find price EMA WITHIN the wick (i.e. each trade). Very # small movements. #prices = dfW.loc[c['pair'], c['freqstr']]['close'] #wick_slope = prices.diff().ewm(span=len(prices)).mean().iloc[-1] # FIXME wick_slope = 0.0 return { 'pair': pair, 'time': now(), 'book': None, 'candle': c, 'indicators': { 'buyRatio': round(buyratio, 2), 'rsi': signals.rsi(df['close'].tail(100), 14), 'wickSlope': wick_slope, 'zscore': signals.zscore(df['close'], c['close'], 21), 'macd': { **dfm_dict, **{ 'ampSlope': round(amp_slope, 2), 'value': round(macd_value, 2) } } } }
def run(e_pairs, e_kill): """Main trading loop thread. Consumes candle data from queue and manages/executes trades. TODO: add in code for tracking unclosed candle wicks prices: # Clear all partial candle data dfW = dfW.drop([(c['pair'], strtofreq(c['freqstr']))]) """ from main import q db = app.get_db() t1 = Timer() tmr1 = Timer(name='pos', expire='every 1 clock min utc', quiet=True) tmr10 = Timer(name='earn', expire='every 10 clock min utc', quiet=True) reports.positions() reports.earnings() n = 0 while True: if e_kill.isSet(): break ent_ids, ex_ids = [], [] # Trading algo inner loop. while q.empty() == False: c = q.get() candles.modify_dfc(c) ss = snapshot(c) query = { 'pair': c['pair'], 'freqstr': c['freqstr'], 'status': 'open' } # Eval position entries/exits for trade in db.trades.find(query): update_stats(trade, ss) ex_ids += eval_exit(trade, c, ss) if c['closed'] and c['pair'] in get_pairs(): ent_ids += eval_entry(c, ss) n += 1 # Reporting outer loop. if tmr1.remain() == 0: reports.positions() tmr1.reset() if tmr10.remain() == 0: reports.earnings() tmr10.reset() if len(ent_ids) + len(ex_ids) > 0: reports.trades(ent_ids + ex_ids) if n > 75: lock.acquire() print('{} queue items processed. [{:,.0f} ms/item]'\ .format(n, t1.elapsed()/n)) lock.release() t1.reset() n = 0 # Outer loop tail if len(ex_ids) > 0: if c['pair'] not in get_pairs(): # TODO: check no other open positions hold this pair, safe # for disabling. set_pairs([c['pair']], 'DISABLED') update_spinner() time.sleep(0.1) print('Trade thread: Terminating...')
def sell(trade, ss, section): """Close off existing position and calculate earnings. @trade: db trade document dict @ss: snapshot dict @section: key name of evaluated algo conditions """ db, client = app.db, app.bot.client # Algorithm criteria details algo = [n for n in TRD_ALGOS \ if n['name'] == trade['algo']][0] details = {'name': algo['name'], 'section': section} if section == 'stoploss': details.update({'desc': algo['stoploss']}) else: details.update({'desc': algo_to_string(algo['name'], section)}) # Get orderbook if not already stored in snapshot. if ss['book'] is None: try: book = odict(client.get_orderbook_ticker(symbol=trade['pair'])) except (BinanceRequestException, ConnectionError) as e: log.debug(str(e)) lock.acquire() print("Error acquiring orderbook. Sell failed.") lock.release() return [] del book['symbol'] [book.update({k: np.float64(v)}) for k, v in book.items()] ss['book'] = book # Profit/loss calculations. pct_fee = BINANCE_PCT_FEE bid = ss['book']['bidPrice'] ask = ss['book']['askPrice'] buy_vol = np.float64(trade['orders'][0]['volume']) buy_quote = np.float64(trade['orders'][0]['quote']) p1 = np.float64(trade['orders'][0]['price']) pct_gain = pct_diff(p1, bid) quote = buy_quote * (1 - pct_fee / 100) fee = (bid * buy_vol) * (pct_fee / 100) pct_net_gain = net_earn = pct_gain - (pct_fee * 2) duration = now() - trade['start_time'] db.trades.update_one({'_id': trade['_id']}, { '$push': { 'snapshots': ss, 'details': details, 'orders': odict({ 'action': 'SELL', 'ex': 'Binance', 'time': now(), 'price': bid, 'volume': 1.0, 'quote': buy_quote, 'fee': fee }) }, '$set': { 'status': 'closed', 'end_time': now(), 'duration': int(duration.total_seconds()), 'pct_gain': pct_gain.round(4), 'pct_net_gain': pct_net_gain.round(4) } }) lock.acquire() print("SELL {} ({}) Details: {}. {}"\ .format(trade['pair'], details['name'], details['section'].title(), details['desc'])) lock.release() return trade['_id']