Add sell-reason to sell-tree
This commit is contained in:
parent
6bb7167b56
commit
f991109b0a
@ -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']
|
||||||
|
@ -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'
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user