Add sell-reason to sell-tree

This commit is contained in:
Matthias 2018-07-11 19:57:01 +02:00
parent 6bb7167b56
commit f991109b0a
4 changed files with 31 additions and 20 deletions

View File

@ -20,6 +20,7 @@ from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.state import State from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from freqtrade.strategy.resolver import IStrategy, StrategyResolver from freqtrade.strategy.resolver import IStrategy, StrategyResolver
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -505,8 +506,9 @@ class FreqtradeBot(object):
(buy, sell) = self.strategy.get_signal(self.exchange, (buy, sell) = self.strategy.get_signal(self.exchange,
trade.pair, self.strategy.ticker_interval) trade.pair, self.strategy.ticker_interval)
if self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell): should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell)
self.execute_sell(trade, current_rate) if should_sell[0]:
self.execute_sell(trade, current_rate, should_sell[1])
return True return True
logger.info('Found no sell signals for whitelisted currencies. Trying again..') logger.info('Found no sell signals for whitelisted currencies. Trying again..')
return False return False
@ -607,17 +609,19 @@ class FreqtradeBot(object):
# TODO: figure out how to handle partially complete sell orders # TODO: figure out how to handle partially complete sell orders
return False return False
def execute_sell(self, trade: Trade, limit: float) -> None: def execute_sell(self, trade: Trade, limit: float, sellreason: SellType) -> None:
""" """
Executes a limit sell for the given trade and limit Executes a limit sell for the given trade and limit
:param trade: Trade instance :param trade: Trade instance
:param limit: limit rate for the sell order :param limit: limit rate for the sell order
:param sellrason: Reaseon the sell was triggered
:return: None :return: None
""" """
# Execute sell and update trade record # Execute sell and update trade record
order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id'] order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id']
trade.open_order_id = order_id trade.open_order_id = order_id
trade.close_rate_requested = limit trade.close_rate_requested = limit
trade.sell_reason = sellreason.value
profit_trade = trade.calc_profit(rate=limit) profit_trade = trade.calc_profit(rate=limit)
current_rate = self.exchange.get_ticker(trade.pair)['bid'] current_rate = self.exchange.get_ticker(trade.pair)['bid']

View File

@ -88,6 +88,7 @@ def check_migrate(engine) -> None:
stop_loss = get_column_def(cols, 'stop_loss', '0.0') stop_loss = get_column_def(cols, 'stop_loss', '0.0')
initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0') initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
max_rate = get_column_def(cols, 'max_rate', '0.0') max_rate = get_column_def(cols, 'max_rate', '0.0')
sell_reason = get_column_def(cols, 'sell_reason', 'null')
# Schema migration necessary # Schema migration necessary
engine.execute(f"alter table trades rename to {table_back_name}") engine.execute(f"alter table trades rename to {table_back_name}")
@ -99,7 +100,7 @@ def check_migrate(engine) -> None:
(id, exchange, pair, is_open, fee_open, fee_close, open_rate, (id, exchange, pair, is_open, fee_open, fee_close, open_rate,
open_rate_requested, close_rate, close_rate_requested, close_profit, open_rate_requested, close_rate, close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id, stake_amount, amount, open_date, close_date, open_order_id,
stop_loss, initial_stop_loss, max_rate stop_loss, initial_stop_loss, max_rate, sell_reason
) )
select id, lower(exchange), select id, lower(exchange),
case case
@ -114,7 +115,7 @@ def check_migrate(engine) -> None:
{close_rate_requested} close_rate_requested, close_profit, {close_rate_requested} close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id, stake_amount, amount, open_date, close_date, open_order_id,
{stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss, {stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss,
{max_rate} max_rate {max_rate} max_rate, {sell_reason} sell_reason
from {table_back_name} from {table_back_name}
""") """)
@ -170,6 +171,7 @@ class Trade(_DECL_BASE):
initial_stop_loss = Column(Float, nullable=True, default=0.0) initial_stop_loss = Column(Float, nullable=True, default=0.0)
# absolute value of the highest reached price # absolute value of the highest reached price
max_rate = Column(Float, nullable=True, default=0.0) max_rate = Column(Float, nullable=True, default=0.0)
sell_reason = Column(String, nullable=True)
def __repr__(self): def __repr__(self):
open_since = arrow.get(self.open_date).humanize() if self.is_open else 'closed' open_since = arrow.get(self.open_date).humanize() if self.is_open else 'closed'

View File

@ -13,6 +13,7 @@ import sqlalchemy as sql
from numpy import mean, nan_to_num from numpy import mean, nan_to_num
from pandas import DataFrame from pandas import DataFrame
from freqtrade.analyze import SellType
from freqtrade.misc import shorten_date from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.state import State from freqtrade.state import State
@ -344,7 +345,7 @@ class RPC(object):
# Get current rate and execute sell # Get current rate and execute sell
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid'] current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
self._freqtrade.execute_sell(trade, current_rate) self._freqtrade.execute_sell(trade, current_rate, SellType.FORCE_SELL)
# ---- EOF def _exec_forcesell ---- # ---- EOF def _exec_forcesell ----
if self._freqtrade.state != State.RUNNING: if self._freqtrade.state != State.RUNNING:

View File

@ -6,7 +6,7 @@ import logging
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from typing import Dict, List, Tuple from typing import Dict, List, Tuple, Optional
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
@ -35,6 +35,7 @@ class SellType(Enum):
STOP_LOSS = "stop_loss" STOP_LOSS = "stop_loss"
TRAILING_STOP_LOSS = "trailing_stop_loss" TRAILING_STOP_LOSS = "trailing_stop_loss"
SELL_SIGNAL = "sell_signal" SELL_SIGNAL = "sell_signal"
FORCE_SELL = "force_sell"
class IStrategy(ABC): class IStrategy(ABC):
@ -147,40 +148,42 @@ class IStrategy(ABC):
) )
return buy, sell return buy, sell
def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, sell: bool) -> bool: def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
sell: bool) -> Tuple[bool, Optional[SellType]]:
""" """
This function evaluate if on the condition required to trigger a sell has been reached This function evaluate if on the condition required to trigger a sell has been reached
if the threshold is reached and updates the trade record. if the threshold is reached and updates the trade record.
:return: True if trade should be sold, False otherwise :return: True if trade should be sold, False otherwise
""" """
current_profit = trade.calc_profit_percent(rate) current_profit = trade.calc_profit_percent(rate)
if self.stop_loss_reached(current_rate=rate, trade=trade, current_time=date, stoplossflag = self.stop_loss_reached(current_rate=rate, trade=trade, current_time=date,
current_profit=current_profit): current_profit=current_profit)
return True if stoplossflag[0]:
return (True, stoplossflag[1])
experimental = self.config.get('experimental', {}) experimental = self.config.get('experimental', {})
if buy and experimental.get('ignore_roi_if_buy_signal', False): if buy and experimental.get('ignore_roi_if_buy_signal', False):
logger.debug('Buy signal still active - not selling.') logger.debug('Buy signal still active - not selling.')
return False return (False, None)
# Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee)
if self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date): if self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date):
logger.debug('Required profit reached. Selling..') logger.debug('Required profit reached. Selling..')
return True return (True, SellType.ROI)
if experimental.get('sell_profit_only', False): if experimental.get('sell_profit_only', False):
logger.debug('Checking if trade is profitable..') logger.debug('Checking if trade is profitable..')
if trade.calc_profit(rate=rate) <= 0: if trade.calc_profit(rate=rate) <= 0:
return False return (False, None)
if sell and not buy and experimental.get('use_sell_signal', False): if sell and not buy and experimental.get('use_sell_signal', False):
logger.debug('Sell signal received. Selling..') logger.debug('Sell signal received. Selling..')
return True return (True, SellType.SELL_SIGNAL)
return False return (False, None)
def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime, def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime,
current_profit: float) -> bool: current_profit: float) -> Tuple[bool, Optional[SellType]]:
""" """
Based on current profit of the trade and configured (trailing) stoploss, Based on current profit of the trade and configured (trailing) stoploss,
decides to sell or not decides to sell or not
@ -192,8 +195,9 @@ class IStrategy(ABC):
# evaluate if the stoploss was hit # evaluate if the stoploss was hit
if self.stoploss is not None and trade.stop_loss >= current_rate: if self.stoploss is not None and trade.stop_loss >= current_rate:
selltype = SellType.STOP_LOSS
if trailing_stop: if trailing_stop:
selltype = SellType.TRAILING_STOP_LOSS
logger.debug( logger.debug(
f"HIT STOP: current price at {current_rate:.6f}, " f"HIT STOP: current price at {current_rate:.6f}, "
f"stop loss is {trade.stop_loss:.6f}, " f"stop loss is {trade.stop_loss:.6f}, "
@ -202,7 +206,7 @@ class IStrategy(ABC):
logger.debug(f"trailing stop saved {trade.stop_loss - trade.initial_stop_loss:.6f}") logger.debug(f"trailing stop saved {trade.stop_loss - trade.initial_stop_loss:.6f}")
logger.debug('Stop loss hit.') logger.debug('Stop loss hit.')
return True return (True, selltype)
# update the stop loss afterwards, after all by definition it's supposed to be hanging # update the stop loss afterwards, after all by definition it's supposed to be hanging
if trailing_stop: if trailing_stop:
@ -219,7 +223,7 @@ class IStrategy(ABC):
trade.adjust_stop_loss(current_rate, stop_loss_value) trade.adjust_stop_loss(current_rate, stop_loss_value)
return False return (False, None)
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool: def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
""" """