` (`list[str]`)
| `/pair_history` | GET | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy. **Alpha**
diff --git a/docs/stoploss.md b/docs/stoploss.md
index 7fbe6dabc..bb7a2003f 100644
--- a/docs/stoploss.md
+++ b/docs/stoploss.md
@@ -256,4 +256,4 @@ The new stoploss value will be applied to open trades (and corresponding log-mes
### Limitations
-Stoploss values cannot be changed if `trailing_stop` is enabled and the stoploss has already been adjusted, or if [Edge](edge.md) is enabled (since Edge would recalculate stoploss based on the current market situation).
+Stoploss values cannot be changed if `trailing_stop` is enabled and the stoploss has already been adjusted.
diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index a7a27f150..2c7411243 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -188,7 +188,7 @@ You can create your own keyboard in `config.json`:
!!! Note "Supported Commands"
Only the following commands are allowed. Command arguments are not supported!
- `/start`, `/pause`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version`, `/marketdir`
+ `/start`, `/pause`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/help`, `/version`, `/marketdir`
## Telegram commands
@@ -240,7 +240,6 @@ official commands. You can ask at any moment for help with `/help`.
| `/entries` | Shows Wins / losses by Exit reason as well as Avg. holding durations for buys and sells
| `/whitelist [sorted] [baseonly]` | Show the current whitelist. Optionally display in alphabetical order and/or with just the base currency of each pairing.
| `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
-| `/edge` | Show validated pairs by Edge if it is enabled.
## Telegram commands in action
@@ -451,21 +450,6 @@ Use `/reload_config` to reset the blacklist.
> Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`.
-### /edge
-
-Shows pairs validated by Edge along with their corresponding win-rate, expectancy and stoploss values.
-
-> **Edge only validated following pairs:**
-```
-Pair Winrate Expectancy Stoploss
--------- --------- ------------ ----------
-DOCK/ETH 0.522727 0.881821 -0.03
-PHX/ETH 0.677419 0.560488 -0.03
-HOT/ETH 0.733333 0.490492 -0.03
-HC/ETH 0.588235 0.280988 -0.02
-ARDR/ETH 0.366667 0.143059 -0.01
-```
-
### /version
> **Version:** `0.14.3`
diff --git a/docs/utils.md b/docs/utils.md
index 335785539..651dcb9d1 100644
--- a/docs/utils.md
+++ b/docs/utils.md
@@ -1,6 +1,6 @@
# Utility Subcommands
-Besides the Live-Trade and Dry-Run run modes, the `backtesting`, `edge` and `hyperopt` optimization subcommands, and the `download-data` subcommand which prepares historical data, the bot contains a number of utility subcommands. They are described in this section.
+Besides the Live-Trade and Dry-Run run modes, the `backtesting` and `hyperopt` optimization subcommands, and the `download-data` subcommand which prepares historical data, the bot contains a number of utility subcommands. They are described in this section.
## Create userdir
diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py
index df5775183..2e3de0a98 100755
--- a/freqtrade/commands/arguments.py
+++ b/freqtrade/commands/arguments.py
@@ -82,7 +82,7 @@ ARGS_HYPEROPT = [
"early_stop",
]
-ARGS_EDGE = [*ARGS_COMMON_OPTIMIZE, "stoploss_range"]
+ARGS_EDGE = [*ARGS_COMMON_OPTIMIZE]
ARGS_LIST_STRATEGIES = [
"strategy_path",
@@ -506,7 +506,9 @@ class Arguments:
# Add edge subcommand
edge_cmd = subparsers.add_parser(
- "edge", help="Edge module.", parents=[_common_parser, _strategy_parser]
+ "edge",
+ help="Edge module. No longer part of Freqtrade",
+ parents=[_common_parser, _strategy_parser],
)
edge_cmd.set_defaults(func=start_edge)
self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd)
diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py
index 669f68f5a..45e50b2ce 100755
--- a/freqtrade/commands/cli_options.py
+++ b/freqtrade/commands/cli_options.py
@@ -240,13 +240,6 @@ AVAILABLE_CLI_OPTIONS = {
default=constants.BACKTEST_CACHE_DEFAULT,
choices=constants.BACKTEST_CACHE_AGE,
),
- # Edge
- "stoploss_range": Arg(
- "--stoplosses",
- help="Defines a range of stoploss values against which edge will assess the strategy. "
- 'The format is "min,max,step" (without any space). '
- "Example: `--stoplosses=-0.01,-0.1,-0.001`",
- ),
# Hyperopt
"hyperopt": Arg(
"--hyperopt",
diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py
index 9695a313b..5ec3dfe01 100644
--- a/freqtrade/commands/optimize_commands.py
+++ b/freqtrade/commands/optimize_commands.py
@@ -129,15 +129,10 @@ def start_edge(args: dict[str, Any]) -> None:
:param args: Cli args from Arguments()
:return: None
"""
- from freqtrade.optimize.edge_cli import EdgeCli
-
- # Initialize configuration
- config = setup_optimize_configuration(args, RunMode.EDGE)
- logger.info("Starting freqtrade in Edge mode")
-
- # Initialize Edge object
- edge_cli = EdgeCli(config)
- edge_cli.start()
+ raise ConfigurationError(
+ "The Edge module has been deprecated in 2023.9 and removed in 2025.6. "
+ "All functionalities of edge have been removed."
+ )
def start_lookahead_analysis(args: dict[str, Any]) -> None:
diff --git a/freqtrade/config_schema/config_schema.py b/freqtrade/config_schema/config_schema.py
index ce71e8f78..a75737c1c 100644
--- a/freqtrade/config_schema/config_schema.py
+++ b/freqtrade/config_schema/config_schema.py
@@ -423,10 +423,6 @@ CONF_SCHEMA = {
"description": "Exchange configuration.",
"$ref": "#/definitions/exchange",
},
- "edge": {
- "description": "Edge configuration.",
- "$ref": "#/definitions/edge",
- },
"log_config": {
"description": "Logging configuration.",
"$ref": "#/definitions/logging",
@@ -929,24 +925,6 @@ CONF_SCHEMA = {
},
"required": ["name"],
},
- "edge": {
- "type": "object",
- "properties": {
- "enabled": {"type": "boolean"},
- "process_throttle_secs": {"type": "integer", "minimum": 600},
- "calculate_since_number_of_days": {"type": "integer"},
- "allowed_risk": {"type": "number"},
- "stoploss_range_min": {"type": "number"},
- "stoploss_range_max": {"type": "number"},
- "stoploss_range_step": {"type": "number"},
- "minimum_winrate": {"type": "number"},
- "minimum_expectancy": {"type": "number"},
- "min_trade_number": {"type": "number"},
- "max_trade_duration_minute": {"type": "integer"},
- "remove_pumps": {"type": "boolean"},
- },
- "required": ["process_throttle_secs", "allowed_risk"],
- },
"logging": {
"type": "object",
"properties": {
diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py
index 678db63a3..49e1f2649 100644
--- a/freqtrade/configuration/config_validation.py
+++ b/freqtrade/configuration/config_validation.py
@@ -99,14 +99,12 @@ def validate_config_consistency(conf: dict[str, Any], *, preliminary: bool = Fal
def _validate_unlimited_amount(conf: dict[str, Any]) -> None:
"""
- If edge is disabled, either max_open_trades or stake_amount need to be set.
+ Either max_open_trades or stake_amount need to be set.
:raise: ConfigurationError if config validation failed
"""
if (
- not conf.get("edge", {}).get("enabled")
- and (conf.get("max_open_trades") == float("inf") or conf.get("max_open_trades") == -1)
- and conf.get("stake_amount") == UNLIMITED_STAKE_AMOUNT
- ):
+ conf.get("max_open_trades") == float("inf") or conf.get("max_open_trades") == -1
+ ) and conf.get("stake_amount") == UNLIMITED_STAKE_AMOUNT:
raise ConfigurationError("`max_open_trades` and `stake_amount` cannot both be unlimited.")
@@ -164,12 +162,9 @@ def _validate_edge(conf: dict[str, Any]) -> None:
Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists.
"""
- if not conf.get("edge", {}).get("enabled"):
- return
-
- if not conf.get("use_exit_signal", True):
+ if conf.get("edge", {}).get("enabled"):
raise ConfigurationError(
- "Edge requires `use_exit_signal` to be True, otherwise no sells will happen."
+ "Edge is no longer supported and has been removed from Freqtrade with 2025.6."
)
diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py
index e1e62e778..b9da05d0c 100644
--- a/freqtrade/configuration/configuration.py
+++ b/freqtrade/configuration/configuration.py
@@ -2,7 +2,6 @@
This module contains the configuration class
"""
-import ast
import logging
import warnings
from collections.abc import Callable
@@ -314,14 +313,6 @@ class Configuration:
]
self._args_to_config_loop(config, configurations)
- # Edge section:
- if self.args.get("stoploss_range"):
- txt_range = ast.literal_eval(self.args["stoploss_range"])
- config["edge"].update({"stoploss_range_min": txt_range[0]})
- config["edge"].update({"stoploss_range_max": txt_range[1]})
- config["edge"].update({"stoploss_range_step": txt_range[2]})
- logger.info("Parameter --stoplosses detected: %s ...", self.args["stoploss_range"])
-
# Hyperopt section
configurations = [
diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py
index 202c317de..47842d04b 100644
--- a/freqtrade/configuration/deprecated_settings.py
+++ b/freqtrade/configuration/deprecated_settings.py
@@ -159,16 +159,6 @@ def process_temporary_deprecated_settings(config: Config) -> None:
process_removed_setting(
config, "ask_strategy", "ignore_roi_if_buy_signal", None, "ignore_roi_if_entry_signal"
)
- if config.get("edge", {}).get(
- "enabled", False
- ) and "capital_available_percentage" in config.get("edge", {}):
- raise ConfigurationError(
- "DEPRECATED: "
- "Using 'edge.capital_available_percentage' has been deprecated in favor of "
- "'tradable_balance_ratio'. Please migrate your configuration to "
- "'tradable_balance_ratio' and remove 'capital_available_percentage' "
- "from the edge configuration."
- )
if "ticker_interval" in config:
raise ConfigurationError(
"DEPRECATED: 'ticker_interval' detected. "
diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py
index 1f2bcd82d..d5e7937c5 100644
--- a/freqtrade/data/dataprovider.py
+++ b/freqtrade/data/dataprovider.py
@@ -405,7 +405,7 @@ class DataProvider:
def runmode(self) -> RunMode:
"""
Get runmode of the bot
- can be "live", "dry-run", "backtest", "edgecli", "hyperopt" or "other".
+ can be "live", "dry-run", "backtest", "hyperopt" or "other".
"""
return RunMode(self._config.get("runmode", RunMode.OTHER))
diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py
deleted file mode 100644
index d275a80e3..000000000
--- a/freqtrade/edge/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .edge_positioning import Edge, PairInfo # noqa: F401
diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py
deleted file mode 100644
index 546126513..000000000
--- a/freqtrade/edge/edge_positioning.py
+++ /dev/null
@@ -1,524 +0,0 @@
-# pragma pylint: disable=W0603
-"""Edge positioning package"""
-
-import logging
-from collections import defaultdict
-from copy import deepcopy
-from datetime import timedelta
-from typing import Any, NamedTuple
-
-import numpy as np
-import utils_find_1st as utf1st
-from pandas import DataFrame
-
-from freqtrade.configuration import TimeRange
-from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT, Config
-from freqtrade.data.history import get_timerange, load_data, refresh_data
-from freqtrade.enums import CandleType, ExitType, RunMode
-from freqtrade.exceptions import OperationalException
-from freqtrade.exchange import timeframe_to_seconds
-from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
-from freqtrade.strategy.interface import IStrategy
-from freqtrade.util import dt_now
-
-
-logger = logging.getLogger(__name__)
-
-
-class PairInfo(NamedTuple):
- stoploss: float
- winrate: float
- risk_reward_ratio: float
- required_risk_reward: float
- expectancy: float
- nb_trades: int
- avg_trade_duration: float
-
-
-class Edge:
- """
- Calculates Win Rate, Risk Reward Ratio, Expectancy
- against historical data for a give set of markets and a strategy
- it then adjusts stoploss and position size accordingly
- and force it into the strategy
- Author: https://github.com/mishaker
- """
-
- _cached_pairs: dict[str, Any] = {} # Keeps a list of pairs
-
- def __init__(self, config: Config, exchange, strategy) -> None:
- self.config = config
- self.exchange = exchange
- self.strategy: IStrategy = strategy
-
- self.edge_config = self.config.get("edge", {})
- self._cached_pairs: dict[str, Any] = {} # Keeps a list of pairs
- self._final_pairs: list = []
-
- # checking max_open_trades. it should be -1 as with Edge
- # the number of trades is determined by position size
- if self.config["max_open_trades"] != float("inf"):
- logger.critical("max_open_trades should be -1 in config !")
-
- if self.config["stake_amount"] != UNLIMITED_STAKE_AMOUNT:
- raise OperationalException("Edge works only with unlimited stake amount")
-
- self._capital_ratio: float = self.config["tradable_balance_ratio"]
- self._allowed_risk: float = self.edge_config.get("allowed_risk")
- self._since_number_of_days: int = self.edge_config.get("calculate_since_number_of_days", 14)
- self._last_updated: int = 0 # Timestamp of pairs last updated time
- self._refresh_pairs = True
-
- self._stoploss_range_min = float(self.edge_config.get("stoploss_range_min", -0.01))
- self._stoploss_range_max = float(self.edge_config.get("stoploss_range_max", -0.05))
- self._stoploss_range_step = float(self.edge_config.get("stoploss_range_step", -0.001))
-
- # calculating stoploss range
- self._stoploss_range = np.arange(
- self._stoploss_range_min, self._stoploss_range_max, self._stoploss_range_step
- )
-
- self._timerange: TimeRange = TimeRange.parse_timerange(
- f"{(dt_now() - timedelta(days=self._since_number_of_days)).strftime('%Y%m%d')}-"
- )
- if config.get("fee"):
- self.fee = config["fee"]
- else:
- try:
- self.fee = self.exchange.get_fee(
- symbol=expand_pairlist(
- self.config["exchange"]["pair_whitelist"], list(self.exchange.markets)
- )[0]
- )
- except IndexError:
- self.fee = None
-
- def calculate(self, pairs: list[str]) -> bool:
- if self.fee is None and pairs:
- self.fee = self.exchange.get_fee(pairs[0])
-
- heartbeat = self.edge_config.get("process_throttle_secs")
-
- if (self._last_updated > 0) and (
- self._last_updated + heartbeat > int(dt_now().timestamp())
- ):
- return False
-
- data: dict[str, Any] = {}
- logger.info("Using stake_currency: %s ...", self.config["stake_currency"])
- logger.info("Using local backtesting data (using whitelist in given config) ...")
-
- if self._refresh_pairs:
- timerange_startup = deepcopy(self._timerange)
- timerange_startup.subtract_start(
- timeframe_to_seconds(self.strategy.timeframe) * self.strategy.startup_candle_count
- )
- refresh_data(
- datadir=self.config["datadir"],
- pairs=pairs,
- exchange=self.exchange,
- timeframe=self.strategy.timeframe,
- timerange=timerange_startup,
- data_format=self.config["dataformat_ohlcv"],
- candle_type=self.config.get("candle_type_def", CandleType.SPOT),
- )
- # Download informative pairs too
- res = defaultdict(list)
- for pair, timeframe, _ in self.strategy.gather_informative_pairs():
- res[timeframe].append(pair)
- for timeframe, inf_pairs in res.items():
- timerange_startup = deepcopy(self._timerange)
- timerange_startup.subtract_start(
- timeframe_to_seconds(timeframe) * self.strategy.startup_candle_count
- )
- refresh_data(
- datadir=self.config["datadir"],
- pairs=inf_pairs,
- exchange=self.exchange,
- timeframe=timeframe,
- timerange=timerange_startup,
- data_format=self.config["dataformat_ohlcv"],
- candle_type=self.config.get("candle_type_def", CandleType.SPOT),
- )
-
- data = load_data(
- datadir=self.config["datadir"],
- pairs=pairs,
- timeframe=self.strategy.timeframe,
- timerange=self._timerange,
- startup_candles=self.strategy.startup_candle_count,
- data_format=self.config["dataformat_ohlcv"],
- candle_type=self.config.get("candle_type_def", CandleType.SPOT),
- )
-
- if not data:
- # Reinitializing cached pairs
- self._cached_pairs = {}
- logger.critical("No data found. Edge is stopped ...")
- return False
- # Fake run-mode to Edge
- prior_rm = self.config["runmode"]
- self.config["runmode"] = RunMode.EDGE
- preprocessed = self.strategy.advise_all_indicators(data)
- self.config["runmode"] = prior_rm
-
- # Print timeframe
- min_date, max_date = get_timerange(preprocessed)
- logger.info(
- f"Measuring data from {min_date.strftime(DATETIME_PRINT_FORMAT)} "
- f"up to {max_date.strftime(DATETIME_PRINT_FORMAT)} "
- f"({(max_date - min_date).days} days).."
- )
- # TODO: Should edge support shorts? needs to be investigated further
- # * (add enter_short exit_short)
- headers = ["date", "open", "high", "low", "close", "enter_long", "exit_long"]
-
- trades: list = []
- for pair, pair_data in preprocessed.items():
- # Sorting dataframe by date and reset index
- pair_data = pair_data.sort_values(by=["date"])
- pair_data = pair_data.reset_index(drop=True)
-
- df_analyzed = self.strategy.ft_advise_signals(pair_data, {"pair": pair})[headers].copy()
-
- trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range)
-
- # If no trade found then exit
- if len(trades) == 0:
- logger.info("No trades found.")
- return False
-
- # Fill missing, calculable columns, profit, duration , abs etc.
- trades_df = self._fill_calculable_fields(DataFrame(trades))
- self._cached_pairs = self._process_expectancy(trades_df)
- self._last_updated = int(dt_now().timestamp())
-
- return True
-
- def stake_amount(
- self, pair: str, free_capital: float, total_capital: float, capital_in_trade: float
- ) -> float:
- stoploss = self.get_stoploss(pair)
- available_capital = (total_capital + capital_in_trade) * self._capital_ratio
- allowed_capital_at_risk = available_capital * self._allowed_risk
- max_position_size = abs(allowed_capital_at_risk / stoploss)
- # Position size must be below available capital.
- position_size = min(min(max_position_size, free_capital), available_capital)
- if pair in self._cached_pairs:
- logger.info(
- "winrate: %s, expectancy: %s, position size: %s, pair: %s,"
- " capital in trade: %s, free capital: %s, total capital: %s,"
- " stoploss: %s, available capital: %s.",
- self._cached_pairs[pair].winrate,
- self._cached_pairs[pair].expectancy,
- position_size,
- pair,
- capital_in_trade,
- free_capital,
- total_capital,
- stoploss,
- available_capital,
- )
- return round(position_size, 15)
-
- def get_stoploss(self, pair: str) -> float:
- if pair in self._cached_pairs:
- return self._cached_pairs[pair].stoploss
- else:
- logger.warning(
- f"Tried to access stoploss of non-existing pair {pair}, "
- "strategy stoploss is returned instead."
- )
- return self.strategy.stoploss
-
- def adjust(self, pairs: list[str]) -> list:
- """
- Filters out and sorts "pairs" according to Edge calculated pairs
- """
- final = []
- for pair, info in self._cached_pairs.items():
- if (
- info.expectancy > float(self.edge_config.get("minimum_expectancy", 0.2))
- and info.winrate > float(self.edge_config.get("minimum_winrate", 0.60))
- and pair in pairs
- ):
- final.append(pair)
-
- if self._final_pairs != final:
- self._final_pairs = final
- if self._final_pairs:
- logger.info(
- "Minimum expectancy and minimum winrate are met only for %s,"
- " so other pairs are filtered out.",
- self._final_pairs,
- )
- else:
- logger.info(
- "Edge removed all pairs as no pair with minimum expectancy "
- "and minimum winrate was found !"
- )
-
- return self._final_pairs
-
- def accepted_pairs(self) -> list[dict[str, Any]]:
- """
- return a list of accepted pairs along with their winrate, expectancy and stoploss
- """
- final = []
- for pair, info in self._cached_pairs.items():
- if info.expectancy > float(
- self.edge_config.get("minimum_expectancy", 0.2)
- ) and info.winrate > float(self.edge_config.get("minimum_winrate", 0.60)):
- final.append(
- {
- "Pair": pair,
- "Winrate": info.winrate,
- "Expectancy": info.expectancy,
- "Stoploss": info.stoploss,
- }
- )
- return final
-
- def _fill_calculable_fields(self, result: DataFrame) -> DataFrame:
- """
- The result frame contains a number of columns that are calculable
- from other columns. These are left blank till all rows are added,
- to be populated in single vector calls.
-
- Columns to be populated are:
- - Profit
- - trade duration
- - profit abs
- :param result Dataframe
- :return: result Dataframe
- """
- # We set stake amount to an arbitrary amount, as it doesn't change the calculation.
- # All returned values are relative, they are defined as ratios.
- stake = 0.015
-
- result["trade_duration"] = result["close_date"] - result["open_date"]
-
- result["trade_duration"] = result["trade_duration"].map(
- lambda x: int(x.total_seconds() / 60)
- )
-
- # Spends, Takes, Profit, Absolute Profit
-
- # Buy Price
- result["buy_vol"] = stake / result["open_rate"] # How many target are we buying
- result["buy_fee"] = stake * self.fee
- result["buy_spend"] = stake + result["buy_fee"] # How much we're spending
-
- # Sell price
- result["sell_sum"] = result["buy_vol"] * result["close_rate"]
- result["sell_fee"] = result["sell_sum"] * self.fee
- result["sell_take"] = result["sell_sum"] - result["sell_fee"]
-
- # profit_ratio
- result["profit_ratio"] = (result["sell_take"] - result["buy_spend"]) / result["buy_spend"]
-
- # Absolute profit
- result["profit_abs"] = result["sell_take"] - result["buy_spend"]
-
- return result
-
- def _process_expectancy(self, results: DataFrame) -> dict[str, Any]:
- """
- This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs
- The calculation will be done per pair and per strategy.
- """
- # Removing pairs having less than min_trades_number
- min_trades_number = self.edge_config.get("min_trade_number", 10)
- results = results.groupby(["pair", "stoploss"]).filter(lambda x: len(x) > min_trades_number)
- ###################################
-
- # Removing outliers (Only Pumps) from the dataset
- # The method to detect outliers is to calculate standard deviation
- # Then every value more than (standard deviation + 2*average) is out (pump)
- #
- # Removing Pumps
- if self.edge_config.get("remove_pumps", False):
- results = results[
- results["profit_abs"]
- < 2 * results["profit_abs"].std() + results["profit_abs"].mean()
- ]
- ##########################################################################
-
- # Removing trades having a duration more than X minutes (set in config)
- max_trade_duration = self.edge_config.get("max_trade_duration_minute", 1440)
- results = results[results.trade_duration < max_trade_duration]
- #######################################################################
-
- if results.empty:
- return {}
-
- groupby_aggregator = {
- "profit_abs": [
- ("nb_trades", "count"), # number of all trades
- ("profit_sum", lambda x: x[x > 0].sum()), # cumulative profit of all winning trades
- ("loss_sum", lambda x: abs(x[x < 0].sum())), # cumulative loss of all losing trades
- ("nb_win_trades", lambda x: x[x > 0].count()), # number of winning trades
- ],
- "trade_duration": [("avg_trade_duration", "mean")],
- }
-
- # Group by (pair and stoploss) by applying above aggregator
- df = (
- results.groupby(["pair", "stoploss"])[["profit_abs", "trade_duration"]]
- .agg(groupby_aggregator)
- .reset_index(col_level=1)
- )
-
- # Dropping level 0 as we don't need it
- df.columns = df.columns.droplevel(0)
-
- # Calculating number of losing trades, average win and average loss
- df["nb_loss_trades"] = df["nb_trades"] - df["nb_win_trades"]
- df["average_win"] = np.where(
- df["nb_win_trades"] == 0, 0.0, df["profit_sum"] / df["nb_win_trades"]
- )
- df["average_loss"] = np.where(
- df["nb_loss_trades"] == 0, 0.0, df["loss_sum"] / df["nb_loss_trades"]
- )
-
- # Win rate = number of profitable trades / number of trades
- df["winrate"] = df["nb_win_trades"] / df["nb_trades"]
-
- # risk_reward_ratio = average win / average loss
- df["risk_reward_ratio"] = df["average_win"] / df["average_loss"]
-
- # required_risk_reward = (1 / winrate) - 1
- df["required_risk_reward"] = (1 / df["winrate"]) - 1
-
- # expectancy = (risk_reward_ratio * winrate) - (lossrate)
- df["expectancy"] = (df["risk_reward_ratio"] * df["winrate"]) - (1 - df["winrate"])
-
- # sort by expectancy and stoploss
- df = (
- df.sort_values(by=["expectancy", "stoploss"], ascending=False)
- .groupby("pair")
- .first()
- .sort_values(by=["expectancy"], ascending=False)
- .reset_index()
- )
-
- final = {}
- for x in df.itertuples():
- final[x.pair] = PairInfo(
- x.stoploss,
- x.winrate,
- x.risk_reward_ratio,
- x.required_risk_reward,
- x.expectancy,
- x.nb_trades,
- x.avg_trade_duration,
- )
-
- # Returning a list of pairs in order of "expectancy"
- return final
-
- def _find_trades_for_stoploss_range(self, df, pair: str, stoploss_range) -> list:
- buy_column = df["enter_long"].values
- sell_column = df["exit_long"].values
- date_column = df["date"].values
- ohlc_columns = df[["open", "high", "low", "close"]].values
-
- result: list = []
- for stoploss in stoploss_range:
- result += self._detect_next_stop_or_sell_point(
- buy_column, sell_column, date_column, ohlc_columns, round(stoploss, 6), pair
- )
-
- return result
-
- def _detect_next_stop_or_sell_point(
- self, buy_column, sell_column, date_column, ohlc_columns, stoploss, pair: str
- ):
- """
- Iterate through ohlc_columns in order to find the next trade
- Next trade opens from the first buy signal noticed to
- The sell or stoploss signal after it.
- It then cuts OHLC, buy_column, sell_column and date_column.
- Cut from (the exit trade index) + 1.
-
- Author: https://github.com/mishaker
- """
-
- result: list = []
- start_point = 0
-
- while True:
- open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal)
-
- # Return empty if we don't find trade entry (i.e. buy==1) or
- # we find a buy but at the end of array
- if open_trade_index == -1 or open_trade_index == len(buy_column) - 1:
- break
- else:
- # When a buy signal is seen,
- # trade opens in reality on the next candle
- open_trade_index += 1
-
- open_price = ohlc_columns[open_trade_index, 0]
- stop_price = open_price * (stoploss + 1)
-
- # Searching for the index where stoploss is hit
- stop_index = utf1st.find_1st(
- ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller
- )
-
- # If we don't find it then we assume stop_index will be far in future (infinite number)
- if stop_index == -1:
- stop_index = float("inf")
-
- # Searching for the index where sell is hit
- sell_index = utf1st.find_1st(sell_column[open_trade_index:], 1, utf1st.cmp_equal)
-
- # If we don't find it then we assume sell_index will be far in future (infinite number)
- if sell_index == -1:
- sell_index = float("inf")
-
- # Check if we don't find any stop or sell point (in that case trade remains open)
- # It is not interesting for Edge to consider it so we simply ignore the trade
- # And stop iterating there is no more entry
- if stop_index == sell_index == float("inf"):
- break
-
- if stop_index <= sell_index:
- exit_index = open_trade_index + stop_index
- exit_type = ExitType.STOP_LOSS
- exit_price = stop_price
- elif stop_index > sell_index:
- # If exit is SELL then we exit at the next candle
- exit_index = open_trade_index + sell_index + 1
-
- # Check if we have the next candle
- if len(ohlc_columns) - 1 < exit_index:
- break
-
- exit_type = ExitType.EXIT_SIGNAL
- exit_price = ohlc_columns[exit_index, 0]
-
- trade = {
- "pair": pair,
- "stoploss": stoploss,
- "profit_ratio": "",
- "profit_abs": "",
- "open_date": date_column[open_trade_index],
- "close_date": date_column[exit_index],
- "trade_duration": "",
- "open_rate": round(open_price, 15),
- "close_rate": round(exit_price, 15),
- "exit_type": exit_type,
- }
-
- result.append(trade)
-
- # Giving a view of exit_index till the end of array
- buy_column = buy_column[exit_index:]
- sell_column = sell_column[exit_index:]
- date_column = date_column[exit_index:]
- ohlc_columns = ohlc_columns[exit_index:]
- start_point += exit_index
-
- return result
diff --git a/freqtrade/enums/runmode.py b/freqtrade/enums/runmode.py
index a24dd6e2c..f3316629a 100644
--- a/freqtrade/enums/runmode.py
+++ b/freqtrade/enums/runmode.py
@@ -4,13 +4,12 @@ from enum import Enum
class RunMode(str, Enum):
"""
Bot running mode (backtest, hyperopt, ...)
- can be "live", "dry-run", "backtest", "edge", "hyperopt".
+ can be "live", "dry-run", "backtest", "hyperopt".
"""
LIVE = "live"
DRY_RUN = "dry_run"
BACKTEST = "backtest"
- EDGE = "edge"
HYPEROPT = "hyperopt"
UTIL_EXCHANGE = "util_exchange"
UTIL_NO_EXCHANGE = "util_no_exchange"
@@ -20,5 +19,5 @@ class RunMode(str, Enum):
TRADE_MODES = [RunMode.LIVE, RunMode.DRY_RUN]
-OPTIMIZE_MODES = [RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT]
+OPTIMIZE_MODES = [RunMode.BACKTEST, RunMode.HYPEROPT]
NON_UTIL_MODES = TRADE_MODES + OPTIMIZE_MODES
diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py
index 617fe0d01..5e4f6bf07 100644
--- a/freqtrade/exchange/common.py
+++ b/freqtrade/exchange/common.py
@@ -107,7 +107,7 @@ EXCHANGE_HAS_OPTIONAL = [
def remove_exchange_credentials(exchange_config: ExchangeConfig, dry_run: bool) -> None:
"""
Removes exchange keys from the configuration and specifies dry-run
- Used for backtesting / hyperopt / edge and utils.
+ Used for backtesting / hyperopt and utils.
Modifies the input dict!
"""
if dry_run:
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index f400e8757..a99c40643 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -18,7 +18,6 @@ from freqtrade.configuration import validate_config_consistency
from freqtrade.constants import BuySell, Config, EntryExecuteMode, ExchangeConfig, LongShort
from freqtrade.data.converter import order_book_to_dataframe
from freqtrade.data.dataprovider import DataProvider
-from freqtrade.edge import Edge
from freqtrade.enums import (
ExitCheckTuple,
ExitType,
@@ -131,13 +130,6 @@ class FreqtradeBot(LoggingMixin):
# Attach Wallets to strategy instance
self.strategy.wallets = self.wallets
- # Initializing Edge only if enabled
- self.edge = (
- Edge(self.config, self.exchange, self.strategy)
- if self.config.get("edge", {}).get("enabled", False)
- else None
- )
-
# Init ExternalMessageConsumer if enabled
self.emc = (
ExternalMessageConsumer(self.config, self.dataprovider)
@@ -242,9 +234,8 @@ class FreqtradeBot(LoggingMixin):
self.rpc.startup_messages(self.config, self.pairlists, self.protections)
# Update older trades with precision and precision mode
self.startup_backpopulate_precision()
- if not self.edge:
- # Adjust stoploss if it was changed
- Trade.stoploss_reinitialization(self.strategy.stoploss)
+ # Adjust stoploss if it was changed
+ Trade.stoploss_reinitialization(self.strategy.stoploss)
# Only update open orders on startup
# This will update the database after the initial migration
@@ -335,7 +326,7 @@ class FreqtradeBot(LoggingMixin):
def _refresh_active_whitelist(self, trades: list[Trade] | None = None) -> list[str]:
"""
- Refresh active whitelist from pairlist or edge and extend it with
+ Refresh active whitelist from pairlist and extend it with
pairs that have open trades.
"""
# Refresh whitelist
@@ -343,11 +334,6 @@ class FreqtradeBot(LoggingMixin):
self.pairlists.refresh_pairlist()
_whitelist = self.pairlists.whitelist
- # Calculating Edge positioning
- if self.edge:
- self.edge.calculate(_whitelist)
- _whitelist = self.edge.adjust(_whitelist)
-
if trades:
# Extend active-pair whitelist with pairs of open trades
# It ensures that candle (OHLCV) data are downloaded for open trades as well
@@ -701,9 +687,7 @@ class FreqtradeBot(LoggingMixin):
else:
self.log_once(f"Pair {pair} is currently locked.", logger.info)
return False
- stake_amount = self.wallets.get_trade_stake_amount(
- pair, self.config["max_open_trades"], self.edge
- )
+ stake_amount = self.wallets.get_trade_stake_amount(pair, self.config["max_open_trades"])
bid_check_dom = self.config.get("entry_pricing", {}).get("check_depth_of_market", {})
if (bid_check_dom.get("enabled", False)) and (
@@ -1042,7 +1026,7 @@ class FreqtradeBot(LoggingMixin):
precision_mode_price=self.exchange.precision_mode_price,
contract_size=self.exchange.get_contract_size(pair),
)
- stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair)
+ stoploss = self.strategy.stoploss
trade.adjust_stop_loss(trade.open_rate, stoploss, initial=True)
else:
@@ -1170,7 +1154,7 @@ class FreqtradeBot(LoggingMixin):
pair, enter_limit_requested, leverage
)
- if not self.edge and trade is None:
+ if trade is None:
stake_available = self.wallets.get_available_stake_amount()
stake_amount = strategy_safe_wrapper(
self.strategy.custom_stake_amount, default_retval=stake_amount
@@ -1382,7 +1366,7 @@ class FreqtradeBot(LoggingMixin):
datetime.now(timezone.utc),
enter=enter,
exit_=exit_,
- force_stoploss=self.edge.get_stoploss(trade.pair) if self.edge else 0,
+ force_stoploss=0,
)
for should_exit in exits:
if should_exit.exit_flag:
@@ -1487,13 +1471,6 @@ class FreqtradeBot(LoggingMixin):
# If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange
if len(stoploss_orders) == 0:
stop_price = trade.stoploss_or_liquidation
- if self.edge:
- stoploss = self.edge.get_stoploss(pair=trade.pair)
- stop_price = (
- trade.open_rate * (1 - stoploss)
- if trade.is_short
- else trade.open_rate * (1 + stoploss)
- )
if self.create_stoploss_order(trade=trade, stop_price=stop_price):
# The above will return False if the placement failed and the trade was force-sold.
@@ -2370,10 +2347,7 @@ class FreqtradeBot(LoggingMixin):
if send_msg:
# Don't cancel stoploss in recovery modes immediately
trade = self.cancel_stoploss_on_exchange(trade)
- if not self.edge:
- # TODO: should shorting/leverage be supported by Edge,
- # then this will need to be fixed.
- trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True)
+ trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True)
if (
order.ft_order_side == trade.entry_side
or (trade.amount > 0 and trade.is_open)
diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py
deleted file mode 100644
index 65dd30efc..000000000
--- a/freqtrade/optimize/edge_cli.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# pragma pylint: disable=missing-docstring, W0212, too-many-arguments
-
-"""
-This module contains the edge backtesting interface
-"""
-
-import logging
-
-from freqtrade import constants
-from freqtrade.configuration import TimeRange, validate_config_consistency
-from freqtrade.constants import Config
-from freqtrade.data.dataprovider import DataProvider
-from freqtrade.edge import Edge
-from freqtrade.optimize.optimize_reports import generate_edge_table
-from freqtrade.resolvers import ExchangeResolver, StrategyResolver
-
-
-logger = logging.getLogger(__name__)
-
-
-class EdgeCli:
- """
- EdgeCli class, this class contains all the logic to run edge backtesting
-
- To run a edge backtest:
- edge = EdgeCli(config)
- edge.start()
- """
-
- def __init__(self, config: Config) -> None:
- self.config = config
-
- # Ensure using dry-run
- self.config["dry_run"] = True
- self.config["stake_amount"] = constants.UNLIMITED_STAKE_AMOUNT
- self.exchange = ExchangeResolver.load_exchange(self.config)
- self.strategy = StrategyResolver.load_strategy(self.config)
- self.strategy.dp = DataProvider(config, self.exchange)
-
- validate_config_consistency(self.config)
-
- self.edge = Edge(config, self.exchange, self.strategy)
- # Set refresh_pairs to false for edge-cli (it must be true for edge)
- self.edge._refresh_pairs = False
-
- self.edge._timerange = TimeRange.parse_timerange(
- None if self.config.get("timerange") is None else str(self.config.get("timerange"))
- )
- self.strategy.ft_bot_start()
-
- def start(self) -> None:
- result = self.edge.calculate(self.config["exchange"]["pair_whitelist"])
- if result:
- print("") # blank line for readability
- generate_edge_table(self.edge._cached_pairs)
diff --git a/freqtrade/optimize/optimize_reports/__init__.py b/freqtrade/optimize/optimize_reports/__init__.py
index c08bae4be..5cf8e51ad 100644
--- a/freqtrade/optimize/optimize_reports/__init__.py
+++ b/freqtrade/optimize/optimize_reports/__init__.py
@@ -1,6 +1,5 @@
# flake8: noqa: F401
from freqtrade.optimize.optimize_reports.bt_output import (
- generate_edge_table,
generate_wins_draws_losses,
show_backtest_result,
show_backtest_results,
diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py
index c305a7b0e..193f3d8f1 100644
--- a/freqtrade/optimize/optimize_reports/bt_output.py
+++ b/freqtrade/optimize/optimize_reports/bt_output.py
@@ -499,33 +499,3 @@ def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType):
if result["key"] != "TOTAL":
print(f'"{result["key"]}", // {result["profit_mean"]:.2%}')
print("]")
-
-
-def generate_edge_table(results: dict) -> None:
- tabular_data = []
- headers = [
- "Pair",
- "Stoploss",
- "Win Rate",
- "Risk Reward Ratio",
- "Required Risk Reward",
- "Expectancy",
- "Total Number of Trades",
- "Average Duration (min)",
- ]
-
- for result in results.items():
- if result[1].nb_trades > 0:
- tabular_data.append(
- [
- result[0],
- f"{result[1].stoploss:.10g}",
- f"{result[1].winrate:.2f}",
- f"{result[1].risk_reward_ratio:.2f}",
- f"{result[1].required_risk_reward:.2f}",
- f"{result[1].expectancy:.2f}",
- result[1].nb_trades,
- round(result[1].avg_trade_duration),
- ]
- )
- print_rich_table(tabular_data, headers, summary="EDGE TABLE")
diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py
index 73786cba6..bc66d00c3 100644
--- a/freqtrade/plugins/pairlistmanager.py
+++ b/freqtrade/plugins/pairlistmanager.py
@@ -61,7 +61,7 @@ class PairListManager(LoggingMixin):
LoggingMixin.__init__(self, logger, refresh_period)
def _check_backtest(self) -> None:
- if self._config["runmode"] not in (RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT):
+ if self._config["runmode"] not in (RunMode.BACKTEST, RunMode.HYPEROPT):
return
pairlist_errors: list[str] = []
diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py
index 348ffd7a3..9c4cf94e6 100644
--- a/freqtrade/rpc/api_server/api_v1.py
+++ b/freqtrade/rpc/api_server/api_v1.py
@@ -263,12 +263,6 @@ def list_custom_data(trade_id: int, key: str | None = Query(None), rpc: RPC = De
raise HTTPException(status_code=404, detail=str(e))
-# TODO: Missing response model
-@router.get("/edge", tags=["info"])
-def edge(rpc: RPC = Depends(get_rpc)):
- return rpc._rpc_edge()
-
-
@router.get("/show_config", response_model=ShowConfig, tags=["info"])
def show_config(rpc: RPC | None = Depends(get_rpc_optional), config=Depends(get_config)):
state: State | str = ""
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 464ac020c..01f649ba3 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -1345,12 +1345,6 @@ class RPC:
return {"log_count": len(records), "logs": records}
- def _rpc_edge(self) -> list[dict[str, Any]]:
- """Returns information related to Edge"""
- if not self._freqtrade.edge:
- raise RPCException("Edge is not enabled.")
- return self._freqtrade.edge.accepted_pairs()
-
@staticmethod
def _convert_dataframe_to_dict(
strategy: str,
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 2d41a98d0..7230a8681 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -215,7 +215,6 @@ class Telegram(RPCHandler):
r"/forceshort$",
r"/forcesell$",
r"/forceexit$",
- r"/edge$",
r"/health$",
r"/help$",
r"/version$",
@@ -299,7 +298,6 @@ class Telegram(RPCHandler):
CommandHandler("blacklist", self._blacklist),
CommandHandler(["blacklist_delete", "bl_delete"], self._blacklist_delete),
CommandHandler("logs", self._logs),
- CommandHandler("edge", self._edge),
CommandHandler("health", self._health),
CommandHandler("help", self._help),
CommandHandler("version", self._version),
@@ -1795,23 +1793,6 @@ class Telegram(RPCHandler):
if msgs:
await self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2)
- @authorized_only
- async def _edge(self, update: Update, context: CallbackContext) -> None:
- """
- Handler for /edge
- Shows information related to Edge
- """
- edge_pairs = self._rpc._rpc_edge()
- if not edge_pairs:
- message = "Edge only validated following pairs:"
- await self._send_msg(message, parse_mode=ParseMode.HTML)
-
- for chunk in chunks(edge_pairs, 25):
- edge_pairs_tab = tabulate(chunk, headers="keys", tablefmt="simple")
- message = f"Edge only validated following pairs:\n{edge_pairs_tab}"
-
- await self._send_msg(message, parse_mode=ParseMode.HTML)
-
@authorized_only
async def _help(self, update: Update, context: CallbackContext) -> None:
"""
@@ -1864,7 +1845,6 @@ class Telegram(RPCHandler):
"*/balance total:* `Show account balance per currency`\n"
"*/logs [limit]:* `Show latest logs - defaults to 10` \n"
"*/count:* `Show number of active trades compared to allowed number of trades`\n"
- "*/edge:* `Shows validated pairs by Edge if it is enabled` \n"
"*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n"
"*/marketdir [long | short | even | none]:* `Updates the user managed variable "
"that represents the current market direction. If no direction is provided `"
diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py
index cb0887415..db14ec499 100644
--- a/freqtrade/wallets.py
+++ b/freqtrade/wallets.py
@@ -352,7 +352,7 @@ class Wallets:
return max(stake_amount, 0)
def get_trade_stake_amount(
- self, pair: str, max_open_trades: IntOrInf, edge=None, update: bool = True
+ self, pair: str, max_open_trades: IntOrInf, update: bool = True
) -> float:
"""
Calculate stake amount for the trade
@@ -366,19 +366,11 @@ class Wallets:
val_tied_up = Trade.total_open_trades_stakes()
available_amount = self.get_available_stake_amount()
- if edge:
- stake_amount = edge.stake_amount(
- pair,
- self.get_free(self._stake_currency),
- self.get_total(self._stake_currency),
- val_tied_up,
+ stake_amount = self._config["stake_amount"]
+ if stake_amount == UNLIMITED_STAKE_AMOUNT:
+ stake_amount = self._calculate_unlimited_stake_amount(
+ available_amount, val_tied_up, max_open_trades
)
- else:
- stake_amount = self._config["stake_amount"]
- if stake_amount == UNLIMITED_STAKE_AMOUNT:
- stake_amount = self._calculate_unlimited_stake_amount(
- available_amount, val_tied_up, max_open_trades
- )
return self._check_available_stake_amount(stake_amount, available_amount)
diff --git a/ft_client/freqtrade_client/ft_rest_client.py b/ft_client/freqtrade_client/ft_rest_client.py
index 15de7d6c8..1f14c5b1a 100755
--- a/ft_client/freqtrade_client/ft_rest_client.py
+++ b/ft_client/freqtrade_client/ft_rest_client.py
@@ -189,13 +189,6 @@ class FtRestClient:
"""
return self._get("monthly", params={"timescale": months} if months else None)
- def edge(self):
- """Return information about edge.
-
- :return: json object
- """
- return self._get("edge")
-
def profit(self):
"""Return the profit summary.
diff --git a/ft_client/test_client/test_rest_client.py b/ft_client/test_client/test_rest_client.py
index 9d5e484aa..ad25b156b 100644
--- a/ft_client/test_client/test_rest_client.py
+++ b/ft_client/test_client/test_rest_client.py
@@ -72,7 +72,6 @@ def test_FtRestClient_call_invalid(caplog):
("weekly", [15], {}),
("monthly", [], {}),
("monthly", [12], {}),
- ("edge", [], {}),
("profit", [], {}),
("stats", [], {}),
("performance", [], {}),
diff --git a/mkdocs.yml b/mkdocs.yml
index a9f448a3e..cfd22bb3d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -52,7 +52,6 @@ nav:
- Orderflow: advanced-orderflow.md
- Producer/Consumer mode: producer-consumer.md
- SQL Cheat-sheet: sql_cheatsheet.md
- - Edge Positioning: edge.md
- FAQ: faq.md
- Strategy migration: strategy_migration.md
- Updating Freqtrade: updating.md
diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
index 9dd5ffebc..3544bc2d6 100644
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -16,6 +16,7 @@ from freqtrade.commands import (
start_convert_trades,
start_create_userdir,
start_download_data,
+ start_edge,
start_hyperopt_list,
start_hyperopt_show,
start_install_ui,
@@ -1937,3 +1938,15 @@ def test_start_show_config(capsys, caplog):
assert '"max_open_trades":' in captured.out
assert '"secret": "REDACTED"' not in captured.out
assert log_has_re(r"Sensitive information will be shown in the upcoming output.*", caplog)
+
+
+def test_start_edge():
+ args = [
+ "edge",
+ "--config",
+ "tests/testdata/testconfigs/main_test_config.json",
+ ]
+
+ pargs = get_args(args)
+ with pytest.raises(OperationalException, match="The Edge module has been deprecated in 2023.9"):
+ start_edge(pargs)
diff --git a/tests/conftest.py b/tests/conftest.py
index 892b49d8c..7703ffa5e 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -16,8 +16,7 @@ from xdist.scheduler.loadscope import LoadScopeScheduling
from freqtrade import constants
from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe, trades_list_to_df
-from freqtrade.edge import PairInfo
-from freqtrade.enums import CandleType, MarginMode, RunMode, SignalDirection, TradingMode
+from freqtrade.enums import CandleType, MarginMode, SignalDirection, TradingMode
from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_seconds
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
@@ -298,24 +297,6 @@ def patch_whitelist(mocker, conf) -> None:
)
-def patch_edge(mocker) -> None:
- # "ETH/BTC",
- # "LTC/BTC",
- # "XRP/BTC",
- # "NEO/BTC"
-
- mocker.patch(
- "freqtrade.edge.Edge._cached_pairs",
- mocker.PropertyMock(
- return_value={
- "NEO/BTC": PairInfo(-0.20, 0.66, 3.71, 0.50, 1.71, 10, 25),
- "LTC/BTC": PairInfo(-0.21, 0.66, 3.71, 0.50, 1.71, 11, 20),
- }
- ),
- )
- mocker.patch("freqtrade.edge.Edge.calculate", MagicMock(return_value=True))
-
-
# Functions for recurrent object patching
@@ -2603,31 +2584,6 @@ def buy_order_fee():
}
-@pytest.fixture(scope="function")
-def edge_conf(default_conf):
- conf = deepcopy(default_conf)
- conf["runmode"] = RunMode.DRY_RUN
- conf["max_open_trades"] = -1
- conf["tradable_balance_ratio"] = 0.5
- conf["stake_amount"] = constants.UNLIMITED_STAKE_AMOUNT
- conf["edge"] = {
- "enabled": True,
- "process_throttle_secs": 1800,
- "calculate_since_number_of_days": 14,
- "allowed_risk": 0.01,
- "stoploss_range_min": -0.01,
- "stoploss_range_max": -0.1,
- "stoploss_range_step": -0.01,
- "maximum_winrate": 0.80,
- "minimum_expectancy": 0.20,
- "min_trade_number": 15,
- "max_trade_duration_minute": 1440,
- "remove_pumps": False,
- }
-
- return conf
-
-
@pytest.fixture
def rpc_balance():
return {
diff --git a/tests/edge/__init__.py b/tests/edge/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py
deleted file mode 100644
index 50c03f0f3..000000000
--- a/tests/edge/test_edge.py
+++ /dev/null
@@ -1,606 +0,0 @@
-# pragma pylint: disable=missing-docstring, C0103, C0330
-# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
-
-import logging
-import math
-from datetime import timedelta
-from unittest.mock import MagicMock
-
-import numpy as np
-import pytest
-from pandas import DataFrame
-
-from freqtrade.data.converter import ohlcv_to_dataframe
-from freqtrade.edge import Edge, PairInfo
-from freqtrade.enums import ExitType
-from freqtrade.exceptions import OperationalException
-from freqtrade.util.datetime_helpers import dt_ts, dt_utc
-from tests.conftest import EXMS, get_patched_freqtradebot, log_has
-from tests.optimize import (
- BTContainer,
- BTrade,
- _build_backtest_dataframe,
- _get_frame_time_from_offset,
-)
-
-
-# Cases to be tested:
-# 1) Open trade should be removed from the end
-# 2) Two complete trades within dataframe (with sell hit for all)
-# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
-# 4) Entered, sl 3%, candle drops 4%, recovers to 1% => Trade closed, 3% loss
-# 5) Stoploss and sell are hit. should sell on stoploss
-####################################################################
-
-tests_start_time = dt_utc(2018, 10, 3)
-timeframe_in_minute = 60
-
-# End helper functions
-# Open trade should be removed from the end
-tc0 = BTContainer(
- data=[
- # D O H L C V B S
- [0, 5000, 5025, 4975, 4987, 6172, 1, 0],
- [1, 5000, 5025, 4975, 4987, 6172, 0, 1],
- ], # enter trade (signal on last candle)
- stop_loss=-0.99,
- roi={"0": float("inf")},
- profit_perc=0.00,
- trades=[],
-)
-
-# Two complete trades within dataframe(with sell hit for all)
-tc1 = BTContainer(
- data=[
- # D O H L C V B S
- [0, 5000, 5025, 4975, 4987, 6172, 1, 0],
- [1, 5000, 5025, 4975, 4987, 6172, 0, 1], # enter trade (signal on last candle)
- [2, 5000, 5025, 4975, 4987, 6172, 0, 0], # exit at open
- [3, 5000, 5025, 4975, 4987, 6172, 1, 0], # no action
- [4, 5000, 5025, 4975, 4987, 6172, 0, 0], # should enter the trade
- [5, 5000, 5025, 4975, 4987, 6172, 0, 1], # no action
- [6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
- ],
- stop_loss=-0.99,
- roi={"0": float("inf")},
- profit_perc=0.00,
- trades=[
- BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=2),
- BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=4, close_tick=6),
- ],
-)
-
-# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
-tc2 = BTContainer(
- data=[
- # D O H L C V B S
- [0, 5000, 5025, 4975, 4987, 6172, 1, 0],
- [1, 5000, 5025, 4600, 4987, 6172, 0, 0], # enter trade, stoploss hit
- [2, 5000, 5025, 4975, 4987, 6172, 0, 0],
- ],
- stop_loss=-0.01,
- roi={"0": float("inf")},
- profit_perc=-0.01,
- trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)],
-)
-
-# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
-tc3 = BTContainer(
- data=[
- # D O H L C V B S
- [0, 5000, 5025, 4975, 4987, 6172, 1, 0],
- [1, 5000, 5025, 4800, 4987, 6172, 0, 0], # enter trade, stoploss hit
- [2, 5000, 5025, 4975, 4987, 6172, 0, 0],
- ],
- stop_loss=-0.03,
- roi={"0": float("inf")},
- profit_perc=-0.03,
- trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)],
-)
-
-# 5) Stoploss and sell are hit. should sell on stoploss
-tc4 = BTContainer(
- data=[
- # D O H L C V B S
- [0, 5000, 5025, 4975, 4987, 6172, 1, 0],
- [1, 5000, 5025, 4800, 4987, 6172, 0, 1], # enter trade, stoploss hit, sell signal
- [2, 5000, 5025, 4975, 4987, 6172, 0, 0],
- ],
- stop_loss=-0.03,
- roi={"0": float("inf")},
- profit_perc=-0.03,
- trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)],
-)
-
-TESTS = [tc0, tc1, tc2, tc3, tc4]
-
-
-@pytest.mark.parametrize("data", TESTS)
-def test_edge_results(edge_conf, mocker, caplog, data) -> None:
- """
- run functional tests
- """
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
- frame = _build_backtest_dataframe(data.data)
- caplog.set_level(logging.DEBUG)
- edge.fee = 0
-
- trades = edge._find_trades_for_stoploss_range(frame, "TEST/BTC", [data.stop_loss])
- results = edge._fill_calculable_fields(DataFrame(trades)) if trades else DataFrame()
-
- assert len(trades) == len(data.trades)
-
- if not results.empty:
- assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
-
- for c, trade in enumerate(data.trades):
- res = results.iloc[c]
- assert res.exit_type == trade.exit_reason
- assert res.open_date == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None)
- assert res.close_date == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None)
-
-
-def test_adjust(mocker, edge_conf):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
- mocker.patch(
- "freqtrade.edge.Edge._cached_pairs",
- mocker.PropertyMock(
- return_value={
- "E/F": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
- "C/D": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
- "N/O": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
- }
- ),
- )
-
- pairs = ["A/B", "C/D", "E/F", "G/H"]
- assert edge.adjust(pairs) == ["E/F", "C/D"]
-
-
-def test_edge_get_stoploss(mocker, edge_conf):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
- mocker.patch(
- "freqtrade.edge.Edge._cached_pairs",
- mocker.PropertyMock(
- return_value={
- "E/F": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
- "C/D": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
- "N/O": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
- }
- ),
- )
-
- assert edge.get_stoploss("E/F") == -0.01
-
-
-def test_nonexisting_get_stoploss(mocker, edge_conf):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
- mocker.patch(
- "freqtrade.edge.Edge._cached_pairs",
- mocker.PropertyMock(
- return_value={
- "E/F": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
- }
- ),
- )
-
- assert edge.get_stoploss("N/O") == -0.1
-
-
-def test_edge_stake_amount(mocker, edge_conf):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
- mocker.patch(
- "freqtrade.edge.Edge._cached_pairs",
- mocker.PropertyMock(
- return_value={
- "E/F": PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
- }
- ),
- )
- assert edge._capital_ratio == 0.5
- assert (
- edge.stake_amount("E/F", free_capital=100, total_capital=100, capital_in_trade=25) == 31.25
- )
-
- assert edge.stake_amount("E/F", free_capital=20, total_capital=100, capital_in_trade=25) == 20
-
- assert edge.stake_amount("E/F", free_capital=0, total_capital=100, capital_in_trade=25) == 0
-
- # Test with increased allowed_risk
- # Result should be no more than allowed capital
- edge._allowed_risk = 0.4
- edge._capital_ratio = 0.5
- assert (
- edge.stake_amount("E/F", free_capital=100, total_capital=100, capital_in_trade=25) == 62.5
- )
-
- assert edge.stake_amount("E/F", free_capital=100, total_capital=100, capital_in_trade=0) == 50
-
- edge._capital_ratio = 1
- # Full capital is available
- assert edge.stake_amount("E/F", free_capital=100, total_capital=100, capital_in_trade=0) == 100
- # Full capital is available
- assert edge.stake_amount("E/F", free_capital=0, total_capital=100, capital_in_trade=0) == 0
-
-
-def test_nonexisting_stake_amount(mocker, edge_conf):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
- mocker.patch(
- "freqtrade.edge.Edge._cached_pairs",
- mocker.PropertyMock(
- return_value={
- "E/F": PairInfo(-0.11, 0.66, 3.71, 0.50, 1.71, 10, 60),
- }
- ),
- )
- # should use strategy stoploss
- assert edge.stake_amount("N/O", 1, 2, 1) == 0.15
-
-
-def test_edge_heartbeat_calculate(mocker, edge_conf):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
- heartbeat = edge_conf["edge"]["process_throttle_secs"]
-
- # should not recalculate if heartbeat not reached
- edge._last_updated = dt_ts() - heartbeat + 1
-
- assert edge.calculate(edge_conf["exchange"]["pair_whitelist"]) is False
-
-
-def mocked_load_data(datadir, pairs=None, timeframe="0m", timerange=None, *args, **kwargs):
- if pairs is None:
- pairs = []
- hz = 0.1
- base = 0.001
-
- NEOBTC = [
- [
- dt_ts(tests_start_time + timedelta(minutes=(x * timeframe_in_minute))),
- math.sin(x * hz) / 1000 + base,
- math.sin(x * hz) / 1000 + base + 0.0001,
- math.sin(x * hz) / 1000 + base - 0.0001,
- math.sin(x * hz) / 1000 + base,
- 123.45,
- ]
- for x in range(0, 500)
- ]
-
- hz = 0.2
- base = 0.002
- LTCBTC = [
- [
- dt_ts(tests_start_time + timedelta(minutes=(x * timeframe_in_minute))),
- math.sin(x * hz) / 1000 + base,
- math.sin(x * hz) / 1000 + base + 0.0001,
- math.sin(x * hz) / 1000 + base - 0.0001,
- math.sin(x * hz) / 1000 + base,
- 123.45,
- ]
- for x in range(0, 500)
- ]
-
- pairdata = {
- "NEO/BTC": ohlcv_to_dataframe(NEOBTC, "1h", pair="NEO/BTC", fill_missing=True),
- "LTC/BTC": ohlcv_to_dataframe(LTCBTC, "1h", pair="LTC/BTC", fill_missing=True),
- }
- return pairdata
-
-
-def test_edge_process_downloaded_data(mocker, edge_conf):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.001))
- mocker.patch("freqtrade.edge.edge_positioning.refresh_data", MagicMock())
- mocker.patch("freqtrade.edge.edge_positioning.load_data", mocked_load_data)
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
-
- assert edge.calculate(edge_conf["exchange"]["pair_whitelist"])
- assert len(edge._cached_pairs) == 2
- assert edge._last_updated <= dt_ts() + 2
-
-
-def test_edge_process_no_data(mocker, edge_conf, caplog):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.001))
- mocker.patch("freqtrade.edge.edge_positioning.refresh_data", MagicMock())
- mocker.patch("freqtrade.edge.edge_positioning.load_data", MagicMock(return_value={}))
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
-
- assert not edge.calculate(edge_conf["exchange"]["pair_whitelist"])
- assert len(edge._cached_pairs) == 0
- assert log_has("No data found. Edge is stopped ...", caplog)
- assert edge._last_updated == 0
-
-
-def test_edge_process_no_trades(mocker, edge_conf, caplog):
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- mocker.patch(f"{EXMS}.get_fee", return_value=0.001)
- mocker.patch(
- "freqtrade.edge.edge_positioning.refresh_data",
- )
- mocker.patch("freqtrade.edge.edge_positioning.load_data", mocked_load_data)
- # Return empty
- mocker.patch("freqtrade.edge.Edge._find_trades_for_stoploss_range", return_value=[])
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
-
- assert not edge.calculate(edge_conf["exchange"]["pair_whitelist"])
- assert len(edge._cached_pairs) == 0
- assert log_has("No trades found.", caplog)
-
-
-def test_edge_process_no_pairs(mocker, edge_conf, caplog):
- edge_conf["exchange"]["pair_whitelist"] = []
- mocker.patch("freqtrade.freqtradebot.validate_config_consistency")
-
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- fee_mock = mocker.patch(f"{EXMS}.get_fee", return_value=0.001)
- mocker.patch("freqtrade.edge.edge_positioning.refresh_data")
- mocker.patch("freqtrade.edge.edge_positioning.load_data", mocked_load_data)
- # Return empty
- mocker.patch("freqtrade.edge.Edge._find_trades_for_stoploss_range", return_value=[])
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
- assert fee_mock.call_count == 0
- assert edge.fee is None
-
- assert not edge.calculate(["XRP/USDT"])
- assert fee_mock.call_count == 1
- assert edge.fee == 0.001
-
-
-def test_edge_init_error(mocker, edge_conf):
- edge_conf["stake_amount"] = 0.5
- mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.001))
- with pytest.raises(OperationalException, match="Edge works only with unlimited stake amount"):
- get_patched_freqtradebot(mocker, edge_conf)
-
-
-@pytest.mark.parametrize(
- "fee,risk_reward_ratio,expectancy",
- [
- (0.0005, 306.5384615384, 101.5128205128),
- (0.001, 152.6923076923, 50.2307692308),
- ],
-)
-def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectancy):
- edge_conf["edge"]["min_trade_number"] = 2
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
-
- def get_fee(*args, **kwargs):
- return fee
-
- freqtrade.exchange.get_fee = get_fee
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
-
- trades = [
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:05:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:10:00.000000000"),
- "trade_duration": "",
- "open_rate": 17,
- "close_rate": 17,
- "exit_type": "exit_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
- "trade_duration": "",
- "open_rate": 20,
- "close_rate": 20,
- "exit_type": "exit_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:30:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:40:00.000000000"),
- "trade_duration": "",
- "open_rate": 26,
- "close_rate": 34,
- "exit_type": "exit_signal",
- },
- ]
-
- trades_df = DataFrame(trades)
- trades_df = edge._fill_calculable_fields(trades_df)
- final = edge._process_expectancy(trades_df)
- assert len(final) == 1
-
- assert "TEST/BTC" in final
- assert final["TEST/BTC"].stoploss == -0.9
- assert round(final["TEST/BTC"].winrate, 10) == 0.3333333333
- assert round(final["TEST/BTC"].risk_reward_ratio, 10) == risk_reward_ratio
- assert round(final["TEST/BTC"].required_risk_reward, 10) == 2.0
- assert round(final["TEST/BTC"].expectancy, 10) == expectancy
-
- # Pop last item so no trade is profitable
- trades.pop()
- trades_df = DataFrame(trades)
- trades_df = edge._fill_calculable_fields(trades_df)
- final = edge._process_expectancy(trades_df)
- assert len(final) == 0
- assert isinstance(final, dict)
-
-
-def test_process_expectancy_remove_pumps(mocker, edge_conf, fee):
- edge_conf["edge"]["min_trade_number"] = 2
- edge_conf["edge"]["remove_pumps"] = True
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
-
- freqtrade.exchange.get_fee = fee
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
-
- trades = [
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:05:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:10:00.000000000"),
- "open_index": 1,
- "close_index": 1,
- "trade_duration": "",
- "open_rate": 17,
- "close_rate": 15,
- "exit_type": "sell_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
- "open_index": 4,
- "close_index": 4,
- "trade_duration": "",
- "open_rate": 20,
- "close_rate": 10,
- "exit_type": "sell_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
- "open_index": 4,
- "close_index": 4,
- "trade_duration": "",
- "open_rate": 20,
- "close_rate": 10,
- "exit_type": "sell_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
- "open_index": 4,
- "close_index": 4,
- "trade_duration": "",
- "open_rate": 20,
- "close_rate": 10,
- "exit_type": "sell_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
- "open_index": 4,
- "close_index": 4,
- "trade_duration": "",
- "open_rate": 20,
- "close_rate": 10,
- "exit_type": "sell_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:30:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:40:00.000000000"),
- "open_index": 6,
- "close_index": 7,
- "trade_duration": "",
- "open_rate": 26,
- "close_rate": 134,
- "exit_type": "sell_signal",
- },
- ]
-
- trades_df = DataFrame(trades)
- trades_df = edge._fill_calculable_fields(trades_df)
- final = edge._process_expectancy(trades_df)
-
- assert "TEST/BTC" in final
- assert final["TEST/BTC"].stoploss == -0.9
- assert final["TEST/BTC"].nb_trades == len(trades_df) - 1
- assert round(final["TEST/BTC"].winrate, 10) == 0.0
-
-
-def test_process_expectancy_only_wins(mocker, edge_conf, fee):
- edge_conf["edge"]["min_trade_number"] = 2
- freqtrade = get_patched_freqtradebot(mocker, edge_conf)
-
- freqtrade.exchange.get_fee = fee
- edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
-
- trades = [
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:05:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:10:00.000000000"),
- "open_index": 1,
- "close_index": 1,
- "trade_duration": "",
- "open_rate": 15,
- "close_rate": 17,
- "exit_type": "sell_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
- "open_index": 4,
- "close_index": 4,
- "trade_duration": "",
- "open_rate": 10,
- "close_rate": 20,
- "exit_type": "sell_signal",
- },
- {
- "pair": "TEST/BTC",
- "stoploss": -0.9,
- "profit_percent": "",
- "profit_abs": "",
- "open_date": np.datetime64("2018-10-03T00:30:00.000000000"),
- "close_date": np.datetime64("2018-10-03T00:40:00.000000000"),
- "open_index": 6,
- "close_index": 7,
- "trade_duration": "",
- "open_rate": 26,
- "close_rate": 134,
- "exit_type": "sell_signal",
- },
- ]
-
- trades_df = DataFrame(trades)
- trades_df = edge._fill_calculable_fields(trades_df)
- final = edge._process_expectancy(trades_df)
-
- assert "TEST/BTC" in final
- assert final["TEST/BTC"].stoploss == -0.9
- assert final["TEST/BTC"].nb_trades == len(trades_df)
- assert round(final["TEST/BTC"].winrate, 10) == 1.0
- assert round(final["TEST/BTC"].risk_reward_ratio, 10) == float("inf")
- assert round(final["TEST/BTC"].expectancy, 10) == float("inf")
diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py
index 3ff8fcb5e..7edf30d2d 100644
--- a/tests/freqtradebot/test_freqtradebot.py
+++ b/tests/freqtradebot/test_freqtradebot.py
@@ -43,7 +43,6 @@ from tests.conftest import (
get_patched_worker,
log_has,
log_has_re,
- patch_edge,
patch_exchange,
patch_get_signal,
patch_wallet,
@@ -253,92 +252,6 @@ def test_check_available_stake_amount(
freqtrade.wallets.get_trade_stake_amount("ETH/USDT", 1)
-def test_edge_called_in_process(mocker, edge_conf) -> None:
- patch_RPCManager(mocker)
- patch_edge(mocker)
-
- patch_exchange(mocker)
- freqtrade = FreqtradeBot(edge_conf)
- patch_get_signal(freqtrade)
- freqtrade.process()
- assert freqtrade.active_pair_whitelist == ["NEO/BTC", "LTC/BTC"]
-
-
-def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
- patch_RPCManager(mocker)
- patch_exchange(mocker)
- patch_edge(mocker)
- edge_conf["dry_run_wallet"] = 999.9
- freqtrade = FreqtradeBot(edge_conf)
-
- assert (
- freqtrade.wallets.get_trade_stake_amount("NEO/BTC", 1, freqtrade.edge)
- == (999.9 * 0.5 * 0.01) / 0.20
- )
- assert (
- freqtrade.wallets.get_trade_stake_amount("LTC/BTC", 1, freqtrade.edge)
- == (999.9 * 0.5 * 0.01) / 0.21
- )
-
-
-@pytest.mark.parametrize(
- "buy_price_mult,ignore_strat_sl",
- [
- (0.79, False), # Override stoploss
- (0.85, True), # Override strategy stoploss
- ],
-)
-def test_edge_overrides_stoploss(
- limit_order, fee, caplog, mocker, buy_price_mult, ignore_strat_sl, edge_conf
-) -> None:
- patch_RPCManager(mocker)
- patch_exchange(mocker)
- patch_edge(mocker)
- edge_conf["max_open_trades"] = float("inf")
-
- # Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
- # Thus, if price falls 21%, stoploss should be triggered
- #
- # mocking the ticker: price is falling ...
- enter_price = limit_order["buy"]["price"]
- ticker_val = {
- "bid": enter_price,
- "ask": enter_price,
- "last": enter_price,
- }
- mocker.patch.multiple(
- EXMS,
- fetch_ticker=MagicMock(return_value=ticker_val),
- get_fee=fee,
- )
- #############################################
-
- # Create a trade with "limit_buy_order_usdt" price
- freqtrade = FreqtradeBot(edge_conf)
- freqtrade.active_pair_whitelist = ["NEO/BTC"]
- patch_get_signal(freqtrade)
- freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
- freqtrade.enter_positions()
- trade = Trade.session.scalars(select(Trade)).first()
- caplog.clear()
- #############################################
- ticker_val.update(
- {
- "bid": enter_price * buy_price_mult,
- "ask": enter_price * buy_price_mult,
- "last": enter_price * buy_price_mult,
- }
- )
-
- # stoploss should be hit
- assert freqtrade.handle_trade(trade) is not ignore_strat_sl
- if not ignore_strat_sl:
- assert log_has_re("Exit for NEO/BTC detected. Reason: stop_loss.*", caplog)
- assert trade.exit_reason == ExitType.STOP_LOSS.value
- # Test compatibility ...
- assert trade.sell_reason == ExitType.STOP_LOSS.value
-
-
def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
@@ -483,7 +396,7 @@ def test_create_trade_minimal_amount(
if not max_open_trades:
assert (
freqtrade.wallets.get_trade_stake_amount(
- "ETH/USDT", default_conf_usdt["max_open_trades"], freqtrade.edge
+ "ETH/USDT", default_conf_usdt["max_open_trades"]
)
== 0
)
@@ -4479,7 +4392,7 @@ def test_startup_state(default_conf_usdt, mocker):
assert worker.freqtrade.state is State.RUNNING
-def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker):
+def test_startup_trade_reinit(default_conf_usdt, mocker):
mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True))
reinit_mock = MagicMock()
mocker.patch("freqtrade.persistence.Trade.stoploss_reinitialization", reinit_mock)
@@ -4488,12 +4401,6 @@ def test_startup_trade_reinit(default_conf_usdt, edge_conf, mocker):
ftbot.startup()
assert reinit_mock.call_count == 1
- reinit_mock.reset_mock()
-
- ftbot = get_patched_freqtradebot(mocker, edge_conf)
- ftbot.startup()
- assert reinit_mock.call_count == 0
-
@pytest.mark.usefixtures("init_persistence")
def test_sync_wallet_dry_run(
diff --git a/tests/freqtradebot/test_stoploss_on_exchange.py b/tests/freqtradebot/test_stoploss_on_exchange.py
index c5dc01b7e..c71621620 100644
--- a/tests/freqtradebot/test_stoploss_on_exchange.py
+++ b/tests/freqtradebot/test_stoploss_on_exchange.py
@@ -16,7 +16,6 @@ from tests.conftest import (
get_patched_freqtradebot,
log_has,
log_has_re,
- patch_edge,
patch_exchange,
patch_get_signal,
patch_whitelist,
@@ -971,128 +970,6 @@ def test_handle_stoploss_on_exchange_custom_stop(
assert freqtrade.handle_trade(trade) is True
-def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_order) -> None:
- enter_order = limit_order["buy"]
- exit_order = limit_order["sell"]
- enter_order["average"] = 2.19
- # When trailing stoploss is set
- stoploss = MagicMock(return_value={"id": "13434334", "status": "open"})
- patch_RPCManager(mocker)
- patch_exchange(mocker)
- patch_edge(mocker)
- edge_conf["max_open_trades"] = float("inf")
- edge_conf["dry_run_wallet"] = 999.9
- edge_conf["exchange"]["name"] = "binance"
- mocker.patch.multiple(
- EXMS,
- fetch_ticker=MagicMock(return_value={"bid": 2.19, "ask": 2.2, "last": 2.19}),
- create_order=MagicMock(
- side_effect=[
- enter_order,
- exit_order,
- ]
- ),
- get_fee=fee,
- create_stoploss=stoploss,
- )
-
- # enabling TSL
- edge_conf["trailing_stop"] = True
- edge_conf["trailing_stop_positive"] = 0.01
- edge_conf["trailing_stop_positive_offset"] = 0.011
-
- # disabling ROI
- edge_conf["minimal_roi"]["0"] = 999999999
-
- freqtrade = FreqtradeBot(edge_conf)
-
- # enabling stoploss on exchange
- freqtrade.strategy.order_types["stoploss_on_exchange"] = True
-
- # setting stoploss
- freqtrade.strategy.stoploss = -0.02
-
- # setting stoploss_on_exchange_interval to 0 seconds
- freqtrade.strategy.order_types["stoploss_on_exchange_interval"] = 0
-
- patch_get_signal(freqtrade)
-
- freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist)
-
- freqtrade.enter_positions()
- trade = Trade.session.scalars(select(Trade)).first()
- trade.is_open = True
-
- trade.stoploss_last_update = dt_now()
- trade.orders.append(
- Order(
- ft_order_side="stoploss",
- ft_pair=trade.pair,
- ft_is_open=True,
- ft_amount=trade.amount,
- ft_price=trade.stop_loss,
- order_id="100",
- )
- )
-
- stoploss_order_hanging = MagicMock(
- return_value={
- "id": "100",
- "status": "open",
- "type": "stop_loss_limit",
- "price": 3,
- "average": 2,
- "stopPrice": "2.178",
- }
- )
-
- mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_order_hanging)
-
- # stoploss initially at 20% as edge dictated it.
- assert freqtrade.handle_trade(trade) is False
- assert freqtrade.handle_stoploss_on_exchange(trade) is False
- assert pytest.approx(trade.stop_loss) == 1.76
-
- cancel_order_mock = MagicMock()
- stoploss_order_mock = MagicMock()
- mocker.patch(f"{EXMS}.cancel_stoploss_order", cancel_order_mock)
- mocker.patch(f"{EXMS}.create_stoploss", stoploss_order_mock)
-
- # price goes down 5%
- mocker.patch(
- f"{EXMS}.fetch_ticker",
- MagicMock(return_value={"bid": 2.19 * 0.95, "ask": 2.2 * 0.95, "last": 2.19 * 0.95}),
- )
- assert freqtrade.handle_trade(trade) is False
- assert freqtrade.handle_stoploss_on_exchange(trade) is False
-
- # stoploss should remain the same
- assert pytest.approx(trade.stop_loss) == 1.76
-
- # stoploss on exchange should not be canceled
- cancel_order_mock.assert_not_called()
-
- # price jumped 2x
- mocker.patch(
- f"{EXMS}.fetch_ticker", MagicMock(return_value={"bid": 4.38, "ask": 4.4, "last": 4.38})
- )
-
- assert freqtrade.handle_trade(trade) is False
- assert freqtrade.handle_stoploss_on_exchange(trade) is False
-
- # stoploss should be set to 1% as trailing is on
- assert trade.stop_loss == 4.4 * 0.99
- cancel_order_mock.assert_called_once_with("100", "NEO/BTC")
- stoploss_order_mock.assert_called_once_with(
- amount=30,
- pair="NEO/BTC",
- order_types=freqtrade.strategy.order_types,
- stop_price=4.4 * 0.99,
- side="sell",
- leverage=1.0,
- )
-
-
@pytest.mark.parametrize("is_short", [False, True])
def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
default_conf_usdt,
diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py
deleted file mode 100644
index 3f515bebe..000000000
--- a/tests/optimize/test_edge_cli.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# pragma pylint: disable=missing-docstring, C0103, C0330
-# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
-
-from unittest.mock import MagicMock
-
-from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge
-from freqtrade.enums import RunMode
-from freqtrade.optimize.edge_cli import EdgeCli
-from tests.conftest import (
- CURRENT_TEST_STRATEGY,
- EXMS,
- get_args,
- log_has,
- patch_exchange,
- patched_configuration_load_config_file,
-)
-
-
-def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None:
- patched_configuration_load_config_file(mocker, default_conf)
-
- args = [
- "edge",
- "--config",
- "config.json",
- "--strategy",
- CURRENT_TEST_STRATEGY,
- ]
-
- config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
- assert config["runmode"] == RunMode.EDGE
-
- assert "max_open_trades" in config
- assert "stake_currency" in config
- assert "stake_amount" in config
- assert "exchange" in config
- assert "pair_whitelist" in config["exchange"]
- assert "datadir" in config
- assert log_has("Using data directory: {} ...".format(config["datadir"]), caplog)
- assert "timeframe" in config
-
- assert "timerange" not in config
- assert "stoploss_range" not in config
-
-
-def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> None:
- patched_configuration_load_config_file(mocker, edge_conf)
- mocker.patch("freqtrade.configuration.configuration.create_datadir", lambda c, x: x)
-
- args = [
- "edge",
- "--config",
- "config.json",
- "--strategy",
- CURRENT_TEST_STRATEGY,
- "--datadir",
- "/foo/bar",
- "--timeframe",
- "1m",
- "--timerange",
- ":100",
- "--stoplosses=-0.01,-0.10,-0.001",
- ]
-
- config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
- assert "max_open_trades" in config
- assert "stake_currency" in config
- assert "stake_amount" in config
- assert "exchange" in config
- assert "pair_whitelist" in config["exchange"]
- assert "datadir" in config
- assert config["runmode"] == RunMode.EDGE
- assert log_has("Using data directory: {} ...".format(config["datadir"]), caplog)
- assert "timeframe" in config
- assert log_has("Parameter -i/--timeframe detected ... Using timeframe: 1m ...", caplog)
-
- assert "timerange" in config
- assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog)
-
-
-def test_start(mocker, fee, edge_conf, caplog) -> None:
- start_mock = MagicMock()
- mocker.patch(f"{EXMS}.get_fee", fee)
- patch_exchange(mocker)
- mocker.patch("freqtrade.optimize.edge_cli.EdgeCli.start", start_mock)
- patched_configuration_load_config_file(mocker, edge_conf)
-
- args = [
- "edge",
- "--config",
- "config.json",
- "--strategy",
- CURRENT_TEST_STRATEGY,
- ]
- pargs = get_args(args)
- start_edge(pargs)
- assert log_has("Starting freqtrade in Edge mode", caplog)
- assert start_mock.call_count == 1
-
-
-def test_edge_init(mocker, edge_conf) -> None:
- patch_exchange(mocker)
- edge_conf["stake_amount"] = 20
- edge_cli = EdgeCli(edge_conf)
- assert edge_cli.config == edge_conf
- assert edge_cli.config["stake_amount"] == "unlimited"
- assert callable(edge_cli.edge.calculate)
- assert edge_cli.strategy.bot_started is True
-
-
-def test_edge_init_fee(mocker, edge_conf) -> None:
- patch_exchange(mocker)
- edge_conf["fee"] = 0.01234
- edge_conf["stake_amount"] = 20
- fee_mock = mocker.patch(f"{EXMS}.get_fee", return_value=0.5)
- edge_cli = EdgeCli(edge_conf)
- assert edge_cli.edge.fee == 0.01234
- assert fee_mock.call_count == 0
-
-
-def test_edge_start(mocker, edge_conf) -> None:
- mock_calculate = mocker.patch(
- "freqtrade.edge.edge_positioning.Edge.calculate", return_value=True
- )
- table_mock = mocker.patch("freqtrade.optimize.edge_cli.generate_edge_table")
-
- patch_exchange(mocker)
- edge_conf["stake_amount"] = 20
-
- edge_cli = EdgeCli(edge_conf)
- edge_cli.start()
- assert mock_calculate.call_count == 1
- assert table_mock.call_count == 1
diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py
index 4667f3e1c..be13d2605 100644
--- a/tests/optimize/test_optimize_reports.py
+++ b/tests/optimize/test_optimize_reports.py
@@ -18,12 +18,10 @@ from freqtrade.data.btanalysis import (
load_backtest_data,
load_backtest_stats,
)
-from freqtrade.edge import PairInfo
from freqtrade.enums import ExitType
from freqtrade.optimize.optimize_reports import (
generate_backtest_stats,
generate_daily_stats,
- generate_edge_table,
generate_pair_metrics,
generate_periodic_breakdown_stats,
generate_strategy_comparison,
@@ -646,15 +644,6 @@ def test_text_table_strategy(testdatadir, capsys):
)
-def test_generate_edge_table(capsys):
- results = {}
- results["ETH/BTC"] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
- generate_edge_table(results)
- text = capsys.readouterr().out
- assert re.search(r".* ETH/BTC .*", text)
- assert re.search(r".* Risk Reward Ratio .* Required Risk Reward .* Expectancy .*", text)
-
-
def test_generate_periodic_breakdown_stats(testdatadir):
filename = testdatadir / "backtest_results/backtest-result.json"
bt_data = load_backtest_data(filename).to_dict(orient="records")
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index 7d1b87b55..54f427f66 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -6,7 +6,6 @@ import pytest
from numpy import isnan
from sqlalchemy import select
-from freqtrade.edge import PairInfo
from freqtrade.enums import SignalDirection, State, TradingMode
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Order, Trade
@@ -1393,36 +1392,6 @@ def test_rpc_blacklist(mocker, default_conf) -> None:
assert isinstance(ret["errors"], dict)
-def test_rpc_edge_disabled(mocker, default_conf) -> None:
- mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
- freqtradebot = get_patched_freqtradebot(mocker, default_conf)
- rpc = RPC(freqtradebot)
- with pytest.raises(RPCException, match=r"Edge is not enabled."):
- rpc._rpc_edge()
-
-
-def test_rpc_edge_enabled(mocker, edge_conf) -> None:
- mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
- mocker.patch(
- "freqtrade.edge.Edge._cached_pairs",
- mocker.PropertyMock(
- return_value={
- "E/F": PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
- }
- ),
- )
- freqtradebot = get_patched_freqtradebot(mocker, edge_conf)
-
- rpc = RPC(freqtradebot)
- ret = rpc._rpc_edge()
-
- assert len(ret) == 1
- assert ret[0]["Pair"] == "E/F"
- assert ret[0]["Winrate"] == 0.66
- assert ret[0]["Expectancy"] == 1.71
- assert ret[0]["Stoploss"] == -0.02
-
-
def test_rpc_health(mocker, default_conf) -> None:
mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock())
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index c327a416b..d0b235638 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -1167,21 +1167,6 @@ def test_api_logs(botclient):
assert len(rc1.json()["logs"]) == rc1.json()["log_count"]
-def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
- ftbot, client = botclient
- patch_get_signal(ftbot)
- mocker.patch.multiple(
- EXMS,
- get_balances=MagicMock(return_value=ticker),
- fetch_ticker=ticker,
- get_fee=fee,
- markets=PropertyMock(return_value=markets),
- )
- rc = client_get(client, f"{BASE_URI}/edge")
- assert_response(rc, 502)
- assert rc.json() == {"error": "Error querying /api/v1/edge: Edge is not enabled."}
-
-
@pytest.mark.parametrize(
"is_short,expected",
[
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 3409cc0e5..4d489c415 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -21,7 +21,6 @@ from telegram.error import BadRequest, NetworkError, TelegramError
from freqtrade import __version__
from freqtrade.constants import CANCEL_REASON
-from freqtrade.edge import PairInfo
from freqtrade.enums import (
ExitType,
MarketDirection,
@@ -171,7 +170,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
"['reload_conf', 'reload_config'], ['show_conf', 'show_config'], "
"['pause', 'stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], "
"['bl_delete', 'blacklist_delete'], "
- "['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir'], "
+ "['logs'], ['health'], ['help'], ['version'], ['marketdir'], "
"['order'], ['list_custom_data'], ['tg_info']]"
)
@@ -1952,40 +1951,6 @@ async def test_telegram_logs(default_conf, update, mocker) -> None:
assert msg_mock.call_count >= 2
-async def test_edge_disabled(default_conf, update, mocker) -> None:
- telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
-
- await telegram._edge(update=update, context=MagicMock())
- assert msg_mock.call_count == 1
- assert "Edge is not enabled." in msg_mock.call_args_list[0][0][0]
-
-
-async def test_edge_enabled(edge_conf, update, mocker) -> None:
- mocker.patch(
- "freqtrade.edge.Edge._cached_pairs",
- mocker.PropertyMock(
- return_value={
- "E/F": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
- }
- ),
- )
-
- telegram, _, msg_mock = get_telegram_testobject(mocker, edge_conf)
-
- await telegram._edge(update=update, context=MagicMock())
- assert msg_mock.call_count == 1
- assert "Edge only validated following pairs:\n" in msg_mock.call_args_list[0][0][0]
- assert "Pair Winrate Expectancy Stoploss" in msg_mock.call_args_list[0][0][0]
-
- msg_mock.reset_mock()
-
- mocker.patch("freqtrade.edge.Edge._cached_pairs", mocker.PropertyMock(return_value={}))
- await telegram._edge(update=update, context=MagicMock())
- assert msg_mock.call_count == 1
- assert "Edge only validated following pairs:" in msg_mock.call_args_list[0][0][0]
- assert "Winrate" not in msg_mock.call_args_list[0][0][0]
-
-
@pytest.mark.parametrize(
"is_short,regex_pattern",
[(True, r"now[ ]*XRP\/BTC \(#3\) -1.00% \("), (False, r"now[ ]*XRP\/BTC \(#3\) 1.00% \(")],
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index 32ece7b4e..2ae72692b 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -756,27 +756,6 @@ def test_validate_tsl(default_conf):
validate_config_consistency(default_conf)
-def test_validate_edge2(edge_conf):
- edge_conf.update(
- {
- "use_exit_signal": True,
- }
- )
- # Passes test
- validate_config_consistency(edge_conf)
-
- edge_conf.update(
- {
- "use_exit_signal": False,
- }
- )
- with pytest.raises(
- OperationalException,
- match="Edge requires `use_exit_signal` to be True, otherwise no sells will happen.",
- ):
- validate_config_consistency(edge_conf)
-
-
def test_validate_whitelist(default_conf):
default_conf["runmode"] = RunMode.DRY_RUN
# Test regular case - has whitelist and uses StaticPairlist
@@ -1062,6 +1041,17 @@ def test__validate_orderflow(default_conf) -> None:
validate_config_consistency(conf)
+def test_validate_edge_removal(default_conf):
+ default_conf["edge"] = {
+ "enabled": True,
+ }
+ with pytest.raises(
+ ConfigurationError,
+ match="Edge is no longer supported and has been removed from Freqtrade with 2025.6.",
+ ):
+ validate_config_consistency(default_conf)
+
+
def test_load_config_test_comments() -> None:
"""
Load config with comments
@@ -1315,23 +1305,6 @@ def test_process_removed_settings(mocker, default_conf, setting):
process_temporary_deprecated_settings(default_conf)
-def test_process_deprecated_setting_edge(mocker, edge_conf):
- patched_configuration_load_config_file(mocker, edge_conf)
- edge_conf.update(
- {
- "edge": {
- "enabled": True,
- "capital_available_percentage": 0.5,
- }
- }
- )
-
- with pytest.raises(
- OperationalException, match=r"DEPRECATED.*Using 'edge.capital_available_percentage'*"
- ):
- process_temporary_deprecated_settings(edge_conf)
-
-
def test_check_conflicting_settings(mocker, default_conf, caplog):
patched_configuration_load_config_file(mocker, default_conf)