From f223f89f8a44e903ec09d40f5760f7f482c9cbe8 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 27 Aug 2017 15:37:11 +0300 Subject: [PATCH 1/9] add pylint rule file that allows to use logger in lowercase --- .pylintrc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..eddcbf7a5 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +[BASIC] +good-names=logger From e229ba0ba853268d0c5cb1c908350e7617124569 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 27 Aug 2017 15:38:20 +0300 Subject: [PATCH 2/9] rename conf to CONFIG as it is a constant --- main.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 7ee13f031..6ed2e8319 100755 --- a/main.py +++ b/main.py @@ -28,8 +28,8 @@ __license__ = "GPLv3" __version__ = "0.8.0" -conf = get_conf() -api_wrapper = get_exchange_api(conf) +CONFIG = get_conf() +api_wrapper = get_exchange_api(CONFIG) @synchronized @@ -85,10 +85,10 @@ class TradeThread(threading.Thread): """ # Query trades from persistence layer trades = Trade.query.filter(Trade.is_open.is_(True)).all() - if len(trades) < conf['max_open_trades']: + if len(trades) < CONFIG['max_open_trades']: # Create entity and execute trade try: - Session.add(create_trade(float(conf['stake_amount']), api_wrapper.exchange)) + Session.add(create_trade(float(CONFIG['stake_amount']), api_wrapper.exchange)) except ValueError: logger.exception('ValueError during trade creation') for trade in trades: @@ -154,7 +154,7 @@ def handle_trade(trade): currency = trade.pair.split('_')[1] balance = api_wrapper.get_balance(currency) - for duration, threshold in sorted(conf['minimal_roi'].items()): + for duration, threshold in sorted(CONFIG['minimal_roi'].items()): duration, threshold = float(duration), float(threshold) # Check if time matches and current rate is above threshold time_diff = (datetime.utcnow() - trade.open_date).total_seconds() / 60 @@ -184,10 +184,10 @@ def create_trade(stake_amount: float, exchange): :param exchange: exchange to use """ logger.info('Creating new trade with stake_amount: {} ...'.format(stake_amount)) - whitelist = conf[exchange.name.lower()]['pair_whitelist'] + whitelist = CONFIG[exchange.name.lower()]['pair_whitelist'] # Check if btc_amount is fulfilled - if api_wrapper.get_balance(conf['stake_currency']) < stake_amount: - raise ValueError('stake amount is not fulfilled (currency={}'.format(conf['stake_currency'])) + if api_wrapper.get_balance(CONFIG['stake_currency']) < stake_amount: + raise ValueError('stake amount is not fulfilled (currency={}'.format(CONFIG['stake_currency'])) # Remove currently opened and latest pairs from whitelist trades = Trade.query.filter(Trade.is_open.is_(True)).all() From e82f6e0da84ba49473cfd9558d359b1ae770ea0b Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 27 Aug 2017 15:47:09 +0300 Subject: [PATCH 3/9] fix using JSONDecodeError in two different ways --- main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.py b/main.py index 6ed2e8319..9245ed11b 100755 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -import json import logging import threading import time @@ -70,7 +69,7 @@ class TradeThread(threading.Thread): finally: Session.flush() time.sleep(25) - except (RuntimeError, json.decoder.JSONDecodeError) as e: + except (RuntimeError, JSONDecodeError) as e: TelegramHandler.send_msg('*Status:* Got RuntimeError: ```\n{}\n```'.format(traceback.format_exc())) logger.exception('RuntimeError. Stopping trader ...') finally: From 02b4929e3f221bc4f1f1cf17bf23837efdcaa494 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Sun, 27 Aug 2017 18:15:00 +0300 Subject: [PATCH 4/9] Add docker support --- Dockerfile | 9 +++++++++ README.md | 8 ++++++++ 2 files changed, 17 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..f43f12e16 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.6.2 + +RUN mkdir -p /freqtrade +WORKDIR /freqtrade + +ADD ./requirements.txt /freqtrade/requirements.txt +RUN pip install -r requirements.txt +ADD . /freqtrade +CMD python main.py diff --git a/README.md b/README.md index 73a4ce644..6479bf8ea 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,11 @@ $ source .env/bin/activate $ pip install -r requirements.txt $ ./main.py ``` + + +#### Docker +``` +$ cd freqtrade +$ docker build -t freqtrade . +$ docker run --rm -it freqtrade +``` From 06d92042fd54a2fb13aa3c9316c10159150a87c6 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 27 Aug 2017 16:40:27 +0300 Subject: [PATCH 5/9] avoid single char variable names --- analyze.py | 8 ++++---- main.py | 4 ++-- rpc/telegram.py | 4 ++-- utils.py | 18 +++++++++--------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/analyze.py b/analyze.py index 2e5945b54..bc14b439e 100644 --- a/analyze.py +++ b/analyze.py @@ -107,8 +107,8 @@ def plot_dataframe(dataframe, pair): import matplotlib.pyplot as plt # Three subplots sharing x axe - f, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) - f.suptitle(pair, fontsize=14, fontweight='bold') + fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) + fig.suptitle(pair, fontsize=14, fontweight='bold') ax1.plot(dataframe.index.values, dataframe['close'], label='close') ax1.plot(dataframe.index.values, dataframe['close_30_ema'], label='EMA(60)') ax1.plot(dataframe.index.values, dataframe['close_90_ema'], label='EMA(120)') @@ -129,8 +129,8 @@ def plot_dataframe(dataframe, pair): # Fine-tune figure; make subplots close to each other and hide x ticks for # all but bottom plot. - f.subplots_adjust(hspace=0) - plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False) + fig.subplots_adjust(hspace=0) + plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False) plt.show() diff --git a/main.py b/main.py index 9245ed11b..7a60a73f8 100755 --- a/main.py +++ b/main.py @@ -63,8 +63,8 @@ class TradeThread(threading.Thread): while not _should_stop: try: self._process() - except (ConnectionError, JSONDecodeError, ValueError) as e: - msg = 'Got {} during _process()'.format(e.__class__.__name__) + except (ConnectionError, JSONDecodeError, ValueError) as error: + msg = 'Got {} during _process()'.format(error.__class__.__name__) logger.exception(msg) finally: Session.flush() diff --git a/rpc/telegram.py b/rpc/telegram.py index 654570e19..1c6c1225f 100644 --- a/rpc/telegram.py +++ b/rpc/telegram.py @@ -304,10 +304,10 @@ class TelegramHandler(object): bot = bot or TelegramHandler.get_updater(conf).bot try: bot.send_message(conf['telegram']['chat_id'], msg, parse_mode=parse_mode) - except NetworkError as e: + except NetworkError as error: # Sometimes the telegram server resets the current connection, # if this is the case we send the message again. - logger.warning('Got Telegram NetworkError: {}! trying one more time'.format(e.message)) + logger.warning('Got Telegram NetworkError: {}! trying one more time'.format(error.message)) bot.send_message(conf['telegram']['chat_id'], msg, parse_mode=parse_mode) except Exception: logger.exception('Exception occurred within Telegram API') diff --git a/utils.py b/utils.py index 304c55e1e..e87119b5b 100644 --- a/utils.py +++ b/utils.py @@ -17,8 +17,8 @@ def get_conf(filename='config.json'): """ global _cur_conf if not _cur_conf: - with open(filename) as fp: - _cur_conf = json.load(fp) + with open(filename) as file: + _cur_conf = json.load(file) validate_conf(_cur_conf) return _cur_conf @@ -40,11 +40,11 @@ def validate_conf(conf): if not isinstance(conf.get('minimal_roi'), dict): raise ValueError('minimal_roi must be a dict') - for i, (minutes, threshold) in enumerate(conf.get('minimal_roi').items()): + for index, (minutes, threshold) in enumerate(conf.get('minimal_roi').items()): if not isinstance(minutes, str): - raise ValueError('minimal_roi[{}].key must be a string'.format(i)) + raise ValueError('minimal_roi[{}].key must be a string'.format(index)) if not isinstance(threshold, float): - raise ValueError('minimal_roi[{}].value must be a float'.format(i)) + raise ValueError('minimal_roi[{}].value must be a float'.format(index)) if conf.get('telegram'): telegram = conf.get('telegram') @@ -95,7 +95,7 @@ def validate_bittrex_pairs(pairs): data = Bittrex(None, None).get_markets() if not data['success']: raise RuntimeError('BITTREX: {}'.format(data['message'])) - available_markets = [m['MarketName'].replace('-', '_')for m in data['result']] - for p in pairs: - if p not in available_markets: - raise ValueError('Invalid pair: {}'.format(p)) + available_markets = [market['MarketName'].replace('-', '_')for market in data['result']] + for pair in pairs: + if pair not in available_markets: + raise ValueError('Invalid pair: {}'.format(pair)) From 98f9d7cc21e00a8b47e053051a92ea044c43b215 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 27 Aug 2017 16:50:59 +0300 Subject: [PATCH 6/9] use lazy-evaluating form of logger (pylint W1202) --- analyze.py | 2 +- main.py | 12 ++++++------ rpc/telegram.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/analyze.py b/analyze.py index bc14b439e..1e53ffdd5 100644 --- a/analyze.py +++ b/analyze.py @@ -89,7 +89,7 @@ def get_buy_signal(pair): return False signal = latest['underpriced'] == 1 - logger.debug('buy_trigger: {} (pair={}, signal={})'.format(latest['date'], pair, signal)) + logger.debug('buy_trigger: %s (pair=%s, signal=%s)', latest['date'], pair, signal) return signal diff --git a/main.py b/main.py index 7a60a73f8..d6e6b6701 100755 --- a/main.py +++ b/main.py @@ -109,7 +109,7 @@ class TradeThread(threading.Thread): trade.open_order_id = None # Check if this trade can be marked as closed if close_trade_if_fulfilled(trade): - logger.info('No open orders found and trade is fulfilled. Marking {} as closed ...'.format(trade)) + logger.info('No open orders found and trade is fulfilled. Marking %s as closed ...', trade) continue # Check if we can sell our current pair @@ -144,7 +144,7 @@ def handle_trade(trade): if not trade.is_open: raise ValueError('attempt to handle closed trade: {}'.format(trade)) - logger.debug('Handling open trade {} ...'.format(trade)) + logger.debug('Handling open trade %s ...', trade) # Get current rate current_rate = api_wrapper.get_ticker(trade.pair)['bid'] current_profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate) @@ -171,7 +171,7 @@ def handle_trade(trade): TelegramHandler.send_msg(message) return else: - logger.debug('Threshold not reached. (cur_profit: {}%)'.format(round(current_profit, 2))) + logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', current_profit) except ValueError: logger.exception('Unable to handle open order') @@ -182,7 +182,7 @@ def create_trade(stake_amount: float, exchange): :param stake_amount: amount of btc to spend :param exchange: exchange to use """ - logger.info('Creating new trade with stake_amount: {} ...'.format(stake_amount)) + logger.info('Creating new trade with stake_amount: %f ...', stake_amount) whitelist = CONFIG[exchange.name.lower()]['pair_whitelist'] # Check if btc_amount is fulfilled if api_wrapper.get_balance(CONFIG['stake_currency']) < stake_amount: @@ -196,7 +196,7 @@ def create_trade(stake_amount: float, exchange): for trade in trades: if trade.pair in whitelist: whitelist.remove(trade.pair) - logger.debug('Ignoring {} in pair whitelist'.format(trade.pair)) + logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: raise ValueError('No pair in whitelist') @@ -231,7 +231,7 @@ def create_trade(stake_amount: float, exchange): if __name__ == '__main__': - logger.info('Starting freqtrade {}'.format(__version__)) + logger.info('Starting freqtrade %s', __version__) TelegramHandler.listen() while True: time.sleep(0.5) diff --git a/rpc/telegram.py b/rpc/telegram.py index 1c6c1225f..7c18ec691 100644 --- a/rpc/telegram.py +++ b/rpc/telegram.py @@ -36,10 +36,10 @@ def authorized_only(command_handler): chat_id = int(conf['telegram']['chat_id']) if int(update.message.chat_id) == chat_id: - logger.info('Executing handler: {} for chat_id: {}'.format(command_handler.__name__, chat_id)) + logger.info('Executing handler: %s for chat_id: %s', command_handler.__name__, chat_id) return command_handler(*args, **kwargs) else: - logger.info('Rejected unauthorized message from: {}'.format(update.message.chat_id)) + logger.info('Rejected unauthorized message from: %s', update.message.chat_id) return wrapper @@ -307,7 +307,7 @@ class TelegramHandler(object): except NetworkError as error: # Sometimes the telegram server resets the current connection, # if this is the case we send the message again. - logger.warning('Got Telegram NetworkError: {}! trying one more time'.format(error.message)) + logger.warning('Got Telegram NetworkError: %s! Trying one more time.', error.message) bot.send_message(conf['telegram']['chat_id'], msg, parse_mode=parse_mode) except Exception: logger.exception('Exception occurred within Telegram API') From 5e940e964beb18c6b4065536d4e4a58cfde83704 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 27 Aug 2017 17:12:28 +0300 Subject: [PATCH 7/9] reordering import statements --- analyze.py | 2 +- main.py | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/analyze.py b/analyze.py index 1e53ffdd5..68b7fa1c6 100644 --- a/analyze.py +++ b/analyze.py @@ -1,7 +1,7 @@ import time from datetime import timedelta -import arrow import logging +import arrow import requests from pandas.io.json import json_normalize from stockstats import StockDataFrame diff --git a/main.py b/main.py index d6e6b6701..81f16607a 100755 --- a/main.py +++ b/main.py @@ -6,20 +6,16 @@ import traceback from datetime import datetime from json import JSONDecodeError from requests import ConnectionError - from wrapt import synchronized - from analyze import get_buy_signal - -logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -logger = logging.getLogger(__name__) - from persistence import Trade, Session from exchange import get_exchange_api from rpc.telegram import TelegramHandler from utils import get_conf +logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) __author__ = "gcarq" __copyright__ = "gcarq 2017" From b9e53bcaa61099272c04bd93a831924cb9569eb4 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Sun, 27 Aug 2017 22:17:29 +0300 Subject: [PATCH 8/9] fix indentation --- rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/telegram.py b/rpc/telegram.py index 7c18ec691..26aaa3005 100644 --- a/rpc/telegram.py +++ b/rpc/telegram.py @@ -80,7 +80,7 @@ class TelegramHandler(object): *Close Profit:* `{close_profit}` *Current Profit:* `{current_profit}%` *Open Order:* `{open_order}` - """.format( + """.format( trade_id=trade.id, pair=trade.pair, market_url=api_wrapper.get_pair_detail_url(trade.pair), @@ -136,7 +136,7 @@ class TelegramHandler(object): *Latest Trade opened:* `{latest_trade_date}` *Avg. Duration:* `{avg_duration}` *Best Performing:* `{best_pair}: {best_rate}%` - """.format( + """.format( profit_btc=round(sum(profit_amounts), 8), profit=round(sum(profits), 2), trade_count=len(trades), From c46f718b40eca1f52db9c74d9cafa31c7b31e651 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Wed, 30 Aug 2017 22:07:33 +0300 Subject: [PATCH 9/9] upgrade several dependencies --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5c66e3142..a15c0f3f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -e git+https://github.com/s4w3d0ff/python-poloniex.git#egg=Poloniex -e git+https://github.com/ericsomdahl/python-bittrex.git#egg=python-bittrex SQLAlchemy==1.1.9 -python-telegram-bot==6.1b2 +python-telegram-bot==7.0.1 arrow==0.10.0 -requests==2.17.3 -urllib3==1.21.1 +requests==2.18.4 +urllib3==1.22 wrapt==1.10.10 pandas==0.20.1 matplotlib==2.0.0