updated tests

This commit is contained in:
மனோஜ்குமார் பழனிச்சாமி
2022-04-04 19:14:52 +05:30
parent b646d8ba0e
commit 606e0f1b68
17 changed files with 141 additions and 10499 deletions

View File

@@ -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):

View File

@@ -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

View File

@@ -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
@@ -1384,7 +1385,7 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
# if stoploss is on exchange and we are on dry_run mode,
# we consider the sell price stop price
if (self.config['dry_run'] and exit_type == 'stoploss'
and self.strategy.order_types['stoploss_on_exchange']):
and self.strategy.order_types['stoploss_on_exchange']):
limit = trade.stop_loss
# set custom_exit_price if available
@@ -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)
@@ -1794,4 +1795,4 @@ max_entry_stake=min(max_entry_stake, stake_available), max_exit_stake=min(max_ex
# Bracket between min_custom_price_allowed and max_custom_price_allowed
return max(
min(valid_custom_price, max_custom_price_allowed),
min_custom_price_allowed)
min_custom_price_allowed)

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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