Merge pull request #2253 from hroff-1902/backtesting-improve-logs
Improve logs for backtesting
This commit is contained in:
commit
76e45883bd
@ -239,14 +239,16 @@ class Backtesting:
|
|||||||
stake_amount: float, max_open_trades: int) -> Optional[BacktestResult]:
|
stake_amount: float, max_open_trades: int) -> Optional[BacktestResult]:
|
||||||
|
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
|
pair=pair,
|
||||||
open_rate=buy_row.open,
|
open_rate=buy_row.open,
|
||||||
open_date=buy_row.date,
|
open_date=buy_row.date,
|
||||||
stake_amount=stake_amount,
|
stake_amount=stake_amount,
|
||||||
amount=stake_amount / buy_row.open,
|
amount=stake_amount / buy_row.open,
|
||||||
fee_open=self.fee,
|
fee_open=self.fee,
|
||||||
fee_close=self.fee
|
fee_close=self.fee,
|
||||||
|
is_open=True,
|
||||||
)
|
)
|
||||||
|
logger.debug(f"{pair} - Backtesting emulates creation of new trade: {trade}.")
|
||||||
# calculate win/lose forwards from buy point
|
# calculate win/lose forwards from buy point
|
||||||
for sell_row in partial_ticker:
|
for sell_row in partial_ticker:
|
||||||
if max_open_trades > 0:
|
if max_open_trades > 0:
|
||||||
@ -289,23 +291,25 @@ class Backtesting:
|
|||||||
if partial_ticker:
|
if partial_ticker:
|
||||||
# no sell condition found - trade stil open at end of backtest period
|
# no sell condition found - trade stil open at end of backtest period
|
||||||
sell_row = partial_ticker[-1]
|
sell_row = partial_ticker[-1]
|
||||||
btr = BacktestResult(pair=pair,
|
bt_res = BacktestResult(pair=pair,
|
||||||
profit_percent=trade.calc_profit_percent(rate=sell_row.open),
|
profit_percent=trade.calc_profit_percent(rate=sell_row.open),
|
||||||
profit_abs=trade.calc_profit(rate=sell_row.open),
|
profit_abs=trade.calc_profit(rate=sell_row.open),
|
||||||
open_time=buy_row.date,
|
open_time=buy_row.date,
|
||||||
close_time=sell_row.date,
|
close_time=sell_row.date,
|
||||||
trade_duration=int((
|
trade_duration=int((
|
||||||
sell_row.date - buy_row.date).total_seconds() // 60),
|
sell_row.date - buy_row.date).total_seconds() // 60),
|
||||||
open_index=buy_row.Index,
|
open_index=buy_row.Index,
|
||||||
close_index=sell_row.Index,
|
close_index=sell_row.Index,
|
||||||
open_at_end=True,
|
open_at_end=True,
|
||||||
open_rate=buy_row.open,
|
open_rate=buy_row.open,
|
||||||
close_rate=sell_row.open,
|
close_rate=sell_row.open,
|
||||||
sell_reason=SellType.FORCE_SELL
|
sell_reason=SellType.FORCE_SELL
|
||||||
)
|
)
|
||||||
logger.debug('Force_selling still open trade %s with %s perc - %s', btr.pair,
|
logger.debug(f"{pair} - Force selling still open trade, "
|
||||||
btr.profit_percent, btr.profit_abs)
|
f"profit percent: {bt_res.profit_percent}, "
|
||||||
return btr
|
f"profit abs: {bt_res.profit_abs}")
|
||||||
|
|
||||||
|
return bt_res
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def backtest(self, args: Dict) -> DataFrame:
|
def backtest(self, args: Dict) -> DataFrame:
|
||||||
@ -384,6 +388,8 @@ class Backtesting:
|
|||||||
max_open_trades)
|
max_open_trades)
|
||||||
|
|
||||||
if trade_entry:
|
if trade_entry:
|
||||||
|
logger.debug(f"{pair} - Locking pair till "
|
||||||
|
f"close_time={trade_entry.close_time}")
|
||||||
lock_pair_until[pair] = trade_entry.close_time
|
lock_pair_until[pair] = trade_entry.close_time
|
||||||
trades.append(trade_entry)
|
trades.append(trade_entry)
|
||||||
else:
|
else:
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
This module contains the class to persist trades into SQLite
|
This module contains the class to persist trades into SQLite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
@ -19,8 +18,10 @@ from sqlalchemy.pool import StaticPool
|
|||||||
|
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_DECL_BASE: Any = declarative_base()
|
_DECL_BASE: Any = declarative_base()
|
||||||
_SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls'
|
_SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls'
|
||||||
|
|
||||||
@ -209,7 +210,7 @@ class Trade(_DECL_BASE):
|
|||||||
ticker_interval = Column(Integer, nullable=True)
|
ticker_interval = Column(Integer, nullable=True)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
open_since = arrow.get(self.open_date).humanize() if self.is_open else 'closed'
|
open_since = self.open_date.strftime('%Y-%m-%d %H:%M:%S') if self.is_open else 'closed'
|
||||||
|
|
||||||
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
|
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
|
||||||
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
|
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
|
||||||
@ -250,7 +251,6 @@ class Trade(_DECL_BASE):
|
|||||||
:param initial: Called to initiate stop_loss.
|
:param initial: Called to initiate stop_loss.
|
||||||
Skips everything if self.stop_loss is already set.
|
Skips everything if self.stop_loss is already set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if initial and not (self.stop_loss is None or self.stop_loss == 0):
|
if initial and not (self.stop_loss is None or self.stop_loss == 0):
|
||||||
# Don't modify if called with initial and nothing to do
|
# Don't modify if called with initial and nothing to do
|
||||||
return
|
return
|
||||||
@ -259,7 +259,7 @@ class Trade(_DECL_BASE):
|
|||||||
|
|
||||||
# no stop loss assigned yet
|
# no stop loss assigned yet
|
||||||
if not self.stop_loss:
|
if not self.stop_loss:
|
||||||
logger.debug("assigning new stop loss")
|
logger.debug(f"{self.pair} - Assigning new stoploss...")
|
||||||
self.stop_loss = new_loss
|
self.stop_loss = new_loss
|
||||||
self.stop_loss_pct = -1 * abs(stoploss)
|
self.stop_loss_pct = -1 * abs(stoploss)
|
||||||
self.initial_stop_loss = new_loss
|
self.initial_stop_loss = new_loss
|
||||||
@ -269,21 +269,20 @@ class Trade(_DECL_BASE):
|
|||||||
# evaluate if the stop loss needs to be updated
|
# evaluate if the stop loss needs to be updated
|
||||||
else:
|
else:
|
||||||
if new_loss > self.stop_loss: # stop losses only walk up, never down!
|
if new_loss > self.stop_loss: # stop losses only walk up, never down!
|
||||||
|
logger.debug(f"{self.pair} - Adjusting stoploss...")
|
||||||
self.stop_loss = new_loss
|
self.stop_loss = new_loss
|
||||||
self.stop_loss_pct = -1 * abs(stoploss)
|
self.stop_loss_pct = -1 * abs(stoploss)
|
||||||
self.stoploss_last_update = datetime.utcnow()
|
self.stoploss_last_update = datetime.utcnow()
|
||||||
logger.debug("adjusted stop loss")
|
|
||||||
else:
|
else:
|
||||||
logger.debug("keeping current stop loss")
|
logger.debug(f"{self.pair} - Keeping current stoploss...")
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{self.pair} - current price {current_price:.8f}, "
|
f"{self.pair} - Stoploss adjusted. current_price={current_price:.8f}, "
|
||||||
f"bought at {self.open_rate:.8f} and calculated "
|
f"open_rate={self.open_rate:.8f}, max_rate={self.max_rate:.8f}, "
|
||||||
f"stop loss is at: {self.initial_stop_loss:.8f} initial "
|
f"initial_stop_loss={self.initial_stop_loss:.8f}, "
|
||||||
f"stop at {self.stop_loss:.8f}. "
|
f"stop_loss={self.stop_loss:.8f}. "
|
||||||
f"trailing stop loss saved us: "
|
f"Trailing stoploss saved us: "
|
||||||
f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f} "
|
f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.")
|
||||||
f"and max observed rate was {self.max_rate:.8f}")
|
|
||||||
|
|
||||||
def update(self, order: Dict) -> None:
|
def update(self, order: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
@ -331,24 +330,19 @@ class Trade(_DECL_BASE):
|
|||||||
self
|
self
|
||||||
)
|
)
|
||||||
|
|
||||||
def calc_open_trade_price(
|
def calc_open_trade_price(self, fee: Optional[float] = None) -> float:
|
||||||
self,
|
|
||||||
fee: Optional[float] = None) -> float:
|
|
||||||
"""
|
"""
|
||||||
Calculate the open_rate including fee.
|
Calculate the open_rate including fee.
|
||||||
:param fee: fee to use on the open rate (optional).
|
:param fee: fee to use on the open rate (optional).
|
||||||
If rate is not set self.fee will be used
|
If rate is not set self.fee will be used
|
||||||
:return: Price in of the open trade incl. Fees
|
:return: Price in of the open trade incl. Fees
|
||||||
"""
|
"""
|
||||||
|
|
||||||
buy_trade = (Decimal(self.amount) * Decimal(self.open_rate))
|
buy_trade = (Decimal(self.amount) * Decimal(self.open_rate))
|
||||||
fees = buy_trade * Decimal(fee or self.fee_open)
|
fees = buy_trade * Decimal(fee or self.fee_open)
|
||||||
return float(buy_trade + fees)
|
return float(buy_trade + fees)
|
||||||
|
|
||||||
def calc_close_trade_price(
|
def calc_close_trade_price(self, rate: Optional[float] = None,
|
||||||
self,
|
fee: Optional[float] = None) -> float:
|
||||||
rate: Optional[float] = None,
|
|
||||||
fee: Optional[float] = None) -> float:
|
|
||||||
"""
|
"""
|
||||||
Calculate the close_rate including fee
|
Calculate the close_rate including fee
|
||||||
:param fee: fee to use on the close rate (optional).
|
:param fee: fee to use on the close rate (optional).
|
||||||
@ -357,7 +351,6 @@ class Trade(_DECL_BASE):
|
|||||||
If rate is not set self.close_rate will be used
|
If rate is not set self.close_rate will be used
|
||||||
:return: Price in BTC of the open trade
|
:return: Price in BTC of the open trade
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if rate is None and not self.close_rate:
|
if rate is None and not self.close_rate:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
@ -365,10 +358,8 @@ class Trade(_DECL_BASE):
|
|||||||
fees = sell_trade * Decimal(fee or self.fee_close)
|
fees = sell_trade * Decimal(fee or self.fee_close)
|
||||||
return float(sell_trade - fees)
|
return float(sell_trade - fees)
|
||||||
|
|
||||||
def calc_profit(
|
def calc_profit(self, rate: Optional[float] = None,
|
||||||
self,
|
fee: Optional[float] = None) -> float:
|
||||||
rate: Optional[float] = None,
|
|
||||||
fee: Optional[float] = None) -> float:
|
|
||||||
"""
|
"""
|
||||||
Calculate the absolute profit in stake currency between Close and Open trade
|
Calculate the absolute profit in stake currency between Close and Open trade
|
||||||
:param fee: fee to use on the close rate (optional).
|
:param fee: fee to use on the close rate (optional).
|
||||||
@ -385,10 +376,8 @@ class Trade(_DECL_BASE):
|
|||||||
profit = close_trade_price - open_trade_price
|
profit = close_trade_price - open_trade_price
|
||||||
return float(f"{profit:.8f}")
|
return float(f"{profit:.8f}")
|
||||||
|
|
||||||
def calc_profit_percent(
|
def calc_profit_percent(self, rate: Optional[float] = None,
|
||||||
self,
|
fee: Optional[float] = None) -> float:
|
||||||
rate: Optional[float] = None,
|
|
||||||
fee: Optional[float] = None) -> float:
|
|
||||||
"""
|
"""
|
||||||
Calculates the profit in percentage (including fee).
|
Calculates the profit in percentage (including fee).
|
||||||
:param rate: rate to compare with (optional).
|
:param rate: rate to compare with (optional).
|
||||||
@ -396,7 +385,6 @@ class Trade(_DECL_BASE):
|
|||||||
:param fee: fee to use on the close rate (optional).
|
:param fee: fee to use on the close rate (optional).
|
||||||
:return: profit in percentage as float
|
:return: profit in percentage as float
|
||||||
"""
|
"""
|
||||||
|
|
||||||
open_trade_price = self.calc_open_trade_price()
|
open_trade_price = self.calc_open_trade_price()
|
||||||
close_trade_price = self.calc_close_trade_price(
|
close_trade_price = self.calc_close_trade_price(
|
||||||
rate=(rate or self.close_rate),
|
rate=(rate or self.close_rate),
|
||||||
@ -436,8 +424,8 @@ class Trade(_DECL_BASE):
|
|||||||
and trade.initial_stop_loss_pct != desired_stoploss):
|
and trade.initial_stop_loss_pct != desired_stoploss):
|
||||||
# Stoploss value got changed
|
# Stoploss value got changed
|
||||||
|
|
||||||
logger.info(f"Stoploss for {trade} needs adjustment.")
|
logger.info(f"Stoploss for {trade} needs adjustment...")
|
||||||
# Force reset of stoploss
|
# Force reset of stoploss
|
||||||
trade.stop_loss = None
|
trade.stop_loss = None
|
||||||
trade.adjust_stop_loss(trade.open_rate, desired_stoploss)
|
trade.adjust_stop_loss(trade.open_rate, desired_stoploss)
|
||||||
logger.info(f"new stoploss: {trade.stop_loss}, ")
|
logger.info(f"New stoploss: {trade.stop_loss}.")
|
||||||
|
@ -202,7 +202,6 @@ class IStrategy(ABC):
|
|||||||
:param metadata: Metadata dictionary with additional data (e.g. 'pair')
|
:param metadata: Metadata dictionary with additional data (e.g. 'pair')
|
||||||
:return: DataFrame with ticker data and indicator data
|
:return: DataFrame with ticker data and indicator data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pair = str(metadata.get('pair'))
|
pair = str(metadata.get('pair'))
|
||||||
|
|
||||||
# Test if seen this pair and last candle before.
|
# Test if seen this pair and last candle before.
|
||||||
@ -292,7 +291,6 @@ class IStrategy(ABC):
|
|||||||
:param force_stoploss: Externally provided stoploss
|
:param force_stoploss: Externally provided stoploss
|
||||||
:return: True if trade should be sold, False otherwise
|
:return: True if trade should be sold, False otherwise
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Set current rate to low for backtesting sell
|
# Set current rate to low for backtesting sell
|
||||||
current_rate = low or rate
|
current_rate = low or rate
|
||||||
current_profit = trade.calc_profit_percent(current_rate)
|
current_profit = trade.calc_profit_percent(current_rate)
|
||||||
@ -304,6 +302,8 @@ class IStrategy(ABC):
|
|||||||
force_stoploss=force_stoploss, high=high)
|
force_stoploss=force_stoploss, high=high)
|
||||||
|
|
||||||
if stoplossflag.sell_flag:
|
if stoplossflag.sell_flag:
|
||||||
|
logger.debug(f"{trade.pair} - Stoploss hit. sell_flag=True, "
|
||||||
|
f"sell_type={stoplossflag.sell_type}")
|
||||||
return stoplossflag
|
return stoplossflag
|
||||||
|
|
||||||
# Set current rate to high for backtesting sell
|
# Set current rate to high for backtesting sell
|
||||||
@ -312,22 +312,31 @@ class IStrategy(ABC):
|
|||||||
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.')
|
# This one is noisy, commented out
|
||||||
|
# logger.debug(f"{trade.pair} - Buy signal still active. sell_flag=False")
|
||||||
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
return SellCheckTuple(sell_flag=False, sell_type=SellType.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(f"{trade.pair} - Required profit reached. sell_flag=True, "
|
||||||
|
f"sell_type=SellType.ROI")
|
||||||
return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI)
|
return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI)
|
||||||
|
|
||||||
if experimental.get('sell_profit_only', False):
|
if experimental.get('sell_profit_only', False):
|
||||||
logger.debug('Checking if trade is profitable..')
|
# This one is noisy, commented out
|
||||||
|
# logger.debug(f"{trade.pair} - Checking if trade is profitable...")
|
||||||
if trade.calc_profit(rate=rate) <= 0:
|
if trade.calc_profit(rate=rate) <= 0:
|
||||||
|
# This one is noisy, commented out
|
||||||
|
# logger.debug(f"{trade.pair} - Trade is not profitable. sell_flag=False")
|
||||||
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
return SellCheckTuple(sell_flag=False, sell_type=SellType.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(f"{trade.pair} - Sell signal received. sell_flag=True, "
|
||||||
|
f"sell_type=SellType.SELL_SIGNAL")
|
||||||
return SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL)
|
return SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL)
|
||||||
|
|
||||||
|
# This one is noisy, commented out...
|
||||||
|
# logger.debug(f"{trade.pair} - No sell signal. sell_flag=False")
|
||||||
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
||||||
|
|
||||||
def stop_loss_reached(self, current_rate: float, trade: Trade,
|
def stop_loss_reached(self, current_rate: float, trade: Trade,
|
||||||
@ -338,7 +347,6 @@ class IStrategy(ABC):
|
|||||||
decides to sell or not
|
decides to sell or not
|
||||||
:param current_profit: current profit in percent
|
:param current_profit: current profit in percent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trailing_stop = self.config.get('trailing_stop', False)
|
trailing_stop = self.config.get('trailing_stop', False)
|
||||||
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
|
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
|
||||||
|
|
||||||
@ -359,7 +367,7 @@ class IStrategy(ABC):
|
|||||||
if 'trailing_stop_positive' in self.config and high_profit > sl_offset:
|
if 'trailing_stop_positive' in self.config and high_profit > sl_offset:
|
||||||
# Ignore mypy error check in configuration that this is a float
|
# Ignore mypy error check in configuration that this is a float
|
||||||
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
|
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
|
||||||
logger.debug(f"using positive stop loss: {stop_loss_value} "
|
logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} "
|
||||||
f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%")
|
f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%")
|
||||||
|
|
||||||
trade.adjust_stop_loss(high or current_rate, stop_loss_value)
|
trade.adjust_stop_loss(high or current_rate, stop_loss_value)
|
||||||
@ -369,20 +377,20 @@ class IStrategy(ABC):
|
|||||||
(trade.stop_loss >= current_rate) and
|
(trade.stop_loss >= current_rate) and
|
||||||
(not self.order_types.get('stoploss_on_exchange'))):
|
(not self.order_types.get('stoploss_on_exchange'))):
|
||||||
|
|
||||||
selltype = SellType.STOP_LOSS
|
sell_type = SellType.STOP_LOSS
|
||||||
|
|
||||||
# If initial stoploss is not the same as current one then it is trailing.
|
# If initial stoploss is not the same as current one then it is trailing.
|
||||||
if trade.initial_stop_loss != trade.stop_loss:
|
if trade.initial_stop_loss != trade.stop_loss:
|
||||||
selltype = SellType.TRAILING_STOP_LOSS
|
sell_type = SellType.TRAILING_STOP_LOSS
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"HIT STOP: current price at {current_rate:.6f}, "
|
f"{trade.pair} - HIT STOP: current price at {current_rate:.6f}, "
|
||||||
f"stop loss is {trade.stop_loss:.6f}, "
|
f"stoploss is {trade.stop_loss:.6f}, "
|
||||||
f"initial stop loss was at {trade.initial_stop_loss:.6f}, "
|
f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
|
||||||
f"trade opened at {trade.open_rate:.6f}")
|
f"trade opened at {trade.open_rate:.6f}")
|
||||||
logger.debug(f"trailing stop saved {trade.stop_loss - trade.initial_stop_loss:.6f}")
|
logger.debug(f"{trade.pair} - Trailing stop saved "
|
||||||
|
f"{trade.stop_loss - trade.initial_stop_loss:.6f}")
|
||||||
|
|
||||||
logger.debug('Stop loss hit.')
|
return SellCheckTuple(sell_flag=True, sell_type=sell_type)
|
||||||
return SellCheckTuple(sell_flag=True, sell_type=selltype)
|
|
||||||
|
|
||||||
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
||||||
|
|
||||||
|
@ -1590,6 +1590,8 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
|
|||||||
Trade.session = MagicMock()
|
Trade.session = MagicMock()
|
||||||
trade.open_order_id = '123'
|
trade.open_order_id = '123'
|
||||||
trade.open_fee = 0.001
|
trade.open_fee = 0.001
|
||||||
|
# Add datetime explicitly since sqlalchemy defaults apply only once written to database
|
||||||
|
trade.open_date = arrow.utcnow().datetime
|
||||||
freqtrade.update_trade_state(trade)
|
freqtrade.update_trade_state(trade)
|
||||||
# Test amount not modified by fee-logic
|
# Test amount not modified by fee-logic
|
||||||
assert not log_has_re(r'Applying fee to .*', caplog)
|
assert not log_has_re(r'Applying fee to .*', caplog)
|
||||||
@ -1823,7 +1825,8 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
|
|||||||
# if ROI is reached we must sell
|
# if ROI is reached we must sell
|
||||||
patch_get_signal(freqtrade, value=(False, True))
|
patch_get_signal(freqtrade, value=(False, True))
|
||||||
assert freqtrade.handle_trade(trade)
|
assert freqtrade.handle_trade(trade)
|
||||||
assert log_has('Required profit reached. Selling..', caplog)
|
assert log_has("ETH/BTC - Required profit reached. sell_flag=True, sell_type=SellType.ROI",
|
||||||
|
caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_handle_trade_experimental(
|
def test_handle_trade_experimental(
|
||||||
@ -1853,7 +1856,8 @@ def test_handle_trade_experimental(
|
|||||||
|
|
||||||
patch_get_signal(freqtrade, value=(False, True))
|
patch_get_signal(freqtrade, value=(False, True))
|
||||||
assert freqtrade.handle_trade(trade)
|
assert freqtrade.handle_trade(trade)
|
||||||
assert log_has('Sell signal received. Selling..', caplog)
|
assert log_has("ETH/BTC - Sell signal received. sell_flag=True, sell_type=SellType.SELL_SIGNAL",
|
||||||
|
caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
|
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
|
||||||
@ -2132,6 +2136,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
|
|||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
|
open_date = arrow.utcnow().shift(minutes=-601)
|
||||||
trade_buy = Trade(
|
trade_buy = Trade(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
open_rate=0.00001099,
|
open_rate=0.00001099,
|
||||||
@ -2141,16 +2146,18 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
|
|||||||
fee_open=0.0,
|
fee_open=0.0,
|
||||||
fee_close=0.0,
|
fee_close=0.0,
|
||||||
stake_amount=1,
|
stake_amount=1,
|
||||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
open_date=open_date.datetime,
|
||||||
is_open=True
|
is_open=True
|
||||||
)
|
)
|
||||||
|
|
||||||
Trade.session.add(trade_buy)
|
Trade.session.add(trade_buy)
|
||||||
|
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert log_has_re(r'Cannot query order for Trade\(id=1, pair=ETH/BTC, amount=90.99181073, '
|
assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ETH/BTC, amount=90.99181073, "
|
||||||
r'open_rate=0.00001099, open_since=10 hours ago\) due to Traceback \(most '
|
r"open_rate=0.00001099, open_since="
|
||||||
r'recent call last\):\n.*', caplog)
|
f"{open_date.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
r"\) due to Traceback \(most recent call last\):\n*",
|
||||||
|
caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
|
def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
|
||||||
@ -2793,8 +2800,9 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog,
|
|||||||
# Sell as trailing-stop is reached
|
# Sell as trailing-stop is reached
|
||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
assert log_has(
|
assert log_has(
|
||||||
f'HIT STOP: current price at 0.000012, stop loss is 0.000015, '
|
f"ETH/BTC - HIT STOP: current price at 0.000012, "
|
||||||
f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog)
|
f"stoploss is 0.000015, "
|
||||||
|
f"initial stoploss was at 0.000010, trade opened at 0.000011", caplog)
|
||||||
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
|
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
|
||||||
|
|
||||||
|
|
||||||
@ -2836,8 +2844,8 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets
|
|||||||
}))
|
}))
|
||||||
# stop-loss not reached, adjusted stoploss
|
# stop-loss not reached, adjusted stoploss
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f'using positive stop loss: 0.01 offset: 0 profit: 0.2666%', caplog)
|
assert log_has(f"ETH/BTC - Using positive stoploss: 0.01 offset: 0 profit: 0.2666%", caplog)
|
||||||
assert log_has(f'adjusted stop loss', caplog)
|
assert log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
|
||||||
assert trade.stop_loss == 0.0000138501
|
assert trade.stop_loss == 0.0000138501
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
||||||
@ -2849,9 +2857,9 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets
|
|||||||
# Lower price again (but still positive)
|
# Lower price again (but still positive)
|
||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
assert log_has(
|
assert log_has(
|
||||||
f'HIT STOP: current price at {buy_price + 0.000002:.6f}, '
|
f"ETH/BTC - HIT STOP: current price at {buy_price + 0.000002:.6f}, "
|
||||||
f'stop loss is {trade.stop_loss:.6f}, '
|
f"stoploss is {trade.stop_loss:.6f}, "
|
||||||
f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog)
|
f"initial stoploss was at 0.000010, trade opened at 0.000011", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
|
def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
|
||||||
@ -2894,8 +2902,9 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
|
|||||||
}))
|
}))
|
||||||
# stop-loss not reached, adjusted stoploss
|
# stop-loss not reached, adjusted stoploss
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f'using positive stop loss: 0.01 offset: 0.011 profit: 0.2666%', caplog)
|
assert log_has(f"ETH/BTC - Using positive stoploss: 0.01 offset: 0.011 profit: 0.2666%",
|
||||||
assert log_has(f'adjusted stop loss', caplog)
|
caplog)
|
||||||
|
assert log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
|
||||||
assert trade.stop_loss == 0.0000138501
|
assert trade.stop_loss == 0.0000138501
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
||||||
@ -2907,9 +2916,9 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
|
|||||||
# Lower price again (but still positive)
|
# Lower price again (but still positive)
|
||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
assert log_has(
|
assert log_has(
|
||||||
f'HIT STOP: current price at {buy_price + 0.000002:.6f}, '
|
f"ETH/BTC - HIT STOP: current price at {buy_price + 0.000002:.6f}, "
|
||||||
f'stop loss is {trade.stop_loss:.6f}, '
|
f"stoploss is {trade.stop_loss:.6f}, "
|
||||||
f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog)
|
f"initial stoploss was at 0.000010, trade opened at 0.000011", caplog)
|
||||||
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
|
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
|
||||||
|
|
||||||
|
|
||||||
@ -2960,7 +2969,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
|
|||||||
# stop-loss should not be adjusted as offset is not reached yet
|
# stop-loss should not be adjusted as offset is not reached yet
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
|
|
||||||
assert not log_has(f'adjusted stop loss', caplog)
|
assert not log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
|
||||||
assert trade.stop_loss == 0.0000098910
|
assert trade.stop_loss == 0.0000098910
|
||||||
|
|
||||||
# price rises above the offset (rises 12% when the offset is 5.5%)
|
# price rises above the offset (rises 12% when the offset is 5.5%)
|
||||||
@ -2972,8 +2981,9 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f'using positive stop loss: 0.05 offset: 0.055 profit: 0.1218%', caplog)
|
assert log_has(f"ETH/BTC - Using positive stoploss: 0.05 offset: 0.055 profit: 0.1218%",
|
||||||
assert log_has(f'adjusted stop loss', caplog)
|
caplog)
|
||||||
|
assert log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
|
||||||
assert trade.stop_loss == 0.0000117705
|
assert trade.stop_loss == 0.0000117705
|
||||||
|
|
||||||
|
|
||||||
@ -3342,8 +3352,8 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2, markets)
|
|||||||
default_conf['telegram']['enabled'] = False
|
default_conf['telegram']['enabled'] = False
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
# ordrebook shall be used even if tickers would be lower.
|
# orderbook shall be used even if tickers would be lower.
|
||||||
assert freqtrade.get_target_bid('ETH/BTC', ) != 0.042
|
assert freqtrade.get_target_bid('ETH/BTC') != 0.042
|
||||||
assert ticker_mock.call_count == 0
|
assert ticker_mock.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user