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/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. 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/main.py b/main.py index d0c9287d6..af96640e0 100755 --- a/main.py +++ b/main.py @@ -118,6 +118,29 @@ def close_trade_if_fulfilled(trade: Trade) -> bool: return False +def execute_sell(trade: Trade, current_rate: float) -> None: + """ + Executes a sell for the given trade and current rate + :param trade: Trade instance + :param current_rate: current rate + :return: None + """ + # Get available balance + currency = trade.pair.split('_')[1] + balance = exchange.get_balance(currency) + + profit = trade.exec_sell_order(current_rate, balance) + message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format( + trade.exchange.name, + trade.pair.replace('_', '/'), + exchange.get_pair_detail_url(trade.pair), + trade.close_rate, + round(profit, 2) + ) + logger.info(message) + telegram.send_msg(message) + + def handle_trade(trade: Trade) -> None: """ Sells the current pair if the threshold is reached and updates the trade record. @@ -130,28 +153,19 @@ def handle_trade(trade: Trade) -> None: logger.debug('Handling open trade %s ...', trade) # Get current rate current_rate = exchange.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) - # Get available balance - currency = trade.pair.split('_')[1] - balance = exchange.get_balance(currency) + if 'stoploss' in _conf and current_profit < float(_conf['stoploss']) * 100.0: + logger.debug('Stop loss hit.') + execute_sell(trade, current_rate) + return for duration, threshold in sorted(_conf['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('_', '/'), - exchange.get_pair_detail_url(trade.pair), - trade.close_rate, - round(profit, 2) - ) - logger.info(message) - telegram.send_msg(message) + execute_sell(trade, current_rate) return else: logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', current_profit) diff --git a/misc.py b/misc.py index 6b67d0a64..4fd92d1d4 100644 --- a/misc.py +++ b/misc.py @@ -3,9 +3,9 @@ conf_schema = { 'type': 'object', 'properties': { - 'max_open_trades': {'type': 'integer'}, - 'stake_currency': {'type': 'string'}, - 'stake_amount': {'type': 'number'}, + 'max_open_trades': {'type': 'integer', 'minimum': 1}, + 'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']}, + 'stake_amount': {'type': 'number', 'minimum': 0.0005}, 'dry_run': {'type': 'boolean'}, 'minimal_roi': { 'type': 'object', @@ -14,6 +14,7 @@ conf_schema = { }, 'minProperties': 1 }, + 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'poloniex': {'$ref': '#/definitions/exchange'}, 'bittrex': {'$ref': '#/definitions/exchange'}, 'telegram': { diff --git a/persistence.py b/persistence.py index a0a85ed3d..47bb016a5 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 import exchange @@ -33,7 +35,7 @@ def init(config: dict) -> None: engine = create_engine(_db_handle, echo=False) _session = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=True)) - Trade.session = _session + Trade.session = _session() Trade.query = _session.query_property() Base.metadata.create_all(engine) diff --git a/rpc/telegram.py b/rpc/telegram.py index d6abeba61..bd44cd190 100644 --- a/rpc/telegram.py +++ b/rpc/telegram.py @@ -151,7 +151,7 @@ def _profit(bot: Bot, update: Update) -> None: profit_amounts.append((profit / 100) * trade.btc_amount) profits.append(profit) - bp_pair, bp_rate = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ + bp_pair, bp_rate = Trade.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') \ @@ -272,7 +272,7 @@ def _performance(bot: Bot, update: Update) -> None: send_msg('`trader is not running`', bot=bot) return - pair_rates = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ + pair_rates = Trade.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') \