def buy(candle, criteria): """Create or update existing position for zscore above threshold value. """ global client orderbook = client.get_orderbook_ticker(symbol=candle['pair']) return get_db().trades.insert_one( odict({ 'pair': candle['pair'], 'freq': candle['freq'], 'status': 'open', 'start_time': now(), 'strategy': criteria['snapshot']['strategy'], 'snapshots': [criteria['snapshot']], 'orders': [ odict({ 'action': 'BUY', 'ex': 'Binance', 'time': now(), 'price': candle['close'], # np.float64(orderbook['askPrice']), 'volume': 1.0, 'quote': BINANCE['TRADE_AMT'], 'fee': BINANCE['TRADE_AMT'] * (BINANCE['PCT_FEE'] / 100), 'orderbook': orderbook, 'candle': candle }) ] })).inserted_id
class User(mongo.Document): """ User model """ # pylint: disable=E1101 username = mongo.StringField(required=True, unique=True) password = mongo.StringField(required=True) email = mongo.EmailField(required=True, unique=True) first_name = mongo.StringField(max_length=50) last_name = mongo.StringField(max_length=50) created_at = mongo.IntField(default=now()) updated_at = mongo.IntField(default=now()) # pylint: enable=E1101 @classmethod # pylint: disable=W0613 def pre_save(cls, sender, document, **kwargs): """ Before save, lower case the username, enail and hash the password """ # pylint: enable=W0613 document.username = document.username.lower() document.email = document.email.lower() document.password = hash_password(document.password) meta = { 'indexes': ['username', 'email', '-created_at'] }
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 query_api(pair, freq, start=None, end=None, force=False): """Get Historical Klines (candles) from Binance. @freq: Binance kline frequency: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M] m -> minutes; h -> hours; d -> days; w -> weeks; M -> months @force: if False, only query unstored data (faster). If True, query all. Return: list of OHLCV value """ t1 = Timer() limit = 500 idx = 0 results = [] periodlen = intrvl_to_ms(freq) end_ts = datestr_to_ms(end) if end else dt_to_ms(now()) start_ts = datestr_to_ms(start) if start else end_ts - (periodlen * 20) # Skip queries for records already stored if force == False: query = {"pair":pair, "freq":freq} if start: query["open_time"] = {"$gt": datestr_to_dt(start)} newer = app.get_db().candles.find(query).sort('open_time',-1).limit(1) if newer.count() > 0: dt = list(newer)[0]['open_time'] start_ts = int(dt.timestamp()*1000 + periodlen) if start_ts > end_ts: log.debug("All records for %s already stored.", pair) return [] client = Client("", "") #while len(results) < 500 and start_ts < end_ts: while start_ts < end_ts: try: data = client.get_klines(symbol=pair, interval=freq, limit=limit, startTime=start_ts, endTime=end_ts) if len(data) == 0: start_ts += periodlen else: # Don't want candles that aren't closed yet if data[-1][6] >= dt_to_ms(now()): results += data[:-1] break results += data start_ts = data[-1][0] + periodlen except Exception as e: log.exception("Binance API request error. e=%s", str(e)) log.debug('%s %s %s queried [%ss].', len(results), freq, pair, t1.elapsed(unit='s')) return results
def sell(doc, candle, orderbook=None, criteria=None): """Close off existing position and calculate earnings. """ global client ob = orderbook if orderbook else client.get_orderbook_ticker( symbol=candle['pair']) bid = np.float64(ob['bidPrice']) pct_fee = BINANCE['PCT_FEE'] buy_vol = np.float64(doc['orders'][0]['volume']) buy_quote = np.float64(doc['orders'][0]['quote']) p1 = np.float64(doc['orders'][0]['price']) pct_gain = pct_diff(p1, candle['close']) quote = buy_quote * (1 - pct_fee / 100) fee = (bid * buy_vol) * (pct_fee / 100) pct_net_gain = net_earn = pct_gain - (pct_fee * 2) #quote - buy_quote duration = now() - doc['start_time'] candle['buy_ratio'] = candle['buy_ratio'].round(4) get_db().trades.update_one({'_id': doc['_id']}, { '$push': { 'snapshots': criteria['snapshot'] }, '$push': { 'orders': odict({ 'action': 'SELL', 'ex': 'Binance', 'time': now(), 'price': candle['close'], 'volume': 1.0, 'quote': buy_quote, 'fee': fee, 'orderbook': ob, 'candle': candle, }) }, '$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), } }) return doc['_id']
def trades(trade_ids): db = app.get_db() cols = [ 'freq', "type", "Δprice", "macd", "rsi", "zscore", "time", "algo", "details" ] data, indexes = [], [] for _id in trade_ids: record = db.trades.find_one({"_id": _id}) indexes.append(record['pair']) ss1 = record['snapshots'][0] ss_new = record['snapshots'][-1] df = app.bot.dfc.loc[record['pair'], strtofreq(record['freqstr'])].tail(100) if len(record['orders']) > 1: c1 = ss1['candle'] c2 = ss_new['candle'] data.append([ c2['freqstr'], 'SELL', pct_diff(c1['close'], c2['close']), ss_new['indicators']['macd']['value'], ss_new['indicators']['rsi'], ss_new['indicators']['zscore'], to_relative_str(now() - record['start_time']), record['algo'], record['details'][-1]['section'].title() ]) # Buy trade else: c1 = ss1['candle'] data.append([ c1['freqstr'], 'BUY', 0.0, ss_new['indicators']['macd']['value'], ss_new['indicators']['rsi'], ss_new['indicators']['zscore'], "-", record['algo'], record['details'][-1]['section'].title() ]) if len(data) == 0: return tradelog("0 trades executed") df = pd.DataFrame(data, index=pd.Index(indexes), columns=cols) df = df[cols] lines = df.to_string( formatters={ cols[0]: ' {}'.format, cols[1]: ' {}'.format, cols[2]: ' {:+.2f}%'.format, cols[3]: ' {:+.3f}'.format, cols[4]: '{:.0f}'.format, cols[5]: '{}'.format, cols[5]: '{}'.format, cols[6]: '{}'.format }).split("\n") tradelog('-' * TRADELOG_WIDTH) tradelog("{} trade(s) executed:".format(len(df))) [tradelog(line) for line in lines]
def put(self, user_id): """ :param user_id: The user id """ user = User.objects.get_or_404(id=user_id) form = UpdateUserForm() if form.validate_on_submit(): user.username = form.username.data user.email = form.email.data user.first_name = form.first_name.data user.last_name = form.last_name.data user.updated_at = now() user.save() return make_api_response(user_schema, user) raise ValidationError(form.errors)
def snapshot(candle): z = signals.z_score(candle, rules['z-score']['periods']).to_dict() client = Client("", "") ob = client.get_orderbook_ticker(symbol=candle['pair']) macd_desc = macd.describe(candle) phase = macd_desc['phase'] # Convert datetime index to str for mongodb storage. phase.index = [str(n)[:-10] for n in phase.index.values] last = phase.iloc[-1] return odict({ 'time': now(), 'strategy': None, 'details': macd_desc['details'], 'price': odict({ 'close': candle['close'], 'z-score': round(z['close'], 2), 'emaDiff': signals.ema_pct_change(candle, rules['ema']['span']).iloc[-1], 'ask': float(ob['askPrice']), 'bid': float(ob['bidPrice']) }), 'volume': odict({ 'value': candle['volume'], 'z-score': round(z['volume'], 2), }), 'buyRatio': odict({ 'value': round(candle['buy_ratio'], 2), 'z-score': round(z['buy_ratio'], 2), }), 'macd': odict({ 'value': last.round(10), 'phase': phase.round(10).to_dict(odict), 'desc': phase.describe().round(10).to_dict() }) })
def new_trades(trade_ids): db = app.get_db() dfc = app.bot.dfc cols = ["Type", "ΔPrice", "Slope", "Z-Score", "ΔZ-Score", "Time"] data, indexes = [], [] for _id in trade_ids: record = db.trades.find_one({"_id": _id}) freq_str = record['orders'][0]['candle']['freq'] indexes.append(record['pair']) candle = candles.newest(record['pair'], freq_str, df=dfc) ss1 = record['snapshots'][0] ss2 = record['snapshots'][-1] if len(record['orders']) > 1: c1 = record['orders'][0]['candle'] data.append([ 'SELL', pct_diff(c1['close'], candle['close']), ss2['price']['emaDiff'], ss2['price']['z-score'], ss2['price']['z-score'] - ss1['price']['z-score'], to_relative_str(now() - record['start_time']) ]) # Buy trade else: data.append([ 'BUY', 0.0, ss2['price']['emaDiff'], ss1['price']['z-score'], 0.0, "-" ]) if len(data) == 0: return tradelog("0 trades executed") df = pd.DataFrame(data, index=pd.Index(indexes), columns=cols) df = df[cols] lines = df.to_string( formatters={ cols[0]: ' {}'.format, cols[1]: ' {:+.2f}%'.format, cols[2]: ' {:+.2f}'.format, cols[3]: ' {:+.2f}'.format, cols[4]: ' {:+.2f}'.format, cols[5]: '{}'.format }).split("\n") tradelog("{} trade(s) executed:".format(len(df))) [tradelog(line) for line in lines]
def positions(): """Position summary. """ db = app.get_db() cols = ["freq", "price", "Δprice", "macd", "rsi", "zscore", "time", "algo"] data, indexes = [], [] opentrades = db.trades.find({'status': 'open'}) for record in opentrades: ss1 = record['snapshots'][0] c1 = ss1['candle'] ss_new = record['snapshots'][-1] freq = strtofreq(record['freqstr']) df = app.bot.dfc.loc[record['pair'], freq] dfmacd, phases = macd.histo_phases(df, record['pair'], record['freqstr'], 100) data.append([ c1['freqstr'], df.iloc[-1]['close'], pct_diff(c1['close'], df.iloc[-1]['close']), phases[-1].iloc[-1], signals.rsi(df['close'], 14), signals.zscore(df['close'], df.iloc[-1]['close'], 21), to_relative_str(now() - record['start_time']), record['algo'] ]) indexes.append(record['pair']) if opentrades.count() == 0: tradelog("0 open positions") else: df = pd.DataFrame(data, index=pd.Index(indexes), columns=cols) df = df[cols] lines = df.to_string( formatters={ cols[0]: ' {}'.format, cols[1]: ' {:g}'.format, cols[2]: ' {:+.2f}%'.format, cols[3]: ' {:+.3f}'.format, cols[4]: '{:.0f}'.format, cols[5]: '{}'.format, cols[6]: ' {}'.format }).split("\n") tradelog('-' * TRADELOG_WIDTH) tradelog("{} position(s):".format(len(df))) [tradelog(line) for line in lines] return df
def merge_new(dfc, pairs, span=None): """Merge only newly updated DB records into dataframe to avoid ~150k DB reads every main loop. """ global last_update t1 = Timer() columns = ['open', 'close', 'trades', 'volume', 'buy_ratio'] exclude = ['_id','high','low','quote_vol','sell_vol', 'close_time'] projection = dict(zip(exclude, [False]*len(exclude))) idx, data = [], [] db = app.get_db() if span is None and last_update: # If no span, query/merge db records inserted since last update. oid = ObjectId.from_datetime(last_update) last_update = now() _filter = {'_id':{'$gte':oid}} else: # Else query/merge all since timespan. span = span if span else timedelta(days=7) last_update = now() _filter = {'pair':{'$in':pairs}, 'close_time':{'$gte':now()-span}} batches = db.candles.find_raw_batches(_filter, projection) if batches.count() < 1: return dfc try: ndarray = bsonnumpy.sequence_to_ndarray( batches, dtype, db.candles.count() ) except Exception as e: log.error(str(e)) return dfc #raise df = pd.DataFrame(ndarray) df['open_time'] = pd.to_datetime(df['open_time'], unit='ms') df['freq'] = df['freq'].str.decode('utf-8') df['pair'] = df['pair'].str.decode('utf-8') df['freq'] = df['freq'].replace('1m',60) df['freq'] = df['freq'].replace('5m',300) df['freq'] = df['freq'].replace('1h',3600) df['freq'] = df['freq'].replace('1d',86400) df = df.sort_values(by=['pair','freq','open_time']) df2 = pd.DataFrame(df[columns].values, index = pd.MultiIndex.from_arrays( [df['pair'], df['freq'], df['open_time']], names = ['pair','freq','open_time']), columns = columns ).sort_index() df3 = pd.concat([dfc, df2]).drop_duplicates().sort_index() log.debug("{:,} records loaded into numpy. [{:,.1f} ms]".format( len(df3), t1)) #print("Done in %s ms" % t1) return df3
def aggregate_mkt(freqstr=None): try: dfT = binance_24h() except Exception as e: lock.acquire() return print("Binance client error. {}".format(str(e))) lock.release() dfV = pd.DataFrame( dfT.groupby('quoteAsset').apply(lambda x: x['quoteVol'].sum()), columns=['volume']) summaries = [] for idx, row in dfV.iterrows(): summaries.append(summarize(dfT, idx)) dfA = pd.DataFrame(summaries, index=[n['symbol'] for n in summaries]) dfA = dfA[['pairs', '24hPriceChange', '24hAggVol']]\ .round(2).sort_values('24hAggVol') # Diff in both 24h_delta_price's is freq price change. formatters = {} k = None if freqstr: db = app.get_db() last = list( db.tickers.find({ 'freqstr': freqstr }, { '_id': 0, 'freqstr': 0, 'ex': 0, 'time': 0 }).sort('time', -1)) db.tickers.insert_one({ **{ 'ex': 'Binance', 'time': now(), 'freqstr': freqstr }, **dfA.to_dict('index') }) if len(last) > 0: k = '{}.Δprice'.format(freqstr) dfA[k] = dfA['24hPriceChange'] - pd.DataFrame( last[0]).T['24hPriceChange'] formatters[k] = ' {:+.2f}%'.format formatters.update({ '24h.Δprice': ' {:+.2f}%'.format, '24h.agg.vol': ' {:,.0f}'.format }) scanlog("Aggregate Markets") columns = ['pairs', k, '24h.Δprice', '24h.agg.vol'] if not k: columns = [n for n in columns if n] _cols = dfA.columns.tolist() dfA.columns = columns lines = dfA.to_string(columns=columns, formatters=formatters).split("\n") [scanlog(line) for line in lines] scanlog("") dfA.columns = _cols return dfA
def update(_freq_str): """Evaluate Binance market data and execute buy/sell trades. """ global n_cycles, freq_str, freq trade_ids = [] freq_str = _freq_str freq = strtofreq[freq_str] t1 = Timer() db = get_db() # Update candles updated by websocket app.bot.dfc = candles.merge_new(app.bot.dfc, pairs, span=None) tradelog('*' * 80) duration = to_relative_str(now() - start) hdr = "Cycle #{}, Period {} {:>%s}" % (61 - len(str(n_cycles))) tradelog(hdr.format(n_cycles, freq_str, duration)) tradelog('*' * 80) # Output candle signals to siglog if freq_str in siglog_freq: siglog('-' * 80) for pair in pairs: printer.candle_sig(candles.newest(pair, freq_str, df=app.bot.dfc)) # Evaluate existing positions active = list(db.trades.find({'status': 'open', 'freq': freq_str})) for trade in active: candle = candles.newest(trade['pair'], freq_str, df=app.bot.dfc) result = strategy.update(candle, trade) print('{} {} {}'.format(candle['pair'], candle['freq'], result['snapshot']['details'])) if result['action'] == 'SELL': trade_ids += [sell(trade, candle, criteria=result)] else: db.trades.update_one({"_id": trade["_id"]}, {"$push": { "snapshots": result['snapshot'] }}) # Inverse active list and evaluate opening new positions inactive = sorted(list(set(pairs) - set([n['pair'] for n in active]))) for pair in inactive: candle = candles.newest(pair, freq_str, df=app.bot.dfc) results = strategy.evaluate(candle) for res in results: print('{} {} {}'.format(candle['pair'], candle['freq'], res['snapshot']['details'])) if res['action'] == 'BUY': trade_ids += [buy(candle, criteria=res)] tradelog('-' * 80) printer.new_trades([n for n in trade_ids if n]) tradelog('-' * 80) printer.positions('open') tradelog('-' * 80) printer.positions('closed') n_cycles += 1
def positions(_type): """Position summary. @_type: 'open', 'closed' @start: datetime.datetime for closed trades """ from docs.rules import TRADING_PAIRS as pairs db = app.get_db() dfc = app.bot.dfc if _type == 'open': cols = ["ΔPrice", "Slope", " Z-Score", " ΔZ-Score", "Macd", "Time"] data, indexes = [], [] _trades = list( db.trades.find({ 'status': 'open', 'pair': { "$in": pairs } })) for record in _trades: c1 = record['orders'][0]['candle'] c2 = candles.newest(record['pair'], c1['freq'], df=dfc) ss1 = record['snapshots'][0] ss2 = record['snapshots'][-1] data.append([ pct_diff(c1['close'], c2['close']), ss2['price']['emaDiff'], ss2['price']['z-score'], ss2['price']['z-score'] - ss1['price']['z-score'], ss2['macd']['value'], to_relative_str(now() - record['start_time']) ]) indexes.append(record['pair']) if len(_trades) == 0: tradelog("0 open positions") else: df = pd.DataFrame(data, index=pd.Index(indexes), columns=cols) df = df[cols] lines = df.to_string( formatters={ cols[0]: ' {:+.2f}%'.format, cols[1]: ' {:+.2f}%'.format, cols[2]: ' {:.2f}'.format, cols[3]: ' {:+.2f}'.format, cols[4]: ' {:+.2f}'.format, cols[5]: '{}'.format }).split("\n") tradelog("{} position(s):".format(len(df))) [tradelog(line) for line in lines] return df elif _type == 'closed': if datetime.now().time().hour >= 8: start = dateparser.parse("8 am today").replace( tzinfo=tzlocal.get_localzone()).astimezone(pytz.utc) else: start = dateparser.parse("8 am yesterday").replace( tzinfo=tzlocal.get_localzone()).astimezone(pytz.utc) closed = list( db.trades.find({ 'status': 'closed', 'end_time': { '$gte': start } })) #print("%s trades today ending after %s" % (len(closed), start)) n_win, pct_net_gain = 0, 0 for n in closed: if n['pct_net_gain'] > 0: n_win += 1 pct_net_gain += n['pct_net_gain'] ratio = (n_win / len(closed)) * 100 if len(closed) > 0 else 0 tradelog("{} of {} trade(s) today were profitable.".format( n_win, len(closed))) duration = to_relative_str(now() - start) tradelog("{:+.2f}% net profit today.".format(pct_net_gain))
def tradelog(msg): log.log(99, msg) def siglog(msg): log.log(100, msg) log = logging.getLogger('trade') # GLOBALS siglog_freq = ['5m', '1h', '1d'] n_cycles = 0 start = now() freq = None freq_str = None client = None #------------------------------------------------------------------------------ def init(): """Preload candles records from mongoDB to global dataframe. Performance: ~3,000ms/100k records """ t1 = Timer() log.info('Preloading historic data...') span = delta(days=7) app.bot.dfc = candles.merge_new(pd.DataFrame(), pairs, span=span)
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']
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 plot(pair, units, n_units, n_periods): from dateparser import parse import plotly.offline as offline import plotly.tools as tools, plotly.graph_objs as go from app.common.utils import utc_datetime as now from . import candles freqstr = ('%s%s'%(n_units, units[1]), '%s%s'%(n_units, units[2])) freq = strtofreq[freqstr[0]] start_str = "{} {} ago utc".format((n_periods + 75) * n_units, units[0]) candles.update([pair], freqstr[0], start=start_str, force=True) app.bot.dfc = candles.merge_new(pd.DataFrame(), [pair], span=now()-parse(start_str)) df_macd = generate(app.bot.dfc.loc[pair, freq]) scan = agg_describe(pair, freqstr[0], n_periods, pdfreqstr=freqstr[1]) scan['summary'] = scan['summary'].replace("\n", "<br>") # Stacked Subplots with a Shared X-Axis t1 = go.Scatter( x=df_macd.index, y=df_macd['close'], name="Price") t2 = go.Bar( x=df_macd.index, y=df_macd['macd_diff'], name="MACD_diff (normalized)", yaxis='y2') t3 = go.Bar( x=df_macd.index, y=df_macd['volume'], name="Volume", yaxis='y3') data = [t1, t2, t3] layout = go.Layout( title='{} MACD'.format(pair), yaxis=dict( domain=[0.4, 1] ), yaxis2=dict( domain=[0.2, 0.4] ), yaxis3=dict( domain=[0, 0.2] ), xaxis=dict( anchor = "y3", #domain=[0.0, 0.1], title="<BR>" + scan['summary'] ), margin = dict(l=100, r=100, b=400, t=75, pad=25) #fig['layout']['xaxis1'].update(titlefont=dict( # family='Arial, sans-serif', # size=18, # color='grey' #)) ) fig = go.Figure(data=data, layout=layout) return fig """fig = tools.make_subplots(
def indicators(df): freq_str, freq = '1h', 3600 start_str, periods = '48 hours ago utc', 48 top = df.sort_values('close - open') #.tail(n) _df = pd.DataFrame(columns=[ '∆(Close - Open)', '∆(High - Low)', 'SD(∆P)', 'Σ(∆P > 0)', 'μ(∆P > 0)', 'Σ(∆P < 0)', 'μ(∆P < 0)', '+MACD/-MACD', '∆ema', 'BuyVol', ], index=top.index).astype('float64') for pair, row in df.iterrows(): # API query/load candle data candles.update([pair], freq_str, start=start, force=True) app.bot.dfc = candles.merge_new(app.bot.dfc, [pair], span=now() - parse(start_str)) dfc = app.bot.dfc.loc[pair].xs(freq, level=0).tail(periods) candle = candles.newest(pair, freq_str, df=app.bot.dfc) # Calc indicators pct_close_std = np.float64( dfc['close'].pct_change().describe()['std'] * 100) br_mean = dfc['buy_ratio'].describe()['mean'] * 100 ema_slope = signals.ema_pct_change(candle, strats['ema']['span']).iloc[-1] # Price movement (total, velocity, momentum) pdelta = dfc['close'].tail(24).pct_change() * 100 pos_pdelta = pdelta[pdelta > 0] neg_pdelta = pdelta[pdelta < 0] macd = signals.macd(dfc, strats['macd']['fast_span'], strats['macd']['slow_span']) pos_mom = macd[macd['macd_diff'] > 0]['macd_diff'].sum() neg_mom = abs(macd[macd['macd_diff'] < 0]['macd_diff'].sum()) mom_ratio = pos_mom / neg_mom # MACD Histogram analysis. # Iterate through macd_diff, group histograms, calc avg length/depth _df.loc[pair] = [ # done already, # done already, pct_close_std, p_up.sum(), p_up.mean(), p_down.sum(), p_down.mean(), mom_ratio, br_mean, ema_slope, ] return _df
def _momentum(candle, record=None): """Evaluate positive momentum for buy/sell decision. Buy: on confirmation of price/volume. Sell: at peak price slope. FIXME: confirm high volume across multiple 1m candles, not just 1. """ if candle['freq'] != '5m': return None periods = rules['z-score']['periods'] z = signals.z_score(candle, periods) ema = signals.ema_pct_change(candle) client = Client("", "") ob = client.get_orderbook_ticker(symbol=candle['pair']) snapshot = { 'time': now(), 'candle_price': candle['close'], 'volume': candle['volume'], 'buy_ratio': candle['buy_ratio'], 'z-score': z.to_dict(), 'ema_pct_change': ema.iloc[-1] } if record is None: if (ema.tail(5) < 0).any(): print('ema.tail(5).any() < 0') return False if z.volume < 2.0 or z.buy_ratio < 0.5: print('z.volume < 2.0 or z.buy_ratio < 0.5') return False return { 'strategy': 'momentum', 'snapshot': { **snapshot, **{ 'ask_price': float(ob['askPrice']) } }, 'details': { 'ema:slope:tail(5):min > thresh': '{:+.2f}% > 0'.format(ema.tail(5).min()), 'z-score:volume > thresh': '{:+.2f} > 0.5'.format(z.volume), 'z-score:buy-ratio > thresh': '{:+.2f} > 0.5'.format(z.buy_ratio) } } elif record: if ema.iloc[-1] >= max( [x['ema_pct_change'] for x in record['snapshots']]): return {'action': None, 'snapshot': snapshot} return { 'action': 'sell', 'snapshot': { **snapshot, **{ 'bid_price': float(ob['bidPrice']) } }, 'details': { 'ema:slope <= thresh': '{:+.2f}% <= 0'.format(ema.iloc[-1]), 'AND bid < buy': '{:} < {:.8f}'.format(ob['bidPrice'], record['snapshots'][0]['ask_price']) } }
def _zscore(candle, record=None): """Evaluate z-score vs buy/sell thresholds. Buy: Z-Score below threshold Sell: Z-Score returning to mean """ if candle['freq'] != '1m': return None periods = rules['z-score']['periods'] z = signals.z_score(candle, periods) ema = signals.ema_pct_change(candle, rules['ema']['span']) snapshot = { 'time': now(), 'price': candle['close'], 'volume': candle['volume'], 'buy_ratio': candle['buy_ratio'], 'z-score': z.to_dict(), 'ema_pct_change': ema.iloc[-1] } if record is None: threshold = rules['z-score']['buy_thresh'] if z.close > threshold: print('z.close < threshold') return None client = Client("", "") ob = client.get_orderbook_ticker(symbol=candle['pair']) return { 'strategy': 'z-score', 'snapshot': { **snapshot, **{ 'ask_price': float(ob['askPrice']) } }, 'details': { 'close:z-score < thresh': '{:+.2f} < {:+.2f}'.format(z.close, threshold) } } elif record: threshold = rules['z-score']['sell_thresh'] if z.close < threshold: return {'action': None, 'snapshot': snapshot} if ema.iloc[-1] > 0.10: return {'action': None, 'snapshot': snapshot} client = Client("", "") ob = client.get_orderbook_ticker(symbol=candle['pair']) return { 'action': 'sell', 'snapshot': { **snapshot, **{ 'bid_price': float(ob['bidPrice']) } }, 'details': { 'close:z-score > thresh': '{:+.2f} > -0.75'.format(z.close), 'ema:slope < thresh': '{:+.2f}% <= 0.10'.format(ema.iloc[-1]) } }