updated tests
This commit is contained in:
parent
b646d8ba0e
commit
606e0f1b68
@ -13,6 +13,7 @@ class ExitType(Enum):
|
||||
FORCE_SELL = "force_sell"
|
||||
EMERGENCY_SELL = "emergency_sell"
|
||||
CUSTOM_SELL = "custom_sell"
|
||||
PARTIAL_SELL = "partial_sell"
|
||||
NONE = ""
|
||||
|
||||
def __str__(self):
|
||||
|
@ -1428,8 +1428,9 @@ class Exchange:
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
def get_rate(self, pair: str, refresh: bool,
|
||||
side: Literal['entry', 'exit'], is_short: bool, order_book: Optional[dict] = None, ticker: Optional[dict] = Non) -> float:
|
||||
def get_rate(self, pair: str, refresh: bool, # noqa: max-complexity: 13
|
||||
side: Literal['entry', 'exit'], is_short: bool,
|
||||
order_book: Optional[dict] = None, ticker: Optional[dict] = None) -> float:
|
||||
"""
|
||||
Calculates bid/ask target
|
||||
bid rate - between current ask price and last price
|
||||
@ -1533,7 +1534,8 @@ class Exchange:
|
||||
if not entry_rate:
|
||||
entry_rate = self.get_rate(pair, refresh, 'entry', is_short, ticker=ticker)
|
||||
if not exit_rate:
|
||||
exit_rate = self.get_rate(pair, refresh, 'exit', order_book=order_book, ticker=ticker)
|
||||
exit_rate = self.get_rate(pair, refresh, 'exit',
|
||||
is_short, order_book=order_book, ticker=ticker)
|
||||
return entry_rate, exit_rate
|
||||
|
||||
# Fee handling
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -459,7 +459,6 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
if signal:
|
||||
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
|
||||
|
||||
bid_check_dom = self.config.get('entry_pricing', {}).get('check_depth_of_market', {})
|
||||
if ((bid_check_dom.get('enabled', False)) and
|
||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||
@ -505,7 +504,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
If the strategy triggers the adjustment, a new order gets issued.
|
||||
Once that completes, the existing trade is modified to match new data.
|
||||
"""
|
||||
current_entry_rate, current_exit_rate = self.exchange.get_rates(trade.pair, True, is_short)
|
||||
current_entry_rate, current_exit_rate = self.exchange.get_rates(
|
||||
trade.pair, True, trade.is_short)
|
||||
|
||||
current_entry_profit = trade.calc_profit_ratio(current_entry_rate)
|
||||
current_exit_profit = trade.calc_profit_ratio(current_exit_rate)
|
||||
@ -528,7 +528,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
current_entry_rate=current_entry_rate, current_exit_rate=current_exit_rate,
|
||||
current_entry_profit=current_entry_profit, current_exit_profit=current_exit_profit,
|
||||
min_entry_stake=min_entry_stake, min_exit_stake=min_exit_stake,
|
||||
max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_exit_stake, stake_available),
|
||||
max_entry_stake=min(max_entry_stake, stake_available),
|
||||
max_exit_stake=min(max_exit_stake, stake_available)
|
||||
)
|
||||
|
||||
if stake_amount is not None and stake_amount > 0.0:
|
||||
@ -540,7 +541,8 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
|
||||
return
|
||||
else:
|
||||
logger.debug("Max adjustment entries is set to unlimited.")
|
||||
self.execute_entry(trade.pair, stake_amount, current_entry_rate, trade=trade, is_short=trade.is_short)
|
||||
self.execute_entry(trade.pair, stake_amount, current_entry_rate,
|
||||
trade=trade, is_short=trade.is_short)
|
||||
|
||||
if stake_amount is not None and stake_amount < 0.0:
|
||||
# We should decrease our position
|
||||
@ -553,8 +555,8 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
|
||||
logger.info(
|
||||
f"Adjusting amount to trade.amount as it is higher. {amount} > {trade.amount}")
|
||||
amount = trade.amount
|
||||
self.execute_trade_exit(trade, current_exit_rate, sell_reason=SellCheckTuple(
|
||||
sell_type=SellType.CUSTOM_SELL), sub_trade_amt=amount)
|
||||
self.execute_trade_exit(trade, current_exit_rate, exit_check=ExitCheckTuple(
|
||||
exit_type=ExitType.PARTIAL_SELL), sub_trade_amt=amount)
|
||||
|
||||
def _check_depth_of_market(self, pair: str, conf: Dict, side: SignalDirection) -> bool:
|
||||
"""
|
||||
@ -628,7 +630,6 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
|
||||
|
||||
amount = (stake_amount / enter_limit_requested) * leverage
|
||||
order_type = ordertype or self.strategy.order_types['entry']
|
||||
|
||||
if not pos_adjust and not strategy_safe_wrapper(
|
||||
self.strategy.confirm_trade_entry, default_retval=True)(
|
||||
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
|
||||
@ -648,7 +649,7 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
|
||||
)
|
||||
order_obj = Order.parse_from_ccxt_object(order, pair, side)
|
||||
order_id = order['id']
|
||||
order_status = order.get('status', None)
|
||||
order_status = order.get('status')
|
||||
logger.info(f"Order #{order_id} was created for {pair} and status is {order_status}.")
|
||||
|
||||
# we assume the order is executed at the price requested
|
||||
@ -744,8 +745,8 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
|
||||
else:
|
||||
logger.info(f"DCA order {order_status}, will wait for resolution: {trade}")
|
||||
|
||||
# Update fees if order is closed
|
||||
if order_status == 'closed':
|
||||
# Update fees if order is non-opened
|
||||
if order_status in constants.NON_OPEN_EXCHANGE_STATES:
|
||||
self.update_trade_state(trade, order_id, order)
|
||||
|
||||
return True
|
||||
@ -1471,7 +1472,7 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
|
||||
profit_rate = order.safe_price
|
||||
|
||||
if not fill:
|
||||
trade.process_sell_sub_trade(order, is_closed=False)
|
||||
trade.process_exit_sub_trade(order, is_closed=False)
|
||||
|
||||
profit_ratio = trade.close_profit
|
||||
profit = trade.close_profit_abs
|
||||
@ -1637,12 +1638,12 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
|
||||
# Updating wallets when order is closed
|
||||
self.wallets.update()
|
||||
|
||||
sub_trade = not isclose(order_obj.safe_amount_after_fee,
|
||||
trade.amount, abs_tol=constants.MATH_CLOSE_PREC)
|
||||
if not trade.is_open:
|
||||
self.handle_protections(trade.pair)
|
||||
sub_trade = order_obj.safe_amount_after_fee != trade.amount
|
||||
if order.get('side', None) == 'sell':
|
||||
if send_msg and not stoploss_order and not trade.open_order_id:
|
||||
self._notify_exit(trade, '', True, sub_trade=sub_trade, order=order_obj)
|
||||
self.handle_protections(trade.pair)
|
||||
elif send_msg and not trade.open_order_id:
|
||||
# Enter fill
|
||||
self._notify_enter(trade, order_obj, fill=True, sub_trade=sub_trade)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -480,10 +480,11 @@ class Backtesting:
|
||||
stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position,
|
||||
default_retval=None)(
|
||||
trade=trade, current_time=row[DATE_IDX].to_pydatetime(), current_rate=current_rate,
|
||||
current_profit=current_profit, min_stake=min_stake, max_stake=min(max_stake, stake_available),
|
||||
current_profit=current_profit, min_stake=min_stake,
|
||||
max_stake=min(max_stake, stake_available),
|
||||
current_entry_rate=current_rate, current_exit_rate=current_rate,
|
||||
max_entry_stake=min(max_stake, stake_available),
|
||||
max_exit_stake=min(max_stake, stake_available))
|
||||
max_entry_stake=min(max_stake, stake_available),
|
||||
max_exit_stake=min(max_stake, stake_available))
|
||||
|
||||
# Check if we should increase our position
|
||||
if stake_amount is not None and stake_amount > 0.0:
|
||||
@ -586,7 +587,7 @@ max_exit_stake=min(max_stake, stake_available))
|
||||
close_rate: float, amount: float = None) -> Optional[LocalTrade]:
|
||||
self.order_id_counter += 1
|
||||
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||
order_type = self.strategy.order_types['sell']
|
||||
order_type = self.strategy.order_types['exit']
|
||||
amount = amount or trade.amount
|
||||
order = Order(
|
||||
id=self.order_id_counter,
|
||||
@ -905,7 +906,7 @@ max_exit_stake=min(max_stake, stake_available))
|
||||
return None
|
||||
return row
|
||||
|
||||
def backtest(self, processed: Dict,
|
||||
def backtest(self, processed: Dict, # noqa: max-complexity: 13
|
||||
start_date: datetime, end_date: datetime,
|
||||
max_open_trades: int = 0, position_stacking: bool = False,
|
||||
enable_protections: bool = False) -> Dict[str, Any]:
|
||||
@ -1007,7 +1008,7 @@ max_exit_stake=min(max_stake, stake_available))
|
||||
sub_trade = order.safe_amount_after_fee != trade.amount
|
||||
if sub_trade:
|
||||
order.close_bt_order(current_time)
|
||||
trade.process_sell_sub_trade(order)
|
||||
trade.process_exit_sub_trade(order)
|
||||
trade.recalc_trade_from_orders()
|
||||
else:
|
||||
trade.close_date = current_time
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@ This module contains the class to persist trades into SQLite
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from math import isclose
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy import (Boolean, Column, DateTime, Enum, Float, ForeignKey, Integer, String,
|
||||
@ -13,7 +14,7 @@ from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session
|
||||
from sqlalchemy.pool import StaticPool
|
||||
from sqlalchemy.sql.schema import UniqueConstraint
|
||||
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES, MATH_CLOSE_PREC
|
||||
from freqtrade.enums import ExitType, TradingMode
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.leverage import interest
|
||||
@ -193,7 +194,7 @@ class Order(_DECL_BASE):
|
||||
self.order_filled_date = datetime.now(timezone.utc)
|
||||
self.order_update_date = datetime.now(timezone.utc)
|
||||
|
||||
def to_json(self, entry_side: str) -> Dict[str, Any]:
|
||||
def to_json(self, enter_side: str) -> Dict[str, Any]:
|
||||
return {
|
||||
'pair': self.ft_pair,
|
||||
'order_id': self.order_id,
|
||||
@ -215,7 +216,7 @@ class Order(_DECL_BASE):
|
||||
tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None,
|
||||
'order_type': self.order_type,
|
||||
'price': self.price,
|
||||
'ft_is_entry': self.ft_order_side == entry_side,
|
||||
'ft_is_entry': self.ft_order_side == enter_side,
|
||||
'remaining': self.remaining,
|
||||
}
|
||||
|
||||
@ -359,7 +360,7 @@ class LocalTrade():
|
||||
if self.has_no_leverage:
|
||||
return 0.0
|
||||
elif not self.is_short:
|
||||
return (self.amount * self.open_rate) * ((self.leverage-1)/self.leverage)
|
||||
return (self.amount * self.open_rate) * ((self.leverage - 1) / self.leverage)
|
||||
else:
|
||||
return self.amount
|
||||
|
||||
@ -613,6 +614,9 @@ class LocalTrade():
|
||||
# condition to avoid reset value when updating fees
|
||||
if self.open_order_id == order.order_id:
|
||||
self.open_order_id = None
|
||||
else:
|
||||
logger.warning(
|
||||
f'Got different open_order_id {self.open_order_id} != {order.order_id}')
|
||||
self.recalc_trade_from_orders()
|
||||
elif order.ft_order_side == self.exit_side:
|
||||
if self.is_open:
|
||||
@ -622,10 +626,16 @@ class LocalTrade():
|
||||
# condition to avoid reset value when updating fees
|
||||
if self.open_order_id == order.order_id:
|
||||
self.open_order_id = None
|
||||
if self.amount == order.safe_amount_after_fee:
|
||||
else:
|
||||
logger.warning(
|
||||
f'Got different open_order_id {self.open_order_id} != {order.order_id}')
|
||||
if isclose(order.safe_amount_after_fee,
|
||||
self.amount, abs_tol=MATH_CLOSE_PREC):
|
||||
self.close(order.safe_price)
|
||||
else:
|
||||
self.process_sell_sub_trade(order)
|
||||
logger.info((self.amount, self.to_json(), order.to_json(
|
||||
self.enter_side), order.safe_amount_after_fee))
|
||||
self.process_exit_sub_trade(order)
|
||||
elif order.ft_order_side == 'stoploss':
|
||||
self.stoploss_order_id = None
|
||||
self.close_rate_requested = self.stop_loss
|
||||
@ -637,25 +647,29 @@ class LocalTrade():
|
||||
raise ValueError(f'Unknown order type: {order.order_type}')
|
||||
Trade.commit()
|
||||
|
||||
def process_sell_sub_trade(self, order: Order, is_closed: bool = True) -> None:
|
||||
sell_amount = order.safe_amount_after_fee
|
||||
sell_rate = order.safe_price
|
||||
sell_stake_amount = sell_rate * sell_amount * (1 - self.fee_close)
|
||||
profit = self.calc_profit2(self.open_rate, sell_rate, sell_amount)
|
||||
def process_exit_sub_trade(self, order: Order, is_closed: bool = True) -> None:
|
||||
exit_amount = order.safe_amount_after_fee
|
||||
exit_rate = order.safe_price
|
||||
exit_stake_amount = exit_rate * exit_amount * (1 - self.fee_close)
|
||||
profit = self.calc_profit2(self.open_rate, exit_rate, exit_amount)
|
||||
if is_closed:
|
||||
self.amount -= sell_amount
|
||||
self.amount -= exit_amount
|
||||
self.stake_amount = self.open_rate * self.amount
|
||||
self.realized_profit += profit
|
||||
logger.info(
|
||||
'Processed exit sub trade for %s',
|
||||
self
|
||||
)
|
||||
|
||||
self.close_profit_abs = profit
|
||||
self.close_profit = sell_stake_amount / (sell_stake_amount - profit) - 1
|
||||
self.close_profit = exit_stake_amount / (exit_stake_amount - profit) - 1
|
||||
self.recalc_open_trade_value()
|
||||
|
||||
def calc_profit2(self, open_rate: float, close_rate: float,
|
||||
amount: float) -> float:
|
||||
return float(Decimal(amount) *
|
||||
(Decimal(1 - self.fee_close) * Decimal(close_rate) -
|
||||
Decimal(1 + self.fee_open) * Decimal(open_rate)))
|
||||
return float(Decimal(amount)
|
||||
* (Decimal(1 - self.fee_close) * Decimal(close_rate)
|
||||
- Decimal(1 + self.fee_open) * Decimal(open_rate)))
|
||||
|
||||
def close(self, rate: float, *, show_msg: bool = True) -> None:
|
||||
"""
|
||||
@ -729,7 +743,7 @@ class LocalTrade():
|
||||
def recalc_open_trade_value(self) -> None:
|
||||
"""
|
||||
Recalculate open_trade_value.
|
||||
Must be called whenever open_rate, fee_open or is_short is changed.
|
||||
Must be called whenever open_rate, fee_open is changed.
|
||||
"""
|
||||
self.open_trade_value = self._calc_open_trade_value()
|
||||
|
||||
@ -747,7 +761,7 @@ class LocalTrade():
|
||||
now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None)
|
||||
sec_per_hour = Decimal(3600)
|
||||
total_seconds = Decimal((now - open_date).total_seconds())
|
||||
hours = total_seconds/sec_per_hour or zero
|
||||
hours = total_seconds / sec_per_hour or zero
|
||||
|
||||
rate = Decimal(interest_rate or self.interest_rate)
|
||||
borrowed = Decimal(self.borrowed)
|
||||
@ -861,9 +875,9 @@ class LocalTrade():
|
||||
return 0.0
|
||||
else:
|
||||
if self.is_short:
|
||||
profit_ratio = (1 - (close_trade_value/self.open_trade_value)) * leverage
|
||||
profit_ratio = (1 - (close_trade_value / self.open_trade_value)) * leverage
|
||||
else:
|
||||
profit_ratio = ((close_trade_value/self.open_trade_value) - 1) * leverage
|
||||
profit_ratio = ((close_trade_value / self.open_trade_value) - 1) * leverage
|
||||
|
||||
return float(f"{profit_ratio:.8f}")
|
||||
|
||||
@ -878,11 +892,11 @@ class LocalTrade():
|
||||
|
||||
tmp_amount = o.safe_amount_after_fee
|
||||
tmp_price = o.safe_price
|
||||
is_sell = o.ft_order_side != 'buy'
|
||||
side = -1 if is_sell else 1
|
||||
is_exit = o.ft_order_side != self.enter_side
|
||||
side = -1 if is_exit else 1
|
||||
if tmp_amount > 0.0 and tmp_price is not None:
|
||||
total_amount += tmp_amount * side
|
||||
price = avg_price if is_sell else tmp_price
|
||||
price = avg_price if is_exit else tmp_price
|
||||
total_stake += price * tmp_amount * side
|
||||
if total_amount > 0:
|
||||
avg_price = total_stake / total_amount
|
||||
@ -932,9 +946,9 @@ class LocalTrade():
|
||||
:return: array of Order objects
|
||||
"""
|
||||
return [o for o in self.orders if ((o.ft_order_side == order_side) or (order_side is None))
|
||||
and o.ft_is_open is False and
|
||||
(o.filled or 0) > 0 and
|
||||
o.status in NON_OPEN_EXCHANGE_STATES]
|
||||
and o.ft_is_open is False
|
||||
and o.filled
|
||||
and o.status in NON_OPEN_EXCHANGE_STATES]
|
||||
|
||||
@property
|
||||
def nr_of_successful_entries(self) -> int:
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -969,7 +969,12 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
# Make sure current_profit is calculated using high for backtesting.
|
||||
bound = low if trade.is_short else high
|
||||
bound_profit = current_profit if not bound else trade.calc_profit_ratio(bound)
|
||||
|
||||
from pprint import pformat
|
||||
logger.info(pformat(trade.to_json()))
|
||||
logger.info((self.trailing_only_offset_is_reached,
|
||||
self.trailing_stop_positive, bound_profit, sl_offset))
|
||||
logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} "
|
||||
f"offset: {sl_offset:.4g} profit: {current_profit:.2%}")
|
||||
# Don't update stoploss if trailing_only_offset_is_reached is true.
|
||||
if not (self.trailing_only_offset_is_reached and bound_profit < sl_offset):
|
||||
# Specific handling for trailing_stop_positive
|
||||
|
@ -2469,7 +2469,7 @@ def test_get_exit_rate_exception(default_conf, mocker, is_short):
|
||||
assert exchange.get_rate(pair, refresh=True, side="exit", is_short=is_short) == 0.13
|
||||
|
||||
|
||||
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_buy_rate_data)
|
||||
@pytest.mark.parametrize("side,ask,bid,last,last_ab,expected", get_entry_rate_data)
|
||||
@pytest.mark.parametrize("side2", ['bid', 'ask'])
|
||||
@pytest.mark.parametrize("use_order_book", [True, False])
|
||||
def test_get_rates_testing_buy(mocker, default_conf, caplog, side, ask, bid,
|
||||
@ -2477,26 +2477,26 @@ def test_get_rates_testing_buy(mocker, default_conf, caplog, side, ask, bid,
|
||||
side2, use_order_book, order_book_l2) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
if last_ab is None:
|
||||
del default_conf['bid_strategy']['ask_last_balance']
|
||||
del default_conf['entry_pricing']['price_last_balance']
|
||||
else:
|
||||
default_conf['bid_strategy']['ask_last_balance'] = last_ab
|
||||
default_conf['bid_strategy']['price_side'] = side
|
||||
default_conf['ask_strategy']['price_side'] = side2
|
||||
default_conf['ask_strategy']['use_order_book'] = use_order_book
|
||||
default_conf['entry_pricing']['price_last_balance'] = last_ab
|
||||
default_conf['entry_pricing']['price_side'] = side
|
||||
default_conf['exit_pricing']['price_side'] = side2
|
||||
default_conf['exit_pricing']['use_order_book'] = use_order_book
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_l2_order_book = order_book_l2
|
||||
api_mock.fetch_ticker = MagicMock(
|
||||
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
|
||||
assert exchange.get_rates('ETH/BTC', refresh=True)[0] == expected
|
||||
assert exchange.get_rates('ETH/BTC', refresh=True, is_short=False)[0] == expected
|
||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
|
||||
assert exchange.get_rates('ETH/BTC', refresh=False)[0] == expected
|
||||
assert exchange.get_rates('ETH/BTC', refresh=False, is_short=False)[0] == expected
|
||||
assert log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
# Running a 2nd time with Refresh on!
|
||||
caplog.clear()
|
||||
assert exchange.get_rates('ETH/BTC', refresh=True)[0] == expected
|
||||
assert exchange.get_rates('ETH/BTC', refresh=True, is_short=False)[0] == expected
|
||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||
|
||||
api_mock.fetch_l2_order_book.call_count = int(use_order_book)
|
||||
@ -2511,12 +2511,12 @@ def test_get_rates_testing_sell(default_conf, mocker, caplog, side, bid, ask,
|
||||
side2, use_order_book, order_book_l2) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
default_conf['ask_strategy']['price_side'] = side
|
||||
default_conf['exit_pricing']['price_side'] = side
|
||||
if last_ab is not None:
|
||||
default_conf['ask_strategy']['bid_last_balance'] = last_ab
|
||||
default_conf['exit_pricing']['price_last_balance'] = last_ab
|
||||
|
||||
default_conf['bid_strategy']['price_side'] = side2
|
||||
default_conf['bid_strategy']['use_order_book'] = use_order_book
|
||||
default_conf['entry_pricing']['price_side'] = side2
|
||||
default_conf['entry_pricing']['use_order_book'] = use_order_book
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_l2_order_book = order_book_l2
|
||||
api_mock.fetch_ticker = MagicMock(
|
||||
@ -2526,12 +2526,12 @@ def test_get_rates_testing_sell(default_conf, mocker, caplog, side, bid, ask,
|
||||
pair = "ETH/BTC"
|
||||
|
||||
# Test regular mode
|
||||
rate = exchange.get_rates(pair, refresh=True)[1]
|
||||
rate = exchange.get_rates(pair, refresh=True, is_short=False)[1]
|
||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
assert isinstance(rate, float)
|
||||
assert rate == expected
|
||||
# Use caching
|
||||
rate = exchange.get_rates(pair, refresh=False)[1]
|
||||
rate = exchange.get_rates(pair, refresh=False, is_short=False)[1]
|
||||
assert rate == expected
|
||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||
|
||||
|
@ -297,6 +297,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||
|
||||
# Simulate buy & sell
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
trade.orders[0] = oobj
|
||||
trade.update_trade(oobj)
|
||||
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
|
||||
trade.update_trade(oobj)
|
||||
@ -438,7 +439,8 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
freqtradebot.enter_positions()
|
||||
trade = Trade.query.first()
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'sell')
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
trade.orders[0] = oobj
|
||||
trade.update_trade(oobj)
|
||||
|
||||
# Update the ticker with a market going up
|
||||
@ -452,20 +454,23 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
trade.is_open = False
|
||||
|
||||
freqtradebot.enter_positions()
|
||||
trade = Trade.query.first()
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
trade.update_trade(oobj)
|
||||
|
||||
# TODO: updated the first trade again
|
||||
# trade = Trade.query.first()
|
||||
# # Simulate fulfilled LIMIT_BUY order for trade
|
||||
# oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
# trade.orders[0] = oobj
|
||||
# trade.update_trade(oobj)
|
||||
|
||||
# Update the ticker with a market going up
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker_sell_up
|
||||
)
|
||||
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
|
||||
trade.update_trade(oobj)
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.is_open = False
|
||||
# mocker.patch.multiple(
|
||||
# 'freqtrade.exchange.Exchange',
|
||||
# fetch_ticker=ticker_sell_up
|
||||
# )
|
||||
# oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
|
||||
# trade.update_trade(oobj)
|
||||
# trade.close_date = datetime.utcnow()
|
||||
# trade.is_open = False
|
||||
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05)
|
||||
@ -523,6 +528,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
||||
trade = Trade.query.first()
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
trade.orders[0] = oobj
|
||||
trade.update_trade(oobj)
|
||||
# Update the ticker with a market going up
|
||||
mocker.patch.multiple(
|
||||
@ -924,6 +930,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
trade.orders[0] = oobj
|
||||
trade.update_trade(oobj)
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
@ -960,6 +967,7 @@ def test_enter_tag_performance_handle(default_conf, ticker, limit_buy_order, fee
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
trade.orders[0] = oobj
|
||||
trade.update_trade(oobj)
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
@ -1034,6 +1042,7 @@ def test_exit_reason_performance_handle(default_conf, ticker, limit_buy_order, f
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
trade.orders[0] = oobj
|
||||
trade.update_trade(oobj)
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
@ -1108,6 +1117,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
|
||||
trade.orders[0] = oobj
|
||||
trade.update_trade(oobj)
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2057,7 +2057,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'enter_tag': 'buy_signal1',
|
||||
'exit_reason': SellType.STOP_LOSS.value,
|
||||
'exit_reason': ExitType.STOP_LOSS.value,
|
||||
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
@ -2144,7 +2144,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
|
||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
||||
assert msg_mock.call_args[0][0] == (
|
||||
'\N{WARNING SIGN} *Binance:* Exited KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41% (loss: -0.05746268 ETH)`\`\n'
|
||||
'*Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||
f'*Enter Tag:* `{enter_signal}`\n'
|
||||
'*Exit Reason:* `stop_loss`\n'
|
||||
f"*Direction:* `{direction}`\n"
|
||||
|
@ -845,7 +845,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == 10
|
||||
assert trade.stake_amount == round(order['price'] * order['filled'] / leverage, 8)
|
||||
assert trade.stake_amount == round(order['average'] * order['filled'] / leverage, 8)
|
||||
|
||||
# In case of rejected or expired order and partially filled
|
||||
order['status'] = 'expired'
|
||||
@ -861,7 +861,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
|
||||
trade = Trade.query.all()[3]
|
||||
trade.is_short = is_short
|
||||
assert trade
|
||||
assert trade.open_order_id == '555'
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == 0.5
|
||||
assert trade.stake_amount == round(order['average'] * order['filled'] / leverage, 8)
|
||||
|
||||
@ -1075,7 +1075,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
'last': 1.9
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': enter_order['id']},
|
||||
enter_order,
|
||||
exit_order,
|
||||
]),
|
||||
get_fee=fee,
|
||||
@ -1101,20 +1101,20 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
# should do nothing and return false
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_order_id = "100"
|
||||
|
||||
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order)
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert trade.stoploss_order_id == 100
|
||||
assert trade.stoploss_order_id == "100"
|
||||
|
||||
# Third case: when stoploss was set but it was canceled for some reason
|
||||
# should set a stoploss immediately and return False
|
||||
caplog.clear()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
trade.stoploss_order_id = "100"
|
||||
|
||||
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order)
|
||||
@ -1134,6 +1134,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = "100"
|
||||
|
||||
trade.orders.append(Order(
|
||||
ft_order_side='stoploss',
|
||||
order_id='100',
|
||||
@ -1201,7 +1202,6 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
||||
trade.is_open = True
|
||||
trade.stoploss_last_update = arrow.utcnow().shift(hours=-1).datetime
|
||||
trade.stop_loss = 24
|
||||
trade.amount = limit_buy_order_usdt['amount']
|
||||
freqtrade.config['trailing_stop'] = True
|
||||
stoploss = MagicMock(side_effect=InvalidOrderException())
|
||||
|
||||
@ -2341,9 +2341,9 @@ def test_close_trade(
|
||||
trade.is_short = is_short
|
||||
assert trade
|
||||
|
||||
oobj = Order.parse_from_ccxt_object(enter_order, enter_order['symbol'], 'buy')
|
||||
oobj = Order.parse_from_ccxt_object(enter_order, enter_order['symbol'], trade.enter_side)
|
||||
trade.update_trade(oobj)
|
||||
oobj = Order.parse_from_ccxt_object(exit_order, exit_order['symbol'], 'sell')
|
||||
oobj = Order.parse_from_ccxt_object(exit_order, exit_order['symbol'], trade.exit_side)
|
||||
trade.update_trade(oobj)
|
||||
assert trade.is_open is False
|
||||
|
||||
@ -2534,9 +2534,9 @@ def test_check_handle_timedout_exit_usercustom(
|
||||
is_short, open_trade_usdt, caplog
|
||||
) -> None:
|
||||
default_conf_usdt["unfilledtimeout"] = {"entry": 1440, "exit": 1440, "exit_timeout_count": 1}
|
||||
open_trade.open_order_id = limit_sell_order_old['id']
|
||||
open_trade_usdt.open_order_id = limit_sell_order_old['id']
|
||||
order = Order.parse_from_ccxt_object(limit_sell_order_old, 'mocked', 'sell')
|
||||
open_trade.orders[0] = order
|
||||
open_trade_usdt.orders[0] = order
|
||||
if is_short:
|
||||
limit_sell_order_old['side'] = 'buy'
|
||||
open_trade_usdt.is_short = is_short
|
||||
@ -3505,11 +3505,11 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
|
||||
if is_short:
|
||||
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.SHORT
|
||||
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.SHORT_FILL
|
||||
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
||||
|
||||
else:
|
||||
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
|
||||
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
|
||||
|
||||
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
||||
|
||||
|
||||
@ -3674,7 +3674,7 @@ def test_sell_profit_only(
|
||||
'last': bid
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_order_open[eside],
|
||||
limit_order[eside],
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
@ -3956,7 +3956,7 @@ def test_trailing_stop_loss_positive(
|
||||
'last': enter_price - (-0.01 if is_short else 0.01),
|
||||
}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_order_open[eside],
|
||||
limit_order[eside],
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
@ -5420,7 +5420,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_1))
|
||||
assert freqtrade.execute_trade_exit(trade=trade, limit=8,
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS),
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_SELL),
|
||||
sub_trade_amt=15)
|
||||
|
||||
# Assert trade is as expected (averaged dca)
|
||||
@ -5552,7 +5552,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_1))
|
||||
assert freqtrade.execute_trade_exit(trade=trade, limit=ask,
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS),
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_SELL),
|
||||
sub_trade_amt=amount)
|
||||
trades: List[Trade] = trade.get_open_trades_without_assigned_fees()
|
||||
assert len(trades) == 1
|
||||
@ -5598,7 +5598,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||
MagicMock(return_value=closed_sell_dca_order_2))
|
||||
assert freqtrade.execute_trade_exit(trade=trade, limit=ask,
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS),
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_SELL),
|
||||
sub_trade_amt=amount)
|
||||
# Assert trade is as expected (averaged dca)
|
||||
|
||||
@ -5694,7 +5694,7 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None:
|
||||
else:
|
||||
assert freqtrade.execute_trade_exit(
|
||||
trade=trade, limit=price,
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS),
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_SELL),
|
||||
sub_trade_amt=amount)
|
||||
|
||||
orders1 = Order.query.all()
|
||||
|
@ -478,7 +478,7 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
trade.open_order_id = 'mocked_limit_buy_usdt'
|
||||
trade.open_order_id = enter_order['id']
|
||||
oobj = Order.parse_from_ccxt_object(enter_order, 'ADA/USDT', enter_side)
|
||||
trade.update_trade(oobj)
|
||||
assert trade.open_order_id is None
|
||||
@ -492,7 +492,7 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
|
||||
caplog)
|
||||
|
||||
caplog.clear()
|
||||
trade.open_order_id = 'mocked_limit_sell_usdt'
|
||||
trade.open_order_id = enter_order['id']
|
||||
time_machine.move_to("2022-03-31 21:45:05 +00:00")
|
||||
oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', exit_side)
|
||||
trade.update_trade(oobj)
|
||||
@ -2543,7 +2543,6 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
|
||||
assert trade.open_trade_value == o1_trade_val
|
||||
assert trade.nr_of_successful_entries == 2
|
||||
|
||||
|
||||
# Check with 1 order
|
||||
order_noavg = Order(
|
||||
ft_order_side=enter_side,
|
||||
|
Loading…
Reference in New Issue
Block a user