diff --git a/CHANGELOG.md b/CHANGELOG.md index 00faf315..3bddabf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ Upgrade version: Upgrade library dependancies (if required): - python3 -m pip install -r requirements.txt -U +## [2.38.0] - 2021-06-23 + +### Changed + +-- Added statstartdate flag to ignore trades before a given date in stats function +-- Added statgroup flag to merge stats of multiple currency pairs +-- Fixed stats for coinbase pro + ## [2.37.3] - 2021-06-22 ### Changed diff --git a/README.md b/README.md index 0865556b..5501cdda 100644 --- a/README.md +++ b/README.md @@ -455,6 +455,32 @@ Some of you may have been helping test the new code for a few months in the "bin Please note you need to be using Python 3.9.x or greater. The previous bot version only required Python 3.x. +## Stats Module + +To keep track of the bots performance over time you can run the stats module. e.g. + + python3 pycryptobot.py --stats + +This will analyse all the completed buy/sell trade pairs to give stats on todays trades, the trades over the last 7 days, the trades over the last 30 days, and all-time trades. + +An optional flag of --statstartdate can be given to ignore all trades that happened before a specified date. The date must be of the format: yyyy-mm-dd. e.g. + + python3 pycryptobot.py --stats --statstartdate 2021-6-01 + +To get the stats from all your bots, another optional flag of --statgroup can be used. This takes a list of markets and merges the results into one output. e.g. + + python3 pycryptobot.py --stats --statgroup BTCGBP ETHGBP ADAGBP + +or via the config.json file e.g. + + "config": { + .... + "stats": 1, + "statgroup": ["BTCGBP", "ETHGBP", "ADAGBP"], + .... + } +Note: --statgroup only accepts a group of markets if the quote currency (in this example GBP) is the same. + ## Upgrading the bots I push updates regularly and it's best to always be running the latest code. In each bot directory make sure you run this regularly. diff --git a/models/PyCryptoBot.py b/models/PyCryptoBot.py index f2af9626..86682f03 100644 --- a/models/PyCryptoBot.py +++ b/models/PyCryptoBot.py @@ -55,6 +55,8 @@ def parse_arguments(): parser.add_argument('--sellatresistance', action="store_true", help="sell at resistance or upper fibonacci band") parser.add_argument('--autorestart', action="store_true", help="Auto restart the bot in case of exception") parser.add_argument('--stats', action="store_true", help="display summary of completed trades") + parser.add_argument('--statgroup', nargs='+', help="add multiple currency pairs to merge stats") + parser.add_argument('--statstartdate', type=str, help="trades before this date are ignored in stats function e.g 2021-01-15") # disable defaults parser.add_argument('--disablebullonly', action="store_true", help="disable only buying in bull market") @@ -164,6 +166,8 @@ def __init__(self, exchange='', filename='config.json'): self.sellatresistance = False self.autorestart = False self.stats = False + self.statgroup = None + self.statstartdate = None self.disablebullonly = False self.disablebuynearhigh = False diff --git a/models/Stats.py b/models/Stats.py index 1608d930..191071f0 100644 --- a/models/Stats.py +++ b/models/Stats.py @@ -8,126 +8,176 @@ class Stats(): def __init__(self, app: PyCryptoBot=None, account: TradingAccount=None) -> None: self.app = app self.account = account + self.order_pairs = [] + self.fiat_currency = None - def show(self): - if self.app.getStats(): - # get completed live orders - self.app.setLive(1) - self.orders = self.account.getOrders(self.app.getMarket(), '', 'done') - - # get buy/sell pairs (merge as necessary) - order_pairs = [] - last_order = None - # pylint: disable=unused-variable - for index, row in self.orders.iterrows(): - time = row['created_at'].to_pydatetime() - if row['action'] == 'buy': - if last_order in ['sell', None]: - last_order = 'buy' - order_pairs.append({'buy': {'time':time, 'size': row['size']}, 'sell': None}) + def get_data(self, market): + # get completed live orders + self.app.setLive(1) + self.orders = self.account.getOrders(market, '', 'done') + self.app.setMarket(market) + if self.fiat_currency != None: + if self.app.getQuoteCurrency() != self.fiat_currency: + raise ValueError("all currency pairs in statgroup must use the same quote currency") + else: + self.fiat_currency = self.app.getQuoteCurrency() + + # get buy/sell pairs (merge as necessary) + last_order = None + # pylint: disable=unused-variable + for index, row in self.orders.iterrows(): + time = row['created_at'].to_pydatetime() + if row['action'] == 'buy': + if self.app.exchange == 'coinbasepro': + amount = row['filled'] * row['price'] + if last_order in ['sell', None]: + last_order = 'buy' + if self.app.exchange == 'coinbasepro': + self.order_pairs.append({'buy': {'time':time, 'size': amount, 'buy_fees': row['fees']}, 'sell': None}) else: - last_order = 'buy' - order_pairs[-1]['buy']['size'] += row['size'] + self.order_pairs.append({'buy': {'time':time, 'size': row['size']}, 'sell': None}) else: - if last_order == None: # first order is a sell (no pair) - continue - if last_order == 'buy': - last_order = 'sell' - order_pairs[-1]['sell'] = {'time':time, 'size': row['size']} + if self.app.exchange == 'coinbasepro': + self.order_pairs[-1]['buy']['size'] += amount + self.order_pairs[-1]['buy']['buy_fees'] += row['fees'] else: - order_pairs[-1]['sell']['size'] += row['size'] + self.order_pairs[-1]['buy']['size'] += row['size'] + else: + if self.app.exchange == 'coinbasepro': + amount = (row['filled'] * row['price']) + else: + amount = row['size'] + if last_order == None: # first order is a sell (no pair) + continue + if last_order == 'buy': + last_order = 'sell' + if self.app.exchange == 'coinbasepro': + self.order_pairs[-1]['sell'] = {'time':time, 'size': amount, 'sell_fees': row['fees']} + else: + self.order_pairs[-1]['sell'] = {'time':time, 'size': amount} + else: + if self.app.exchange == 'coinbasepro': + self.order_pairs[-1]['sell']['size'] += amount + self.order_pairs[-1]['sell']['sell_fees'] += row['fees'] + else: + self.order_pairs[-1]['sell']['size'] += amount + # remove open trade + if len(self.order_pairs) > 0: + if self.order_pairs[-1]['sell'] == None: + self.order_pairs = self.order_pairs[:-1] - # remove open trade - if len(order_pairs) > 0: - if order_pairs[-1]['sell'] == None: - order_pairs = order_pairs[:-1] + def show(self): + if self.app.getStats(): + if self.app.statgroup: + for currency in self.app.statgroup: + self.get_data(currency) + else: + self.get_data(self.app.getMarket()) + self.data_display() - # get % gains and delta - for pair in order_pairs: + def data_display(self): + # get % gains and delta + for pair in self.order_pairs: + if self.app.exchange == 'coinbasepro': + pair['delta'] = pair['sell']['size'] - (pair['buy']['size'] + pair['buy']['buy_fees'] + pair['sell']['sell_fees']) + pair['gain'] = (pair['delta'] / pair['buy']['size']) * 100 + else: pair['gain'] = ((pair['sell']['size'] - pair['buy']['size']) / pair['buy']['size']) * 100 pair['delta'] = pair['sell']['size'] - pair['buy']['size'] + + # get day/week/month/all time totals + totals = {'today': [], 'week': [], 'month': [], 'all_time': []} + today = datetime.today().date() + lastweek = today - timedelta(days=7) + lastmonth = today - timedelta(days=30) + if self.app.statstartdate: + try: + start = datetime.strptime(self.app.statstartdate, '%Y-%m-%d').date() + except: + raise ValueError("format of --statstartdate must be yyyy-mm-dd") + else: + start = None + for pair in self.order_pairs: + if start: + if pair['sell']['time'].date() < start: + continue + totals['all_time'].append(pair) + if pair['sell']['time'].date() == today: + totals['today'].append(pair) + if pair['sell']['time'].date() > lastweek: + totals['week'].append(pair) + if pair['sell']['time'].date() > lastmonth: + totals['month'].append(pair) + + # prepare data for output + today_per = [x['gain'] for x in totals['today']] + week_per = [x['gain'] for x in totals['week']] + month_per = [x['gain'] for x in totals['month']] + all_time_per = [x['gain'] for x in totals['all_time']] + today_gain = [x['delta'] for x in totals['today']] + week_gain = [x['delta'] for x in totals['week']] + month_gain = [x['delta'] for x in totals['month']] + all_time_gain = [x['delta'] for x in totals['all_time']] - # get day/week/month/all time totals - totals = {'today': [], 'week': [], 'month': [], 'all_time': []} - today = datetime.today().date() - lastweek = today - timedelta(days=7) - lastmonth = today - timedelta(days=30) - for pair in order_pairs: - totals['all_time'].append(pair) - if pair['sell']['time'].date() == today: - totals['today'].append(pair) - if pair['sell']['time'].date() > lastweek: - totals['week'].append(pair) - if pair['sell']['time'].date() > lastmonth: - totals['month'].append(pair) - - # prepare data for output - today_per = [x['gain'] for x in totals['today']] - week_per = [x['gain'] for x in totals['week']] - month_per = [x['gain'] for x in totals['month']] - all_time_per = [x['gain'] for x in totals['all_time']] - today_gain = [x['delta'] for x in totals['today']] - week_gain = [x['delta'] for x in totals['week']] - month_gain = [x['delta'] for x in totals['month']] - all_time_gain = [x['delta'] for x in totals['all_time']] - - if len(today_per) > 0: - today_delta = [(x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['today']] - today_delta = timedelta(seconds=int(sum(today_delta) / len(today_delta))) - else: today_delta = '0:0:0' - if len(week_per) > 0: - week_delta = [(x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['week']] - week_delta = timedelta(seconds=int(sum(week_delta) / len(week_delta))) - else: week_delta = '0:0:0' - if len(month_per) > 0: - month_delta = [(x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['month']] - month_delta = timedelta(seconds=int(sum(month_delta) / len(month_delta))) - else: month_delta = '0:0:0' - if len(all_time_per) > 0: - all_time_delta = [(x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['all_time']] - all_time_delta = timedelta(seconds=int(sum(all_time_delta) / len(all_time_delta))) - else: all_time_delta = '0:0:0' + if len(today_per) > 0: + today_delta = [(x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['today']] + today_delta = timedelta(seconds=int(sum(today_delta) / len(today_delta))) + else: today_delta = '0:0:0' + if len(week_per) > 0: + week_delta = [(x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['week']] + week_delta = timedelta(seconds=int(sum(week_delta) / len(week_delta))) + else: week_delta = '0:0:0' + if len(month_per) > 0: + month_delta = [(x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['month']] + month_delta = timedelta(seconds=int(sum(month_delta) / len(month_delta))) + else: month_delta = '0:0:0' + if len(all_time_per) > 0: + all_time_delta = [(x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['all_time']] + all_time_delta = timedelta(seconds=int(sum(all_time_delta) / len(all_time_delta))) + else: all_time_delta = '0:0:0' - # popular currencies - symbol = self.app.getQuoteCurrency() - if symbol in ['USD', 'AUD', 'CAD', 'SGD', 'NZD']: symbol = '$' - if symbol == 'EUR': symbol = '€' - if symbol == 'GBP': symbol = '£' + # popular currencies + symbol = self.app.getQuoteCurrency() + if symbol in ['USD', 'AUD', 'CAD', 'SGD', 'NZD']: symbol = '$' + if symbol == 'EUR': symbol = '€' + if symbol == 'GBP': symbol = '£' - today_sum = symbol + ' {:.2f}'.format(round(sum(today_gain), 2)) if len(today_gain) > 0 else symbol + ' 0.00' - week_sum = symbol + ' {:.2f}'.format(round(sum(week_gain), 2)) if len(week_gain) > 0 else symbol + ' 0.00' - month_sum= symbol + ' {:.2f}'.format(round(sum(month_gain), 2)) if len(month_gain) > 0 else symbol + ' 0.00' - all_time_sum = symbol + ' {:.2f}'.format(round(sum(all_time_gain), 2)) if len(all_time_gain) > 0 else symbol + ' 0.00' - today_percent = str(round(sum(today_per), 4)) + '%' if len(today_per) > 0 else '0.0000%' - week_percent = str(round(sum(week_per), 4)) + '%' if len(week_per) > 0 else '0.0000%' - month_percent = str(round(sum(month_per), 4)) + '%' if len(month_per) > 0 else '0.0000%' - all_time_percent = str(round(sum(all_time_per), 4)) + '%' if len(all_time_per) > 0 else '0.0000%' + today_sum = symbol + ' {:.2f}'.format(round(sum(today_gain), 2)) if len(today_gain) > 0 else symbol + ' 0.00' + week_sum = symbol + ' {:.2f}'.format(round(sum(week_gain), 2)) if len(week_gain) > 0 else symbol + ' 0.00' + month_sum= symbol + ' {:.2f}'.format(round(sum(month_gain), 2)) if len(month_gain) > 0 else symbol + ' 0.00' + all_time_sum = symbol + ' {:.2f}'.format(round(sum(all_time_gain), 2)) if len(all_time_gain) > 0 else symbol + ' 0.00' + today_percent = str(round(sum(today_per), 4)) + '%' if len(today_per) > 0 else '0.0000%' + week_percent = str(round(sum(week_per), 4)) + '%' if len(week_per) > 0 else '0.0000%' + month_percent = str(round(sum(month_per), 4)) + '%' if len(month_per) > 0 else '0.0000%' + all_time_percent = str(round(sum(all_time_per), 4)) + '%' if len(all_time_per) > 0 else '0.0000%' - trades = 'Number of Completed Trades:' - gains = 'Percentage Gains:' - aver = 'Average Time Held (H:M:S):' - success = 'Total Profit/Loss:' - width = 30 + trades = 'Number of Completed Trades:' + gains = 'Percentage Gains:' + aver = 'Average Time Held (H:M:S):' + success = 'Total Profit/Loss:' + width = 30 + if self.app.statgroup: header = 'MERGE' + else: header = self.app.getMarket() - Logger.info(f'------------- TODAY : {self.app.getMarket()} --------------') - Logger.info(trades + ' ' * (width-len(trades)) + str(len(today_per))) - Logger.info(gains + ' ' * (width-len(gains)) + today_percent) - Logger.info(aver + ' ' * (width-len(aver)) + str(today_delta)) - Logger.info(success + ' ' * (width-len(success)) + today_sum) - Logger.info(f'\n-------------- WEEK : {self.app.getMarket()} --------------') - Logger.info(trades + ' ' * (width-len(trades)) + str(len(week_per))) - Logger.info(gains + ' ' * (width-len(gains)) + week_percent) - Logger.info(aver + ' ' * (width-len(aver)) + str(week_delta)) - Logger.info(success + ' ' * (width-len(success)) + week_sum) - Logger.info(f'\n------------- MONTH : {self.app.getMarket()} --------------') - Logger.info(trades + ' ' * (width-len(trades)) + str(len(month_per))) - Logger.info(gains + ' ' * (width-len(gains)) + month_percent) - Logger.info(aver + ' ' * (width-len(aver)) + str(month_delta)) - Logger.info(success + ' ' * (width-len(success)) + month_sum) - Logger.info(f'\n------------ ALL TIME : {self.app.getMarket()} ------------') - Logger.info(trades + ' ' * (width-len(trades)) + str(len(all_time_per))) - Logger.info(gains + ' ' * (width-len(gains)) + all_time_percent) - Logger.info(aver + ' ' * (width-len(aver)) + str(all_time_delta)) - Logger.info(success + ' ' * (width-len(success)) + all_time_sum) + Logger.info(f'------------- TODAY : {header} --------------') + Logger.info(trades + ' ' * (width-len(trades)) + str(len(today_per))) + Logger.info(gains + ' ' * (width-len(gains)) + today_percent) + Logger.info(aver + ' ' * (width-len(aver)) + str(today_delta)) + Logger.info(success + ' ' * (width-len(success)) + today_sum) + Logger.info(f'\n-------------- WEEK : {header} --------------') + Logger.info(trades + ' ' * (width-len(trades)) + str(len(week_per))) + Logger.info(gains + ' ' * (width-len(gains)) + week_percent) + Logger.info(aver + ' ' * (width-len(aver)) + str(week_delta)) + Logger.info(success + ' ' * (width-len(success)) + week_sum) + Logger.info(f'\n------------- MONTH : {header} --------------') + Logger.info(trades + ' ' * (width-len(trades)) + str(len(month_per))) + Logger.info(gains + ' ' * (width-len(gains)) + month_percent) + Logger.info(aver + ' ' * (width-len(aver)) + str(month_delta)) + Logger.info(success + ' ' * (width-len(success)) + month_sum) + Logger.info(f'\n------------ ALL TIME : {header} ------------') + Logger.info(trades + ' ' * (width-len(trades)) + str(len(all_time_per))) + Logger.info(gains + ' ' * (width-len(gains)) + all_time_percent) + Logger.info(aver + ' ' * (width-len(aver)) + str(all_time_delta)) + Logger.info(success + ' ' * (width-len(success)) + all_time_sum) - sys.exit() \ No newline at end of file + sys.exit() \ No newline at end of file diff --git a/models/config/default_parser.py b/models/config/default_parser.py index 326f8407..9392d8e4 100644 --- a/models/config/default_parser.py +++ b/models/config/default_parser.py @@ -119,9 +119,13 @@ def defaultConfigParse(app, config): if isinstance(config['stats'], int): if bool(config['stats']): app.stats = True + if 'statgroup' in config: + app.statgroup = config['statgroup'] + if 'statstartdate' in config: + app.statstartdate = config['statstartdate'] else: raise TypeError('stats must be of type int') - + if 'sellatloss' in config: if isinstance(config['sellatloss'], int): if config['sellatloss'] in [ 0, 1 ]: