Merge branch 'freqtrade:develop' into backtest_bias_tester

This commit is contained in:
hippocritical 2023-03-10 12:41:41 +01:00 committed by GitHub
commit e0d8ce877a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 150 additions and 86 deletions

View File

@ -17,8 +17,8 @@ repos:
- types-filelock==3.2.7 - types-filelock==3.2.7
- types-requests==2.28.11.15 - types-requests==2.28.11.15
- types-tabulate==0.9.0.1 - types-tabulate==0.9.0.1
- types-python-dateutil==2.8.19.9 - types-python-dateutil==2.8.19.10
- SQLAlchemy==2.0.4 - SQLAlchemy==2.0.5.post1
# stages: [push] # stages: [push]
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort

View File

@ -84,6 +84,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `add_state_info` | Tell FreqAI to include state information in the feature set for training and inferencing. The current state variables include trade duration, current profit, trade position. This is only available in dry/live runs, and is automatically switched to false for backtesting. <br> **Datatype:** bool. <br> Default: `False`. | `add_state_info` | Tell FreqAI to include state information in the feature set for training and inferencing. The current state variables include trade duration, current profit, trade position. This is only available in dry/live runs, and is automatically switched to false for backtesting. <br> **Datatype:** bool. <br> Default: `False`.
| `net_arch` | Network architecture which is well described in [`stable_baselines3` doc](https://stable-baselines3.readthedocs.io/en/master/guide/custom_policy.html#examples). In summary: `[<shared layers>, dict(vf=[<non-shared value network layers>], pi=[<non-shared policy network layers>])]`. By default this is set to `[128, 128]`, which defines 2 shared hidden layers with 128 units each. | `net_arch` | Network architecture which is well described in [`stable_baselines3` doc](https://stable-baselines3.readthedocs.io/en/master/guide/custom_policy.html#examples). In summary: `[<shared layers>, dict(vf=[<non-shared value network layers>], pi=[<non-shared policy network layers>])]`. By default this is set to `[128, 128]`, which defines 2 shared hidden layers with 128 units each.
| `randomize_starting_position` | Randomize the starting point of each episode to avoid overfitting. <br> **Datatype:** bool. <br> Default: `False`. | `randomize_starting_position` | Randomize the starting point of each episode to avoid overfitting. <br> **Datatype:** bool. <br> Default: `False`.
| `drop_ohlc_from_features` | Do not include the normalized ohlc data in the feature set passed to the agent during training (ohlc will still be used for driving the environment in all cases) <br> **Datatype:** Boolean. <br> **Default:** `False`
### Additional parameters ### Additional parameters

View File

@ -176,9 +176,11 @@ As you begin to modify the strategy and the prediction model, you will quickly r
factor = 100 factor = 100
pair = self.pair.replace(':', '')
# you can use feature values from dataframe # you can use feature values from dataframe
# Assumes the shifted RSI indicator has been generated in the strategy. # Assumes the shifted RSI indicator has been generated in the strategy.
rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{self.pair}_" rsi_now = self.raw_features[f"%-rsi-period-10_shift-1_{pair}_"
f"{self.config['timeframe']}"].iloc[self._current_tick] f"{self.config['timeframe']}"].iloc[self._current_tick]
# reward agent for entering trades # reward agent for entering trades

View File

@ -1,6 +1,6 @@
markdown==3.3.7 markdown==3.3.7
mkdocs==1.4.2 mkdocs==1.4.2
mkdocs-material==9.0.15 mkdocs-material==9.1.1
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==9.9.2 pymdown-extensions==9.10
jinja2==3.1.2 jinja2==3.1.2

View File

@ -316,11 +316,11 @@ class AwesomeStrategy(IStrategy):
# evaluate highest to lowest, so that highest possible stop is used # evaluate highest to lowest, so that highest possible stop is used
if current_profit > 0.40: if current_profit > 0.40:
return stoploss_from_open(0.25, current_profit, is_short=trade.is_short) return stoploss_from_open(0.25, current_profit, is_short=trade.is_short, leverage=trade.leverage)
elif current_profit > 0.25: elif current_profit > 0.25:
return stoploss_from_open(0.15, current_profit, is_short=trade.is_short) return stoploss_from_open(0.15, current_profit, is_short=trade.is_short, leverage=trade.leverage)
elif current_profit > 0.20: elif current_profit > 0.20:
return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) return stoploss_from_open(0.07, current_profit, is_short=trade.is_short, leverage=trade.leverage)
# return maximum stoploss value, keeping current stoploss price unchanged # return maximum stoploss value, keeping current stoploss price unchanged
return 1 return 1

View File

@ -881,7 +881,7 @@ All columns of the informative dataframe will be available on the returning data
### *stoploss_from_open()* ### *stoploss_from_open()*
Stoploss values returned from `custom_stoploss` must specify a percentage relative to `current_rate`, but sometimes you may want to specify a stoploss relative to the open price instead. `stoploss_from_open()` is a helper function to calculate a stoploss value that can be returned from `custom_stoploss` which will be equivalent to the desired percentage above the open price. Stoploss values returned from `custom_stoploss` must specify a percentage relative to `current_rate`, but sometimes you may want to specify a stoploss relative to the entry point instead. `stoploss_from_open()` is a helper function to calculate a stoploss value that can be returned from `custom_stoploss` which will be equivalent to the desired trade profit above the entry point.
??? Example "Returning a stoploss relative to the open price from the custom stoploss function" ??? Example "Returning a stoploss relative to the open price from the custom stoploss function"
@ -889,6 +889,8 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati
If we want a stop price at 7% above the open price we can call `stoploss_from_open(0.07, current_profit, False)` which will return `0.1157024793`. 11.57% below $121 is $107, which is the same as 7% above $100. If we want a stop price at 7% above the open price we can call `stoploss_from_open(0.07, current_profit, False)` which will return `0.1157024793`. 11.57% below $121 is $107, which is the same as 7% above $100.
This function will consider leverage - so at 10x leverage, the actual stoploss would be 0.7% above $100 (0.7% * 10x = 7%).
``` python ``` python
@ -907,7 +909,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati
# once the profit has risen above 10%, keep the stoploss at 7% above the open price # once the profit has risen above 10%, keep the stoploss at 7% above the open price
if current_profit > 0.10: if current_profit > 0.10:
return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) return stoploss_from_open(0.07, current_profit, is_short=trade.is_short, leverage=trade.leverage)
return 1 return 1

View File

@ -588,6 +588,7 @@ CONF_SCHEMA = {
"rl_config": { "rl_config": {
"type": "object", "type": "object",
"properties": { "properties": {
"drop_ohlc_from_features": {"type": "boolean", "default": False},
"train_cycles": {"type": "integer"}, "train_cycles": {"type": "integer"},
"max_trade_duration_candles": {"type": "integer"}, "max_trade_duration_candles": {"type": "integer"},
"add_state_info": {"type": "boolean", "default": False}, "add_state_info": {"type": "boolean", "default": False},

View File

@ -69,6 +69,7 @@ class Exchange:
# Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency # Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency
"ohlcv_volume_currency": "base", # "base" or "quote" "ohlcv_volume_currency": "base", # "base" or "quote"
"tickers_have_quoteVolume": True, "tickers_have_quoteVolume": True,
"tickers_have_bid_ask": True, # bid / ask empty for fetch_tickers
"tickers_have_price": True, "tickers_have_price": True,
"trades_pagination": "time", # Possible are "time" or "id" "trades_pagination": "time", # Possible are "time" or "id"
"trades_pagination_arg": "since", "trades_pagination_arg": "since",

View File

@ -32,6 +32,7 @@ class Gate(Exchange):
_ft_has_futures: Dict = { _ft_has_futures: Dict = {
"needs_trading_fees": True, "needs_trading_fees": True,
"tickers_have_bid_ask": False,
"fee_cost_in_contracts": False, # Set explicitly to false for clarity "fee_cost_in_contracts": False, # Set explicitly to false for clarity
"order_props_in_contracts": ['amount', 'filled', 'remaining'], "order_props_in_contracts": ['amount', 'filled', 'remaining'],
"stop_price_type_field": "price_type", "stop_price_type_field": "price_type",

View File

@ -114,6 +114,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
# normalize all data based on train_dataset only # normalize all data based on train_dataset only
prices_train, prices_test = self.build_ohlc_price_dataframes(dk.data_dictionary, pair, dk) prices_train, prices_test = self.build_ohlc_price_dataframes(dk.data_dictionary, pair, dk)
data_dictionary = dk.normalize_data(data_dictionary) data_dictionary = dk.normalize_data(data_dictionary)
# data cleaning/analysis # data cleaning/analysis
@ -148,12 +149,8 @@ class BaseReinforcementLearningModel(IFreqaiModel):
env_info = self.pack_env_dict(dk.pair) env_info = self.pack_env_dict(dk.pair)
self.train_env = self.MyRLEnv(df=train_df, self.train_env = self.MyRLEnv(df=train_df, prices=prices_train, **env_info)
prices=prices_train, self.eval_env = Monitor(self.MyRLEnv(df=test_df, prices=prices_test, **env_info))
**env_info)
self.eval_env = Monitor(self.MyRLEnv(df=test_df,
prices=prices_test,
**env_info))
self.eval_callback = EvalCallback(self.eval_env, deterministic=True, self.eval_callback = EvalCallback(self.eval_env, deterministic=True,
render=False, eval_freq=len(train_df), render=False, eval_freq=len(train_df),
best_model_save_path=str(dk.data_path)) best_model_save_path=str(dk.data_path))
@ -238,6 +235,9 @@ class BaseReinforcementLearningModel(IFreqaiModel):
filtered_dataframe, _ = dk.filter_features( filtered_dataframe, _ = dk.filter_features(
unfiltered_df, dk.training_features_list, training_filter=False unfiltered_df, dk.training_features_list, training_filter=False
) )
filtered_dataframe = self.drop_ohlc_from_df(filtered_dataframe, dk)
filtered_dataframe = dk.normalize_data_from_metadata(filtered_dataframe) filtered_dataframe = dk.normalize_data_from_metadata(filtered_dataframe)
dk.data_dictionary["prediction_features"] = filtered_dataframe dk.data_dictionary["prediction_features"] = filtered_dataframe
@ -285,7 +285,6 @@ class BaseReinforcementLearningModel(IFreqaiModel):
train_df = data_dictionary["train_features"] train_df = data_dictionary["train_features"]
test_df = data_dictionary["test_features"] test_df = data_dictionary["test_features"]
# %-raw_volume_gen_shift-2_ETH/USDT_1h
# price data for model training and evaluation # price data for model training and evaluation
tf = self.config['timeframe'] tf = self.config['timeframe']
rename_dict = {'%-raw_open': 'open', '%-raw_low': 'low', rename_dict = {'%-raw_open': 'open', '%-raw_low': 'low',
@ -318,8 +317,24 @@ class BaseReinforcementLearningModel(IFreqaiModel):
prices_test.rename(columns=rename_dict, inplace=True) prices_test.rename(columns=rename_dict, inplace=True)
prices_test.reset_index(drop=True) prices_test.reset_index(drop=True)
train_df = self.drop_ohlc_from_df(train_df, dk)
test_df = self.drop_ohlc_from_df(test_df, dk)
return prices_train, prices_test return prices_train, prices_test
def drop_ohlc_from_df(self, df: DataFrame, dk: FreqaiDataKitchen):
"""
Given a dataframe, drop the ohlc data
"""
drop_list = ['%-raw_open', '%-raw_low', '%-raw_high', '%-raw_close']
if self.rl_config["drop_ohlc_from_features"]:
df.drop(drop_list, axis=1, inplace=True)
feature_list = dk.training_features_list
dk.training_features_list = [e for e in feature_list if e not in drop_list]
return df
def load_model_from_disk(self, dk: FreqaiDataKitchen) -> Any: def load_model_from_disk(self, dk: FreqaiDataKitchen) -> Any:
""" """
Can be used by user if they are trying to limit_ram_usage *and* Can be used by user if they are trying to limit_ram_usage *and*

View File

@ -2,8 +2,7 @@ from datetime import datetime, timezone
from typing import Any, ClassVar, Dict, Optional from typing import Any, ClassVar, Dict, Optional
from sqlalchemy import String, or_ from sqlalchemy import String, or_
from sqlalchemy.orm import Mapped, Query, mapped_column from sqlalchemy.orm import Mapped, Query, QueryPropertyDescriptor, mapped_column
from sqlalchemy.orm.scoping import _QueryDescriptorType
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.persistence.base import ModelBase, SessionType from freqtrade.persistence.base import ModelBase, SessionType
@ -14,7 +13,7 @@ class PairLock(ModelBase):
Pair Locks database model. Pair Locks database model.
""" """
__tablename__ = 'pairlocks' __tablename__ = 'pairlocks'
query: ClassVar[_QueryDescriptorType] query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType] _session: ClassVar[SessionType]
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)

View File

@ -8,8 +8,8 @@ from math import isclose
from typing import Any, ClassVar, Dict, List, Optional, cast from typing import Any, ClassVar, Dict, List, Optional, cast
from sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func from sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func
from sqlalchemy.orm import Mapped, Query, lazyload, mapped_column, relationship from sqlalchemy.orm import (Mapped, Query, QueryPropertyDescriptor, lazyload, mapped_column,
from sqlalchemy.orm.scoping import _QueryDescriptorType relationship)
from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES, from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES,
BuySell, LongShort) BuySell, LongShort)
@ -36,7 +36,7 @@ class Order(ModelBase):
Mirrors CCXT Order structure Mirrors CCXT Order structure
""" """
__tablename__ = 'orders' __tablename__ = 'orders'
query: ClassVar[_QueryDescriptorType] query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType] _session: ClassVar[SessionType]
# Uniqueness should be ensured over pair, order_id # Uniqueness should be ensured over pair, order_id
@ -1181,7 +1181,7 @@ class Trade(ModelBase, LocalTrade):
Note: Fields must be aligned with LocalTrade class Note: Fields must be aligned with LocalTrade class
""" """
__tablename__ = 'trades' __tablename__ = 'trades'
query: ClassVar[_QueryDescriptorType] query: ClassVar[QueryPropertyDescriptor]
_session: ClassVar[SessionType] _session: ClassVar[SessionType]
use_db: bool = True use_db: bool = True

View File

@ -5,6 +5,7 @@ import logging
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from freqtrade.constants import Config from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
from freqtrade.exchange.types import Ticker from freqtrade.exchange.types import Ticker
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -22,6 +23,12 @@ class SpreadFilter(IPairList):
self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005) self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005)
self._enabled = self._max_spread_ratio != 0 self._enabled = self._max_spread_ratio != 0
if not self._exchange.get_option('tickers_have_bid_ask'):
raise OperationalException(
f"{self.name} requires exchange to have bid/ask data for tickers, "
"which is not available for the selected exchange / trading mode."
)
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """

View File

@ -286,6 +286,7 @@ class OpenTradeSchema(TradeSchema):
current_rate: float current_rate: float
total_profit_abs: float total_profit_abs: float
total_profit_fiat: Optional[float] total_profit_fiat: Optional[float]
total_profit_ratio: Optional[float]
open_order: Optional[str] open_order: Optional[str]

View File

@ -42,7 +42,8 @@ logger = logging.getLogger(__name__)
# 2.22: Add FreqAI to backtesting # 2.22: Add FreqAI to backtesting
# 2.23: Allow plot config request in webserver mode # 2.23: Allow plot config request in webserver mode
# 2.24: Add cancel_open_order endpoint # 2.24: Add cancel_open_order endpoint
API_VERSION = 2.24 # 2.25: Add several profit values to /status endpoint
API_VERSION = 2.25
# Public API, requires no auth. # Public API, requires no auth.
router_public = APIRouter() router_public = APIRouter()

View File

@ -192,6 +192,11 @@ class RPC:
current_profit = trade.close_profit or 0.0 current_profit = trade.close_profit or 0.0
current_profit_abs = trade.close_profit_abs or 0.0 current_profit_abs = trade.close_profit_abs or 0.0
total_profit_abs = trade.realized_profit + current_profit_abs total_profit_abs = trade.realized_profit + current_profit_abs
total_profit_ratio: Optional[float] = None
if trade.max_stake_amount:
total_profit_ratio = (
(total_profit_abs / trade.max_stake_amount) * trade.leverage
)
# Calculate fiat profit # Calculate fiat profit
if not isnan(current_profit_abs) and self._fiat_converter: if not isnan(current_profit_abs) and self._fiat_converter:
@ -224,6 +229,7 @@ class RPC:
total_profit_abs=total_profit_abs, total_profit_abs=total_profit_abs,
total_profit_fiat=total_profit_fiat, total_profit_fiat=total_profit_fiat,
total_profit_ratio=total_profit_ratio,
stoploss_current_dist=stoploss_current_dist, stoploss_current_dist=stoploss_current_dist,
stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8),
stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2), stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2),

View File

@ -510,14 +510,14 @@ class Telegram(RPCHandler):
if prev_avg_price: if prev_avg_price:
minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price
lines.append(f"*{wording} #{order_nr}:* at {minus_on_entry:.2%} avg profit") lines.append(f"*{wording} #{order_nr}:* at {minus_on_entry:.2%} avg Profit")
if is_open: if is_open:
lines.append("({})".format(cur_entry_datetime lines.append("({})".format(cur_entry_datetime
.humanize(granularity=["day", "hour", "minute"]))) .humanize(granularity=["day", "hour", "minute"])))
lines.append(f"*Amount:* {cur_entry_amount} " lines.append(f"*Amount:* {cur_entry_amount} "
f"({round_coin_value(order['cost'], quote_currency)})") f"({round_coin_value(order['cost'], quote_currency)})")
lines.append(f"*Average {wording} Price:* {cur_entry_average} " lines.append(f"*Average {wording} Price:* {cur_entry_average} "
f"({price_to_1st_entry:.2%} from 1st entry rate)") f"({price_to_1st_entry:.2%} from 1st entry Rate)")
lines.append(f"*Order filled:* {order['order_filled_date']}") lines.append(f"*Order filled:* {order['order_filled_date']}")
# TODO: is this really useful? # TODO: is this really useful?
@ -569,6 +569,8 @@ class Telegram(RPCHandler):
and not o['ft_order_side'] == 'stoploss']) and not o['ft_order_side'] == 'stoploss'])
r['exit_reason'] = r.get('exit_reason', "") r['exit_reason'] = r.get('exit_reason', "")
r['stake_amount_r'] = round_coin_value(r['stake_amount'], r['quote_currency']) r['stake_amount_r'] = round_coin_value(r['stake_amount'], r['quote_currency'])
r['max_stake_amount_r'] = round_coin_value(
r['max_stake_amount'] or r['stake_amount'], r['quote_currency'])
r['profit_abs_r'] = round_coin_value(r['profit_abs'], r['quote_currency']) r['profit_abs_r'] = round_coin_value(r['profit_abs'], r['quote_currency'])
r['realized_profit_r'] = round_coin_value(r['realized_profit'], r['quote_currency']) r['realized_profit_r'] = round_coin_value(r['realized_profit'], r['quote_currency'])
r['total_profit_abs_r'] = round_coin_value( r['total_profit_abs_r'] = round_coin_value(
@ -580,31 +582,37 @@ class Telegram(RPCHandler):
f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}" f"*Direction:* {'`Short`' if r.get('is_short') else '`Long`'}"
+ " ` ({leverage}x)`" if r.get('leverage') else "", + " ` ({leverage}x)`" if r.get('leverage') else "",
"*Amount:* `{amount} ({stake_amount_r})`", "*Amount:* `{amount} ({stake_amount_r})`",
"*Total invested:* `{max_stake_amount_r}`" if position_adjust else "",
"*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "",
"*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "",
] ]
if position_adjust: if position_adjust:
max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "") max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "")
lines.append("*Number of Entries:* `{num_entries}" + max_buy_str + "`") lines.extend([
lines.append("*Number of Exits:* `{num_exits}`") "*Number of Entries:* `{num_entries}" + max_buy_str + "`",
"*Number of Exits:* `{num_exits}`"
])
lines.extend([ lines.extend([
"*Open Rate:* `{open_rate:.8f}`", "*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "", "*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "",
"*Open Date:* `{open_date}`", "*Open Date:* `{open_date}`",
"*Close Date:* `{close_date}`" if r['close_date'] else "", "*Close Date:* `{close_date}`" if r['close_date'] else "",
"*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "", " \n*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "",
("*Unrealized Profit:* " if r['is_open'] else "*Close Profit: *") ("*Unrealized Profit:* " if r['is_open'] else "*Close Profit: *")
+ "`{profit_ratio:.2%}` `({profit_abs_r})`", + "`{profit_ratio:.2%}` `({profit_abs_r})`",
]) ])
if r['is_open']: if r['is_open']:
if r.get('realized_profit'): if r.get('realized_profit'):
lines.append( lines.extend([
"*Realized Profit:* `{realized_profit_r} {realized_profit_ratio:.2%}`") "*Realized Profit:* `{realized_profit_ratio:.2%} ({realized_profit_r})`",
lines.append("*Total Profit:* `{total_profit_abs_r}` ") "*Total Profit:* `{total_profit_ratio:.2%} ({total_profit_abs_r})`"
])
# Append empty line to improve readability
lines.append(" ")
if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] if (r['stop_loss_abs'] != r['initial_stop_loss_abs']
and r['initial_stop_loss_ratio'] is not None): and r['initial_stop_loss_ratio'] is not None):
# Adding initial stoploss only if it is different from stoploss # Adding initial stoploss only if it is different from stoploss

View File

@ -86,37 +86,41 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
def stoploss_from_open( def stoploss_from_open(
open_relative_stop: float, open_relative_stop: float,
current_profit: float, current_profit: float,
is_short: bool = False is_short: bool = False,
leverage: float = 1.0
) -> float: ) -> float:
""" """
Given the current profit, and a desired stop loss value relative to the trade entry price,
Given the current profit, and a desired stop loss value relative to the open price,
return a stop loss value that is relative to the current price, and which can be return a stop loss value that is relative to the current price, and which can be
returned from `custom_stoploss`. returned from `custom_stoploss`.
The requested stop can be positive for a stop above the open price, or negative for The requested stop can be positive for a stop above the open price, or negative for
a stop below the open price. The return value is always >= 0. a stop below the open price. The return value is always >= 0.
`open_relative_stop` will be considered as adjusted for leverage if leverage is provided..
Returns 0 if the resulting stop price would be above/below (longs/shorts) the current price Returns 0 if the resulting stop price would be above/below (longs/shorts) the current price
:param open_relative_stop: Desired stop loss percentage relative to open price :param open_relative_stop: Desired stop loss percentage, relative to the open price,
adjusted for leverage
:param current_profit: The current profit percentage :param current_profit: The current profit percentage
:param is_short: When true, perform the calculation for short instead of long :param is_short: When true, perform the calculation for short instead of long
:param leverage: Leverage to use for the calculation
:return: Stop loss value relative to current price :return: Stop loss value relative to current price
""" """
# formula is undefined for current_profit -1 (longs) or 1 (shorts), return maximum value # formula is undefined for current_profit -1 (longs) or 1 (shorts), return maximum value
if (current_profit == -1 and not is_short) or (is_short and current_profit == 1): _current_profit = current_profit / leverage
if (_current_profit == -1 and not is_short) or (is_short and _current_profit == 1):
return 1 return 1
if is_short is True: if is_short is True:
stoploss = -1 + ((1 - open_relative_stop) / (1 - current_profit)) stoploss = -1 + ((1 - open_relative_stop / leverage) / (1 - _current_profit))
else: else:
stoploss = 1 - ((1 + open_relative_stop) / (1 + current_profit)) stoploss = 1 - ((1 + open_relative_stop / leverage) / (1 + _current_profit))
# negative stoploss values indicate the requested stop price is higher/lower # negative stoploss values indicate the requested stop price is higher/lower
# (long/short) than the current price # (long/short) than the current price
return max(stoploss, 0.0) return max(stoploss * leverage, 0.0)
def stoploss_from_absolute(stop_rate: float, current_rate: float, is_short: bool = False) -> float: def stoploss_from_absolute(stop_rate: float, current_rate: float, is_short: bool = False) -> float:

View File

@ -7,7 +7,7 @@
-r docs/requirements-docs.txt -r docs/requirements-docs.txt
coveralls==3.3.1 coveralls==3.3.1
ruff==0.0.253 ruff==0.0.254
mypy==1.0.1 mypy==1.0.1
pre-commit==3.1.1 pre-commit==3.1.1
pytest==7.2.1 pytest==7.2.1
@ -29,4 +29,4 @@ types-cachetools==5.3.0.4
types-filelock==3.2.7 types-filelock==3.2.7
types-requests==2.28.11.15 types-requests==2.28.11.15
types-tabulate==0.9.0.1 types-tabulate==0.9.0.1
types-python-dateutil==2.8.19.9 types-python-dateutil==2.8.19.10

View File

@ -2,10 +2,10 @@ numpy==1.24.2
pandas==1.5.3 pandas==1.5.3
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==2.8.54 ccxt==2.9.4
cryptography==39.0.1 cryptography==39.0.2
aiohttp==3.8.4 aiohttp==3.8.4
SQLAlchemy==2.0.4 SQLAlchemy==2.0.5.post1
python-telegram-bot==13.15 python-telegram-bot==13.15
arrow==1.2.3 arrow==1.2.3
cachetools==4.2.2 cachetools==4.2.2
@ -28,7 +28,7 @@ py_find_1st==1.1.5
# Load ticker files 30% faster # Load ticker files 30% faster
python-rapidjson==1.9 python-rapidjson==1.9
# Properly format api responses # Properly format api responses
orjson==3.8.6 orjson==3.8.7
# Notify systemd # Notify systemd
sdnotify==0.3.2 sdnotify==0.3.2
@ -45,7 +45,7 @@ psutil==5.9.4
colorama==0.4.6 colorama==0.4.6
# Building config files interactively # Building config files interactively
questionary==1.10.0 questionary==1.10.0
prompt-toolkit==3.0.37 prompt-toolkit==3.0.38
# Extensions to datetime library # Extensions to datetime library
python-dateutil==2.8.2 python-dateutil==2.8.2

View File

@ -78,7 +78,9 @@ def make_rl_config(conf):
"rr": 1, "rr": 1,
"profit_aim": 0.02, "profit_aim": 0.02,
"win_reward_factor": 2 "win_reward_factor": 2
}} },
"drop_ohlc_from_features": False
}
return conf return conf

View File

@ -68,13 +68,6 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
freqai_conf['freqai']['feature_parameters'].update({"shuffle_after_split": shuffle}) freqai_conf['freqai']['feature_parameters'].update({"shuffle_after_split": shuffle})
freqai_conf['freqai']['feature_parameters'].update({"buffer_train_data_candles": buffer}) freqai_conf['freqai']['feature_parameters'].update({"buffer_train_data_candles": buffer})
if 'ReinforcementLearner' in model:
model_save_ext = 'zip'
freqai_conf = make_rl_config(freqai_conf)
# test the RL guardrails
freqai_conf['freqai']['feature_parameters'].update({"use_SVM_to_remove_outliers": True})
freqai_conf['freqai']['data_split_parameters'].update({'shuffle': True})
if 'ReinforcementLearner' in model: if 'ReinforcementLearner' in model:
model_save_ext = 'zip' model_save_ext = 'zip'
freqai_conf = make_rl_config(freqai_conf) freqai_conf = make_rl_config(freqai_conf)
@ -84,6 +77,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
if 'test_3ac' in model or 'test_4ac' in model: if 'test_3ac' in model or 'test_4ac' in model:
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models") freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
freqai_conf["freqai"]["rl_config"]["drop_ohlc_from_features"] = True
strategy = get_patched_freqai_strategy(mocker, freqai_conf) strategy = get_patched_freqai_strategy(mocker, freqai_conf)
exchange = get_patched_exchange(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf)

View File

@ -924,7 +924,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data: BTContainer)
mocker.patch(f"{EXMS}.get_fee", return_value=0.0) mocker.patch(f"{EXMS}.get_fee", return_value=0.0)
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float('inf'))
mocker.patch('freqtrade.exchange.binance.Binance.get_max_leverage', return_value=100) mocker.patch(f"{EXMS}.get_max_leverage", return_value=100)
patch_exchange(mocker) patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data) frame = _build_backtest_dataframe(data.data)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)

View File

@ -828,6 +828,12 @@ def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> N
match=r'Exchange does not support fetchTickers, .*'): match=r'Exchange does not support fetchTickers, .*'):
get_patched_freqtradebot(mocker, default_conf) get_patched_freqtradebot(mocker, default_conf)
mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True))
mocker.patch(f'{EXMS}.get_option', MagicMock(return_value=False))
with pytest.raises(OperationalException,
match=r'.*requires exchange to have bid/ask data'):
get_patched_freqtradebot(mocker, default_conf)
@pytest.mark.parametrize("pairlist", TESTABLE_PAIRLISTS) @pytest.mark.parametrize("pairlist", TESTABLE_PAIRLISTS)
def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):

View File

@ -50,7 +50,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'amount': 91.07468123, 'amount': 91.07468123,
'amount_requested': 91.07468124, 'amount_requested': 91.07468124,
'stake_amount': 0.001, 'stake_amount': 0.001,
'max_stake_amount': ANY, 'max_stake_amount': None,
'trade_duration': None, 'trade_duration': None,
'trade_duration_s': None, 'trade_duration_s': None,
'close_profit': None, 'close_profit': None,
@ -79,6 +79,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'realized_profit_ratio': None, 'realized_profit_ratio': None,
'total_profit_abs': -4.09e-06, 'total_profit_abs': -4.09e-06,
'total_profit_fiat': ANY, 'total_profit_fiat': ANY,
'total_profit_ratio': None,
'exchange': 'binance', 'exchange': 'binance',
'leverage': 1.0, 'leverage': 1.0,
'interest_rate': 0.0, 'interest_rate': 0.0,
@ -168,6 +169,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
results = rpc._rpc_trade_status() results = rpc._rpc_trade_status()
response = deepcopy(gen_response) response = deepcopy(gen_response)
response.update({
'max_stake_amount': 0.001,
'total_profit_ratio': pytest.approx(-0.00409),
})
assert results[0] == response assert results[0] == response
mocker.patch(f'{EXMS}.get_rate', mocker.patch(f'{EXMS}.get_rate',
@ -181,10 +186,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_current_dist': ANY, 'stoploss_current_dist': ANY,
'stoploss_current_dist_ratio': ANY, 'stoploss_current_dist_ratio': ANY,
'stoploss_current_dist_pct': ANY, 'stoploss_current_dist_pct': ANY,
'max_stake_amount': 0.001,
'profit_ratio': ANY, 'profit_ratio': ANY,
'profit_pct': ANY, 'profit_pct': ANY,
'profit_abs': ANY, 'profit_abs': ANY,
'total_profit_abs': ANY, 'total_profit_abs': ANY,
'total_profit_ratio': ANY,
'current_rate': ANY, 'current_rate': ANY,
}) })
assert results[0] == response_norate assert results[0] == response_norate

View File

@ -1012,6 +1012,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'profit_fiat': ANY, 'profit_fiat': ANY,
'total_profit_abs': ANY, 'total_profit_abs': ANY,
'total_profit_fiat': ANY, 'total_profit_fiat': ANY,
'total_profit_ratio': ANY,
'realized_profit': 0.0, 'realized_profit': 0.0,
'realized_profit_ratio': None, 'realized_profit_ratio': None,
'current_rate': current_rate, 'current_rate': current_rate,

View File

@ -198,6 +198,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'current_rate': 1.098e-05, 'current_rate': 1.098e-05,
'amount': 90.99181074, 'amount': 90.99181074,
'stake_amount': 90.99181074, 'stake_amount': 90.99181074,
'max_stake_amount': 90.99181074,
'buy_tag': None, 'buy_tag': None,
'enter_tag': None, 'enter_tag': None,
'close_profit_ratio': None, 'close_profit_ratio': None,

View File

@ -177,26 +177,30 @@ def test_stoploss_from_open(side, profitrange):
("long", 0.1, 0.2, 1, 0.08333333), ("long", 0.1, 0.2, 1, 0.08333333),
("long", 0.1, 0.5, 1, 0.266666666), ("long", 0.1, 0.5, 1, 0.266666666),
("long", 0.1, 5, 1, 0.816666666), # 500% profit, set stoploss to 10% above open price ("long", 0.1, 5, 1, 0.816666666), # 500% profit, set stoploss to 10% above open price
("long", 0, 5, 10, 3.3333333), # 500% profit, set stoploss break even
("long", 0.1, 5, 10, 3.26666666), # 500% profit, set stoploss to 10% above open price
("long", -0.1, 5, 10, 3.3999999), # 500% profit, set stoploss to 10% belowopen price
("short", 0, 0.1, 1, 0.1111111), ("short", 0, 0.1, 1, 0.1111111),
("short", -0.1, 0.1, 1, 0.2222222), ("short", -0.1, 0.1, 1, 0.2222222),
("short", 0.1, 0.2, 1, 0.125), ("short", 0.1, 0.2, 1, 0.125),
("short", 0.1, 1, 1, 1), ("short", 0.1, 1, 1, 1),
("short", -0.01, 5, 10, 10.01999999), # 500% profit at 10x
]) ])
def test_stoploss_from_open_leverage(side, rel_stop, curr_profit, leverage, expected): def test_stoploss_from_open_leverage(side, rel_stop, curr_profit, leverage, expected):
stoploss = stoploss_from_open(rel_stop, curr_profit, side == 'short') stoploss = stoploss_from_open(rel_stop, curr_profit, side == 'short', leverage)
assert pytest.approx(stoploss) == expected assert pytest.approx(stoploss) == expected
open_rate = 100 open_rate = 100
if stoploss != 1: if stoploss != 1:
if side == 'long': if side == 'long':
current_rate = open_rate * (1 + curr_profit) current_rate = open_rate * (1 + curr_profit / leverage)
stop = current_rate * (1 - stoploss) stop = current_rate * (1 - stoploss / leverage)
assert pytest.approx(stop) == open_rate * (1 + rel_stop) assert pytest.approx(stop) == open_rate * (1 + rel_stop / leverage)
else: else:
current_rate = open_rate * (1 - curr_profit) current_rate = open_rate * (1 - curr_profit / leverage)
stop = current_rate * (1 + stoploss) stop = current_rate * (1 + stoploss / leverage)
assert pytest.approx(stop) == open_rate * (1 - rel_stop) assert pytest.approx(stop) == open_rate * (1 - rel_stop / leverage)
def test_stoploss_from_absolute(): def test_stoploss_from_absolute():

View File

@ -1068,7 +1068,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[]) mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[])
stoploss = MagicMock(return_value={'id': 13434334}) stoploss = MagicMock(return_value={'id': 13434334})
mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss) mocker.patch(f'{EXMS}.create_stoploss', stoploss)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.strategy.order_types['stoploss_on_exchange'] = True
@ -1263,7 +1263,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
get_fee=fee, get_fee=fee,
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.binance.Binance', EXMS,
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}), fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}),
create_stoploss=MagicMock(side_effect=ExchangeError()), create_stoploss=MagicMock(side_effect=ExchangeError()),
) )
@ -1307,7 +1307,7 @@ def test_create_stoploss_order_invalid_order(
get_fee=fee, get_fee=fee,
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.binance.Binance', EXMS,
fetch_order=MagicMock(return_value={'status': 'canceled'}), fetch_order=MagicMock(return_value={'status': 'canceled'}),
create_stoploss=MagicMock(side_effect=InvalidOrderException()), create_stoploss=MagicMock(side_effect=InvalidOrderException()),
) )
@ -1360,7 +1360,7 @@ def test_create_stoploss_order_insufficient_funds(
fetch_order=MagicMock(return_value={'status': 'canceled'}), fetch_order=MagicMock(return_value={'status': 'canceled'}),
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.binance.Binance', EXMS,
create_stoploss=MagicMock(side_effect=InsufficientFundsError()), create_stoploss=MagicMock(side_effect=InsufficientFundsError()),
) )
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
@ -1410,7 +1410,7 @@ def test_handle_stoploss_on_exchange_trailing(
get_fee=fee, get_fee=fee,
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.binance.Binance', EXMS,
create_stoploss=stoploss, create_stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True), stoploss_adjust=MagicMock(return_value=True),
) )
@ -1453,7 +1453,7 @@ def test_handle_stoploss_on_exchange_trailing(
} }
}) })
mocker.patch('freqtrade.exchange.binance.Binance.fetch_stoploss_order', stoploss_order_hanging) mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging)
# stoploss initially at 5% # stoploss initially at 5%
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -1471,8 +1471,8 @@ def test_handle_stoploss_on_exchange_trailing(
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
mocker.patch('freqtrade.exchange.binance.Binance.cancel_stoploss_order', cancel_order_mock) mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss_order_mock) mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds # stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -1535,7 +1535,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
get_fee=fee, get_fee=fee,
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.binance.Binance', EXMS,
create_stoploss=stoploss, create_stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True), stoploss_adjust=MagicMock(return_value=True),
) )
@ -1573,9 +1573,9 @@ def test_handle_stoploss_on_exchange_trailing_error(
'stopPrice': '0.1' 'stopPrice': '0.1'
} }
} }
mocker.patch('freqtrade.exchange.binance.Binance.cancel_stoploss_order', mocker.patch(f'{EXMS}.cancel_stoploss_order',
side_effect=InvalidOrderException()) side_effect=InvalidOrderException())
mocker.patch('freqtrade.exchange.binance.Binance.fetch_stoploss_order', mocker.patch(f'{EXMS}.fetch_stoploss_order',
return_value=stoploss_order_hanging) return_value=stoploss_order_hanging)
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog) assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog)
@ -1586,8 +1586,8 @@ def test_handle_stoploss_on_exchange_trailing_error(
# Fail creating stoploss order # Fail creating stoploss order
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime
caplog.clear() caplog.clear()
cancel_mock = mocker.patch('freqtrade.exchange.binance.Binance.cancel_stoploss_order') cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order')
mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', side_effect=ExchangeError()) mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError())
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert cancel_mock.call_count == 1 assert cancel_mock.call_count == 1
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog)
@ -1604,7 +1604,7 @@ def test_stoploss_on_exchange_price_rounding(
stoploss_mock = MagicMock(return_value={'id': '13434334'}) stoploss_mock = MagicMock(return_value={'id': '13434334'})
adjust_mock = MagicMock(return_value=False) adjust_mock = MagicMock(return_value=False)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.binance.Binance', EXMS,
create_stoploss=stoploss_mock, create_stoploss=stoploss_mock,
stoploss_adjust=adjust_mock, stoploss_adjust=adjust_mock,
price_to_precision=price_mock, price_to_precision=price_mock,
@ -1643,7 +1643,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
get_fee=fee, get_fee=fee,
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.binance.Binance', EXMS,
create_stoploss=stoploss, create_stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True), stoploss_adjust=MagicMock(return_value=True),
) )
@ -1686,7 +1686,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
} }
}) })
mocker.patch('freqtrade.exchange.binance.Binance.fetch_stoploss_order', stoploss_order_hanging) mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hanging)
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
@ -1703,8 +1703,8 @@ def test_handle_stoploss_on_exchange_custom_stop(
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
mocker.patch('freqtrade.exchange.binance.Binance.cancel_stoploss_order', cancel_order_mock) mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss_order_mock) mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds # stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -1821,7 +1821,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock() stoploss_order_mock = MagicMock()
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock) mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss_order_mock) mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
# price goes down 5% # price goes down 5%
mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={ mocker.patch(f'{EXMS}.fetch_ticker', MagicMock(return_value={
@ -3660,7 +3660,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
} }
}) })
mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss) mocker.patch(f'{EXMS}.create_stoploss', stoploss)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.strategy.order_types['stoploss_on_exchange'] = True

View File

@ -56,9 +56,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]] [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
) )
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.binance.Binance.create_stoploss', stoploss)
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
create_stoploss=stoploss,
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
amount_to_precision=lambda s, x, y: y, amount_to_precision=lambda s, x, y: y,