diff --git a/README.md b/README.md index 6152582b..f5d22076 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![python](https://img.shields.io/badge/python-2.7%20%26%203-blue.svg)![licence](https://img.shields.io/badge/licence-GPL%20v2-blue.svg)](https://github.com/s4w3d0ff/python-poloniex/blob/master/LICENSE) [![release](https://img.shields.io/github/release/s4w3d0ff/python-poloniex.svg)![release build](https://travis-ci.org/s4w3d0ff/python-poloniex.svg?branch=v0.5.6)](https://github.com/s4w3d0ff/python-poloniex/releases) [![master](https://img.shields.io/badge/branch-master-blue.svg)![master build](https://api.travis-ci.org/s4w3d0ff/python-poloniex.svg?branch=master)](https://github.com/s4w3d0ff/python-poloniex/tree/master) [![dev](https://img.shields.io/badge/branch-dev-blue.svg)![dev build](https://api.travis-ci.org/s4w3d0ff/python-poloniex.svg?branch=dev)](https://github.com/s4w3d0ff/python-poloniex/tree/dev) + Inspired by [this](http://pastebin.com/8fBVpjaj) wrapper written by 'oipminer' > I (s4w3d0ff) am not affiliated with, nor paid by [Poloniex](https://poloniex.com). If you wish to contribute to the repository please read [CONTRIBUTING.md](https://github.com/s4w3d0ff/python-poloniex/blob/master/CONTRIBUTING.md). All and any help is appreciated. #### Features: @@ -24,8 +25,10 @@ All api calls are done through an instance of `poloniex.Poloniex`. You can use t ```python # import this package from poloniex import Poloniex + # make an instance of poloniex.Poloniex polo = Poloniex() + # show the ticker print(polo('returnTicker')) ``` @@ -46,13 +49,16 @@ To use the private api commands you first need an api key and secret (supplied b ```python import poloniex polo = poloniex.Poloniex(key='your-Api-Key-Here-xxxx', secret='yourSecretKeyHere123456789') + # or this works polo.key = 'your-Api-Key-Here-xxxx' polo.secret = 'yourSecretKeyHere123456789' + # get your balances balance = polo.returnBalances() print("I have %s ETH!" % balance['ETH']) -# or + +# or use '__call__' balance = polo('returnBalances') print("I have %s BTC!" % balance['BTC']) ``` @@ -68,7 +74,7 @@ print(polo.marketTradeHist('BTC_ETH')) print(polo.returnTradeHistory('BTC_ETH')) ``` -You can also not use the 'helper' methods at all and use `poloniex.PoloniexBase` which only has `returnMarketHist`, `__call__` to make rest api calls. +You can also not use the 'helper' methods at all and use `poloniex.PoloniexBase` which only has `returnMarketHist` and `__call__` to make rest api calls. #### Websocket Usage: To connect to the websocket api just create a child class of `PoloniexSocketed` like so: diff --git a/examples/README.md b/examples/README.md index 0ee3708f..81653f35 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,25 +1,22 @@ # Examples -#### _Examples require [this poloniex module](https://github.com/s4w3d0ff/python-poloniex) to be installed._ +##### _Examples require [this poloniex module](https://github.com/s4w3d0ff/python-poloniex) to be installed._ ## Chart: -Saves chart data in a mongodb collection and returns a pandas dataframe with basic indicators. -### Requirements: -pip: +Saves chart data in a mongodb collection and returns a pandas dataframe. +#### Requirements: ``` pandas numpy pymongo +tqdm ``` Chart examples require [mongod](https://www.mongodb.com/) running locally. ## Loanbot: Helps your loan offers get filled and keeps them from going 'stale' -### Requirements: -Just [this git repository](https://github.com/s4w3d0ff/python-poloniex). - ## Websocket: Examples of how to use the websocket api to create tickers, stoplimits, etc. -### Requirements: -Just [this git repository](https://github.com/s4w3d0ff/python-poloniex) v0.5+. -`mongoTicker.py` requires pymongo and mongod running. + +### Projects using `python-poloniex`: +- [donnie](https://github.com/s4w3d0ff/donnie) - 'Poloniex tradebot toolkit for Python 3+' diff --git a/examples/chart/README.md b/examples/chart/README.md index 15d65109..9a34dfd8 100644 --- a/examples/chart/README.md +++ b/examples/chart/README.md @@ -1,18 +1,9 @@ -chart.py - saves chart data in a mongodb collection and returns a pandas dataframe with basic indicators +mongoDataframe.py - saves chart data in a mongodb collection and returns a pandas dataframe Requires: ``` pandas numpy pymongo -``` - -bokehPlotter.py - same as chart.py with an added `graph` method that plots the data (with indicators) using bokeh - -Requires: -``` -pandas -numpy -pymongo -bokeh +tqdm ``` diff --git a/examples/chart/bokehPlotter.py b/examples/chart/bokehPlotter.py deleted file mode 100644 index e2bbe49f..00000000 --- a/examples/chart/bokehPlotter.py +++ /dev/null @@ -1,410 +0,0 @@ -# Works on python3 / requires: pandas, numpy, pymongo, bokeh -# BTC: 1A7K4kgXLSSzvDRjvoGwomvhrNU4CKezEp -# LTC: LWShTeRrZpYS4aJhb6JdP3R9tNFMnZiDo2 - -import logging -from operator import itemgetter -from math import pi -from time import time - -from pymongo import MongoClient -import pandas as pd -import numpy as np -from bokeh.plotting import figure, show -from bokeh.models import NumeralTickFormatter -from bokeh.models import LinearAxis, Range1d - -logger = logging.getLogger(__name__) - - -def rsi(df, window, targetcol='weightedAverage', colname='rsi'): - """ Calculates the Relative Strength Index (RSI) from a pandas dataframe - http://stackoverflow.com/a/32346692/3389859 - """ - series = df[targetcol] - delta = series.diff().dropna() - u = delta * 0 - d = u.copy() - u[delta > 0] = delta[delta > 0] - d[delta < 0] = -delta[delta < 0] - # first value is sum of avg gains - u[u.index[window - 1]] = np.mean(u[:window]) - u = u.drop(u.index[:(window - 1)]) - # first value is sum of avg losses - d[d.index[window - 1]] = np.mean(d[:window]) - d = d.drop(d.index[:(window - 1)]) - rs = u.ewm(com=window - 1, - ignore_na=False, - min_periods=0, - adjust=False).mean() / d.ewm(com=window - 1, - ignore_na=False, - min_periods=0, - adjust=False).mean() - df[colname] = 100 - 100 / (1 + rs) - df[colname].fillna(df[colname].mean(), inplace=True) - return df - - -def sma(df, window, targetcol='close', colname='sma'): - """ Calculates Simple Moving Average on a 'targetcol' in a pandas dataframe - """ - df[colname] = df[targetcol].rolling( - min_periods=1, window=window, center=False).mean() - return df - - -def ema(df, window, targetcol='close', colname='ema', **kwargs): - """ Calculates Expodential Moving Average on a 'targetcol' in a pandas - dataframe """ - df[colname] = df[targetcol].ewm( - span=window, - min_periods=kwargs.get('min_periods', 1), - adjust=kwargs.get('adjust', True), - ignore_na=kwargs.get('ignore_na', False) - ).mean() - df[colname].fillna(df[colname].mean(), inplace=True) - return df - - -def macd(df, fastcol='emafast', slowcol='sma', colname='macd'): - """ Calculates the differance between 'fastcol' and 'slowcol' in a pandas - dataframe """ - df[colname] = df[fastcol] - df[slowcol] - return df - - -def bbands(df, window, targetcol='close', stddev=2.0): - """ Calculates Bollinger Bands for 'targetcol' of a pandas dataframe """ - if not 'sma' in df: - df = sma(df, window, targetcol) - df['sma'].fillna(df['sma'].mean(), inplace=True) - df['bbtop'] = df['sma'] + stddev * df[targetcol].rolling( - min_periods=1, - window=window, - center=False).std() - df['bbtop'].fillna(df['bbtop'].mean(), inplace=True) - df['bbbottom'] = df['sma'] - stddev * df[targetcol].rolling( - min_periods=1, - window=window, - center=False).std() - df['bbbottom'].fillna(df['bbbottom'].mean(), inplace=True) - df['bbrange'] = df['bbtop'] - df['bbbottom'] - df['bbpercent'] = ((df[targetcol] - df['bbbottom']) / df['bbrange']) - 0.5 - return df - - -def plotRSI(p, df, plotwidth=800, upcolor='green', downcolor='red'): - # create y axis for rsi - p.extra_y_ranges = {"rsi": Range1d(start=0, end=100)} - p.add_layout(LinearAxis(y_range_name="rsi"), 'right') - - # create rsi 'zone' (30-70) - p.patch(np.append(df['date'].values, df['date'].values[::-1]), - np.append([30 for i in df['rsi'].values], - [70 for i in df['rsi'].values[::-1]]), - color='olive', - fill_alpha=0.2, - legend="rsi", - y_range_name="rsi") - - candleWidth = (df.iloc[2]['date'].timestamp() - - df.iloc[1]['date'].timestamp()) * plotwidth - # plot green bars - inc = df.rsi >= 50 - p.vbar(x=df.date[inc], - width=candleWidth, - top=df.rsi[inc], - bottom=50, - fill_color=upcolor, - line_color=upcolor, - alpha=0.5, - y_range_name="rsi") - # Plot red bars - dec = df.rsi <= 50 - p.vbar(x=df.date[dec], - width=candleWidth, - top=50, - bottom=df.rsi[dec], - fill_color=downcolor, - line_color=downcolor, - alpha=0.5, - y_range_name="rsi") - - -def plotMACD(p, df, color='blue'): - # plot macd - p.line(df['date'], df['macd'], line_width=4, - color=color, alpha=0.8, legend="macd") - p.yaxis[0].formatter = NumeralTickFormatter(format='0.00000000') - - -def plotCandlesticks(p, df, plotwidth=750, upcolor='green', downcolor='red'): - candleWidth = (df.iloc[2]['date'].timestamp() - - df.iloc[1]['date'].timestamp()) * plotwidth - # Plot candle 'shadows'/wicks - p.segment(x0=df.date, - y0=df.high, - x1=df.date, - y1=df.low, - color="black", - line_width=2) - # Plot green candles - inc = df.close > df.open - p.vbar(x=df.date[inc], - width=candleWidth, - top=df.open[inc], - bottom=df.close[inc], - fill_color=upcolor, - line_width=0.5, - line_color='black') - # Plot red candles - dec = df.open > df.close - p.vbar(x=df.date[dec], - width=candleWidth, - top=df.open[dec], - bottom=df.close[dec], - fill_color=downcolor, - line_width=0.5, - line_color='black') - # format price labels - p.yaxis[0].formatter = NumeralTickFormatter(format='0.00000000') - - -def plotVolume(p, df, plotwidth=800, upcolor='green', downcolor='red'): - candleWidth = (df.iloc[2]['date'].timestamp() - - df.iloc[1]['date'].timestamp()) * plotwidth - # create new y axis for volume - p.extra_y_ranges = {"volume": Range1d(start=min(df['volume'].values), - end=max(df['volume'].values))} - p.add_layout(LinearAxis(y_range_name="volume"), 'right') - # Plot green candles - inc = df.close > df.open - p.vbar(x=df.date[inc], - width=candleWidth, - top=df.volume[inc], - bottom=0, - alpha=0.1, - fill_color=upcolor, - line_color=upcolor, - y_range_name="volume") - - # Plot red candles - dec = df.open > df.close - p.vbar(x=df.date[dec], - width=candleWidth, - top=df.volume[dec], - bottom=0, - alpha=0.1, - fill_color=downcolor, - line_color=downcolor, - y_range_name="volume") - - -def plotBBands(p, df, color='navy'): - # Plot bbands - p.patch(np.append(df['date'].values, df['date'].values[::-1]), - np.append(df['bbbottom'].values, df['bbtop'].values[::-1]), - color=color, - fill_alpha=0.1, - legend="bband") - # plot sma - p.line(df['date'], df['sma'], color=color, alpha=0.9, legend="sma") - - -def plotMovingAverages(p, df): - # Plot moving averages - p.line(df['date'], df['emaslow'], - color='orange', alpha=0.9, legend="emaslow") - p.line(df['date'], df['emafast'], - color='red', alpha=0.9, legend="emafast") - - -class Charter(object): - """ Retrieves 5min candlestick data for a market and saves it in a mongo - db collection. Can display data in a dataframe or bokeh plot.""" - - def __init__(self, api): - """ - api = poloniex api object - """ - self.api = api - - def __call__(self, pair, frame=False): - """ returns raw chart data from the mongo database, updates/fills the - data if needed, the date column is the '_id' of each candle entry, and - the date column has been removed. Use 'frame' to restrict the amount - of data returned. - Example: 'frame=api.YEAR' will return last years data - """ - # use last pair and period if not specified - if not frame: - frame = self.api.YEAR * 10 - dbcolName = pair + 'chart' - # get db connection - db = MongoClient()['poloniex'][dbcolName] - # get last candle - try: - last = sorted( - list(db.find({"_id": {"$gt": time() - 60 * 20}})), - key=itemgetter('_id'))[-1] - except: - last = False - # no entrys found, get all 5min data from poloniex - if not last: - logger.warning('%s collection is empty!', dbcolName) - new = self.api.returnChartData(pair, - period=60 * 5, - start=time() - self.api.YEAR * 13) - else: - new = self.api.returnChartData(pair, - period=60 * 5, - start=int(last['_id'])) - # add new candles - updateSize = len(new) - logger.info('Updating %s with %s new entrys!', - dbcolName, str(updateSize)) - - # show the progess - for i in range(updateSize): - print("\r%s/%s" % (str(i + 1), str(updateSize)), end=" complete ") - date = new[i]['date'] - del new[i]['date'] - db.update_one({'_id': date}, {"$set": new[i]}, upsert=True) - print('') - - logger.debug('Getting chart data from db') - # return data from db (sorted just in case...) - return sorted( - list(db.find({"_id": {"$gt": time() - frame}})), - key=itemgetter('_id')) - - def dataFrame(self, pair, frame=False, zoom=False, window=120): - """ returns pandas DataFrame from raw db data with indicators. - zoom = passed as the resample(rule) argument to 'merge' candles into a - different timeframe - window = number of candles to use when calculating indicators - """ - data = self.__call__(pair, frame) - # make dataframe - df = pd.DataFrame(data) - # set date column - df['date'] = pd.to_datetime(df["_id"], unit='s') - if zoom: - df.set_index('date', inplace=True) - df = df.resample(rule=zoom, - closed='left', - label='left').apply({'open': 'first', - 'high': 'max', - 'low': 'min', - 'close': 'last', - 'quoteVolume': 'sum', - 'volume': 'sum', - 'weightedAverage': 'mean'}) - df.reset_index(inplace=True) - - # calculate/add sma and bbands - df = bbands(df, window) - # add slow ema - df = ema(df, window, colname='emaslow') - # add fast ema - df = ema(df, int(window // 3.5), colname='emafast') - # add macd - df = macd(df) - # add rsi - df = rsi(df, window // 5) - # add candle body and shadow size - df['bodysize'] = df['close'] - df['open'] - df['shadowsize'] = df['high'] - df['low'] - df['percentChange'] = df['close'].pct_change() - df.dropna(inplace=True) - return df - - def graph(self, pair, frame=False, zoom=False, - window=120, plot_width=1000, min_y_border=40, - border_color="whitesmoke", background_color="white", - background_alpha=0.4, legend_location="top_left", - tools="pan,wheel_zoom,reset"): - """ - Plots market data using bokeh and returns a 2D array for gridplot - """ - df = self.dataFrame(pair, frame, zoom, window) - # - # Start Candlestick Plot ------------------------------------------- - # create figure - candlePlot = figure( - x_axis_type=None, - y_range=(min(df['low'].values) - (min(df['low'].values) * 0.2), - max(df['high'].values) * 1.2), - x_range=(df.tail(int(len(df) // 10)).date.min().timestamp() * 1000, - df.date.max().timestamp() * 1000), - tools=tools, - title=pair, - plot_width=plot_width, - plot_height=int(plot_width // 2.7), - toolbar_location="above") - # add plots - # plot volume - plotVolume(candlePlot, df) - # plot candlesticks - plotCandlesticks(candlePlot, df) - # plot bbands - plotBBands(candlePlot, df) - # plot moving aves - plotMovingAverages(candlePlot, df) - # set legend location - candlePlot.legend.location = legend_location - # set background color - candlePlot.background_fill_color = background_color - candlePlot.background_fill_alpha = background_alpha - # set border color and size - candlePlot.border_fill_color = border_color - candlePlot.min_border_left = min_y_border - candlePlot.min_border_right = candlePlot.min_border_left - # - # Start RSI/MACD Plot ------------------------------------------- - # create a new plot and share x range with candlestick plot - rsiPlot = figure(plot_height=int(candlePlot.plot_height // 2.5), - x_axis_type="datetime", - y_range=(-(max(df['macd'].values) * 2), - max(df['macd'].values) * 2), - x_range=candlePlot.x_range, - plot_width=candlePlot.plot_width, - title=None, - toolbar_location=None) - # plot macd - plotMACD(rsiPlot, df) - # plot rsi - plotRSI(rsiPlot, df) - # set background color - rsiPlot.background_fill_color = candlePlot.background_fill_color - rsiPlot.background_fill_alpha = candlePlot.background_fill_alpha - # set border color and size - rsiPlot.border_fill_color = candlePlot.border_fill_color - rsiPlot.min_border_left = candlePlot.min_border_left - rsiPlot.min_border_right = candlePlot.min_border_right - rsiPlot.min_border_bottom = 20 - # orient x labels - rsiPlot.xaxis.major_label_orientation = pi / 4 - # set legend - rsiPlot.legend.location = legend_location - # set dataframe 'date' as index - df.set_index('date', inplace=True) - # return layout and df - return [[candlePlot], [rsiPlot]], df - - -if __name__ == '__main__': - from poloniex import Poloniex - from bokeh.layouts import gridplot - - logging.basicConfig(level=logging.DEBUG) - logging.getLogger("poloniex").setLevel(logging.INFO) - logging.getLogger('requests').setLevel(logging.ERROR) - - api = Poloniex(jsonNums=float) - - layout, df = Charter(api).graph('USDT_BTC', window=90, - frame=api.YEAR * 12, zoom='1W') - print(df.tail()) - p = gridplot(layout) - show(p) diff --git a/examples/chart/chart.py b/examples/chart/chart.py deleted file mode 100644 index 31b1e79d..00000000 --- a/examples/chart/chart.py +++ /dev/null @@ -1,174 +0,0 @@ -from __future__ import print_function -from time import time -import logging -from operator import itemgetter -from pymongo import MongoClient -import pandas as pd -import numpy as np - - -logger = logging.getLogger(__name__) - - -def rsi(df, window, targetcol='weightedAverage', colname='rsi'): - """ Calculates the Relative Strength Index (RSI) from a pandas dataframe - http://stackoverflow.com/a/32346692/3389859 - """ - series = df[targetcol] - delta = series.diff().dropna() - u = delta * 0 - d = u.copy() - u[delta > 0] = delta[delta > 0] - d[delta < 0] = -delta[delta < 0] - # first value is sum of avg gains - u[u.index[window - 1]] = np.mean(u[:window]) - u = u.drop(u.index[:(window - 1)]) - # first value is sum of avg losses - d[d.index[window - 1]] = np.mean(d[:window]) - d = d.drop(d.index[:(window - 1)]) - rs = u.ewm(com=window - 1, - ignore_na=False, - min_periods=0, - adjust=False).mean() / d.ewm(com=window - 1, - ignore_na=False, - min_periods=0, - adjust=False).mean() - df[colname] = 100 - 100 / (1 + rs) - return df - - -def sma(df, window, targetcol='weightedAverage', colname='sma'): - """ Calculates Simple Moving Average on a 'targetcol' in a pandas dataframe - """ - df[colname] = df[targetcol].rolling(window=window, center=False).mean() - return df - - -def ema(df, window, targetcol='weightedAverage', colname='ema', **kwargs): - """ Calculates Expodential Moving Average on a 'targetcol' in a pandas - dataframe """ - df[colname] = df[targetcol].ewm( - span=window, - min_periods=kwargs.get('min_periods', 1), - adjust=kwargs.get('adjust', True), - ignore_na=kwargs.get('ignore_na', False) - ).mean() - return df - - -def macd(df, fastcol='emafast', slowcol='emaslow', colname='macd'): - """ Calculates the differance between 'fastcol' and 'slowcol' in a pandas - dataframe """ - df[colname] = df[fastcol] - df[slowcol] - return df - - -def bbands(df, window, targetcol='weightedAverage', stddev=2.0): - """ Calculates Bollinger Bands for 'targetcol' of a pandas dataframe """ - if not 'sma' in df: - df = sma(df, window, targetcol) - df['bbtop'] = df['sma'] + stddev * df[targetcol].rolling( - min_periods=window, - window=window, - center=False).std() - df['bbbottom'] = df['sma'] - stddev * df[targetcol].rolling( - min_periods=window, - window=window, - center=False).std() - df['bbrange'] = df['bbtop'] - df['bbbottom'] - df['bbpercent'] = ((df[targetcol] - df['bbbottom']) / df['bbrange']) - 0.5 - return df - - -class Chart(object): - """ Saves and retrieves chart data to/from mongodb. It saves the chart - based on candle size, and when called, it will automaticly update chart - data if needed using the timestamp of the newest candle to determine how - much data needs to be updated """ - - def __init__(self, api, pair, **kwargs): - """ - api = poloniex api object - pair = market pair - period = time period of candles (default: 5 Min) - """ - self.pair = pair - self.api = api - self.period = kwargs.get('period', self.api.MINUTE * 5) - self.db = MongoClient()['poloniex']['%s_%s_chart' % - (self.pair, str(self.period))] - - def __call__(self, size=0): - """ Returns raw data from the db, updates the db if needed """ - # get old data from db - old = sorted(list(self.db.find()), key=itemgetter('_id')) - try: - # get last candle - last = old[-1] - except: - # no candle found, db collection is empty - last = False - # no entrys found, get last year of data to fill the db - if not last: - logger.warning('%s collection is empty!', - '%s_%s_chart' % (self.pair, str(self.period))) - new = self.api.returnChartData(self.pair, - period=self.period, - start=time() - self.api.YEAR) - # we have data in db already - else: - new = self.api.returnChartData(self.pair, - period=self.period, - start=int(last['_id'])) - # add new candles - updateSize = len(new) - logger.info('Updating %s with %s new entrys!', - self.pair + '-' + str(self.period), str(updateSize)) - # show progress - for i in range(updateSize): - print("\r%s/%s" % (str(i + 1), str(updateSize)), end=" complete ") - date = new[i]['date'] - del new[i]['date'] - self.db.update_one({'_id': date}, {"$set": new[i]}, upsert=True) - print('') - logger.debug('Getting chart data from db') - # return data from db - return sorted(list(self.db.find()), key=itemgetter('_id'))[-size:] - - def dataFrame(self, size=0, window=120): - # get data from db - data = self.__call__(size) - # make dataframe - df = pd.DataFrame(data) - # format dates - df['date'] = [pd.to_datetime(c['_id'], unit='s') for c in data] - # del '_id' - del df['_id'] - # set 'date' col as index - df.set_index('date', inplace=True) - # calculate/add sma and bbands - df = bbands(df, window) - # add slow ema - df = ema(df, window // 2, colname='emaslow') - # add fast ema - df = ema(df, window // 4, colname='emafast') - # add macd - df = macd(df) - # add rsi - df = rsi(df, window // 5) - # add candle body and shadow size - df['bodysize'] = df['open'] - df['close'] - df['shadowsize'] = df['high'] - df['low'] - # add percent change - df['percentChange'] = df['close'].pct_change() - return df - -if __name__ == '__main__': - from poloniex import Poloniex - logging.basicConfig(level=logging.DEBUG) - logging.getLogger("poloniex").setLevel(logging.INFO) - logging.getLogger('requests').setLevel(logging.ERROR) - api = Poloniex(jsonNums=float) - df = Chart(api, 'BTC_ETH').dataFrame() - df.dropna(inplace=True) - print(df.tail(3)[['open', 'close', 'percentChange']]) diff --git a/examples/chart/mongoDataframe.py b/examples/chart/mongoDataframe.py new file mode 100644 index 00000000..1592af79 --- /dev/null +++ b/examples/chart/mongoDataframe.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# https://github.com/s4w3d0ff/python-poloniex +# BTC: 1A7K4kgXLSSzvDRjvoGwomvhrNU4CKezEp +# +# Copyright (C) 2019 https://github.com/s4w3d0ff +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import poloniex +import tqdm +import pymongo +import pandas as pd + +import logging +from time import time, gmtime, strftime, strptime +from calendar import timegm + + +DB = pymongo.MongoClient() + + +def epoch2UTCstr(timestamp=False, fmat="%Y-%m-%d %H:%M:%S"): + """ + - takes epoch timestamp + - returns UTC formated string + """ + if not timestamp: + timestamp = time() + return strftime(fmat, gmtime(timestamp)) + +def UTCstr2epoch(datestr=False, fmat="%Y-%m-%d %H:%M:%S"): + """ + - takes UTC date string + - returns epoch + """ + if not datestr: + datestr = epoch2UTCstr() + return timegm(strptime(datestr, fmat)) + +def zoomOHLC(df, zoom): + """ Resamples a ohlc df """ + df.reset_index(inplace=True) + df.set_index('date', inplace=True) + df = df.resample(rule=zoom, + closed='left', + label='left').apply({'_id': 'first', + 'open': 'first', + 'high': 'max', + 'low': 'min', + 'close': 'last', + 'quoteVolume': 'sum', + 'volume': 'sum', + 'weightedAverage': 'mean'}) + df.reset_index(inplace=True) + return df.set_index('_id') + +def getDatabase(db): + """ Returns a mongodb database """ + return DB[db] + +def getLastEntry(db): + """ Get the last entry of a collection """ + return db.find_one(sort=[('_id', pymongo.DESCENDING)]) + +def updateChartData(db, data): + """ Upserts chart data into db with a tqdm wrapper. """ + for i in tqdm.trange(len(data)): + db.update_one({'_id': data[i]['date']}, { + "$set": data[i]}, upsert=True) + +def getChartDataFrame(db, start): + """ + Gets the last collection entrys starting from 'start' and puts them in a df + """ + try: + df = pd.DataFrame(list(db.find({"_id": {"$gt": start}}))) + # set date column to datetime + df['date'] = pd.to_datetime(df["_id"], unit='s') + df.set_index('_id', inplace=True) + return df + except Exception as e: + logger.exception(e) + return False + + +class CPoloniex(poloniex.Poloniex): + def __init__(self, *args, **kwargs): + super(CPoloniex, self).__init__(*args, **kwargs) + if not 'jsonNums' in kwargs: + self.jsonNums = float + self.db = getDatabase('poloniex') + + def chartDataFrame(self, pair, frame=172800, zoom=False): + """ returns chart data in a dataframe from mongodb, updates/fills the + data, the date column is the '_id' of each candle entry. Use 'frame' to + restrict the amount of data returned. + Example: 'frame=self.YEAR' will return last years data + """ + dbcolName = pair.upper() + '-chart' + + # get db collection + db = self.db[dbcolName] + + # get last candle data + last = getLastEntry(db) + + # no entrys found, get all 5min data from poloniex + if not last: + self.logger.warning('%s collection is empty!', dbcolName) + last = { + '_id': UTCstr2epoch("2015-01-01", fmat="%Y-%m-%d") + } + + stop = int(last['_id']) + start = time() + end = time() + flag = True + while not int(stop) == int(start) and flag: + # get 3 months of data at a time + start -= self.MONTH * 3 + + # dont go past 'stop' + if start < stop: + start = stop + + # get needed data + self.logger.debug('Getting %s - %s %s candles from Poloniex...', + epoch2UTCstr(start), epoch2UTCstr(end), pair) + new = self.returnChartData(pair, + period=60 * 5, + start=start, + end=end) + + # stop if data has stopped comming in + if len(new) == 1: + flag = False + + # add new candles + self.logger.debug( + 'Updating %s database with %s entrys...', pair, str(len(new)) + ) + updateChartData(db, new) + + # make new end the old start + end = start + + # make dataframe + self.logger.debug('Getting %s chart data from db', pair) + df = getChartDataFrame(db, time() - frame) + + # adjust candle period 'zoom' + if zoom: + df = zoomOHLC(df, zoom) + + return df + +if __name__ == '__main__': + logging.basicConfig() + polo = CPoloniex() + polo.logger.setLevel(logging.DEBUG) + print(polo.chartDataFrame(pair='BTC_LTC', frame=polo.WEEK, zoom='3H')) diff --git a/examples/websocket/mongoTicker.py b/examples/websocket/mongoTicker.py deleted file mode 100644 index 8330c167..00000000 --- a/examples/websocket/mongoTicker.py +++ /dev/null @@ -1,51 +0,0 @@ -import poloniex -from pymongo import MongoClient # pip install pymongo - -class TickPolo(poloniex.PoloniexSocketed): - def __init__(self, *args, **kwargs): - super(TickPolo, self).__init__(*args, **kwargs) - self.db = MongoClient().poloniex['ticker'] - self.db.drop() - tick = self.returnTicker() - self._ids = {market: int(tick[market]['id']) for market in tick} - for market in tick: - self.db.update_one( - {'_id': market}, - {'$set': - {item: float(tick[market][item]) for item in tick[market]} - }, - upsert=True) - - def ticker(self, market=None): - '''returns ticker data saved from websocket ''' - if not self._t or not self._running: - self.logger.error('Websocket is not running!') - return self.returnTicker() - if market: - return self.db.find_one({'_id': market}) - return list(self.db.find()) - - def on_ticker(self, data): - data = [float(dat) for dat in data] - self.db.update_one( - {"id": int(data[0])}, - {"$set": {'last': data[1], - 'lowestAsk': data[2], - 'highestBid': data[3], - 'percentChange': data[4], - 'baseVolume': data[5], - 'quoteVolume': data[6], - 'isFrozen': int(data[7]), - 'high24hr': data[8], - 'low24hr': data[9] - }}, - upsert=True) - -if __name__ == '__main__': - polo = TickPolo() - polo.startws(['ticker']) - for i in range(3): - print(polo.ticker('BTC_LTC')) - poloniex.sleep(10) - print(polo.ticker()) - polo.stopws(3) diff --git a/poloniex/__init__.py b/poloniex/__init__.py index 2105ec70..3a9250ec 100644 --- a/poloniex/__init__.py +++ b/poloniex/__init__.py @@ -86,6 +86,7 @@ 'buy', 'sell', 'cancelOrder', + 'cancelAllOrders', 'moveOrder', 'withdraw', 'returnFeeInfo',