Merge branch 'master' of https://github.com/gcarq/freqtrade
This commit is contained in:
		
							
								
								
									
										9
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
| @@ -51,3 +51,11 @@ $ source .env/bin/activate | |||||||
| $ pip install -r requirements.txt | $ pip install -r requirements.txt | ||||||
| $ ./main.py | $ ./main.py | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #### Docker | ||||||
|  | ``` | ||||||
|  | $ cd freqtrade | ||||||
|  | $ docker build -t freqtrade . | ||||||
|  | $ docker run --rm -it freqtrade | ||||||
|  | ``` | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								analyze.py
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								analyze.py
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| import time | import time | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
| import arrow |  | ||||||
| import logging | import logging | ||||||
|  | import arrow | ||||||
| import requests | import requests | ||||||
| from pandas.io.json import json_normalize | from pandas.io.json import json_normalize | ||||||
| from stockstats import StockDataFrame | from stockstats import StockDataFrame | ||||||
| @@ -89,7 +89,7 @@ def get_buy_signal(pair): | |||||||
|         return False |         return False | ||||||
|  |  | ||||||
|     signal = latest['underpriced'] == 1 |     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 |     return signal | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -107,8 +107,8 @@ def plot_dataframe(dataframe, pair): | |||||||
|     import matplotlib.pyplot as plt |     import matplotlib.pyplot as plt | ||||||
|  |  | ||||||
|     # Three subplots sharing x axe |     # Three subplots sharing x axe | ||||||
|     f, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) |     fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) | ||||||
|     f.suptitle(pair, fontsize=14, fontweight='bold') |     fig.suptitle(pair, fontsize=14, fontweight='bold') | ||||||
|     ax1.plot(dataframe.index.values, dataframe['close'], label='close') |     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_30_ema'], label='EMA(60)') | ||||||
|     ax1.plot(dataframe.index.values, dataframe['close_90_ema'], label='EMA(120)') |     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 |     # Fine-tune figure; make subplots close to each other and hide x ticks for | ||||||
|     # all but bottom plot. |     # all but bottom plot. | ||||||
|     f.subplots_adjust(hspace=0) |     fig.subplots_adjust(hspace=0) | ||||||
|     plt.setp([a.get_xticklabels() for a in f.axes[:-1]], visible=False) |     plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False) | ||||||
|     plt.show() |     plt.show() | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								main.py
									
									
									
									
									
								
							| @@ -1,5 +1,4 @@ | |||||||
| #!/usr/bin/env python | #!/usr/bin/env python | ||||||
| import json |  | ||||||
| import logging | import logging | ||||||
| import threading | import threading | ||||||
| import time | import time | ||||||
| @@ -7,20 +6,16 @@ import traceback | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from json import JSONDecodeError | from json import JSONDecodeError | ||||||
| from requests import ConnectionError | from requests import ConnectionError | ||||||
|  |  | ||||||
| from wrapt import synchronized | from wrapt import synchronized | ||||||
|  |  | ||||||
| from analyze import get_buy_signal | 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 persistence import Trade, Session | ||||||
| from exchange import get_exchange_api | from exchange import get_exchange_api | ||||||
| from rpc.telegram import TelegramHandler | from rpc.telegram import TelegramHandler | ||||||
| from utils import get_conf | 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" | __author__ = "gcarq" | ||||||
| __copyright__ = "gcarq 2017" | __copyright__ = "gcarq 2017" | ||||||
| @@ -28,8 +23,8 @@ __license__ = "GPLv3" | |||||||
| __version__ = "0.8.0" | __version__ = "0.8.0" | ||||||
|  |  | ||||||
|  |  | ||||||
| conf = get_conf() | CONFIG = get_conf() | ||||||
| api_wrapper = get_exchange_api(conf) | api_wrapper = get_exchange_api(CONFIG) | ||||||
|  |  | ||||||
|  |  | ||||||
| @synchronized | @synchronized | ||||||
| @@ -64,13 +59,13 @@ class TradeThread(threading.Thread): | |||||||
|             while not _should_stop: |             while not _should_stop: | ||||||
|                 try: |                 try: | ||||||
|                     self._process() |                     self._process() | ||||||
|                 except (ConnectionError, JSONDecodeError, ValueError) as e: |                 except (ConnectionError, JSONDecodeError, ValueError) as error: | ||||||
|                     msg = 'Got {} during _process()'.format(e.__class__.__name__) |                     msg = 'Got {} during _process()'.format(error.__class__.__name__) | ||||||
|                     logger.exception(msg) |                     logger.exception(msg) | ||||||
|                 finally: |                 finally: | ||||||
|                     Session.flush() |                     Session.flush() | ||||||
|                     time.sleep(25) |                     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())) |             TelegramHandler.send_msg('*Status:* Got RuntimeError: ```\n{}\n```'.format(traceback.format_exc())) | ||||||
|             logger.exception('RuntimeError. Stopping trader ...') |             logger.exception('RuntimeError. Stopping trader ...') | ||||||
|         finally: |         finally: | ||||||
| @@ -85,10 +80,10 @@ class TradeThread(threading.Thread): | |||||||
|         """ |         """ | ||||||
|         # Query trades from persistence layer |         # Query trades from persistence layer | ||||||
|         trades = Trade.query.filter(Trade.is_open.is_(True)).all() |         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 |             # Create entity and execute trade | ||||||
|             try: |             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: |             except ValueError: | ||||||
|                 logger.exception('ValueError during trade creation') |                 logger.exception('ValueError during trade creation') | ||||||
|         for trade in trades: |         for trade in trades: | ||||||
| @@ -110,7 +105,7 @@ class TradeThread(threading.Thread): | |||||||
|             trade.open_order_id = None |             trade.open_order_id = None | ||||||
|             # Check if this trade can be marked as closed |             # Check if this trade can be marked as closed | ||||||
|             if close_trade_if_fulfilled(trade): |             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 |                 continue | ||||||
|  |  | ||||||
|             # Check if we can sell our current pair |             # Check if we can sell our current pair | ||||||
| @@ -145,7 +140,7 @@ def handle_trade(trade): | |||||||
|         if not trade.is_open: |         if not trade.is_open: | ||||||
|             raise ValueError('attempt to handle closed trade: {}'.format(trade)) |             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 |         # Get current rate | ||||||
|         current_rate = api_wrapper.get_ticker(trade.pair)['bid'] |         current_rate = api_wrapper.get_ticker(trade.pair)['bid'] | ||||||
|         current_profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate) |         current_profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate) | ||||||
| @@ -154,7 +149,7 @@ def handle_trade(trade): | |||||||
|         currency = trade.pair.split('_')[1] |         currency = trade.pair.split('_')[1] | ||||||
|         balance = api_wrapper.get_balance(currency) |         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) |             duration, threshold = float(duration), float(threshold) | ||||||
|             # Check if time matches and current rate is above threshold |             # Check if time matches and current rate is above threshold | ||||||
|             time_diff = (datetime.utcnow() - trade.open_date).total_seconds() / 60 |             time_diff = (datetime.utcnow() - trade.open_date).total_seconds() / 60 | ||||||
| @@ -172,7 +167,7 @@ def handle_trade(trade): | |||||||
|                 TelegramHandler.send_msg(message) |                 TelegramHandler.send_msg(message) | ||||||
|                 return |                 return | ||||||
|         else: |         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: |     except ValueError: | ||||||
|         logger.exception('Unable to handle open order') |         logger.exception('Unable to handle open order') | ||||||
|  |  | ||||||
| @@ -183,11 +178,11 @@ def create_trade(stake_amount: float, exchange): | |||||||
|     :param stake_amount: amount of btc to spend |     :param stake_amount: amount of btc to spend | ||||||
|     :param exchange: exchange to use |     :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 = conf[exchange.name.lower()]['pair_whitelist'] |     whitelist = CONFIG[exchange.name.lower()]['pair_whitelist'] | ||||||
|     # Check if btc_amount is fulfilled |     # Check if btc_amount is fulfilled | ||||||
|     if api_wrapper.get_balance(conf['stake_currency']) < stake_amount: |     if api_wrapper.get_balance(CONFIG['stake_currency']) < stake_amount: | ||||||
|         raise ValueError('stake amount is not fulfilled (currency={}'.format(conf['stake_currency'])) |         raise ValueError('stake amount is not fulfilled (currency={}'.format(CONFIG['stake_currency'])) | ||||||
|  |  | ||||||
|     # Remove currently opened and latest pairs from whitelist |     # Remove currently opened and latest pairs from whitelist | ||||||
|     trades = Trade.query.filter(Trade.is_open.is_(True)).all() |     trades = Trade.query.filter(Trade.is_open.is_(True)).all() | ||||||
| @@ -197,7 +192,7 @@ def create_trade(stake_amount: float, exchange): | |||||||
|     for trade in trades: |     for trade in trades: | ||||||
|         if trade.pair in whitelist: |         if trade.pair in whitelist: | ||||||
|             whitelist.remove(trade.pair) |             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: |     if not whitelist: | ||||||
|         raise ValueError('No pair in whitelist') |         raise ValueError('No pair in whitelist') | ||||||
|  |  | ||||||
| @@ -232,7 +227,7 @@ def create_trade(stake_amount: float, exchange): | |||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     logger.info('Starting freqtrade {}'.format(__version__)) |     logger.info('Starting freqtrade %s', __version__) | ||||||
|     TelegramHandler.listen() |     TelegramHandler.listen() | ||||||
|     while True: |     while True: | ||||||
|         time.sleep(0.5) |         time.sleep(0.5) | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| -e git+https://github.com/s4w3d0ff/python-poloniex.git#egg=Poloniex | -e git+https://github.com/s4w3d0ff/python-poloniex.git#egg=Poloniex | ||||||
| -e git+https://github.com/ericsomdahl/python-bittrex.git#egg=python-bittrex | -e git+https://github.com/ericsomdahl/python-bittrex.git#egg=python-bittrex | ||||||
| SQLAlchemy==1.1.9 | SQLAlchemy==1.1.9 | ||||||
| python-telegram-bot==6.1b2 | python-telegram-bot==7.0.1 | ||||||
| arrow==0.10.0 | arrow==0.10.0 | ||||||
| requests==2.17.3 | requests==2.18.4 | ||||||
| urllib3==1.21.1 | urllib3==1.22 | ||||||
| wrapt==1.10.10 | wrapt==1.10.10 | ||||||
| pandas==0.20.1 | pandas==0.20.1 | ||||||
| matplotlib==2.0.0 | matplotlib==2.0.0 | ||||||
|   | |||||||
| @@ -36,10 +36,10 @@ def authorized_only(command_handler): | |||||||
|  |  | ||||||
|         chat_id = int(conf['telegram']['chat_id']) |         chat_id = int(conf['telegram']['chat_id']) | ||||||
|         if int(update.message.chat_id) == 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) |             return command_handler(*args, **kwargs) | ||||||
|         else: |         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 |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -80,7 +80,7 @@ class TelegramHandler(object): | |||||||
| *Close Profit:* `{close_profit}` | *Close Profit:* `{close_profit}` | ||||||
| *Current Profit:* `{current_profit}%` | *Current Profit:* `{current_profit}%` | ||||||
| *Open Order:* `{open_order}` | *Open Order:* `{open_order}` | ||||||
|                         """.format( |                 """.format( | ||||||
|                     trade_id=trade.id, |                     trade_id=trade.id, | ||||||
|                     pair=trade.pair, |                     pair=trade.pair, | ||||||
|                     market_url=api_wrapper.get_pair_detail_url(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}` | *Latest Trade opened:* `{latest_trade_date}` | ||||||
| *Avg. Duration:* `{avg_duration}` | *Avg. Duration:* `{avg_duration}` | ||||||
| *Best Performing:* `{best_pair}: {best_rate}%` | *Best Performing:* `{best_pair}: {best_rate}%` | ||||||
|     """.format( |         """.format( | ||||||
|             profit_btc=round(sum(profit_amounts), 8), |             profit_btc=round(sum(profit_amounts), 8), | ||||||
|             profit=round(sum(profits), 2), |             profit=round(sum(profits), 2), | ||||||
|             trade_count=len(trades), |             trade_count=len(trades), | ||||||
| @@ -309,10 +309,10 @@ class TelegramHandler(object): | |||||||
|                 bot = bot or TelegramHandler.get_updater(conf).bot |                 bot = bot or TelegramHandler.get_updater(conf).bot | ||||||
|                 try: |                 try: | ||||||
|                     bot.send_message(conf['telegram']['chat_id'], msg, parse_mode=parse_mode) |                     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, |                     # Sometimes the telegram server resets the current connection, | ||||||
|                     # if this is the case we send the message again. |                     # 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: %s! Trying one more time.', error.message) | ||||||
|                     bot.send_message(conf['telegram']['chat_id'], msg, parse_mode=parse_mode) |                     bot.send_message(conf['telegram']['chat_id'], msg, parse_mode=parse_mode) | ||||||
|             except Exception: |             except Exception: | ||||||
|                 logger.exception('Exception occurred within Telegram API') |                 logger.exception('Exception occurred within Telegram API') | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								utils.py
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								utils.py
									
									
									
									
									
								
							| @@ -17,8 +17,8 @@ def get_conf(filename='config.json'): | |||||||
|     """ |     """ | ||||||
|     global _cur_conf |     global _cur_conf | ||||||
|     if not _cur_conf: |     if not _cur_conf: | ||||||
|         with open(filename) as fp: |         with open(filename) as file: | ||||||
|             _cur_conf = json.load(fp) |             _cur_conf = json.load(file) | ||||||
|             validate_conf(_cur_conf) |             validate_conf(_cur_conf) | ||||||
|     return _cur_conf |     return _cur_conf | ||||||
|  |  | ||||||
| @@ -40,11 +40,11 @@ def validate_conf(conf): | |||||||
|     if not isinstance(conf.get('minimal_roi'), dict): |     if not isinstance(conf.get('minimal_roi'), dict): | ||||||
|         raise ValueError('minimal_roi must be a 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): |         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): |         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'): |     if conf.get('telegram'): | ||||||
|         telegram = conf.get('telegram') |         telegram = conf.get('telegram') | ||||||
| @@ -95,7 +95,7 @@ def validate_bittrex_pairs(pairs): | |||||||
|     data = Bittrex(None, None).get_markets() |     data = Bittrex(None, None).get_markets() | ||||||
|     if not data['success']: |     if not data['success']: | ||||||
|         raise RuntimeError('BITTREX: {}'.format(data['message'])) |         raise RuntimeError('BITTREX: {}'.format(data['message'])) | ||||||
|     available_markets = [m['MarketName'].replace('-', '_')for m in data['result']] |     available_markets = [market['MarketName'].replace('-', '_')for market in data['result']] | ||||||
|     for p in pairs: |     for pair in pairs: | ||||||
|         if p not in available_markets: |         if pair not in available_markets: | ||||||
|             raise ValueError('Invalid pair: {}'.format(p)) |             raise ValueError('Invalid pair: {}'.format(pair)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user