From a56340061e2047a1ced86a3cb7c6c8b7095ea366 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Sun, 3 Sep 2017 09:50:48 +0300 Subject: [PATCH 01/12] fix Session lint issues --- .gitignore | 1 + main.py | 6 +++--- persistence.py | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 24d1caaeb..2d4af4dac 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ preprocessor.py .env .venv .idea +.vscode diff --git a/main.py b/main.py index a97419c94..06901b40d 100755 --- a/main.py +++ b/main.py @@ -53,7 +53,7 @@ class TradeThread(threading.Thread): msg = 'Got {} during _process()'.format(error.__class__.__name__) logger.exception(msg) finally: - Session.flush() + Session().flush() time.sleep(25) except (RuntimeError, JSONDecodeError): TelegramHandler.send_msg('*Status:* Got RuntimeError: ```\n{}\n```'.format(traceback.format_exc())) @@ -75,7 +75,7 @@ class TradeThread(threading.Thread): # Create entity and execute trade trade = create_trade(float(CONFIG['stake_amount']), api_wrapper.exchange) if trade: - Session.add(trade) + Session().add(trade) else: logging.info('Got no buy signal...') except ValueError: @@ -134,7 +134,7 @@ def close_trade_if_fulfilled(trade: Trade) -> bool: # we can close this trade. if trade.close_profit and trade.close_date and trade.close_rate and not trade.open_order_id: trade.is_open = False - Session.flush() + Session().flush() return True return False diff --git a/persistence.py b/persistence.py index f8c954983..c43ad5af6 100644 --- a/persistence.py +++ b/persistence.py @@ -2,7 +2,9 @@ from datetime import datetime from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, create_engine from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.orm.scoping import scoped_session +from sqlalchemy.orm.session import sessionmaker + from sqlalchemy.types import Enum from exchange import Exchange, get_exchange_api @@ -18,10 +20,8 @@ engine = create_engine(db_handle, echo=False) Session = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=True)) Base = declarative_base() - class Trade(Base): __tablename__ = 'trades' - query = Session.query_property() id = Column(Integer, primary_key=True) @@ -61,7 +61,7 @@ class Trade(Base): self.close_profit = profit self.close_date = datetime.utcnow() self.open_order_id = order_id - Session.flush() + Session().flush() return profit Base.metadata.create_all(engine) From 5bf244b92d388507389e85429a71838f266fb8cb Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Sun, 3 Sep 2017 09:56:35 +0300 Subject: [PATCH 02/12] fix telegram Session lint issues --- rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/telegram.py b/rpc/telegram.py index efb8746fa..d74636b5b 100644 --- a/rpc/telegram.py +++ b/rpc/telegram.py @@ -124,7 +124,7 @@ class TelegramHandler(object): profit_amounts.append((profit / 100) * trade.btc_amount) profits.append(profit) - bp_pair, bp_rate = Session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ + bp_pair, bp_rate = Session().query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ .filter(Trade.is_open.is_(False)) \ .group_by(Trade.pair) \ .order_by('profit_sum DESC') \ @@ -245,7 +245,7 @@ class TelegramHandler(object): TelegramHandler.send_msg('`trader is not running`', bot=bot) return - pair_rates = Session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ + pair_rates = Session().query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ .filter(Trade.is_open.is_(False)) \ .group_by(Trade.pair) \ .order_by('profit_sum DESC') \ From cb97a30087ebf3735b9156599f5e702701154140 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Wed, 6 Sep 2017 17:13:21 +0300 Subject: [PATCH 03/12] fix sqlalchemy session to use contextual session --- main.py | 6 +++--- persistence.py | 7 ++++--- rpc/telegram.py | 5 ++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index 06901b40d..a97419c94 100755 --- a/main.py +++ b/main.py @@ -53,7 +53,7 @@ class TradeThread(threading.Thread): msg = 'Got {} during _process()'.format(error.__class__.__name__) logger.exception(msg) finally: - Session().flush() + Session.flush() time.sleep(25) except (RuntimeError, JSONDecodeError): TelegramHandler.send_msg('*Status:* Got RuntimeError: ```\n{}\n```'.format(traceback.format_exc())) @@ -75,7 +75,7 @@ class TradeThread(threading.Thread): # Create entity and execute trade trade = create_trade(float(CONFIG['stake_amount']), api_wrapper.exchange) if trade: - Session().add(trade) + Session.add(trade) else: logging.info('Got no buy signal...') except ValueError: @@ -134,7 +134,7 @@ def close_trade_if_fulfilled(trade: Trade) -> bool: # we can close this trade. if trade.close_profit and trade.close_date and trade.close_rate and not trade.open_order_id: trade.is_open = False - Session().flush() + Session.flush() return True return False diff --git a/persistence.py b/persistence.py index c43ad5af6..1abe208b8 100644 --- a/persistence.py +++ b/persistence.py @@ -17,12 +17,13 @@ else: db_handle = 'sqlite:///tradesv2.sqlite' engine = create_engine(db_handle, echo=False) -Session = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=True)) +session = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=True)) +Session = session() Base = declarative_base() class Trade(Base): __tablename__ = 'trades' - query = Session.query_property() + query = session.query_property() id = Column(Integer, primary_key=True) exchange = Column(Enum(Exchange), nullable=False) @@ -61,7 +62,7 @@ class Trade(Base): self.close_profit = profit self.close_date = datetime.utcnow() self.open_order_id = order_id - Session().flush() + Session.flush() return profit Base.metadata.create_all(engine) diff --git a/rpc/telegram.py b/rpc/telegram.py index d74636b5b..7472f7faf 100644 --- a/rpc/telegram.py +++ b/rpc/telegram.py @@ -124,7 +124,7 @@ class TelegramHandler(object): profit_amounts.append((profit / 100) * trade.btc_amount) profits.append(profit) - bp_pair, bp_rate = Session().query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ + bp_pair, bp_rate = Session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ .filter(Trade.is_open.is_(False)) \ .group_by(Trade.pair) \ .order_by('profit_sum DESC') \ @@ -244,8 +244,7 @@ class TelegramHandler(object): if not get_instance().is_alive(): TelegramHandler.send_msg('`trader is not running`', bot=bot) return - - pair_rates = Session().query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ + pair_rates = Session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ .filter(Trade.is_open.is_(False)) \ .group_by(Trade.pair) \ .order_by('profit_sum DESC') \ From 83b14523ca1a60b221229827ff66947616ef1d8f Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Thu, 7 Sep 2017 17:31:55 +0300 Subject: [PATCH 04/12] add stoploss to example config and json schema validation --- config.json.example | 1 + utils.py | 1 + 2 files changed, 2 insertions(+) diff --git a/config.json.example b/config.json.example index 29eb75075..a9fc3be79 100644 --- a/config.json.example +++ b/config.json.example @@ -8,6 +8,7 @@ "720": 0.01, "0": 0.02 }, + "stoploss": -0.10, "poloniex": { "enabled": false, "key": "key", diff --git a/utils.py b/utils.py index e7bb006fd..0bde3fce5 100644 --- a/utils.py +++ b/utils.py @@ -24,6 +24,7 @@ _conf_schema = { }, 'minProperties': 1 }, + 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'poloniex': {'$ref': '#/definitions/exchange'}, 'bittrex': {'$ref': '#/definitions/exchange'}, 'telegram': { From bd161ea0a9167b89be814cc46e0206fc4687b4e9 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Thu, 7 Sep 2017 17:33:04 +0300 Subject: [PATCH 05/12] extract selling to a method --- main.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/main.py b/main.py index a97419c94..881b51f83 100755 --- a/main.py +++ b/main.py @@ -138,6 +138,22 @@ def close_trade_if_fulfilled(trade: Trade) -> bool: return True return False +def execute_sell(trade: Trade, current_rate: float) -> None: + # Get available balance + currency = trade.pair.split('_')[1] + balance = api_wrapper.get_balance(currency) + + profit = trade.exec_sell_order(current_rate, balance) + message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format( + trade.exchange.name, + trade.pair.replace('_', '/'), + api_wrapper.get_pair_detail_url(trade.pair), + trade.close_rate, + round(profit, 2) + ) + logger.info(message) + TelegramHandler.send_msg(message) + def handle_trade(trade: Trade) -> None: """ @@ -153,26 +169,12 @@ def handle_trade(trade: Trade) -> None: current_rate = api_wrapper.get_ticker(trade.pair)['bid'] current_profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate) - # Get available balance - currency = trade.pair.split('_')[1] - balance = api_wrapper.get_balance(currency) - 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 if time_diff > duration and current_rate > (1 + threshold) * trade.open_rate: - # Execute sell - profit = trade.exec_sell_order(current_rate, balance) - message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format( - trade.exchange.name, - trade.pair.replace('_', '/'), - api_wrapper.get_pair_detail_url(trade.pair), - trade.close_rate, - round(profit, 2) - ) - logger.info(message) - TelegramHandler.send_msg(message) + execute_sell(trade, current_rate) return else: logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', current_profit) From 75f891194e0600ed3d8d29a66e8d3401330f8870 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Thu, 7 Sep 2017 17:34:34 +0300 Subject: [PATCH 06/12] sell immediately if we go below stoploss --- main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.py b/main.py index 881b51f83..ee1cbd113 100755 --- a/main.py +++ b/main.py @@ -169,6 +169,11 @@ def handle_trade(trade: Trade) -> None: current_rate = api_wrapper.get_ticker(trade.pair)['bid'] current_profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate) + if ('stoploss' in CONFIG) & (current_profit < float(CONFIG['stoploss'])*100): + logger.debug('Stop loss hit.') + execute_sell(trade, current_rate) + return + 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 From a1d616f8c21876e5a6cef3da645fc4f366b1e822 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Thu, 7 Sep 2017 17:38:28 +0300 Subject: [PATCH 07/12] update README to document stoploss --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e8997a017..bfd640060 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ See the example below: }, ``` +`stoploss` is loss in percentage that should trigger a sale. +For example value `-0.10` will cause immediate sell if the +profit dips below -10% for a given trade. This parameter is optional. The other values should be self-explanatory, if not feel free to raise a github issue. From 69bfc2d777235688aea39902d65507d9d2d1a182 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Fri, 8 Sep 2017 08:03:24 +0300 Subject: [PATCH 08/12] remove unnecessary parentheses --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index ee1cbd113..239666d6f 100755 --- a/main.py +++ b/main.py @@ -169,7 +169,7 @@ def handle_trade(trade: Trade) -> None: current_rate = api_wrapper.get_ticker(trade.pair)['bid'] current_profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate) - if ('stoploss' in CONFIG) & (current_profit < float(CONFIG['stoploss'])*100): + if 'stoploss' in CONFIG & current_profit < float(CONFIG['stoploss'])*100: logger.debug('Stop loss hit.') execute_sell(trade, current_rate) return From ac6d25c8f42280f5cd42090f9da8ef82e83327bf Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Fri, 8 Sep 2017 08:45:27 +0300 Subject: [PATCH 09/12] set stake_amount minimum to match minimum bid in Bittrex --- utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.py b/utils.py index e7bb006fd..e2f362937 100644 --- a/utils.py +++ b/utils.py @@ -15,7 +15,7 @@ _conf_schema = { 'properties': { 'max_open_trades': {'type': 'integer'}, 'stake_currency': {'type': 'string'}, - 'stake_amount': {'type': 'number'}, + 'stake_amount': {'type': 'number', 'minimum': 0.0005}, 'dry_run': {'type': 'boolean'}, 'minimal_roi': { 'type': 'object', From 768eae31e886a2e33dcb9fb19e250337dffaaede Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Fri, 8 Sep 2017 08:49:50 +0300 Subject: [PATCH 10/12] set minimum of max_open_trades to 1 --- utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.py b/utils.py index e2f362937..e4ca5850e 100644 --- a/utils.py +++ b/utils.py @@ -13,7 +13,7 @@ _cur_conf = None _conf_schema = { 'type': 'object', 'properties': { - 'max_open_trades': {'type': 'integer'}, + 'max_open_trades': {'type': 'integer', 'minimum': 1}, 'stake_currency': {'type': 'string'}, 'stake_amount': {'type': 'number', 'minimum': 0.0005}, 'dry_run': {'type': 'boolean'}, From a3b318cb0393bd4f14103f02d83dc6101b24f277 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Fri, 8 Sep 2017 08:51:10 +0300 Subject: [PATCH 11/12] set stake_currency to be an enum of available markets in Bittrex --- utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.py b/utils.py index e4ca5850e..c5d85a5ab 100644 --- a/utils.py +++ b/utils.py @@ -14,7 +14,7 @@ _conf_schema = { 'type': 'object', 'properties': { 'max_open_trades': {'type': 'integer', 'minimum': 1}, - 'stake_currency': {'type': 'string'}, + 'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']}, 'stake_amount': {'type': 'number', 'minimum': 0.0005}, 'dry_run': {'type': 'boolean'}, 'minimal_roi': { From aea5331df8183deeb584d1f56e58cbe6ce7d849b Mon Sep 17 00:00:00 2001 From: gcarq Date: Fri, 8 Sep 2017 16:00:08 +0200 Subject: [PATCH 12/12] remove bitwise operator --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 239666d6f..78b24350b 100755 --- a/main.py +++ b/main.py @@ -167,9 +167,9 @@ def handle_trade(trade: Trade) -> None: 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) + current_profit = 100.0 * ((current_rate - trade.open_rate) / trade.open_rate) - if 'stoploss' in CONFIG & current_profit < float(CONFIG['stoploss'])*100: + if 'stoploss' in CONFIG and current_profit < float(CONFIG['stoploss']) * 100.0: logger.debug('Stop loss hit.') execute_sell(trade, current_rate) return