Merge branch 'develop' into feature/fetch-public-trades

This commit is contained in:
Matthias
2024-07-11 07:06:58 +02:00
66 changed files with 792 additions and 754 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: 'v0.5.0' rev: 'v0.5.1'
hooks: hooks:
- id: ruff - id: ruff
+1 -1
View File
@@ -22,7 +22,7 @@ This will spin up a local server (usually on port 8000) so you can see if everyt
## Developer setup ## Developer setup
To configure a development environment, you can either use the provided [DevContainer](#devcontainer-setup), or use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ". To configure a development environment, you can either use the provided [DevContainer](#devcontainer-setup), or use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ".
Alternatively (e.g. if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -e .[all]`. Alternatively (e.g. if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -r requirements-dev.txt` - followed by `pip3 install -e .[all]`.
This will install all required tools for development, including `pytest`, `ruff`, `mypy`, and `coveralls`. This will install all required tools for development, including `pytest`, `ruff`, `mypy`, and `coveralls`.
+1 -1
View File
@@ -1,6 +1,6 @@
markdown==3.6 markdown==3.6
mkdocs==1.6.0 mkdocs==1.6.0
mkdocs-material==9.5.27 mkdocs-material==9.5.28
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==10.8.1 pymdown-extensions==10.8.1
jinja2==3.1.4 jinja2==3.1.4
+1 -1
View File
@@ -6,7 +6,7 @@ if "dev" in __version__:
from pathlib import Path from pathlib import Path
try: try:
import subprocess import subprocess # noqa: S404
freqtrade_basedir = Path(__file__).parent freqtrade_basedir = Path(__file__).parent
+27 -34
View File
@@ -16,6 +16,7 @@ from freqtrade.exceptions import ConfigurationError
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import print_rich_table
from freqtrade.util.migrations import migrate_data from freqtrade.util.migrations import migrate_data
@@ -119,8 +120,6 @@ def start_list_data(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
from tabulate import tabulate
from freqtrade.data.history import get_datahandler from freqtrade.data.history import get_datahandler
dhc = get_datahandler(config["datadir"], config["dataformat_ohlcv"]) dhc = get_datahandler(config["datadir"], config["dataformat_ohlcv"])
@@ -131,8 +130,7 @@ def start_list_data(args: Dict[str, Any]) -> None:
if args["pairs"]: if args["pairs"]:
paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]] paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]]
title = f"Found {len(paircombs)} pair / timeframe combinations."
print(f"Found {len(paircombs)} pair / timeframe combinations.")
if not config.get("show_timerange"): if not config.get("show_timerange"):
groupedpair = defaultdict(list) groupedpair = defaultdict(list)
for pair, timeframe, candle_type in sorted( for pair, timeframe, candle_type in sorted(
@@ -141,40 +139,35 @@ def start_list_data(args: Dict[str, Any]) -> None:
groupedpair[(pair, candle_type)].append(timeframe) groupedpair[(pair, candle_type)].append(timeframe)
if groupedpair: if groupedpair:
print( print_rich_table(
tabulate( [
[ (pair, ", ".join(timeframes), candle_type)
(pair, ", ".join(timeframes), candle_type) for (pair, candle_type), timeframes in groupedpair.items()
for (pair, candle_type), timeframes in groupedpair.items() ],
], ("Pair", "Timeframe", "Type"),
headers=("Pair", "Timeframe", "Type"), title,
tablefmt="psql", table_kwargs={"min_width": 50},
stralign="right",
)
) )
else: else:
paircombs1 = [ paircombs1 = [
(pair, timeframe, candle_type, *dhc.ohlcv_data_min_max(pair, timeframe, candle_type)) (pair, timeframe, candle_type, *dhc.ohlcv_data_min_max(pair, timeframe, candle_type))
for pair, timeframe, candle_type in paircombs for pair, timeframe, candle_type in paircombs
] ]
print_rich_table(
print( [
tabulate( (
[ pair,
( timeframe,
pair, candle_type,
timeframe, start.strftime(DATETIME_PRINT_FORMAT),
candle_type, end.strftime(DATETIME_PRINT_FORMAT),
start.strftime(DATETIME_PRINT_FORMAT), str(length),
end.strftime(DATETIME_PRINT_FORMAT), )
length, for pair, timeframe, candle_type, start, end, length in sorted(
) paircombs1, key=lambda x: (x[0], timeframe_to_minutes(x[1]), x[2])
for pair, timeframe, candle_type, start, end, length in sorted( )
paircombs1, key=lambda x: (x[0], timeframe_to_minutes(x[1]), x[2]) ],
) ("Pair", "Timeframe", "Type", "From", "To", "Candles"),
], summary=title,
headers=("Pair", "Timeframe", "Type", "From", "To", "Candles"), table_kwargs={"min_width": 50},
tablefmt="psql",
stralign="right",
)
) )
+9 -14
View File
@@ -2,8 +2,6 @@ import logging
from operator import itemgetter from operator import itemgetter
from typing import Any, Dict from typing import Any, Dict
from colorama import init as colorama_init
from freqtrade.configuration import setup_utils_configuration from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
@@ -18,6 +16,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
""" """
List hyperopt epochs previously evaluated List hyperopt epochs previously evaluated
""" """
from freqtrade.optimize.hyperopt_output import HyperoptOutput
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -35,21 +34,17 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
# Previous evaluations # Previous evaluations
epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config) epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config)
if print_colorized:
colorama_init(autoreset=True)
if not export_csv: if not export_csv:
try: try:
print( h_out = HyperoptOutput()
HyperoptTools.get_result_table( h_out.add_data(
config, config,
epochs, epochs,
total_epochs, total_epochs,
not config.get("hyperopt_list_best", False), not config.get("hyperopt_list_best", False),
print_colorized,
0,
)
) )
h_out.print(print_colorized=print_colorized)
except KeyboardInterrupt: except KeyboardInterrupt:
print("User interrupted..") print("User interrupted..")
+68 -60
View File
@@ -4,9 +4,9 @@ import sys
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
import rapidjson import rapidjson
from colorama import Fore, Style from rich.console import Console
from colorama import init as colorama_init from rich.table import Table
from tabulate import tabulate from rich.text import Text
from freqtrade.configuration import setup_utils_configuration from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
@@ -14,7 +14,8 @@ from freqtrade.exceptions import ConfigurationError, OperationalException
from freqtrade.exchange import list_available_exchanges, market_is_active from freqtrade.exchange import list_available_exchanges, market_is_active
from freqtrade.misc import parse_db_uri_for_logging, plural from freqtrade.misc import parse_db_uri_for_logging, plural
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.types import ValidExchangesType from freqtrade.types.valid_exchanges_type import ValidExchangesType
from freqtrade.util import print_rich_table
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -26,72 +27,69 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments() :param args: Cli args from Arguments()
:return: None :return: None
""" """
exchanges = list_available_exchanges(args["list_exchanges_all"]) available_exchanges: List[ValidExchangesType] = list_available_exchanges(
args["list_exchanges_all"]
)
if args["print_one_column"]: if args["print_one_column"]:
print("\n".join([e["name"] for e in exchanges])) print("\n".join([e["name"] for e in available_exchanges]))
else: else:
headers = { if args["list_exchanges_all"]:
"name": "Exchange name", title = (
"supported": "Supported", f"All exchanges supported by the ccxt library "
"trade_modes": "Markets", f"({len(available_exchanges)} exchanges):"
"comment": "Reason", )
} else:
headers.update({"valid": "Valid"} if args["list_exchanges_all"] else {}) available_exchanges = [e for e in available_exchanges if e["valid"] is not False]
title = f"Exchanges available for Freqtrade ({len(available_exchanges)} exchanges):"
def build_entry(exchange: ValidExchangesType, valid: bool): table = Table(title=title)
valid_entry = {"valid": exchange["valid"]} if valid else {}
result: Dict[str, Union[str, bool]] = { table.add_column("Exchange Name")
"name": exchange["name"], table.add_column("Markets")
**valid_entry, table.add_column("Reason")
"supported": "Official" if exchange["supported"] else "",
"trade_modes": ("DEX: " if exchange["dex"] else "") for exchange in available_exchanges:
+ ", ".join( name = Text(exchange["name"])
(f"{a['margin_mode']} " if a["margin_mode"] else "") + a["trading_mode"] if exchange["supported"]:
name.append(" (Official)", style="italic")
name.stylize("green bold")
trade_modes = Text(
", ".join(
(f"{a.get('margin_mode', '')} {a['trading_mode']}").lstrip()
for a in exchange["trade_modes"] for a in exchange["trade_modes"]
), ),
"comment": exchange["comment"], style="",
}
return result
if args["list_exchanges_all"]:
print("All exchanges supported by the ccxt library:")
exchanges = [build_entry(e, True) for e in exchanges]
else:
print("Exchanges available for Freqtrade:")
exchanges = [build_entry(e, False) for e in exchanges if e["valid"] is not False]
print(
tabulate(
exchanges,
headers=headers,
) )
) if exchange["dex"]:
trade_modes = Text("DEX: ") + trade_modes
trade_modes.stylize("bold", 0, 3)
table.add_row(
name,
trade_modes,
exchange["comment"],
style=None if exchange["valid"] else "red",
)
# table.add_row(*[exchange[header] for header in headers])
console = Console()
console.print(table)
def _print_objs_tabular(objs: List, print_colorized: bool) -> None: def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
if print_colorized:
colorama_init(autoreset=True)
red = Fore.RED
yellow = Fore.YELLOW
reset = Style.RESET_ALL
else:
red = ""
yellow = ""
reset = ""
names = [s["name"] for s in objs] names = [s["name"] for s in objs]
objs_to_print = [ objs_to_print: List[Dict[str, Union[Text, str]]] = [
{ {
"name": s["name"] if s["name"] else "--", "name": Text(s["name"] if s["name"] else "--"),
"location": s["location_rel"], "location": s["location_rel"],
"status": ( "status": (
red + "LOAD FAILED" + reset Text("LOAD FAILED", style="bold red")
if s["class"] is None if s["class"] is None
else "OK" else Text("OK", style="bold green")
if names.count(s["name"]) == 1 if names.count(s["name"]) == 1
else yellow + "DUPLICATE NAME" + reset else Text("DUPLICATE NAME", style="bold yellow")
), ),
} }
for s in objs for s in objs
@@ -101,11 +99,23 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
objs_to_print[idx].update( objs_to_print[idx].update(
{ {
"hyperoptable": "Yes" if s["hyperoptable"]["count"] > 0 else "No", "hyperoptable": "Yes" if s["hyperoptable"]["count"] > 0 else "No",
"buy-Params": len(s["hyperoptable"].get("buy", [])), "buy-Params": str(len(s["hyperoptable"].get("buy", []))),
"sell-Params": len(s["hyperoptable"].get("sell", [])), "sell-Params": str(len(s["hyperoptable"].get("sell", []))),
} }
) )
print(tabulate(objs_to_print, headers="keys", tablefmt="psql", stralign="right")) table = Table()
for header in objs_to_print[0].keys():
table.add_column(header.capitalize(), justify="right")
for row in objs_to_print:
table.add_row(*[row[header] for header in objs_to_print[0].keys()])
console = Console(
color_system="auto" if print_colorized else None,
width=200 if "pytest" in sys.modules else None,
)
console.print(table)
def start_list_strategies(args: Dict[str, Any]) -> None: def start_list_strategies(args: Dict[str, Any]) -> None:
@@ -270,9 +280,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
writer.writeheader() writer.writeheader()
writer.writerows(tabular_data) writer.writerows(tabular_data)
else: else:
# print data as a table, with the human-readable summary print_rich_table(tabular_data, headers, summary_str)
print(f"{summary_str}:")
print(tabulate(tabular_data, headers="keys", tablefmt="psql", stralign="right"))
elif not ( elif not (
args.get("print_one_column", False) args.get("print_one_column", False)
or args.get("list_pairs_print_json", False) or args.get("list_pairs_print_json", False)
@@ -38,7 +38,7 @@ def chown_user_directory(directory: Path) -> None:
""" """
if running_in_docker(): if running_in_docker():
try: try:
import subprocess import subprocess # noqa: S404
subprocess.check_output(["sudo", "chown", "-R", "ftuser:", str(directory.resolve())]) subprocess.check_output(["sudo", "chown", "-R", "ftuser:", str(directory.resolve())])
except Exception: except Exception:
+2 -2
View File
@@ -4,7 +4,6 @@ from typing import List
import joblib import joblib
import pandas as pd import pandas as pd
from tabulate import tabulate
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import Config from freqtrade.constants import Config
@@ -14,6 +13,7 @@ from freqtrade.data.btanalysis import (
load_backtest_stats, load_backtest_stats,
) )
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.util import print_df_rich_table
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -307,7 +307,7 @@ def _print_table(
if name is not None: if name is not None:
print(name) print(name)
print(tabulate(data, headers="keys", tablefmt="psql", showindex=show_index)) print_df_rich_table(data, data.keys(), show_index=show_index)
def process_entry_exit_reasons(config: Config): def process_entry_exit_reasons(config: Config):
+1
View File
@@ -65,6 +65,7 @@ SUPPORTED_EXCHANGES = [
EXCHANGE_HAS_REQUIRED: Dict[str, List[str]] = { EXCHANGE_HAS_REQUIRED: Dict[str, List[str]] = {
# Required / private # Required / private
"fetchOrder": ["fetchOpenOrder", "fetchClosedOrder"], "fetchOrder": ["fetchOpenOrder", "fetchClosedOrder"],
"fetchL2OrderBook": ["fetchTicker"],
"cancelOrder": [], "cancelOrder": [],
"createOrder": [], "createOrder": [],
"fetchBalance": [], "fetchBalance": [],
+7 -4
View File
@@ -624,9 +624,12 @@ class Exchange:
# Reload async markets, then assign them to sync api # Reload async markets, then assign them to sync api
self._markets = self._load_async_markets(reload=True) self._markets = self._load_async_markets(reload=True)
self._api.set_markets(self._api_async.markets, self._api_async.currencies) self._api.set_markets(self._api_async.markets, self._api_async.currencies)
# Assign options array, as it contains some temporary information from the exchange.
self._api.options = self._api_async.options
if self._exchange_ws: if self._exchange_ws:
# Set markets to avoid reloading on websocket api # Set markets to avoid reloading on websocket api
self._ws_async.set_markets(self._api.markets, self._api.currencies) self._ws_async.set_markets(self._api.markets, self._api.currencies)
self._ws_async.options = self._api.options
self._last_markets_refresh = dt_ts() self._last_markets_refresh = dt_ts()
if is_initial and self._ft_has["needs_trading_fees"]: if is_initial and self._ft_has["needs_trading_fees"]:
@@ -2084,7 +2087,7 @@ class Exchange:
def get_fee( def get_fee(
self, self,
symbol: str, symbol: str,
type: str = "", order_type: str = "",
side: str = "", side: str = "",
amount: float = 1, amount: float = 1,
price: float = 1, price: float = 1,
@@ -2093,13 +2096,13 @@ class Exchange:
""" """
Retrieve fee from exchange Retrieve fee from exchange
:param symbol: Pair :param symbol: Pair
:param type: Type of order (market, limit, ...) :param order_type: Type of order (market, limit, ...)
:param side: Side of order (buy, sell) :param side: Side of order (buy, sell)
:param amount: Amount of order :param amount: Amount of order
:param price: Price of order :param price: Price of order
:param taker_or_maker: 'maker' or 'taker' (ignored if "type" is provided) :param taker_or_maker: 'maker' or 'taker' (ignored if "type" is provided)
""" """
if type and type == "market": if order_type and order_type == "market":
taker_or_maker = "taker" taker_or_maker = "taker"
try: try:
if self._config["dry_run"] and self._config.get("fee", None) is not None: if self._config["dry_run"] and self._config.get("fee", None) is not None:
@@ -2110,7 +2113,7 @@ class Exchange:
return self._api.calculate_fee( return self._api.calculate_fee(
symbol=symbol, symbol=symbol,
type=type, type=order_type,
side=side, side=side,
amount=amount, amount=amount,
price=price, price=price,
+1 -1
View File
@@ -52,7 +52,7 @@ class BaseEnvironment(gym.Env):
reward_kwargs: dict = {}, reward_kwargs: dict = {},
window_size=10, window_size=10,
starting_point=True, starting_point=True,
id: str = "baseenv-1", id: str = "baseenv-1", # noqa: A002
seed: int = 1, seed: int = 1,
config: dict = {}, config: dict = {},
live: bool = False, live: bool = False,
+10 -10
View File
@@ -238,9 +238,9 @@ class FreqaiDataDrawer:
metadata, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE metadata, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE
) )
def np_encoder(self, object): def np_encoder(self, obj):
if isinstance(object, np.generic): if isinstance(obj, np.generic):
return object.item() return obj.item()
def get_pair_dict_info(self, pair: str) -> Tuple[str, int]: def get_pair_dict_info(self, pair: str) -> Tuple[str, int]:
""" """
@@ -448,8 +448,8 @@ class FreqaiDataDrawer:
delete_dict: Dict[str, Any] = {} delete_dict: Dict[str, Any] = {}
for dir in model_folders: for directory in model_folders:
result = pattern.match(str(dir.name)) result = pattern.match(str(directory.name))
if result is None: if result is None:
continue continue
coin = result.group(1) coin = result.group(1)
@@ -458,10 +458,10 @@ class FreqaiDataDrawer:
if coin not in delete_dict: if coin not in delete_dict:
delete_dict[coin] = {} delete_dict[coin] = {}
delete_dict[coin]["num_folders"] = 1 delete_dict[coin]["num_folders"] = 1
delete_dict[coin]["timestamps"] = {int(timestamp): dir} delete_dict[coin]["timestamps"] = {int(timestamp): directory}
else: else:
delete_dict[coin]["num_folders"] += 1 delete_dict[coin]["num_folders"] += 1
delete_dict[coin]["timestamps"][int(timestamp)] = dir delete_dict[coin]["timestamps"][int(timestamp)] = directory
for coin in delete_dict: for coin in delete_dict:
if delete_dict[coin]["num_folders"] > num_keep: if delete_dict[coin]["num_folders"] > num_keep:
@@ -612,9 +612,9 @@ class FreqaiDataDrawer:
elif self.model_type == "pytorch": elif self.model_type == "pytorch":
import torch import torch
zip = torch.load(dk.data_path / f"{dk.model_filename}_model.zip") zipfile = torch.load(dk.data_path / f"{dk.model_filename}_model.zip")
model = zip["pytrainer"] model = zipfile["pytrainer"]
model = model.load_from_checkpoint(zip) model = model.load_from_checkpoint(zipfile)
if not model: if not model:
raise OperationalException( raise OperationalException(
+2 -2
View File
@@ -45,10 +45,10 @@ class TensorBoardCallback(BaseTensorBoardCallback):
return False return False
evals = ["validation", "train"] evals = ["validation", "train"]
for metric, eval in zip(evals_log.items(), evals): for metric, eval_ in zip(evals_log.items(), evals):
for metric_name, log in metric[1].items(): for metric_name, log in metric[1].items():
score = log[-1][0] if isinstance(log[-1], tuple) else log[-1] score = log[-1][0] if isinstance(log[-1], tuple) else log[-1]
self.writer.add_scalar(f"{eval}-{metric_name}", score, epoch) self.writer.add_scalar(f"{eval_}-{metric_name}", score, epoch)
return False return False
+13 -1
View File
@@ -2370,6 +2370,18 @@ class FreqtradeBot(LoggingMixin):
trade, order, order_obj, order_amount, order.get("trades", []) trade, order, order_obj, order_amount, order.get("trades", [])
) )
def _trades_valid_for_fee(self, trades: List[Dict[str, Any]]) -> bool:
"""
Check if trades are valid for fee detection.
:return: True if trades are valid for fee detection, False otherwise
"""
if not trades:
return False
# We expect amount and cost to be present in all trade objects.
if any(trade.get("amount") is None or trade.get("cost") is None for trade in trades):
return False
return True
def fee_detection_from_trades( def fee_detection_from_trades(
self, trade: Trade, order: Dict, order_obj: Order, order_amount: float, trades: List self, trade: Trade, order: Dict, order_obj: Order, order_amount: float, trades: List
) -> Optional[float]: ) -> Optional[float]:
@@ -2377,7 +2389,7 @@ class FreqtradeBot(LoggingMixin):
fee-detection fallback to Trades. fee-detection fallback to Trades.
Either uses provided trades list or the result of fetch_my_trades to get correct fee. Either uses provided trades list or the result of fetch_my_trades to get correct fee.
""" """
if not trades: if not self._trades_valid_for_fee(trades):
trades = self.exchange.get_trades_for_order( trades = self.exchange.get_trades_for_order(
self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date
) )
@@ -4,11 +4,13 @@ from pathlib import Path
from typing import Any, Dict, List from typing import Any, Dict, List
import pandas as pd import pandas as pd
from rich.text import Text
from freqtrade.constants import Config from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.analysis.lookahead import LookaheadAnalysis from freqtrade.optimize.analysis.lookahead import LookaheadAnalysis
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.util import print_rich_table
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -53,18 +55,18 @@ class LookaheadAnalysisSubFunctions:
[ [
inst.strategy_obj["location"].parts[-1], inst.strategy_obj["location"].parts[-1],
inst.strategy_obj["name"], inst.strategy_obj["name"],
inst.current_analysis.has_bias, Text("Yes", style="bold red")
if inst.current_analysis.has_bias
else Text("No", style="bold green"),
inst.current_analysis.total_signals, inst.current_analysis.total_signals,
inst.current_analysis.false_entry_signals, inst.current_analysis.false_entry_signals,
inst.current_analysis.false_exit_signals, inst.current_analysis.false_exit_signals,
", ".join(inst.current_analysis.false_indicators), ", ".join(inst.current_analysis.false_indicators),
] ]
) )
from tabulate import tabulate
table = tabulate(data, headers=headers, tablefmt="orgtbl") print_rich_table(data, headers, summary="Lookahead Analysis")
print(table) return data
return table, headers, data
@staticmethod @staticmethod
def export_to_csv(config: Dict[str, Any], lookahead_analysis: List[LookaheadAnalysis]): def export_to_csv(config: Dict[str, Any], lookahead_analysis: List[LookaheadAnalysis]):
@@ -7,6 +7,7 @@ from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.analysis.recursive import RecursiveAnalysis from freqtrade.optimize.analysis.recursive import RecursiveAnalysis
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.util import print_rich_table
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -16,9 +17,9 @@ class RecursiveAnalysisSubFunctions:
@staticmethod @staticmethod
def text_table_recursive_analysis_instances(recursive_instances: List[RecursiveAnalysis]): def text_table_recursive_analysis_instances(recursive_instances: List[RecursiveAnalysis]):
startups = recursive_instances[0]._startup_candle startups = recursive_instances[0]._startup_candle
headers = ["indicators"] headers = ["Indicators"]
for candle in startups: for candle in startups:
headers.append(candle) headers.append(str(candle))
data = [] data = []
for inst in recursive_instances: for inst in recursive_instances:
@@ -30,13 +31,11 @@ class RecursiveAnalysisSubFunctions:
data.append(temp_data) data.append(temp_data)
if len(data) > 0: if len(data) > 0:
from tabulate import tabulate print_rich_table(data, headers, summary="Recursive Analysis")
table = tabulate(data, headers=headers, tablefmt="orgtbl") return data
print(table)
return table, headers, data
return None, None, data return data
@staticmethod @staticmethod
def calculate_config_overrides(config: Config): def calculate_config_overrides(config: Config):
+9 -9
View File
@@ -465,25 +465,25 @@ class Backtesting:
return data return data
def _get_close_rate( def _get_close_rate(
self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int
) -> float: ) -> float:
""" """
Get close rate for backtesting result Get close rate for backtesting result
""" """
# Special handling if high or low hit STOP_LOSS or ROI # Special handling if high or low hit STOP_LOSS or ROI
if exit.exit_type in ( if exit_.exit_type in (
ExitType.STOP_LOSS, ExitType.STOP_LOSS,
ExitType.TRAILING_STOP_LOSS, ExitType.TRAILING_STOP_LOSS,
ExitType.LIQUIDATION, ExitType.LIQUIDATION,
): ):
return self._get_close_rate_for_stoploss(row, trade, exit, trade_dur) return self._get_close_rate_for_stoploss(row, trade, exit_, trade_dur)
elif exit.exit_type == (ExitType.ROI): elif exit_.exit_type == (ExitType.ROI):
return self._get_close_rate_for_roi(row, trade, exit, trade_dur) return self._get_close_rate_for_roi(row, trade, exit_, trade_dur)
else: else:
return row[OPEN_IDX] return row[OPEN_IDX]
def _get_close_rate_for_stoploss( def _get_close_rate_for_stoploss(
self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int
) -> float: ) -> float:
# our stoploss was already lower than candle high, # our stoploss was already lower than candle high,
# possibly due to a cancelled trade exit. # possibly due to a cancelled trade exit.
@@ -491,7 +491,7 @@ class Backtesting:
is_short = trade.is_short or False is_short = trade.is_short or False
leverage = trade.leverage or 1.0 leverage = trade.leverage or 1.0
side_1 = -1 if is_short else 1 side_1 = -1 if is_short else 1
if exit.exit_type == ExitType.LIQUIDATION and trade.liquidation_price: if exit_.exit_type == ExitType.LIQUIDATION and trade.liquidation_price:
stoploss_value = trade.liquidation_price stoploss_value = trade.liquidation_price
else: else:
stoploss_value = trade.stop_loss stoploss_value = trade.stop_loss
@@ -506,7 +506,7 @@ class Backtesting:
# Special case: trailing triggers within same candle as trade opened. Assume most # Special case: trailing triggers within same candle as trade opened. Assume most
# pessimistic price movement, which is moving just enough to arm stoploss and # pessimistic price movement, which is moving just enough to arm stoploss and
# immediately going down to stop price. # immediately going down to stop price.
if exit.exit_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0: if exit_.exit_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0:
if ( if (
not self.strategy.use_custom_stoploss not self.strategy.use_custom_stoploss
and self.strategy.trailing_stop and self.strategy.trailing_stop
@@ -537,7 +537,7 @@ class Backtesting:
return stoploss_value return stoploss_value
def _get_close_rate_for_roi( def _get_close_rate_for_roi(
self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int self, row: Tuple, trade: LocalTrade, exit_: ExitCheckTuple, trade_dur: int
) -> float: ) -> float:
is_short = trade.is_short or False is_short = trade.is_short or False
leverage = trade.leverage or 1.0 leverage = trade.leverage or 1.0
+1 -1
View File
@@ -52,4 +52,4 @@ class EdgeCli:
result = self.edge.calculate(self.config["exchange"]["pair_whitelist"]) result = self.edge.calculate(self.config["exchange"]["pair_whitelist"])
if result: if result:
print("") # blank line for readability print("") # blank line for readability
print(generate_edge_table(self.edge._cached_pairs)) generate_edge_table(self.edge._cached_pairs)
+18 -17
View File
@@ -14,14 +14,14 @@ from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
import rapidjson import rapidjson
from colorama import init as colorama_init
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
from joblib.externals import cloudpickle from joblib.externals import cloudpickle
from pandas import DataFrame from pandas import DataFrame
from rich.align import Align
from rich.console import Console
from rich.progress import ( from rich.progress import (
BarColumn, BarColumn,
MofNCompleteColumn, MofNCompleteColumn,
Progress,
TaskProgressColumn, TaskProgressColumn,
TextColumn, TextColumn,
TimeElapsedColumn, TimeElapsedColumn,
@@ -40,6 +40,7 @@ from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules # Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
from freqtrade.optimize.hyperopt_auto import HyperOptAuto from freqtrade.optimize.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
from freqtrade.optimize.hyperopt_output import HyperoptOutput
from freqtrade.optimize.hyperopt_tools import ( from freqtrade.optimize.hyperopt_tools import (
HyperoptStateContainer, HyperoptStateContainer,
HyperoptTools, HyperoptTools,
@@ -47,6 +48,7 @@ from freqtrade.optimize.hyperopt_tools import (
) )
from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
from freqtrade.util import CustomProgress
# Suppress scikit-learn FutureWarnings from skopt # Suppress scikit-learn FutureWarnings from skopt
@@ -86,6 +88,8 @@ class Hyperopt:
self.max_open_trades_space: List[Dimension] = [] self.max_open_trades_space: List[Dimension] = []
self.dimensions: List[Dimension] = [] self.dimensions: List[Dimension] = []
self._hyper_out: HyperoptOutput = HyperoptOutput()
self.config = config self.config = config
self.min_date: datetime self.min_date: datetime
self.max_date: datetime self.max_date: datetime
@@ -260,7 +264,7 @@ class Hyperopt:
result["max_open_trades"] = {"max_open_trades": strategy.max_open_trades} result["max_open_trades"] = {"max_open_trades": strategy.max_open_trades}
return result return result
def print_results(self, results) -> None: def print_results(self, results: Dict[str, Any]) -> None:
""" """
Log results if it is better than any previous evaluation Log results if it is better than any previous evaluation
TODO: this should be moved to HyperoptTools too TODO: this should be moved to HyperoptTools too
@@ -268,17 +272,12 @@ class Hyperopt:
is_best = results["is_best"] is_best = results["is_best"]
if self.print_all or is_best: if self.print_all or is_best:
print( self._hyper_out.add_data(
HyperoptTools.get_result_table( self.config,
self.config, [results],
results, self.total_epochs,
self.total_epochs, self.print_all,
self.print_all,
self.print_colorized,
self.hyperopt_table_header,
)
) )
self.hyperopt_table_header = 2
def init_spaces(self): def init_spaces(self):
""" """
@@ -626,16 +625,16 @@ class Hyperopt:
self.opt = self.get_optimizer(self.dimensions, config_jobs) self.opt = self.get_optimizer(self.dimensions, config_jobs)
if self.print_colorized:
colorama_init(autoreset=True)
try: try:
with Parallel(n_jobs=config_jobs) as parallel: with Parallel(n_jobs=config_jobs) as parallel:
jobs = parallel._effective_n_jobs() jobs = parallel._effective_n_jobs()
logger.info(f"Effective number of parallel workers used: {jobs}") logger.info(f"Effective number of parallel workers used: {jobs}")
console = Console(
color_system="auto" if self.print_colorized else None,
)
# Define progressbar # Define progressbar
with Progress( with CustomProgress(
TextColumn("[progress.description]{task.description}"), TextColumn("[progress.description]{task.description}"),
BarColumn(bar_width=None), BarColumn(bar_width=None),
MofNCompleteColumn(), MofNCompleteColumn(),
@@ -645,6 +644,8 @@ class Hyperopt:
"", "",
TimeRemainingColumn(), TimeRemainingColumn(),
expand=True, expand=True,
console=console,
cust_objs=[Align.center(self._hyper_out.table)],
) as pbar: ) as pbar:
task = pbar.add_task("Epochs", total=self.total_epochs) task = pbar.add_task("Epochs", total=self.total_epochs)
+123
View File
@@ -0,0 +1,123 @@
import sys
from typing import List, Optional, Union
from rich.console import Console
from rich.table import Table
from rich.text import Text
from freqtrade.constants import Config
from freqtrade.optimize.optimize_reports import generate_wins_draws_losses
from freqtrade.util import fmt_coin
class HyperoptOutput:
def __init__(self):
self.table = Table(
title="Hyperopt results",
)
# Headers
self.table.add_column("Best", justify="left")
self.table.add_column("Epoch", justify="right")
self.table.add_column("Trades", justify="right")
self.table.add_column("Win Draw Loss Win%", justify="right")
self.table.add_column("Avg profit", justify="right")
self.table.add_column("Profit", justify="right")
self.table.add_column("Avg duration", justify="right")
self.table.add_column("Objective", justify="right")
self.table.add_column("Max Drawdown (Acct)", justify="right")
def _add_row(self, data: List[Union[str, Text]]):
"""Add single row"""
row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in data]
self.table.add_row(*row_to_add)
def _add_rows(self, data: List[List[Union[str, Text]]]):
"""add multiple rows"""
for row in data:
self._add_row(row)
def print(self, console: Optional[Console] = None, *, print_colorized=True):
if not console:
console = Console(
color_system="auto" if print_colorized else None,
width=200 if "pytest" in sys.modules else None,
)
console.print(self.table)
def add_data(
self,
config: Config,
results: list,
total_epochs: int,
highlight_best: bool,
) -> None:
"""Format one or multiple rows and add them"""
stake_currency = config["stake_currency"]
for r in results:
self.table.add_row(
*[
# "Best":
(
("*" if r["is_initial_point"] or r["is_random"] else "")
+ (" Best" if r["is_best"] else "")
).lstrip(),
# "Epoch":
f"{r['current_epoch']}/{total_epochs}",
# "Trades":
str(r["results_metrics"]["total_trades"]),
# "Win Draw Loss Win%":
generate_wins_draws_losses(
r["results_metrics"]["wins"],
r["results_metrics"]["draws"],
r["results_metrics"]["losses"],
),
# "Avg profit":
f"{r['results_metrics']['profit_mean']:.2%}"
if r["results_metrics"]["profit_mean"] is not None
else "--",
# "Profit":
Text(
"{} {}".format(
fmt_coin(
r["results_metrics"]["profit_total_abs"],
stake_currency,
keep_trailing_zeros=True,
),
f"({r['results_metrics']['profit_total']:,.2%})".rjust(10, " "),
)
if r["results_metrics"].get("profit_total_abs", 0) != 0.0
else "--",
style=(
"green"
if r["results_metrics"].get("profit_total_abs", 0) > 0
else "red"
)
if not r["is_best"]
else "",
),
# "Avg duration":
str(r["results_metrics"]["holding_avg"]),
# "Objective":
f"{r['loss']:,.5f}" if r["loss"] != 100000 else "N/A",
# "Max Drawdown (Acct)":
"{} {}".format(
fmt_coin(
r["results_metrics"]["max_drawdown_abs"],
stake_currency,
keep_trailing_zeros=True,
),
(f"({r['results_metrics']['max_drawdown_account']:,.2%})").rjust(10, " "),
)
if r["results_metrics"]["max_drawdown_account"] != 0.0
else "--",
],
style=" ".join(
[
"bold gold1" if r["is_best"] and highlight_best else "",
"italic " if r["is_initial_point"] else "",
]
),
)
-174
View File
@@ -5,10 +5,7 @@ from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, Tuple from typing import Any, Dict, Iterator, List, Optional, Tuple
import numpy as np import numpy as np
import pandas as pd
import rapidjson import rapidjson
import tabulate
from colorama import Fore, Style
from pandas import isna, json_normalize from pandas import isna, json_normalize
from freqtrade.constants import FTHYPT_FILEVERSION, Config from freqtrade.constants import FTHYPT_FILEVERSION, Config
@@ -16,8 +13,6 @@ from freqtrade.enums import HyperoptState
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, round_dict, safe_value_fallback2 from freqtrade.misc import deep_merge_dicts, round_dict, safe_value_fallback2
from freqtrade.optimize.hyperopt_epoch_filters import hyperopt_filter_epochs from freqtrade.optimize.hyperopt_epoch_filters import hyperopt_filter_epochs
from freqtrade.optimize.optimize_reports import generate_wins_draws_losses
from freqtrade.util import fmt_coin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -357,175 +352,6 @@ class HyperoptTools:
+ f"Objective: {results['loss']:.5f}" + f"Objective: {results['loss']:.5f}"
) )
@staticmethod
def prepare_trials_columns(trials: pd.DataFrame) -> pd.DataFrame:
trials["Best"] = ""
if "results_metrics.winsdrawslosses" not in trials.columns:
# Ensure compatibility with older versions of hyperopt results
trials["results_metrics.winsdrawslosses"] = "N/A"
has_account_drawdown = "results_metrics.max_drawdown_account" in trials.columns
if not has_account_drawdown:
# Ensure compatibility with older versions of hyperopt results
trials["results_metrics.max_drawdown_account"] = None
if "is_random" not in trials.columns:
trials["is_random"] = False
# New mode, using backtest result for metrics
trials["results_metrics.winsdrawslosses"] = trials.apply(
lambda x: generate_wins_draws_losses(
x["results_metrics.wins"], x["results_metrics.draws"], x["results_metrics.losses"]
),
axis=1,
)
trials = trials[
[
"Best",
"current_epoch",
"results_metrics.total_trades",
"results_metrics.winsdrawslosses",
"results_metrics.profit_mean",
"results_metrics.profit_total_abs",
"results_metrics.profit_total",
"results_metrics.holding_avg",
"results_metrics.max_drawdown_account",
"results_metrics.max_drawdown_abs",
"loss",
"is_initial_point",
"is_random",
"is_best",
]
]
trials.columns = [
"Best",
"Epoch",
"Trades",
" Win Draw Loss Win%",
"Avg profit",
"Total profit",
"Profit",
"Avg duration",
"max_drawdown_account",
"max_drawdown_abs",
"Objective",
"is_initial_point",
"is_random",
"is_best",
]
return trials
@staticmethod
def get_result_table(
config: Config,
results: list,
total_epochs: int,
highlight_best: bool,
print_colorized: bool,
remove_header: int,
) -> str:
"""
Log result table
"""
if not results:
return ""
tabulate.PRESERVE_WHITESPACE = True
trials = json_normalize(results, max_level=1)
trials = HyperoptTools.prepare_trials_columns(trials)
trials["is_profit"] = False
trials.loc[trials["is_initial_point"] | trials["is_random"], "Best"] = "* "
trials.loc[trials["is_best"], "Best"] = "Best"
trials.loc[
(trials["is_initial_point"] | trials["is_random"]) & trials["is_best"], "Best"
] = "* Best"
trials.loc[trials["Total profit"] > 0, "is_profit"] = True
trials["Trades"] = trials["Trades"].astype(str)
# perc_multi = 1 if legacy_mode else 100
trials["Epoch"] = trials["Epoch"].apply(
lambda x: "{}/{}".format(str(x).rjust(len(str(total_epochs)), " "), total_epochs)
)
trials["Avg profit"] = trials["Avg profit"].apply(
lambda x: f"{x:,.2%}".rjust(7, " ") if not isna(x) else "--".rjust(7, " ")
)
trials["Avg duration"] = trials["Avg duration"].apply(
lambda x: (
f"{x:,.1f} m".rjust(7, " ")
if isinstance(x, float)
else f"{x}"
if not isna(x)
else "--".rjust(7, " ")
)
)
trials["Objective"] = trials["Objective"].apply(
lambda x: f"{x:,.5f}".rjust(8, " ") if x != 100000 else "N/A".rjust(8, " ")
)
stake_currency = config["stake_currency"]
trials["Max Drawdown (Acct)"] = trials.apply(
lambda x: (
"{} {}".format(
fmt_coin(x["max_drawdown_abs"], stake_currency, keep_trailing_zeros=True),
(f"({x['max_drawdown_account']:,.2%})").rjust(10, " "),
).rjust(25 + len(stake_currency))
if x["max_drawdown_account"] != 0.0
else "--".rjust(25 + len(stake_currency))
),
axis=1,
)
trials = trials.drop(columns=["max_drawdown_abs", "max_drawdown_account"])
trials["Profit"] = trials.apply(
lambda x: (
"{} {}".format(
fmt_coin(x["Total profit"], stake_currency, keep_trailing_zeros=True),
f"({x['Profit']:,.2%})".rjust(10, " "),
).rjust(25 + len(stake_currency))
if x["Total profit"] != 0.0
else "--".rjust(25 + len(stake_currency))
),
axis=1,
)
trials = trials.drop(columns=["Total profit"])
if print_colorized:
trials2 = trials.astype(str)
for i in range(len(trials)):
if trials.loc[i]["is_profit"]:
for j in range(len(trials.loc[i]) - 3):
trials2.iat[i, j] = f"{Fore.GREEN}{str(trials.iloc[i, j])}{Fore.RESET}"
if trials.loc[i]["is_best"] and highlight_best:
for j in range(len(trials.loc[i]) - 3):
trials2.iat[i, j] = (
f"{Style.BRIGHT}{str(trials.iloc[i, j])}{Style.RESET_ALL}"
)
trials = trials2
del trials2
trials = trials.drop(columns=["is_initial_point", "is_best", "is_profit", "is_random"])
if remove_header > 0:
table = tabulate.tabulate(
trials.to_dict(orient="list"), tablefmt="orgtbl", headers="keys", stralign="right"
)
table = table.split("\n", remove_header)[remove_header]
elif remove_header < 0:
table = tabulate.tabulate(
trials.to_dict(orient="list"), tablefmt="psql", headers="keys", stralign="right"
)
table = "\n".join(table.split("\n")[0:remove_header])
else:
table = tabulate.tabulate(
trials.to_dict(orient="list"), tablefmt="psql", headers="keys", stralign="right"
)
return table
@staticmethod @staticmethod
def export_csv_file(config: Config, results: list, csv_file: str) -> None: def export_csv_file(config: Config, results: list, csv_file: str) -> None:
""" """
@@ -1,12 +1,10 @@
import logging import logging
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Literal, Union
from tabulate import tabulate
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.optimize.optimize_reports.optimize_reports import generate_periodic_breakdown_stats from freqtrade.optimize.optimize_reports.optimize_reports import generate_periodic_breakdown_stats
from freqtrade.types import BacktestResultType from freqtrade.types import BacktestResultType
from freqtrade.util import decimals_per_coin, fmt_coin from freqtrade.util import decimals_per_coin, fmt_coin, print_rich_table
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -46,22 +44,23 @@ def generate_wins_draws_losses(wins, draws, losses):
return f"{wins:>4} {draws:>4} {losses:>4} {wl_ratio:>4}" return f"{wins:>4} {draws:>4} {losses:>4} {wl_ratio:>4}"
def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: str) -> str: def text_table_bt_results(
pair_results: List[Dict[str, Any]], stake_currency: str, title: str
) -> None:
""" """
Generates and returns a text table for the given backtest data and the results dataframe Generates and returns a text table for the given backtest data and the results dataframe
:param pair_results: List of Dictionaries - one entry per pair + final TOTAL row :param pair_results: List of Dictionaries - one entry per pair + final TOTAL row
:param stake_currency: stake-currency - used to correctly name headers :param stake_currency: stake-currency - used to correctly name headers
:return: pretty printed table with tabulate as string :param title: Title of the table
""" """
headers = _get_line_header("Pair", stake_currency, "Trades") headers = _get_line_header("Pair", stake_currency, "Trades")
floatfmt = _get_line_floatfmt(stake_currency)
output = [ output = [
[ [
t["key"], t["key"],
t["trades"], t["trades"],
t["profit_mean_pct"], t["profit_mean_pct"],
t["profit_total_abs"], f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}",
t["profit_total_pct"], t["profit_total_pct"],
t["duration_avg"], t["duration_avg"],
generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]), generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]),
@@ -69,26 +68,32 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
for t in pair_results for t in pair_results
] ]
# Ignore type as floatfmt does allow tuples but mypy does not know that # Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(output, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") print_rich_table(output, headers, summary=title)
def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str: def text_table_tags(
tag_type: Literal["enter_tag", "exit_tag", "mix_tag"],
tag_results: List[Dict[str, Any]],
stake_currency: str,
) -> None:
""" """
Generates and returns a text table for the given backtest data and the results dataframe Generates and returns a text table for the given backtest data and the results dataframe
:param pair_results: List of Dictionaries - one entry per pair + final TOTAL row :param pair_results: List of Dictionaries - one entry per pair + final TOTAL row
:param stake_currency: stake-currency - used to correctly name headers :param stake_currency: stake-currency - used to correctly name headers
:return: pretty printed table with tabulate as string
""" """
floatfmt = _get_line_floatfmt(stake_currency) floatfmt = _get_line_floatfmt(stake_currency)
fallback: str = "" fallback: str = ""
is_list = False is_list = False
if tag_type == "enter_tag": if tag_type == "enter_tag":
headers = _get_line_header("Enter Tag", stake_currency, "Entries") title = "Enter Tag"
headers = _get_line_header(title, stake_currency, "Entries")
elif tag_type == "exit_tag": elif tag_type == "exit_tag":
headers = _get_line_header("Exit Reason", stake_currency, "Exits") title = "Exit Reason"
headers = _get_line_header(title, stake_currency, "Exits")
fallback = "exit_reason" fallback = "exit_reason"
else: else:
# Mix tag # Mix tag
title = "Mixed Tag"
headers = _get_line_header(["Enter Tag", "Exit Reason"], stake_currency, "Trades") headers = _get_line_header(["Enter Tag", "Exit Reason"], stake_currency, "Trades")
floatfmt.insert(0, "s") floatfmt.insert(0, "s")
is_list = True is_list = True
@@ -106,7 +111,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
), ),
t["trades"], t["trades"],
t["profit_mean_pct"], t["profit_mean_pct"],
t["profit_total_abs"], f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}",
t["profit_total_pct"], t["profit_total_pct"],
t.get("duration_avg"), t.get("duration_avg"),
generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]), generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]),
@@ -114,17 +119,16 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
for t in tag_results for t in tag_results
] ]
# Ignore type as floatfmt does allow tuples but mypy does not know that # Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(output, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") print_rich_table(output, headers, summary=f"{title.upper()} STATS")
def text_table_periodic_breakdown( def text_table_periodic_breakdown(
days_breakdown_stats: List[Dict[str, Any]], stake_currency: str, period: str days_breakdown_stats: List[Dict[str, Any]], stake_currency: str, period: str
) -> str: ) -> None:
""" """
Generate small table with Backtest results by days Generate small table with Backtest results by days
:param days_breakdown_stats: Days breakdown metrics :param days_breakdown_stats: Days breakdown metrics
:param stake_currency: Stakecurrency used :param stake_currency: Stakecurrency used
:return: pretty printed table with tabulate as string
""" """
headers = [ headers = [
period.capitalize(), period.capitalize(),
@@ -143,17 +147,15 @@ def text_table_periodic_breakdown(
] ]
for d in days_breakdown_stats for d in days_breakdown_stats
] ]
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") print_rich_table(output, headers, summary=f"{period.upper()} BREAKDOWN")
def text_table_strategy(strategy_results, stake_currency: str) -> str: def text_table_strategy(strategy_results, stake_currency: str, title: str):
""" """
Generate summary table per strategy Generate summary table per strategy
:param strategy_results: Dict of <Strategyname: DataFrame> containing results for all strategies :param strategy_results: Dict of <Strategyname: DataFrame> containing results for all strategies
:param stake_currency: stake-currency - used to correctly name headers :param stake_currency: stake-currency - used to correctly name headers
:return: pretty printed table with tabulate as string
""" """
floatfmt = _get_line_floatfmt(stake_currency)
headers = _get_line_header("Strategy", stake_currency, "Trades") headers = _get_line_header("Strategy", stake_currency, "Trades")
# _get_line_header() is also used for per-pair summary. Per-pair drawdown is mostly useless # _get_line_header() is also used for per-pair summary. Per-pair drawdown is mostly useless
# therefore we slip this column in only for strategy summary here. # therefore we slip this column in only for strategy summary here.
@@ -177,8 +179,8 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
[ [
t["key"], t["key"],
t["trades"], t["trades"],
t["profit_mean_pct"], f"{t['profit_mean_pct']:.2f}",
t["profit_total_abs"], f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}",
t["profit_total_pct"], t["profit_total_pct"],
t["duration_avg"], t["duration_avg"],
generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]), generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]),
@@ -186,11 +188,10 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
] ]
for t, drawdown in zip(strategy_results, drawdown) for t, drawdown in zip(strategy_results, drawdown)
] ]
# Ignore type as floatfmt does allow tuples but mypy does not know that print_rich_table(output, headers, summary=title)
return tabulate(output, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
def text_table_add_metrics(strat_results: Dict) -> str: def text_table_add_metrics(strat_results: Dict) -> None:
if len(strat_results["trades"]) > 0: if len(strat_results["trades"]) > 0:
best_trade = max(strat_results["trades"], key=lambda x: x["profit_ratio"]) best_trade = max(strat_results["trades"], key=lambda x: x["profit_ratio"])
worst_trade = min(strat_results["trades"], key=lambda x: x["profit_ratio"]) worst_trade = min(strat_results["trades"], key=lambda x: x["profit_ratio"])
@@ -372,8 +373,8 @@ def text_table_add_metrics(strat_results: Dict) -> str:
*drawdown_metrics, *drawdown_metrics,
("Market change", f"{strat_results['market_change']:.2%}"), ("Market change", f"{strat_results['market_change']:.2%}"),
] ]
print_rich_table(metrics, ["Metric", "Value"], summary="SUMMARY METRICS", justify="left")
return tabulate(metrics, headers=["Metric", "Value"], tablefmt="orgtbl")
else: else:
start_balance = fmt_coin(strat_results["starting_balance"], strat_results["stake_currency"]) start_balance = fmt_coin(strat_results["starting_balance"], strat_results["stake_currency"])
stake_amount = ( stake_amount = (
@@ -387,7 +388,7 @@ def text_table_add_metrics(strat_results: Dict) -> str:
f"Your starting balance was {start_balance}, " f"Your starting balance was {start_balance}, "
f"and your stake was {stake_amount}." f"and your stake was {stake_amount}."
) )
return message print(message)
def _show_tag_subresults(results: Dict[str, Any], stake_currency: str): def _show_tag_subresults(results: Dict[str, Any], stake_currency: str):
@@ -395,25 +396,13 @@ def _show_tag_subresults(results: Dict[str, Any], stake_currency: str):
Print tag subresults (enter_tag, exit_reason_summary, mix_tag_stats) Print tag subresults (enter_tag, exit_reason_summary, mix_tag_stats)
""" """
if (enter_tags := results.get("results_per_enter_tag")) is not None: if (enter_tags := results.get("results_per_enter_tag")) is not None:
table = text_table_tags("enter_tag", enter_tags, stake_currency) text_table_tags("enter_tag", enter_tags, stake_currency)
if isinstance(table, str) and len(table) > 0:
print(" ENTER TAG STATS ".center(len(table.splitlines()[0]), "="))
print(table)
if (exit_reasons := results.get("exit_reason_summary")) is not None: if (exit_reasons := results.get("exit_reason_summary")) is not None:
table = text_table_tags("exit_tag", exit_reasons, stake_currency) text_table_tags("exit_tag", exit_reasons, stake_currency)
if isinstance(table, str) and len(table) > 0:
print(" EXIT REASON STATS ".center(len(table.splitlines()[0]), "="))
print(table)
if (mix_tag := results.get("mix_tag_stats")) is not None: if (mix_tag := results.get("mix_tag_stats")) is not None:
table = text_table_tags("mix_tag", mix_tag, stake_currency) text_table_tags("mix_tag", mix_tag, stake_currency)
if isinstance(table, str) and len(table) > 0:
print(" MIXED TAG STATS ".center(len(table.splitlines()[0]), "="))
print(table)
def show_backtest_result( def show_backtest_result(
@@ -424,15 +413,12 @@ def show_backtest_result(
""" """
# Print results # Print results
print(f"Result for strategy {strategy}") print(f"Result for strategy {strategy}")
table = text_table_bt_results(results["results_per_pair"], stake_currency=stake_currency) text_table_bt_results(
if isinstance(table, str): results["results_per_pair"], stake_currency=stake_currency, title="BACKTESTING REPORT"
print(" BACKTESTING REPORT ".center(len(table.splitlines()[0]), "=")) )
print(table) text_table_bt_results(
results["left_open_trades"], stake_currency=stake_currency, title="LEFT OPEN TRADES REPORT"
table = text_table_bt_results(results["left_open_trades"], stake_currency=stake_currency) )
if isinstance(table, str) and len(table) > 0:
print(" LEFT OPEN TRADES REPORT ".center(len(table.splitlines()[0]), "="))
print(table)
_show_tag_subresults(results, stake_currency) _show_tag_subresults(results, stake_currency)
@@ -443,20 +429,11 @@ def show_backtest_result(
days_breakdown_stats = generate_periodic_breakdown_stats( days_breakdown_stats = generate_periodic_breakdown_stats(
trade_list=results["trades"], period=period trade_list=results["trades"], period=period
) )
table = text_table_periodic_breakdown( text_table_periodic_breakdown(
days_breakdown_stats=days_breakdown_stats, stake_currency=stake_currency, period=period days_breakdown_stats=days_breakdown_stats, stake_currency=stake_currency, period=period
) )
if isinstance(table, str) and len(table) > 0:
print(f" {period.upper()} BREAKDOWN ".center(len(table.splitlines()[0]), "="))
print(table)
table = text_table_add_metrics(results) text_table_add_metrics(results)
if isinstance(table, str) and len(table) > 0:
print(" SUMMARY METRICS ".center(len(table.splitlines()[0]), "="))
print(table)
if isinstance(table, str) and len(table) > 0:
print("=" * len(table.splitlines()[0]))
print() print()
@@ -472,15 +449,13 @@ def show_backtest_results(config: Config, backtest_stats: BacktestResultType):
if len(backtest_stats["strategy"]) > 0: if len(backtest_stats["strategy"]) > 0:
# Print Strategy summary table # Print Strategy summary table
table = text_table_strategy(backtest_stats["strategy_comparison"], stake_currency)
print( print(
f"Backtested {results['backtest_start']} -> {results['backtest_end']} |" f"Backtested {results['backtest_start']} -> {results['backtest_end']} |"
f" Max open trades : {results['max_open_trades']}" f" Max open trades : {results['max_open_trades']}"
) )
print(" STRATEGY SUMMARY ".center(len(table.splitlines()[0]), "=")) text_table_strategy(
print(table) backtest_stats["strategy_comparison"], stake_currency, "STRATEGY SUMMARY"
print("=" * len(table.splitlines()[0])) )
print("\nFor more details, please look at the detail tables above")
def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType): def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType):
@@ -493,8 +468,7 @@ def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType):
print("]") print("]")
def generate_edge_table(results: dict) -> str: def generate_edge_table(results: dict) -> None:
floatfmt = ("s", ".10g", ".2f", ".2f", ".2f", ".2f", "d", "d", "d")
tabular_data = [] tabular_data = []
headers = [ headers = [
"Pair", "Pair",
@@ -512,17 +486,13 @@ def generate_edge_table(results: dict) -> str:
tabular_data.append( tabular_data.append(
[ [
result[0], result[0],
result[1].stoploss, f"{result[1].stoploss:.10g}",
result[1].winrate, f"{result[1].winrate:.2f}",
result[1].risk_reward_ratio, f"{result[1].risk_reward_ratio:.2f}",
result[1].required_risk_reward, f"{result[1].required_risk_reward:.2f}",
result[1].expectancy, f"{result[1].expectancy:.2f}",
result[1].nb_trades, result[1].nb_trades,
round(result[1].avg_trade_duration), round(result[1].avg_trade_duration),
] ]
) )
print_rich_table(tabular_data, headers, summary="EDGE TABLE")
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(
tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="orgtbl", stralign="right"
)
+4 -4
View File
@@ -32,12 +32,12 @@ def get_request_or_thread_id() -> Optional[str]:
""" """
Helper method to get either async context (for fastapi requests), or thread id Helper method to get either async context (for fastapi requests), or thread id
""" """
id = _request_id_ctx_var.get() request_id = _request_id_ctx_var.get()
if id is None: if request_id is None:
# when not in request context - use thread id # when not in request context - use thread id
id = str(threading.current_thread().ident) request_id = str(threading.current_thread().ident)
return id return request_id
_SQL_DOCS_URL = "http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls" _SQL_DOCS_URL = "http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls"
+1 -1
View File
@@ -2012,7 +2012,7 @@ class Trade(ModelBase, LocalTrade):
).all() ).all()
resp: List[Dict] = [] resp: List[Dict] = []
for id, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf: for _, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf:
enter_tag = enter_tag if enter_tag is not None else "Other" enter_tag = enter_tag if enter_tag is not None else "Other"
exit_reason = exit_reason if exit_reason is not None else "Other" exit_reason = exit_reason if exit_reason is not None else "Other"
+2 -2
View File
@@ -63,8 +63,8 @@ class IResolver:
# Add extra directory to the top of the search paths # Add extra directory to the top of the search paths
if extra_dirs: if extra_dirs:
for dir in extra_dirs: for directory in extra_dirs:
abs_paths.insert(0, Path(dir).resolve()) abs_paths.insert(0, Path(directory).resolve())
if cls.extra_path and (extra := config.get(cls.extra_path)): if cls.extra_path and (extra := config.get(cls.extra_path)):
abs_paths.insert(0, Path(extra).resolve()) abs_paths.insert(0, Path(extra).resolve())
+2 -2
View File
@@ -311,9 +311,9 @@ def warn_deprecated_setting(strategy: IStrategy, old: str, new: str, error=False
setattr(strategy, new, getattr(strategy, f"{old}")) setattr(strategy, new, getattr(strategy, f"{old}"))
def check_override(object, parentclass, attribute): def check_override(obj, parentclass, attribute: str):
""" """
Checks if a object overrides the parent class attribute. Checks if a object overrides the parent class attribute.
:returns: True if the object is overridden. :returns: True if the object is overridden.
""" """
return getattr(type(object), attribute) != getattr(parentclass, attribute) return getattr(type(obj), attribute) != getattr(parentclass, attribute)
+11 -11
View File
@@ -11,7 +11,7 @@ from typing import Any, Dict, Generator, List, Optional, Sequence, Tuple, Union
import psutil import psutil
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from dateutil.tz import tzlocal from dateutil.tz import tzlocal
from numpy import NAN, inf, int64, mean from numpy import inf, int64, mean, nan
from pandas import DataFrame, NaT from pandas import DataFrame, NaT
from sqlalchemy import func, select from sqlalchemy import func, select
@@ -204,9 +204,9 @@ class RPC:
trade.pair, side="exit", is_short=trade.is_short, refresh=False trade.pair, side="exit", is_short=trade.is_short, refresh=False
) )
except (ExchangeError, PricingError): except (ExchangeError, PricingError):
current_rate = NAN current_rate = nan
if len(trade.select_filled_orders(trade.entry_side)) > 0: if len(trade.select_filled_orders(trade.entry_side)) > 0:
current_profit = current_profit_abs = current_profit_fiat = NAN current_profit = current_profit_abs = current_profit_fiat = nan
if not isnan(current_rate): if not isnan(current_rate):
prof = trade.calculate_profit(current_rate) prof = trade.calculate_profit(current_rate)
current_profit = prof.profit_ratio current_profit = prof.profit_ratio
@@ -277,7 +277,7 @@ class RPC:
raise RPCException("no active trade") raise RPCException("no active trade")
else: else:
trades_list = [] trades_list = []
fiat_profit_sum = NAN fiat_profit_sum = nan
for trade in trades: for trade in trades:
# calculate profit and send message to user # calculate profit and send message to user
try: try:
@@ -285,9 +285,9 @@ class RPC:
trade.pair, side="exit", is_short=trade.is_short, refresh=False trade.pair, side="exit", is_short=trade.is_short, refresh=False
) )
except (PricingError, ExchangeError): except (PricingError, ExchangeError):
current_rate = NAN current_rate = nan
trade_profit = NAN trade_profit = nan
profit_str = f"{NAN:.2%}" profit_str = f"{nan:.2%}"
else: else:
if trade.nr_of_successful_entries > 0: if trade.nr_of_successful_entries > 0:
profit = trade.calculate_profit(current_rate) profit = trade.calculate_profit(current_rate)
@@ -533,9 +533,9 @@ class RPC:
trade.pair, side="exit", is_short=trade.is_short, refresh=False trade.pair, side="exit", is_short=trade.is_short, refresh=False
) )
except (PricingError, ExchangeError): except (PricingError, ExchangeError):
current_rate = NAN current_rate = nan
profit_ratio = NAN profit_ratio = nan
profit_abs = NAN profit_abs = nan
else: else:
_profit = trade.calculate_profit(trade.close_rate or current_rate) _profit = trade.calculate_profit(trade.close_rate or current_rate)
@@ -1321,7 +1321,7 @@ class RPC:
dataframe = dataframe.drop( dataframe = dataframe.drop(
["orderflow", "trades", "imbalances"], axis=1, errors="ignore" ["orderflow", "trades", "imbalances"], axis=1, errors="ignore"
) )
dataframe = dataframe.replace({inf: None, -inf: None, NAN: None}) dataframe = dataframe.replace({inf: None, -inf: None, nan: None})
res = { res = {
"pair": pair, "pair": pair,
+5
View File
@@ -15,6 +15,8 @@ from freqtrade.util.formatters import decimals_per_coin, fmt_coin, round_value
from freqtrade.util.ft_precise import FtPrecise from freqtrade.util.ft_precise import FtPrecise
from freqtrade.util.measure_time import MeasureTime from freqtrade.util.measure_time import MeasureTime
from freqtrade.util.periodic_cache import PeriodicCache from freqtrade.util.periodic_cache import PeriodicCache
from freqtrade.util.rich_progress import CustomProgress
from freqtrade.util.rich_tables import print_df_rich_table, print_rich_table
from freqtrade.util.template_renderer import render_template, render_template_with_fallback # noqa from freqtrade.util.template_renderer import render_template, render_template_with_fallback # noqa
@@ -36,4 +38,7 @@ __all__ = [
"round_value", "round_value",
"fmt_coin", "fmt_coin",
"MeasureTime", "MeasureTime",
"print_rich_table",
"print_df_rich_table",
"CustomProgress",
] ]
+14
View File
@@ -0,0 +1,14 @@
from typing import Union
from rich.console import ConsoleRenderable, Group, RichCast
from rich.progress import Progress
class CustomProgress(Progress):
def __init__(self, *args, cust_objs, **kwargs) -> None:
self._cust_objs = cust_objs
super().__init__(*args, **kwargs)
def get_renderable(self) -> Union[ConsoleRenderable, RichCast, str]:
renderable = Group(*self._cust_objs, *self.get_renderables())
return renderable
+77
View File
@@ -0,0 +1,77 @@
import sys
from typing import Any, Dict, List, Optional, Sequence, Union
from pandas import DataFrame
from rich.console import Console
from rich.table import Column, Table
from rich.text import Text
TextOrString = Union[str, Text]
def print_rich_table(
tabular_data: Sequence[Union[Dict[str, Any], Sequence[TextOrString]]],
headers: Sequence[str],
summary: Optional[str] = None,
*,
justify="right",
table_kwargs: Optional[Dict[str, Any]] = None,
) -> None:
table = Table(
*[c if isinstance(c, Column) else Column(c, justify=justify) for c in headers],
title=summary,
**(table_kwargs or {}),
)
for row in tabular_data:
if isinstance(row, dict):
table.add_row(
*[
row[header] if isinstance(row[header], Text) else str(row[header])
for header in headers
]
)
else:
row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in row]
table.add_row(*row_to_add)
console = Console(
width=200 if "pytest" in sys.modules else None,
)
console.print(table)
def _format_value(value: Any, *, floatfmt: str) -> str:
if isinstance(value, float):
return f"{value:{floatfmt}}"
return str(value)
def print_df_rich_table(
tabular_data: DataFrame,
headers: Sequence[str],
summary: Optional[str] = None,
*,
show_index=False,
index_name: Optional[str] = None,
table_kwargs: Optional[Dict[str, Any]] = None,
) -> None:
table = Table(title=summary, **(table_kwargs or {}))
if show_index:
index_name = str(index_name) if index_name else tabular_data.index.name
table.add_column(index_name)
for header in headers:
table.add_column(header, justify="right")
for value_list in tabular_data.itertuples(index=show_index):
row = [_format_value(x, floatfmt=".3f") for x in value_list]
table.add_row(*row)
console = Console(
width=200 if "pytest" in sys.modules else None,
)
console.print(table)
+1 -1
View File
@@ -7,7 +7,7 @@ if "dev" in __version__:
from pathlib import Path from pathlib import Path
try: try:
import subprocess import subprocess # noqa: S404
freqtrade_basedir = Path(__file__).parent freqtrade_basedir = Path(__file__).parent
+2 -2
View File
@@ -133,8 +133,8 @@ def test_FtRestClient_call_invalid(caplog):
) )
def test_FtRestClient_call_explicit_methods(method, args, kwargs): def test_FtRestClient_call_explicit_methods(method, args, kwargs):
client, mock = get_rest_client() client, mock = get_rest_client()
exec = getattr(client, method) executor = getattr(client, method)
exec(*args, **kwargs) executor(*args, **kwargs)
assert mock.call_count == 1 assert mock.call_count == 1
+4 -1
View File
@@ -134,17 +134,19 @@ extend-select = [
"W", # pycodestyle "W", # pycodestyle
"UP", # pyupgrade "UP", # pyupgrade
"I", # isort "I", # isort
"A", # flake8-builtins
"TID", # flake8-tidy-imports "TID", # flake8-tidy-imports
# "EXE", # flake8-executable # "EXE", # flake8-executable
# "C4", # flake8-comprehensions # "C4", # flake8-comprehensions
"YTT", # flake8-2020 "YTT", # flake8-2020
"S", # flake8-bandit "S", # flake8-bandit
# "DTZ", # flake8-datetimez # "DTZ", # flake8-datetimez
# "RSE", # flake8-raise # "RSE", # flake8-raise
# "TCH", # flake8-type-checking # "TCH", # flake8-type-checking
"PTH", # flake8-use-pathlib "PTH", # flake8-use-pathlib
# "RUF", # ruff # "RUF", # ruff
"ASYNC", # flake8-async "ASYNC", # flake8-async
"NPY", # numpy
] ]
extend-ignore = [ extend-ignore = [
@@ -155,6 +157,7 @@ extend-ignore = [
"S603", # `subprocess` call: check for execution of untrusted input "S603", # `subprocess` call: check for execution of untrusted input
"S607", # Starting a process with a partial executable path "S607", # Starting a process with a partial executable path
"S608", # Possible SQL injection vector through string-based query construction "S608", # Possible SQL injection vector through string-based query construction
"NPY002", # Numpy legacy random generator
] ]
[tool.ruff.lint.mccabe] [tool.ruff.lint.mccabe]
+1 -1
View File
@@ -7,7 +7,7 @@
-r docs/requirements-docs.txt -r docs/requirements-docs.txt
coveralls==4.0.1 coveralls==4.0.1
ruff==0.5.0 ruff==0.5.1
mypy==1.10.1 mypy==1.10.1
pre-commit==3.7.1 pre-commit==3.7.1
pytest==8.2.2 pytest==8.2.2
+1 -1
View File
@@ -3,7 +3,7 @@
-r requirements-plot.txt -r requirements-plot.txt
# Required for freqai # Required for freqai
scikit-learn==1.5.0 scikit-learn==1.5.1
joblib==1.4.2 joblib==1.4.2
catboost==1.2.5; 'arm' not in platform_machine catboost==1.2.5; 'arm' not in platform_machine
lightgbm==4.4.0 lightgbm==4.4.0
+1 -1
View File
@@ -4,6 +4,6 @@
# Required for hyperopt # Required for hyperopt
scipy==1.14.0; python_version >= "3.10" scipy==1.14.0; python_version >= "3.10"
scipy==1.13.1; python_version < "3.10" scipy==1.13.1; python_version < "3.10"
scikit-learn==1.5.0 scikit-learn==1.5.1
ft-scikit-optimize==0.9.2 ft-scikit-optimize==0.9.2
filelock==3.15.4 filelock==3.15.4
+3 -5
View File
@@ -4,7 +4,7 @@ bottleneck==1.4.0
numexpr==2.10.1 numexpr==2.10.1
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==4.3.54 ccxt==4.3.58
cryptography==42.0.8 cryptography==42.0.8
aiohttp==3.9.5 aiohttp==3.9.5
SQLAlchemy==2.0.31 SQLAlchemy==2.0.31
@@ -32,21 +32,19 @@ py_find_1st==1.1.6
# Load ticker files 30% faster # Load ticker files 30% faster
python-rapidjson==1.18 python-rapidjson==1.18
# Properly format api responses # Properly format api responses
orjson==3.10.5 orjson==3.10.6
# Notify systemd # Notify systemd
sdnotify==0.3.2 sdnotify==0.3.2
# API Server # API Server
fastapi==0.111.0 fastapi==0.111.0
pydantic==2.7.4 pydantic==2.8.2
uvicorn==0.30.1 uvicorn==0.30.1
pyjwt==2.8.0 pyjwt==2.8.0
aiofiles==24.1.0 aiofiles==24.1.0
psutil==6.0.0 psutil==6.0.0
# Support for colorized terminal output
colorama==0.4.6
# Building config files interactively # Building config files interactively
questionary==2.0.1 questionary==2.0.1
prompt-toolkit==3.0.36 prompt-toolkit==3.0.36
+4 -4
View File
@@ -172,10 +172,10 @@ class ClientProtocol:
return readable_timedelta(time_delta) return readable_timedelta(time_delta)
async def _handle_whitelist(self, name, type, data): async def _handle_whitelist(self, name, msgtype, data):
self.logger.info(data) self.logger.info(data)
async def _handle_analyzed_df(self, name, type, data): async def _handle_analyzed_df(self, name, msgtype, data):
key, la, df = data["key"], data["la"], data["df"] key, la, df = data["key"], data["la"], data["df"]
if not df.empty: if not df.empty:
@@ -189,8 +189,8 @@ class ClientProtocol:
else: else:
self.logger.info("Empty DataFrame") self.logger.info("Empty DataFrame")
async def _handle_default(self, name, type, data): async def _handle_default(self, name, msgtype, data):
self.logger.info("Unknown message of type {type} received...") self.logger.info("Unknown message of type {msgtype} received...")
self.logger.info(data) self.logger.info(data)
+1 -2
View File
@@ -78,7 +78,7 @@ setup(
"httpx>=0.24.1", "httpx>=0.24.1",
"urllib3", "urllib3",
"jsonschema", "jsonschema",
"numpy", "numpy<2.0",
"pandas>=2.2.0,<3.0", "pandas>=2.2.0,<3.0",
"TA-Lib", "TA-Lib",
"pandas-ta", "pandas-ta",
@@ -88,7 +88,6 @@ setup(
"py_find_1st", "py_find_1st",
"python-rapidjson", "python-rapidjson",
"orjson", "orjson",
"colorama",
"jinja2", "jinja2",
"questionary", "questionary",
"prompt-toolkit", "prompt-toolkit",
+23 -26
View File
@@ -116,7 +116,7 @@ def test_list_exchanges(capsys):
start_list_exchanges(get_args(args)) start_list_exchanges(get_args(args))
captured = capsys.readouterr() captured = capsys.readouterr()
assert re.match(r"Exchanges available for Freqtrade.*", captured.out) assert re.search(r".*Exchanges available for Freqtrade.*", captured.out)
assert re.search(r".*binance.*", captured.out) assert re.search(r".*binance.*", captured.out)
assert re.search(r".*bybit.*", captured.out) assert re.search(r".*bybit.*", captured.out)
@@ -139,7 +139,7 @@ def test_list_exchanges(capsys):
start_list_exchanges(get_args(args)) start_list_exchanges(get_args(args))
captured = capsys.readouterr() captured = capsys.readouterr()
assert re.match(r"All exchanges supported by the ccxt library.*", captured.out) assert re.search(r"All exchanges supported by the ccxt library.*", captured.out)
assert re.search(r".*binance.*", captured.out) assert re.search(r".*binance.*", captured.out)
assert re.search(r".*bingx.*", captured.out) assert re.search(r".*bingx.*", captured.out)
assert re.search(r".*bitmex.*", captured.out) assert re.search(r".*bitmex.*", captured.out)
@@ -167,7 +167,7 @@ def test_list_timeframes(mocker, capsys):
"1h": "hour", "1h": "hour",
"1d": "day", "1d": "day",
} }
patch_exchange(mocker, api_mock=api_mock, id="bybit") patch_exchange(mocker, api_mock=api_mock, exchange="bybit")
args = [ args = [
"list-timeframes", "list-timeframes",
] ]
@@ -213,7 +213,7 @@ def test_list_timeframes(mocker, capsys):
"1d": "1d", "1d": "1d",
"3d": "3d", "3d": "3d",
} }
patch_exchange(mocker, api_mock=api_mock, id="binance") patch_exchange(mocker, api_mock=api_mock, exchange="binance")
# Test with --exchange binance # Test with --exchange binance
args = [ args = [
"list-timeframes", "list-timeframes",
@@ -258,7 +258,7 @@ def test_list_timeframes(mocker, capsys):
def test_list_markets(mocker, markets_static, capsys): def test_list_markets(mocker, markets_static, capsys):
api_mock = MagicMock() api_mock = MagicMock()
patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) patch_exchange(mocker, api_mock=api_mock, exchange="binance", mock_markets=markets_static)
# Test with no --config # Test with no --config
args = [ args = [
@@ -286,16 +286,16 @@ def test_list_markets(mocker, markets_static, capsys):
"LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out "LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n" in captured.out
) )
patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) patch_exchange(mocker, api_mock=api_mock, exchange="binance", mock_markets=markets_static)
# Test with --exchange # Test with --exchange
args = ["list-markets", "--exchange", "binance"] args = ["list-markets", "--exchange", "binance"]
pargs = get_args(args) pargs = get_args(args)
pargs["config"] = None pargs["config"] = None
start_list_markets(pargs, False) start_list_markets(pargs, False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert re.match("\nExchange Binance has 12 active markets:\n", captured.out) assert re.search(r".*Exchange Binance has 12 active markets.*", captured.out)
patch_exchange(mocker, api_mock=api_mock, id="binance", mock_markets=markets_static) patch_exchange(mocker, api_mock=api_mock, exchange="binance", mock_markets=markets_static)
# Test with --all: all markets # Test with --all: all markets
args = [ args = [
"list-markets", "list-markets",
@@ -491,7 +491,7 @@ def test_list_markets(mocker, markets_static, capsys):
] ]
start_list_markets(get_args(args), False) start_list_markets(get_args(args), False)
captured = capsys.readouterr() captured = capsys.readouterr()
assert "Exchange Binance has 12 active markets:\n" in captured.out assert "Exchange Binance has 12 active markets" in captured.out
# Test tabular output, no markets found # Test tabular output, no markets found
args = [ args = [
@@ -823,7 +823,7 @@ def test_download_data_no_markets(mocker, caplog):
"freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data", "freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data",
MagicMock(return_value=["ETH/BTC", "XRP/BTC"]), MagicMock(return_value=["ETH/BTC", "XRP/BTC"]),
) )
patch_exchange(mocker, id="binance") patch_exchange(mocker, exchange="binance")
mocker.patch(f"{EXMS}.get_markets", return_value={}) mocker.patch(f"{EXMS}.get_markets", return_value={})
args = [ args = [
"download-data", "download-data",
@@ -952,7 +952,7 @@ def test_download_data_trades(mocker):
def test_download_data_data_invalid(mocker): def test_download_data_data_invalid(mocker):
patch_exchange(mocker, id="kraken") patch_exchange(mocker, exchange="kraken")
mocker.patch(f"{EXMS}.get_markets", return_value={}) mocker.patch(f"{EXMS}.get_markets", return_value={})
args = [ args = [
"download-data", "download-data",
@@ -1633,8 +1633,8 @@ def test_start_list_data(testdatadir, capsys):
start_list_data(pargs) start_list_data(pargs)
captured = capsys.readouterr() captured = capsys.readouterr()
assert "Found 16 pair / timeframe combinations." in captured.out assert "Found 16 pair / timeframe combinations." in captured.out
assert "\n| Pair | Timeframe | Type |\n" in captured.out assert re.search(r".*Pair.*Timeframe.*Type.*\n", captured.out)
assert "\n| UNITTEST/BTC | 1m, 5m, 8m, 30m | spot |\n" in captured.out assert re.search(r"\n.* UNITTEST/BTC .* 1m, 5m, 8m, 30m .* spot |\n", captured.out)
args = [ args = [
"list-data", "list-data",
@@ -1650,9 +1650,9 @@ def test_start_list_data(testdatadir, capsys):
start_list_data(pargs) start_list_data(pargs)
captured = capsys.readouterr() captured = capsys.readouterr()
assert "Found 2 pair / timeframe combinations." in captured.out assert "Found 2 pair / timeframe combinations." in captured.out
assert "\n| Pair | Timeframe | Type |\n" in captured.out assert re.search(r".*Pair.*Timeframe.*Type.*\n", captured.out)
assert "UNITTEST/BTC" not in captured.out assert "UNITTEST/BTC" not in captured.out
assert "\n| XRP/ETH | 1m, 5m | spot |\n" in captured.out assert re.search(r"\n.* XRP/ETH .* 1m, 5m .* spot |\n", captured.out)
args = [ args = [
"list-data", "list-data",
@@ -1667,9 +1667,9 @@ def test_start_list_data(testdatadir, capsys):
captured = capsys.readouterr() captured = capsys.readouterr()
assert "Found 6 pair / timeframe combinations." in captured.out assert "Found 6 pair / timeframe combinations." in captured.out
assert "\n| Pair | Timeframe | Type |\n" in captured.out assert re.search(r".*Pair.*Timeframe.*Type.*\n", captured.out)
assert "\n| XRP/USDT:USDT | 5m, 1h | futures |\n" in captured.out assert re.search(r"\n.* XRP/USDT:USDT .* 5m, 1h .* futures |\n", captured.out)
assert "\n| XRP/USDT:USDT | 1h, 8h | mark |\n" in captured.out assert re.search(r"\n.* XRP/USDT:USDT .* 1h, 8h .* mark |\n", captured.out)
args = [ args = [
"list-data", "list-data",
@@ -1684,15 +1684,12 @@ def test_start_list_data(testdatadir, capsys):
start_list_data(pargs) start_list_data(pargs)
captured = capsys.readouterr() captured = capsys.readouterr()
assert "Found 2 pair / timeframe combinations." in captured.out assert "Found 2 pair / timeframe combinations." in captured.out
assert ( assert re.search(r".*Pair.*Timeframe.*Type.*From .* To .* Candles .*\n", captured.out)
"\n| Pair | Timeframe | Type "
"| From | To | Candles |\n"
) in captured.out
assert "UNITTEST/BTC" not in captured.out assert "UNITTEST/BTC" not in captured.out
assert ( assert re.search(
"\n| XRP/ETH | 1m | spot | " r"\n.* XRP/USDT .* 1m .* spot .* 2019-10-11 00:00:00 .* 2019-10-13 11:19:00 .* 2469 |\n",
"2019-10-11 00:00:00 | 2019-10-13 11:19:00 | 2469 |\n" captured.out,
) in captured.out )
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
+10 -9
View File
@@ -137,7 +137,7 @@ def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days=
random_timestamps_in_seconds = np.random.uniform(_start_timestamp, _end_timestamp, n_rows) random_timestamps_in_seconds = np.random.uniform(_start_timestamp, _end_timestamp, n_rows)
timestamp = pd.to_datetime(random_timestamps_in_seconds, unit="s") timestamp = pd.to_datetime(random_timestamps_in_seconds, unit="s")
id = [ trade_id = [
f"a{np.random.randint(1e6, 1e7 - 1)}cd{np.random.randint(100, 999)}" for _ in range(n_rows) f"a{np.random.randint(1e6, 1e7 - 1)}cd{np.random.randint(100, 999)}" for _ in range(n_rows)
] ]
@@ -155,7 +155,7 @@ def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days=
df = pd.DataFrame( df = pd.DataFrame(
{ {
"timestamp": timestamp, "timestamp": timestamp,
"id": id, "id": trade_id,
"type": None, "type": None,
"side": side, "side": side,
"price": price, "price": price,
@@ -236,12 +236,12 @@ def patched_configuration_load_config_file(mocker, config) -> None:
def patch_exchange( def patch_exchange(
mocker, api_mock=None, id="binance", mock_markets=True, mock_supported_modes=True mocker, api_mock=None, exchange="binance", mock_markets=True, mock_supported_modes=True
) -> None: ) -> None:
mocker.patch(f"{EXMS}.validate_config", MagicMock()) mocker.patch(f"{EXMS}.validate_config", MagicMock())
mocker.patch(f"{EXMS}.validate_timeframes", MagicMock()) mocker.patch(f"{EXMS}.validate_timeframes", MagicMock())
mocker.patch(f"{EXMS}.id", PropertyMock(return_value=id)) mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange))
mocker.patch(f"{EXMS}.name", PropertyMock(return_value=id.title())) mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title()))
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2)) mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2))
# Temporary patch ... # Temporary patch ...
mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers") mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers")
@@ -254,7 +254,8 @@ def patch_exchange(
if mock_supported_modes: if mock_supported_modes:
mocker.patch( mocker.patch(
f"freqtrade.exchange.{id}.{id.capitalize()}._supported_trading_mode_margin_pairs", f"freqtrade.exchange.{exchange}.{exchange.capitalize()}"
"._supported_trading_mode_margin_pairs",
PropertyMock( PropertyMock(
return_value=[ return_value=[
(TradingMode.MARGIN, MarginMode.CROSS), (TradingMode.MARGIN, MarginMode.CROSS),
@@ -274,10 +275,10 @@ def patch_exchange(
def get_patched_exchange( def get_patched_exchange(
mocker, config, api_mock=None, id="binance", mock_markets=True, mock_supported_modes=True mocker, config, api_mock=None, exchange="binance", mock_markets=True, mock_supported_modes=True
) -> Exchange: ) -> Exchange:
patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes) patch_exchange(mocker, api_mock, exchange, mock_markets, mock_supported_modes)
config["exchange"]["name"] = id config["exchange"]["name"] = exchange
try: try:
exchange = ExchangeResolver.load_exchange(config, load_leverage_tiers=True) exchange = ExchangeResolver.load_exchange(config, load_leverage_tiers=True)
except ImportError: except ImportError:
+2 -1
View File
@@ -324,7 +324,8 @@ def hyperopt_test_result():
"profit_mean": None, "profit_mean": None,
"profit_median": None, "profit_median": None,
"profit_total": 0, "profit_total": 0,
"profit": 0.0, "max_drawdown_account": 0.0,
"max_drawdown_abs": 0.0,
"holding_avg": timedelta(), "holding_avg": timedelta(),
}, # noqa: E501 }, # noqa: E501
"results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501 "results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501
+3 -3
View File
@@ -83,7 +83,7 @@ def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input,expected", "pair,expected",
[ [
("XMR_USDT", "XMR/USDT"), ("XMR_USDT", "XMR/USDT"),
("BTC_USDT", "BTC/USDT"), ("BTC_USDT", "BTC/USDT"),
@@ -95,8 +95,8 @@ def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype):
("UNITTEST_USDT", "UNITTEST/USDT"), ("UNITTEST_USDT", "UNITTEST/USDT"),
], ],
) )
def test_rebuild_pair_from_filename(input, expected): def test_rebuild_pair_from_filename(pair, expected):
assert IDataHandler.rebuild_pair_from_filename(input) == expected assert IDataHandler.rebuild_pair_from_filename(pair) == expected
def test_datahandler_ohlcv_get_available_data(testdatadir): def test_datahandler_ohlcv_get_available_data(testdatadir):
+1 -1
View File
@@ -286,7 +286,7 @@ def test_refresh(mocker, default_conf):
refresh_mock = mocker.patch(f"{EXMS}.refresh_latest_ohlcv") refresh_mock = mocker.patch(f"{EXMS}.refresh_latest_ohlcv")
mock_refresh_trades = mocker.patch(f"{EXMS}.refresh_latest_trades") mock_refresh_trades = mocker.patch(f"{EXMS}.refresh_latest_trades")
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
timeframe = default_conf["timeframe"] timeframe = default_conf["timeframe"]
pairs = [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe)] pairs = [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe)]
+2 -2
View File
@@ -14,7 +14,7 @@ def test_download_data_main_no_markets(mocker, caplog):
"freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data", "freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data",
MagicMock(return_value=["ETH/BTC", "XRP/BTC"]), MagicMock(return_value=["ETH/BTC", "XRP/BTC"]),
) )
patch_exchange(mocker, id="binance") patch_exchange(mocker, exchange="binance")
mocker.patch(f"{EXMS}.get_markets", return_value={}) mocker.patch(f"{EXMS}.get_markets", return_value={})
config = setup_utils_configuration({"exchange": "binance"}, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration({"exchange": "binance"}, RunMode.UTIL_EXCHANGE)
config.update({"days": 20, "pairs": ["ETH/BTC", "XRP/BTC"], "timeframes": ["5m", "1h"]}) config.update({"days": 20, "pairs": ["ETH/BTC", "XRP/BTC"], "timeframes": ["5m", "1h"]})
@@ -91,7 +91,7 @@ def test_download_data_main_trades(mocker):
def test_download_data_main_data_invalid(mocker): def test_download_data_main_data_invalid(mocker):
patch_exchange(mocker, id="kraken") patch_exchange(mocker, exchange="kraken")
mocker.patch(f"{EXMS}.get_markets", return_value={}) mocker.patch(f"{EXMS}.get_markets", return_value={})
config = setup_utils_configuration({"exchange": "kraken"}, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration({"exchange": "kraken"}, RunMode.UTIL_EXCHANGE)
config.update( config.update(
+3 -3
View File
@@ -154,10 +154,10 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, use
assert "-3.5" in captured.out assert "-3.5" in captured.out
assert "50" in captured.out assert "50" in captured.out
assert "0" in captured.out assert "0" in captured.out
assert "0.01616" in captured.out assert "0.016" in captured.out
assert "34.049" in captured.out assert "34.049" in captured.out
assert "0.104411" in captured.out assert "0.104" in captured.out
assert "52.8292" in captured.out assert "52.829" in captured.out
# test group 1 # test group 1
args = get_args(base_args + ["--analysis-groups", "1"]) args = get_args(base_args + ["--analysis-groups", "1"])
+1 -1
View File
@@ -555,7 +555,7 @@ def test_refresh_backtest_ohlcv_data(
mocker.patch.object(Path, "unlink", MagicMock()) mocker.patch.object(Path, "unlink", MagicMock())
default_conf["trading_mode"] = trademode default_conf["trading_mode"] = trademode
ex = get_patched_exchange(mocker, default_conf, id="bybit") ex = get_patched_exchange(mocker, default_conf, exchange="bybit")
timerange = TimeRange.parse_timerange("20190101-20190102") timerange = TimeRange.parse_timerange("20190101-20190102")
refresh_backtest_ohlcv_data( refresh_backtest_ohlcv_data(
exchange=ex, exchange=ex,
+1 -1
View File
@@ -17,7 +17,7 @@ def test_import_kraken_trades_from_csv(testdatadir, tmp_path, caplog, default_co
default_conf_usdt["exchange"]["name"] = "kraken" default_conf_usdt["exchange"]["name"] = "kraken"
patch_exchange(mocker, id="kraken") patch_exchange(mocker, exchange="kraken")
mocker.patch( mocker.patch(
f"{EXMS}.markets", f"{EXMS}.markets",
PropertyMock( PropertyMock(
+12 -12
View File
@@ -12,7 +12,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
@pytest.mark.parametrize( @pytest.mark.parametrize(
"side,type,time_in_force,expected", "side,order_type,time_in_force,expected",
[ [
("buy", "limit", "gtc", {"timeInForce": "GTC"}), ("buy", "limit", "gtc", {"timeInForce": "GTC"}),
("buy", "limit", "IOC", {"timeInForce": "IOC"}), ("buy", "limit", "IOC", {"timeInForce": "IOC"}),
@@ -22,9 +22,9 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
("sell", "market", "PO", {}), ("sell", "market", "PO", {}),
], ],
) )
def test__get_params_binance(default_conf, mocker, side, type, time_in_force, expected): def test__get_params_binance(default_conf, mocker, side, order_type, time_in_force, expected):
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
assert exchange._get_params(side, type, 1, False, time_in_force) == expected assert exchange._get_params(side, order_type, 1, False, time_in_force) == expected
@pytest.mark.parametrize("trademode", [TradingMode.FUTURES, TradingMode.SPOT]) @pytest.mark.parametrize("trademode", [TradingMode.FUTURES, TradingMode.SPOT])
@@ -159,7 +159,7 @@ def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
"sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")] "sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")]
) )
def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side): def test_stoploss_adjust_binance(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
order = { order = {
"type": "stop_loss_limit", "type": "stop_loss_limit",
"price": 1500, "price": 1500,
@@ -378,7 +378,7 @@ def test_fill_leverage_tiers_binance(default_conf, mocker):
default_conf["dry_run"] = False default_conf["dry_run"] = False
default_conf["trading_mode"] = TradingMode.FUTURES default_conf["trading_mode"] = TradingMode.FUTURES
default_conf["margin_mode"] = MarginMode.ISOLATED default_conf["margin_mode"] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="binance")
exchange.fill_leverage_tiers() exchange.fill_leverage_tiers()
assert exchange._leverage_tiers == { assert exchange._leverage_tiers == {
@@ -497,7 +497,7 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers
api_mock = MagicMock() api_mock = MagicMock()
default_conf["trading_mode"] = TradingMode.FUTURES default_conf["trading_mode"] = TradingMode.FUTURES
default_conf["margin_mode"] = MarginMode.ISOLATED default_conf["margin_mode"] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="binance")
exchange.fill_leverage_tiers() exchange.fill_leverage_tiers()
assert len(exchange._leverage_tiers.keys()) > 100 assert len(exchange._leverage_tiers.keys()) > 100
for key, value in leverage_tiers.items(): for key, value in leverage_tiers.items():
@@ -518,10 +518,10 @@ def test_additional_exchange_init_binance(default_conf, mocker):
OperationalException, OperationalException,
match=r"Hedge Mode is not supported.*\nMulti-Asset Mode is not supported.*", match=r"Hedge Mode is not supported.*\nMulti-Asset Mode is not supported.*",
): ):
get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock) get_patched_exchange(mocker, default_conf, exchange="binance", api_mock=api_mock)
api_mock.fapiPrivateGetPositionSideDual = MagicMock(return_value={"dualSidePosition": False}) api_mock.fapiPrivateGetPositionSideDual = MagicMock(return_value={"dualSidePosition": False})
api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": False}) api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": False})
exchange = get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock) exchange = get_patched_exchange(mocker, default_conf, exchange="binance", api_mock=api_mock)
assert exchange assert exchange
ccxt_exceptionhandlers( ccxt_exceptionhandlers(
mocker, mocker,
@@ -541,7 +541,7 @@ def test__set_leverage_binance(mocker, default_conf):
default_conf["trading_mode"] = TradingMode.FUTURES default_conf["trading_mode"] = TradingMode.FUTURES
default_conf["margin_mode"] = MarginMode.ISOLATED default_conf["margin_mode"] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="binance")
exchange._set_leverage(3.2, "BTC/USDT:USDT") exchange._set_leverage(3.2, "BTC/USDT:USDT")
assert api_mock.set_leverage.call_count == 1 assert api_mock.set_leverage.call_count == 1
# Leverage is rounded to 3. # Leverage is rounded to 3.
@@ -574,7 +574,7 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog, c
] ]
] ]
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
# Monkey-patch async function # Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
@@ -620,7 +620,7 @@ def test_get_maintenance_ratio_and_amt_binance(
amt, amt,
): ):
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
exchange._leverage_tiers = leverage_tiers exchange._leverage_tiers = leverage_tiers
(result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, nominal_value) (result_ratio, result_amt) = exchange.get_maintenance_ratio_and_amt(pair, nominal_value)
assert (round(result_ratio, 8), round(result_amt, 8)) == (mm_ratio, amt) assert (round(result_ratio, 8), round(result_amt, 8)) == (mm_ratio, amt)
+1 -1
View File
@@ -39,7 +39,7 @@ def test_get_trades_for_order(default_conf, mocker):
} }
] ]
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
orders = exchange.get_trades_for_order(order_id, "LTC/BTC", since) orders = exchange.get_trades_for_order(order_id, "LTC/BTC", since)
assert len(orders) == 1 assert len(orders) == 1
+9 -9
View File
@@ -18,7 +18,7 @@ def test_additional_exchange_init_bybit(default_conf, mocker, caplog):
api_mock.set_position_mode = MagicMock(return_value={"dualSidePosition": False}) api_mock.set_position_mode = MagicMock(return_value={"dualSidePosition": False})
api_mock.is_unified_enabled = MagicMock(return_value=[False, False]) api_mock.is_unified_enabled = MagicMock(return_value=[False, False])
exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) exchange = get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock)
assert api_mock.set_position_mode.call_count == 1 assert api_mock.set_position_mode.call_count == 1
assert api_mock.is_unified_enabled.call_count == 1 assert api_mock.is_unified_enabled.call_count == 1
assert exchange.unified_account is False assert exchange.unified_account is False
@@ -28,9 +28,9 @@ def test_additional_exchange_init_bybit(default_conf, mocker, caplog):
api_mock.set_position_mode.reset_mock() api_mock.set_position_mode.reset_mock()
api_mock.is_unified_enabled = MagicMock(return_value=[False, True]) api_mock.is_unified_enabled = MagicMock(return_value=[False, True])
with pytest.raises(OperationalException, match=r"Bybit: Unified account is not supported.*"): with pytest.raises(OperationalException, match=r"Bybit: Unified account is not supported.*"):
get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock)
assert log_has("Bybit: Unified account.", caplog) assert log_has("Bybit: Unified account.", caplog)
# exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) # exchange = get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock)
# assert api_mock.set_position_mode.call_count == 1 # assert api_mock.set_position_mode.call_count == 1
# assert api_mock.is_unified_enabled.call_count == 1 # assert api_mock.is_unified_enabled.call_count == 1
# assert exchange.unified_account is True # assert exchange.unified_account is True
@@ -45,7 +45,7 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker):
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_funding_rate_history = get_mock_coro(return_value=[]) api_mock.fetch_funding_rate_history = get_mock_coro(return_value=[])
exchange = get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock) exchange = get_patched_exchange(mocker, default_conf, exchange="bybit", api_mock=api_mock)
limit = 200 limit = 200
# Test fetch_funding_rate_history (current data) # Test fetch_funding_rate_history (current data)
await exchange._fetch_funding_rate_history( await exchange._fetch_funding_rate_history(
@@ -77,14 +77,14 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker):
def test_bybit_get_funding_fees(default_conf, mocker): def test_bybit_get_funding_fees(default_conf, mocker):
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
exchange = get_patched_exchange(mocker, default_conf, id="bybit") exchange = get_patched_exchange(mocker, default_conf, exchange="bybit")
exchange._fetch_and_calculate_funding_fees = MagicMock() exchange._fetch_and_calculate_funding_fees = MagicMock()
exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now)
assert exchange._fetch_and_calculate_funding_fees.call_count == 0 assert exchange._fetch_and_calculate_funding_fees.call_count == 0
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, id="bybit") exchange = get_patched_exchange(mocker, default_conf, exchange="bybit")
exchange._fetch_and_calculate_funding_fees = MagicMock() exchange._fetch_and_calculate_funding_fees = MagicMock()
exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now)
@@ -105,13 +105,13 @@ def test_bybit_fetch_orders(default_conf, mocker, limit_order):
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
start_time = datetime.now(timezone.utc) - timedelta(days=20) start_time = datetime.now(timezone.utc) - timedelta(days=20)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="bybit") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bybit")
# Not available in dry-run # Not available in dry-run
assert exchange.fetch_orders("mocked", start_time) == [] assert exchange.fetch_orders("mocked", start_time) == []
assert api_mock.fetch_orders.call_count == 0 assert api_mock.fetch_orders.call_count == 0
default_conf["dry_run"] = False default_conf["dry_run"] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="bybit") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bybit")
res = exchange.fetch_orders("mocked", start_time) res = exchange.fetch_orders("mocked", start_time)
# Bybit will call the endpoint 3 times, as it has a limit of 7 days per call # Bybit will call the endpoint 3 times, as it has a limit of 7 days per call
assert api_mock.fetch_orders.call_count == 3 assert api_mock.fetch_orders.call_count == 3
@@ -136,7 +136,7 @@ def test_bybit_fetch_order_canceled_empty(default_conf_usdt, mocker):
) )
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, id="bybit") exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, exchange="bybit")
res = exchange.fetch_order("123", "BTC/USDT") res = exchange.fetch_order("123", "BTC/USDT")
assert res["remaining"] is None assert res["remaining"] is None
+121 -121
View File
@@ -118,19 +118,19 @@ def ccxt_exceptionhandlers(
with patch("freqtrade.exchange.common.time.sleep"): with patch("freqtrade.exchange.common.time.sleep"):
with pytest.raises(DDosProtection): with pytest.raises(DDosProtection):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
getattr(exchange, fun)(**kwargs) getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.OperationFailed("DeaDBeef")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.OperationFailed("DeaDBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
getattr(exchange, fun)(**kwargs) getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
getattr(exchange, fun)(**kwargs) getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
@@ -304,7 +304,7 @@ def test_exchange_resolver(default_conf, mocker, caplog):
def test_validate_order_time_in_force(default_conf, mocker, caplog): def test_validate_order_time_in_force(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
# explicitly test bybit, exchanges implementing other policies need separate tests # explicitly test bybit, exchanges implementing other policies need separate tests
ex = get_patched_exchange(mocker, default_conf, id="bybit") ex = get_patched_exchange(mocker, default_conf, exchange="bybit")
tif = { tif = {
"buy": "gtc", "buy": "gtc",
"sell": "gtc", "sell": "gtc",
@@ -346,7 +346,7 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
) )
def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precision, expected): def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precision, expected):
markets = PropertyMock(return_value={"ETH/BTC": {"precision": {"price": precision}}}) markets = PropertyMock(return_value={"ETH/BTC": {"precision": {"price": precision}}})
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
mocker.patch(f"{EXMS}.markets", markets) mocker.patch(f"{EXMS}.markets", markets)
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode)) mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode))
pair = "ETH/BTC" pair = "ETH/BTC"
@@ -354,7 +354,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio
def test__get_stake_amount_limit(mocker, default_conf) -> None: def test__get_stake_amount_limit(mocker, default_conf) -> None:
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
stoploss = -0.05 stoploss = -0.05
markets = {"ETH/BTC": {"symbol": "ETH/BTC"}} markets = {"ETH/BTC": {"symbol": "ETH/BTC"}}
@@ -463,7 +463,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
markets["ETH/BTC"]["contractSize"] = "0.01" markets["ETH/BTC"]["contractSize"] = "0.01"
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets)) mocker.patch(f"{EXMS}.markets", PropertyMock(return_value=markets))
# Contract size 0.01 # Contract size 0.01
@@ -484,7 +484,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None:
def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
stoploss = -0.05 stoploss = -0.05
markets = {"ETH/BTC": {"symbol": "ETH/BTC"}} markets = {"ETH/BTC": {"symbol": "ETH/BTC"}}
@@ -565,7 +565,7 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine):
api_mock.load_markets = get_mock_coro(return_value=initial_markets) api_mock.load_markets = get_mock_coro(return_value=initial_markets)
default_conf["exchange"]["markets_refresh_interval"] = 10 default_conf["exchange"]["markets_refresh_interval"] = 10
exchange = get_patched_exchange( exchange = get_patched_exchange(
mocker, default_conf, api_mock, id="binance", mock_markets=False mocker, default_conf, api_mock, exchange="binance", mock_markets=False
) )
lam_spy = mocker.spy(exchange, "_load_async_markets") lam_spy = mocker.spy(exchange, "_load_async_markets")
assert exchange._last_markets_refresh == dt_ts() assert exchange._last_markets_refresh == dt_ts()
@@ -600,7 +600,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog):
api_mock.load_markets = get_mock_coro(side_effect=ccxt.NetworkError("LoadError")) api_mock.load_markets = get_mock_coro(side_effect=ccxt.NetworkError("LoadError"))
default_conf["exchange"]["markets_refresh_interval"] = 10 default_conf["exchange"]["markets_refresh_interval"] = 10
exchange = get_patched_exchange( exchange = get_patched_exchange(
mocker, default_conf, api_mock, id="binance", mock_markets=False mocker, default_conf, api_mock, exchange="binance", mock_markets=False
) )
exchange._last_markets_refresh = 2 exchange._last_markets_refresh = 2
@@ -1153,7 +1153,7 @@ def test_exchange_has(default_conf, mocker):
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverage): def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverage):
default_conf["dry_run"] = True default_conf["dry_run"] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
order = exchange.create_dry_run_order( order = exchange.create_dry_run_order(
pair="ETH/BTC", ordertype="limit", side=side, amount=1, rate=200, leverage=leverage pair="ETH/BTC", ordertype="limit", side=side, amount=1, rate=200, leverage=leverage
@@ -1247,7 +1247,7 @@ def test_create_dry_run_order_limit_fill(
leverage, leverage,
): ):
default_conf["dry_run"] = True default_conf["dry_run"] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
exchange_has=MagicMock(return_value=True), exchange_has=MagicMock(return_value=True),
@@ -1316,7 +1316,7 @@ def test_create_dry_run_order_market_fill(
default_conf, mocker, side, rate, amount, endprice, exchange_name, order_book_l2_usd, leverage default_conf, mocker, side, rate, amount, endprice, exchange_name, order_book_l2_usd, leverage
): ):
default_conf["dry_run"] = True default_conf["dry_run"] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
exchange_has=MagicMock(return_value=True), exchange_has=MagicMock(return_value=True),
@@ -1365,7 +1365,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y)
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange._set_leverage = MagicMock() exchange._set_leverage = MagicMock()
exchange.set_margin_mode = MagicMock() exchange.set_margin_mode = MagicMock()
@@ -1393,7 +1393,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
"amount": 1, "amount": 1,
} }
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.trading_mode = TradingMode.FUTURES exchange.trading_mode = TradingMode.FUTURES
exchange._set_leverage = MagicMock() exchange._set_leverage = MagicMock()
exchange.set_margin_mode = MagicMock() exchange.set_margin_mode = MagicMock()
@@ -1412,7 +1412,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_buy_dry_run(default_conf, mocker, exchange_name): def test_buy_dry_run(default_conf, mocker, exchange_name):
default_conf["dry_run"] = True default_conf["dry_run"] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
order = exchange.create_order( order = exchange.create_order(
pair="ETH/BTC", pair="ETH/BTC",
@@ -1440,7 +1440,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
default_conf["dry_run"] = False default_conf["dry_run"] = False
mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y)
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
order = exchange.create_order( order = exchange.create_order(
pair="ETH/BTC", pair="ETH/BTC",
@@ -1484,7 +1484,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
# test exception handling # test exception handling
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", pair="ETH/BTC",
ordertype=order_type, ordertype=order_type,
@@ -1497,7 +1497,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", pair="ETH/BTC",
ordertype="limit", ordertype="limit",
@@ -1510,7 +1510,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", pair="ETH/BTC",
ordertype="market", ordertype="market",
@@ -1523,7 +1523,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect")) api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", pair="ETH/BTC",
ordertype=order_type, ordertype=order_type,
@@ -1536,7 +1536,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error")) api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", pair="ETH/BTC",
ordertype=order_type, ordertype=order_type,
@@ -1559,7 +1559,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
default_conf["dry_run"] = False default_conf["dry_run"] = False
mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y)
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
order_type = "limit" order_type = "limit"
time_in_force = "ioc" time_in_force = "ioc"
@@ -1638,7 +1638,7 @@ def test_sell_prod(default_conf, mocker, exchange_name):
mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y)
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
order = exchange.create_order( order = exchange.create_order(
pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0
@@ -1670,14 +1670,14 @@ def test_sell_prod(default_conf, mocker, exchange_name):
# test exception handling # test exception handling
with pytest.raises(InsufficientFundsError): with pytest.raises(InsufficientFundsError):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0
) )
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", ordertype="limit", side="sell", amount=1, rate=200, leverage=1.0 pair="ETH/BTC", ordertype="limit", side="sell", amount=1, rate=200, leverage=1.0
) )
@@ -1685,21 +1685,21 @@ def test_sell_prod(default_conf, mocker, exchange_name):
# Market orders don't require price, so the behaviour is slightly different # Market orders don't require price, so the behaviour is slightly different
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", ordertype="market", side="sell", amount=1, rate=200, leverage=1.0 pair="ETH/BTC", ordertype="market", side="sell", amount=1, rate=200, leverage=1.0
) )
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection")) api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0
) )
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.create_order( exchange.create_order(
pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0 pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0
) )
@@ -1716,7 +1716,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
default_conf["dry_run"] = False default_conf["dry_run"] = False
mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y)
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
order_type = "limit" order_type = "limit"
time_in_force = "ioc" time_in_force = "ioc"
@@ -1778,7 +1778,7 @@ def test_get_balances_prod(default_conf, mocker, exchange_name):
return_value={"1ST": balance_item, "2ND": balance_item, "3RD": balance_item} return_value={"1ST": balance_item, "2ND": balance_item, "3RD": balance_item}
) )
default_conf["dry_run"] = False default_conf["dry_run"] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert len(exchange.get_balances()) == 3 assert len(exchange.get_balances()) == 3
assert exchange.get_balances()["1ST"]["free"] == 10.0 assert exchange.get_balances()["1ST"]["free"] == 10.0
assert exchange.get_balances()["1ST"]["total"] == 10.0 assert exchange.get_balances()["1ST"]["total"] == 10.0
@@ -1799,12 +1799,12 @@ def test_fetch_positions(default_conf, mocker, exchange_name):
{"symbol": "XRP/USDT:USDT", "leverage": 5}, {"symbol": "XRP/USDT:USDT", "leverage": 5},
] ]
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.fetch_positions() == [] assert exchange.fetch_positions() == []
default_conf["dry_run"] = False default_conf["dry_run"] = False
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
res = exchange.fetch_positions() res = exchange.fetch_positions()
assert len(res) == 2 assert len(res) == 2
@@ -1831,13 +1831,13 @@ def test_fetch_orders(default_conf, mocker, exchange_name, limit_order):
if exchange_name == "bybit": if exchange_name == "bybit":
expected = 3 expected = 3
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
# Not available in dry-run # Not available in dry-run
assert exchange.fetch_orders("mocked", start_time) == [] assert exchange.fetch_orders("mocked", start_time) == []
assert api_mock.fetch_orders.call_count == 0 assert api_mock.fetch_orders.call_count == 0
default_conf["dry_run"] = False default_conf["dry_run"] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
res = exchange.fetch_orders("mocked", start_time) res = exchange.fetch_orders("mocked", start_time)
assert api_mock.fetch_orders.call_count == expected assert api_mock.fetch_orders.call_count == expected
assert api_mock.fetch_open_orders.call_count == 0 assert api_mock.fetch_open_orders.call_count == 0
@@ -1938,7 +1938,7 @@ def test_fetch_trading_fees(default_conf, mocker):
default_conf["margin_mode"] = MarginMode.ISOLATED default_conf["margin_mode"] = MarginMode.ISOLATED
api_mock.fetch_trading_fees = MagicMock(return_value=tick) api_mock.fetch_trading_fees = MagicMock(return_value=tick)
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert "1INCH/USDT:USDT" in exchange._trading_fees assert "1INCH/USDT:USDT" in exchange._trading_fees
assert "ETH/USDT:USDT" in exchange._trading_fees assert "ETH/USDT:USDT" in exchange._trading_fees
@@ -1953,7 +1953,7 @@ def test_fetch_trading_fees(default_conf, mocker):
) )
api_mock.fetch_trading_fees = MagicMock(return_value={}) api_mock.fetch_trading_fees = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_trading_fees() exchange.fetch_trading_fees()
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
assert exchange.fetch_trading_fees() == {} assert exchange.fetch_trading_fees() == {}
@@ -1978,7 +1978,7 @@ def test_fetch_bids_asks(default_conf, mocker):
exchange_name = "binance" exchange_name = "binance"
api_mock.fetch_bids_asks = MagicMock(return_value=tick) api_mock.fetch_bids_asks = MagicMock(return_value=tick)
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
# retrieve original ticker # retrieve original ticker
bidsasks = exchange.fetch_bids_asks() bidsasks = exchange.fetch_bids_asks()
@@ -2005,11 +2005,11 @@ def test_fetch_bids_asks(default_conf, mocker):
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_bids_asks = MagicMock(side_effect=ccxt.NotSupported("DeadBeef")) api_mock.fetch_bids_asks = MagicMock(side_effect=ccxt.NotSupported("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_bids_asks() exchange.fetch_bids_asks()
api_mock.fetch_bids_asks = MagicMock(return_value={}) api_mock.fetch_bids_asks = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_bids_asks() exchange.fetch_bids_asks()
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
assert exchange.fetch_bids_asks() == {} assert exchange.fetch_bids_asks() == {}
@@ -2035,7 +2035,7 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog):
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
api_mock.fetch_tickers = MagicMock(return_value=tick) api_mock.fetch_tickers = MagicMock(return_value=tick)
api_mock.fetch_bids_asks = MagicMock(return_value={}) api_mock.fetch_bids_asks = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
# retrieve original ticker # retrieve original ticker
tickers = exchange.get_tickers() tickers = exchange.get_tickers()
@@ -2065,19 +2065,19 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog):
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported("DeadBeef")) api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.get_tickers() exchange.get_tickers()
caplog.clear() caplog.clear()
api_mock.fetch_tickers = MagicMock(side_effect=[ccxt.BadSymbol("SomeSymbol"), []]) api_mock.fetch_tickers = MagicMock(side_effect=[ccxt.BadSymbol("SomeSymbol"), []])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
x = exchange.get_tickers() x = exchange.get_tickers()
assert x == [] assert x == []
assert log_has_re(r"Could not load tickers due to BadSymbol\..*SomeSymbol", caplog) assert log_has_re(r"Could not load tickers due to BadSymbol\..*SomeSymbol", caplog)
caplog.clear() caplog.clear()
api_mock.fetch_tickers = MagicMock(return_value={}) api_mock.fetch_tickers = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.get_tickers() exchange.get_tickers()
api_mock.fetch_tickers.reset_mock() api_mock.fetch_tickers.reset_mock()
@@ -2085,7 +2085,7 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog):
default_conf["trading_mode"] = TradingMode.FUTURES default_conf["trading_mode"] = TradingMode.FUTURES
default_conf["margin_mode"] = MarginMode.ISOLATED default_conf["margin_mode"] = MarginMode.ISOLATED
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.get_tickers() exchange.get_tickers()
assert api_mock.fetch_tickers.call_count == 1 assert api_mock.fetch_tickers.call_count == 1
@@ -2108,7 +2108,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name):
} }
api_mock.fetch_ticker = MagicMock(return_value=tick) api_mock.fetch_ticker = MagicMock(return_value=tick)
api_mock.markets = {"ETH/BTC": {"active": True}} api_mock.markets = {"ETH/BTC": {"active": True}}
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
# retrieve original ticker # retrieve original ticker
ticker = exchange.fetch_ticker(pair="ETH/BTC") ticker = exchange.fetch_ticker(pair="ETH/BTC")
@@ -2123,7 +2123,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name):
"last": 42, "last": 42,
} }
api_mock.fetch_ticker = MagicMock(return_value=tick) api_mock.fetch_ticker = MagicMock(return_value=tick)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
# if not caching the result we should get the same ticker # if not caching the result we should get the same ticker
# if not fetching a new result we should get the cached ticker # if not fetching a new result we should get the cached ticker
@@ -2144,7 +2144,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name):
) )
api_mock.fetch_ticker = MagicMock(return_value={}) api_mock.fetch_ticker = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_ticker(pair="ETH/BTC") exchange.fetch_ticker(pair="ETH/BTC")
with pytest.raises(DependencyException, match=r"Pair XRP/ETH not available"): with pytest.raises(DependencyException, match=r"Pair XRP/ETH not available"):
@@ -2153,7 +2153,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name):
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test___now_is_time_to_refresh(default_conf, mocker, exchange_name, time_machine): def test___now_is_time_to_refresh(default_conf, mocker, exchange_name, time_machine):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
pair = "BTC/USDT" pair = "BTC/USDT"
candle_type = CandleType.SPOT candle_type = CandleType.SPOT
start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc) start_dt = datetime(2023, 12, 1, 0, 10, 0, tzinfo=timezone.utc)
@@ -2182,7 +2182,7 @@ def test___now_is_time_to_refresh(default_conf, mocker, exchange_name, time_mach
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
@pytest.mark.parametrize("candle_type", ["mark", ""]) @pytest.mark.parametrize("candle_type", ["mark", ""])
def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type): def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_type):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
ohlcv = [ ohlcv = [
[ [
dt_ts(), # unix timestamp ms dt_ts(), # unix timestamp ms
@@ -2237,7 +2237,7 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_
5, # volume (in quote currency) 5, # volume (in quote currency)
] ]
] ]
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
# Monkey-patch async function # Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
@@ -2693,7 +2693,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
] ]
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
# Monkey-patch async function # Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
@@ -2725,7 +2725,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
OperationalException, match=r"Could not fetch historical candle \(OHLCV\) data.*" OperationalException, match=r"Could not fetch historical candle \(OHLCV\) data.*"
): ):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error")) api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
await exchange._async_get_candle_history( await exchange._async_get_candle_history(
pair, "5m", CandleType.SPOT, dt_ts(dt_now() - timedelta(seconds=2000)) pair, "5m", CandleType.SPOT, dt_ts(dt_now() - timedelta(seconds=2000))
) )
@@ -2737,7 +2737,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
match=r"Exchange.* does not support fetching " r"historical candle \(OHLCV\) data\..*", match=r"Exchange.* does not support fetching " r"historical candle \(OHLCV\) data\..*",
): ):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported")) api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
await exchange._async_get_candle_history( await exchange._async_get_candle_history(
pair, "5m", CandleType.SPOT, dt_ts(dt_now() - timedelta(seconds=2000)) pair, "5m", CandleType.SPOT, dt_ts(dt_now() - timedelta(seconds=2000))
) )
@@ -2758,7 +2758,7 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
'{"code":"429000","msg":"Too Many Requests"}' '{"code":"429000","msg":"Too Many Requests"}'
) )
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="kucoin")
mocker.patch(f"{EXMS}.name", PropertyMock(return_value="KuCoin")) mocker.patch(f"{EXMS}.name", PropertyMock(return_value="KuCoin"))
msg = "Kucoin 429 error, avoid triggering DDosProtection backoff delay" msg = "Kucoin 429 error, avoid triggering DDosProtection backoff delay"
@@ -2880,7 +2880,7 @@ def test_fetch_l2_order_book(default_conf, mocker, order_book_l2, exchange_name)
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_l2_order_book = order_book_l2 api_mock.fetch_l2_order_book = order_book_l2
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
order_book = exchange.fetch_l2_order_book(pair="ETH/BTC", limit=10) order_book = exchange.fetch_l2_order_book(pair="ETH/BTC", limit=10)
assert "bids" in order_book assert "bids" in order_book
assert "asks" in order_book assert "asks" in order_book
@@ -2908,15 +2908,15 @@ def test_fetch_l2_order_book_exception(default_conf, mocker, exchange_name):
api_mock = MagicMock() api_mock = MagicMock()
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported("Not supported")) api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50)
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError("DeadBeef")) api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50)
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50) exchange.fetch_l2_order_book(pair="ETH/BTC", limit=50)
@@ -3213,7 +3213,7 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
[1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687], [1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687],
[1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867], [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867],
] ]
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
sort_mock = mocker.patch("freqtrade.exchange.exchange.sorted", MagicMock(side_effect=sort_data)) sort_mock = mocker.patch("freqtrade.exchange.exchange.sorted", MagicMock(side_effect=sort_data))
# Test the OHLCV data sort # Test the OHLCV data sort
@@ -3283,7 +3283,7 @@ async def test__async_fetch_trades(
default_conf, mocker, caplog, exchange_name, fetch_trades_result default_conf, mocker, caplog, exchange_name, fetch_trades_result
): ):
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
# Monkey-patch async function # Monkey-patch async function
exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result) exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result)
@@ -3337,7 +3337,7 @@ async def test__async_fetch_trades(
api_mock = MagicMock() api_mock = MagicMock()
with pytest.raises(OperationalException, match=r"Could not fetch trade data*"): with pytest.raises(OperationalException, match=r"Could not fetch trade data*"):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error")) api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
await exchange._async_fetch_trades(pair, since=dt_ts(dt_now() - timedelta(seconds=2000))) await exchange._async_fetch_trades(pair, since=dt_ts(dt_now() - timedelta(seconds=2000)))
exchange.close() exchange.close()
@@ -3346,7 +3346,7 @@ async def test__async_fetch_trades(
match=r"Exchange.* does not support fetching " r"historical trade data\..*", match=r"Exchange.* does not support fetching " r"historical trade data\..*",
): ):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported")) api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
await exchange._async_fetch_trades(pair, since=dt_ts(dt_now() - timedelta(seconds=2000))) await exchange._async_fetch_trades(pair, since=dt_ts(dt_now() - timedelta(seconds=2000)))
exchange.close() exchange.close()
@@ -3358,7 +3358,7 @@ async def test__async_fetch_trades_contract_size(
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
# Monkey-patch async function # Monkey-patch async function
exchange._api_async.fetch_trades = get_mock_coro( exchange._api_async.fetch_trades = get_mock_coro(
[ [
@@ -3401,7 +3401,7 @@ async def test__async_fetch_trades_contract_size(
async def test__async_get_trade_history_id( async def test__async_get_trade_history_id(
default_conf, mocker, exchange_name, fetch_trades_result default_conf, mocker, exchange_name, fetch_trades_result
): ):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
if exchange._trades_pagination != "id": if exchange._trades_pagination != "id":
exchange.close() exchange.close()
pytest.skip("Exchange does not support pagination by trade id") pytest.skip("Exchange does not support pagination by trade id")
@@ -3460,7 +3460,7 @@ async def test__async_get_trade_history_id(
def test__valid_trade_pagination_id(mocker, default_conf_usdt, exchange_name, trade_id, expected): def test__valid_trade_pagination_id(mocker, default_conf_usdt, exchange_name, trade_id, expected):
if exchange_name == "kraken": if exchange_name == "kraken":
pytest.skip("Kraken has a different pagination id format, and an explicit test.") pytest.skip("Kraken has a different pagination id format, and an explicit test.")
exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name)
assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected
@@ -3479,7 +3479,7 @@ async def test__async_get_trade_history_time(
return fetch_trades_result[-1:] return fetch_trades_result[-1:]
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
if exchange._trades_pagination != "time": if exchange._trades_pagination != "time":
exchange.close() exchange.close()
pytest.skip("Exchange does not support pagination by timestamp") pytest.skip("Exchange does not support pagination by timestamp")
@@ -3521,7 +3521,7 @@ async def test__async_get_trade_history_time_empty(
return [], None return [], None
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
# Monkey-patch async function # Monkey-patch async function
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist) exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
pair = "ETH/BTC" pair = "ETH/BTC"
@@ -3542,7 +3542,7 @@ async def test__async_get_trade_history_time_empty(
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history): def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history):
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
pair = "ETH/BTC" pair = "ETH/BTC"
@@ -3573,7 +3573,7 @@ def test_get_historic_trades_notsupported(
default_conf, mocker, caplog, exchange_name, trades_history default_conf, mocker, caplog, exchange_name, trades_history
): ):
mocker.patch(f"{EXMS}.exchange_has", return_value=False) mocker.patch(f"{EXMS}.exchange_has", return_value=False)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
pair = "ETH/BTC" pair = "ETH/BTC"
@@ -3587,7 +3587,7 @@ def test_get_historic_trades_notsupported(
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_cancel_order_dry_run(default_conf, mocker, exchange_name): def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
default_conf["dry_run"] = True default_conf["dry_run"] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True) mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True)
assert exchange.cancel_order(order_id="123", pair="TKN/BTC") == {} assert exchange.cancel_order(order_id="123", pair="TKN/BTC") == {}
assert exchange.cancel_stoploss_order(order_id="123", pair="TKN/BTC") == {} assert exchange.cancel_stoploss_order(order_id="123", pair="TKN/BTC") == {}
@@ -3622,7 +3622,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
], ],
) )
def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, result): def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, result):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
assert exchange.check_order_canceled_empty(order) == result assert exchange.check_order_canceled_empty(order) == result
@@ -3642,7 +3642,7 @@ def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order,
], ],
) )
def test_is_cancel_order_result_suitable(mocker, default_conf, exchange_name, order, result): def test_is_cancel_order_result_suitable(mocker, default_conf, exchange_name, order, result):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
assert exchange.is_cancel_order_result_suitable(order) == result assert exchange.is_cancel_order_result_suitable(order) == result
@@ -3662,7 +3662,7 @@ def test_cancel_order_with_result(
api_mock = MagicMock() api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value=corder) api_mock.cancel_order = MagicMock(return_value=corder)
api_mock.fetch_order = MagicMock(return_value={}) api_mock.fetch_order = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
res = exchange.cancel_order_with_result("1234", "ETH/BTC", 1234) res = exchange.cancel_order_with_result("1234", "ETH/BTC", 1234)
assert isinstance(res, dict) assert isinstance(res, dict)
assert api_mock.cancel_order.call_count == call_corder assert api_mock.cancel_order.call_count == call_corder
@@ -3676,7 +3676,7 @@ def test_cancel_order_with_result_error(default_conf, mocker, exchange_name, cap
api_mock = MagicMock() api_mock = MagicMock()
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
res = exchange.cancel_order_with_result("1234", "ETH/BTC", 1541) res = exchange.cancel_order_with_result("1234", "ETH/BTC", 1541)
assert isinstance(res, dict) assert isinstance(res, dict)
@@ -3691,12 +3691,12 @@ def test_cancel_order(default_conf, mocker, exchange_name):
default_conf["dry_run"] = False default_conf["dry_run"] = False
api_mock = MagicMock() api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value={"id": "123"}) api_mock.cancel_order = MagicMock(return_value={"id": "123"})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.cancel_order(order_id="_", pair="TKN/BTC") == {"id": "123"} assert exchange.cancel_order(order_id="_", pair="TKN/BTC") == {"id": "123"}
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.cancel_order(order_id="_", pair="TKN/BTC") exchange.cancel_order(order_id="_", pair="TKN/BTC")
assert api_mock.cancel_order.call_count == 1 assert api_mock.cancel_order.call_count == 1
@@ -3717,12 +3717,12 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
default_conf["dry_run"] = False default_conf["dry_run"] = False
api_mock = MagicMock() api_mock = MagicMock()
api_mock.cancel_order = MagicMock(return_value={"id": "123"}) api_mock.cancel_order = MagicMock(return_value={"id": "123"})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.cancel_stoploss_order(order_id="_", pair="TKN/BTC") == {"id": "123"} assert exchange.cancel_stoploss_order(order_id="_", pair="TKN/BTC") == {"id": "123"}
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order")) api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.cancel_stoploss_order(order_id="_", pair="TKN/BTC") exchange.cancel_stoploss_order(order_id="_", pair="TKN/BTC")
assert api_mock.cancel_order.call_count == 1 assert api_mock.cancel_order.call_count == 1
@@ -3746,7 +3746,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
mock_prefix = "freqtrade.exchange.okx.Okx" mock_prefix = "freqtrade.exchange.okx.Okx"
mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value={"for": 123}) mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value={"for": 123})
mocker.patch(f"{mock_prefix}.fetch_stoploss_order", return_value={"for": 123}) mocker.patch(f"{mock_prefix}.fetch_stoploss_order", return_value={"for": 123})
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
res = {"fee": {}, "status": "canceled", "amount": 1234} res = {"fee": {}, "status": "canceled", "amount": 1234}
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=res) mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=res)
@@ -3771,7 +3771,7 @@ def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
exc = InvalidOrderException("Did not find order") exc = InvalidOrderException("Did not find order")
mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=exc) mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=exc)
mocker.patch(f"{mock_prefix}.cancel_stoploss_order", side_effect=exc) mocker.patch(f"{mock_prefix}.cancel_stoploss_order", side_effect=exc)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=123) exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=123)
@@ -3785,7 +3785,7 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
order.symbol = "TKN/BTC" order.symbol = "TKN/BTC"
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
exchange._dry_run_open_orders["X"] = order exchange._dry_run_open_orders["X"] = order
assert exchange.fetch_order("X", "TKN/BTC").myid == 123 assert exchange.fetch_order("X", "TKN/BTC").myid == 123
@@ -3795,18 +3795,18 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
default_conf["dry_run"] = False default_conf["dry_run"] = False
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_order = MagicMock(return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"}) api_mock.fetch_order = MagicMock(return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"}
assert log_has(("API fetch_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog) assert log_has(("API fetch_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog)
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_order(order_id="_", pair="TKN/BTC") exchange.fetch_order(order_id="_", pair="TKN/BTC")
assert api_mock.fetch_order.call_count == 1 assert api_mock.fetch_order.call_count == 1
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
with patch("freqtrade.exchange.common.time.sleep") as tm: with patch("freqtrade.exchange.common.time.sleep") as tm:
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
exchange.fetch_order(order_id="_", pair="TKN/BTC") exchange.fetch_order(order_id="_", pair="TKN/BTC")
@@ -3841,7 +3841,7 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog):
order.myid = 123 order.myid = 123
order.symbol = "TKN/BTC" order.symbol = "TKN/BTC"
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
mocker.patch(f"{EXMS}.exchange_has", return_value=False) mocker.patch(f"{EXMS}.exchange_has", return_value=False)
exchange._dry_run_open_orders["X"] = order exchange._dry_run_open_orders["X"] = order
# Dry run - regular fetch_order behavior # Dry run - regular fetch_order behavior
@@ -3859,7 +3859,7 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog):
api_mock.fetch_closed_order = MagicMock( api_mock.fetch_closed_order = MagicMock(
return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"} return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"}
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"}
assert log_has( assert log_has(
("API fetch_open_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog ("API fetch_open_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog
@@ -3873,7 +3873,7 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog):
api_mock.fetch_closed_order = MagicMock( api_mock.fetch_closed_order = MagicMock(
return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"} return_value={"id": "123", "amount": 2, "symbol": "TKN/BTC"}
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"} assert exchange.fetch_order("X", "TKN/BTC") == {"id": "123", "amount": 2, "symbol": "TKN/BTC"}
assert log_has( assert log_has(
("API fetch_closed_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog ("API fetch_closed_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog
@@ -3885,12 +3885,12 @@ def test_fetch_order_emulated(default_conf, mocker, exchange_name, caplog):
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.fetch_open_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.fetch_open_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
api_mock.fetch_closed_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.fetch_closed_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_order(order_id="_", pair="TKN/BTC") exchange.fetch_order(order_id="_", pair="TKN/BTC")
assert api_mock.fetch_open_order.call_count == 1 assert api_mock.fetch_open_order.call_count == 1
api_mock.fetch_open_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found")) api_mock.fetch_open_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
ccxt_exceptionhandlers( ccxt_exceptionhandlers(
mocker, mocker,
@@ -3913,7 +3913,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
order = MagicMock() order = MagicMock()
order.myid = 123 order.myid = 123
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
exchange._dry_run_open_orders["X"] = order exchange._dry_run_open_orders["X"] = order
assert exchange.fetch_stoploss_order("X", "TKN/BTC").myid == 123 assert exchange.fetch_stoploss_order("X", "TKN/BTC").myid == 123
@@ -3923,7 +3923,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
default_conf["dry_run"] = False default_conf["dry_run"] = False
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_order = MagicMock(return_value={"id": "123", "symbol": "TKN/BTC"}) api_mock.fetch_order = MagicMock(return_value={"id": "123", "symbol": "TKN/BTC"})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
res = {"id": "123", "symbol": "TKN/BTC"} res = {"id": "123", "symbol": "TKN/BTC"}
if exchange_name == "okx": if exchange_name == "okx":
res = {"id": "123", "symbol": "TKN/BTC", "type": "stoploss"} res = {"id": "123", "symbol": "TKN/BTC", "type": "stoploss"}
@@ -3934,7 +3934,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
return return
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange.fetch_stoploss_order(order_id="_", pair="TKN/BTC") exchange.fetch_stoploss_order(order_id="_", pair="TKN/BTC")
assert api_mock.fetch_order.call_count == 1 assert api_mock.fetch_order.call_count == 1
@@ -3952,7 +3952,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
def test_fetch_order_or_stoploss_order(default_conf, mocker): def test_fetch_order_or_stoploss_order(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
fetch_order_mock = MagicMock() fetch_order_mock = MagicMock()
fetch_stoploss_order_mock = MagicMock() fetch_stoploss_order_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@@ -3979,7 +3979,7 @@ def test_fetch_order_or_stoploss_order(default_conf, mocker):
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_name(default_conf, mocker, exchange_name): def test_name(default_conf, mocker, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
assert exchange.name == exchange_name.title() assert exchange.name == exchange_name.title()
assert exchange.id == exchange_name assert exchange.id == exchange_name
@@ -4030,7 +4030,7 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name, trading_mode,
] ]
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
orders = exchange.get_trades_for_order(order_id, "ETH/USDT:USDT", since) orders = exchange.get_trades_for_order(order_id, "ETH/USDT:USDT", since)
assert len(orders) == 1 assert len(orders) == 1
@@ -4069,7 +4069,7 @@ def test_get_fee(default_conf, mocker, exchange_name):
api_mock.calculate_fee = MagicMock( api_mock.calculate_fee = MagicMock(
return_value={"type": "taker", "currency": "BTC", "rate": 0.025, "cost": 0.05} return_value={"type": "taker", "currency": "BTC", "rate": 0.025, "cost": 0.05}
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange._config.pop("fee", None) exchange._config.pop("fee", None)
assert exchange.get_fee("ETH/BTC") == 0.025 assert exchange.get_fee("ETH/BTC") == 0.025
@@ -4087,7 +4087,7 @@ def test_get_fee(default_conf, mocker, exchange_name):
def test_stoploss_order_unsupported_exchange(default_conf, mocker): def test_stoploss_order_unsupported_exchange(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id="bitpanda") exchange = get_patched_exchange(mocker, default_conf, exchange="bitpanda")
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
exchange.create_stoploss( exchange.create_stoploss(
pair="ETH/BTC", amount=1, stop_price=220, order_types={}, side="sell", leverage=1.0 pair="ETH/BTC", amount=1, stop_price=220, order_types={}, side="sell", leverage=1.0
@@ -4111,7 +4111,7 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker):
], ],
) )
def test__get_stop_limit_rate(default_conf_usdt, mocker, side, ratio, expected): def test__get_stop_limit_rate(default_conf_usdt, mocker, side, ratio, expected):
exchange = get_patched_exchange(mocker, default_conf_usdt, id="binance") exchange = get_patched_exchange(mocker, default_conf_usdt, exchange="binance")
order_types = {"stoploss_on_exchange_limit_ratio": ratio} order_types = {"stoploss_on_exchange_limit_ratio": ratio}
if isinstance(expected, type) and issubclass(expected, Exception): if isinstance(expected, type) and issubclass(expected, Exception):
@@ -4469,7 +4469,7 @@ def test_get_markets_error(default_conf, mocker):
def test_ohlcv_candle_limit(default_conf, mocker, exchange_name): def test_ohlcv_candle_limit(default_conf, mocker, exchange_name):
if exchange_name == "okx": if exchange_name == "okx":
pytest.skip("Tested separately for okx") pytest.skip("Tested separately for okx")
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
timeframes = ("1m", "5m", "1h") timeframes = ("1m", "5m", "1h")
expected = exchange._ft_has["ohlcv_candle_limit"] expected = exchange._ft_has["ohlcv_candle_limit"]
for timeframe in timeframes: for timeframe in timeframes:
@@ -4538,7 +4538,7 @@ def test_market_is_tradable(
) -> None: ) -> None:
default_conf["trading_mode"] = trademode default_conf["trading_mode"] = trademode
mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode") mocker.patch(f"{EXMS}.validate_trading_mode_and_margin_mode")
ex = get_patched_exchange(mocker, default_conf, id=exchange) ex = get_patched_exchange(mocker, default_conf, exchange=exchange)
market = { market = {
"symbol": market_symbol, "symbol": market_symbol,
"base": base, "base": base,
@@ -4809,7 +4809,7 @@ def test_get_funding_fees(default_conf_usdt, mocker, exchange_name, caplog):
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
default_conf_usdt["trading_mode"] = "futures" default_conf_usdt["trading_mode"] = "futures"
default_conf_usdt["margin_mode"] = "isolated" default_conf_usdt["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name)
exchange._fetch_and_calculate_funding_fees = MagicMock(side_effect=ExchangeError) exchange._fetch_and_calculate_funding_fees = MagicMock(side_effect=ExchangeError)
assert exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) == 0.0 assert exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) == 0.0
assert exchange._fetch_and_calculate_funding_fees.call_count == 1 assert exchange._fetch_and_calculate_funding_fees.call_count == 1
@@ -4862,7 +4862,7 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
type(api_mock).has = PropertyMock(return_value={"fetchFundingHistory": True}) type(api_mock).has = PropertyMock(return_value={"fetchFundingHistory": True})
# mocker.patch(f'{EXMS}.get_funding_fees', lambda pair, since: y) # mocker.patch(f'{EXMS}.get_funding_fees', lambda pair, since: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
date_time = datetime.strptime("2021-09-01T00:00:01.000Z", "%Y-%m-%dT%H:%M:%S.%fZ") date_time = datetime.strptime("2021-09-01T00:00:01.000Z", "%Y-%m-%dT%H:%M:%S.%fZ")
unix_time = int(date_time.timestamp()) unix_time = int(date_time.timestamp())
expected_fees = -0.001 # 0.14542341 + -0.14642341 expected_fees = -0.001 # 0.14542341 + -0.14642341
@@ -4892,7 +4892,7 @@ def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
def test_get_stake_amount_considering_leverage( def test_get_stake_amount_considering_leverage(
exchange, stake_amount, leverage, min_stake_with_lev, mocker, default_conf exchange, stake_amount, leverage, min_stake_with_lev, mocker, default_conf
): ):
exchange = get_patched_exchange(mocker, default_conf, id=exchange) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange)
assert ( assert (
exchange._get_stake_amount_considering_leverage(stake_amount, leverage) exchange._get_stake_amount_considering_leverage(stake_amount, leverage)
== min_stake_with_lev == min_stake_with_lev
@@ -4959,7 +4959,7 @@ def test_validate_trading_mode_and_margin_mode(
default_conf, mocker, exchange_name, trading_mode, margin_mode, exception_thrown default_conf, mocker, exchange_name, trading_mode, margin_mode, exception_thrown
): ):
exchange = get_patched_exchange( exchange = get_patched_exchange(
mocker, default_conf, id=exchange_name, mock_supported_modes=False mocker, default_conf, exchange=exchange_name, mock_supported_modes=False
) )
if exception_thrown: if exception_thrown:
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
@@ -4986,7 +4986,7 @@ def test_validate_trading_mode_and_margin_mode(
def test__ccxt_config(default_conf, mocker, exchange_name, trading_mode, ccxt_config): def test__ccxt_config(default_conf, mocker, exchange_name, trading_mode, ccxt_config):
default_conf["trading_mode"] = trading_mode default_conf["trading_mode"] = trading_mode
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
assert exchange._ccxt_config == ccxt_config assert exchange._ccxt_config == ccxt_config
@@ -5005,7 +5005,7 @@ def test_get_max_leverage_from_margin(default_conf, mocker, pair, nominal_value,
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
api_mock = MagicMock() api_mock = MagicMock()
type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": False}) type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": False})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="gate") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="gate")
assert exchange.get_max_leverage(pair, nominal_value) == max_lev assert exchange.get_max_leverage(pair, nominal_value) == max_lev
@@ -5022,7 +5022,7 @@ def test_calculate_funding_fees(
default_conf, mocker, size, funding_rate, mark_price, funding_fee, kraken_fee, time_in_ratio default_conf, mocker, size, funding_rate, mark_price, funding_fee, kraken_fee, time_in_ratio
): ):
exchange = get_patched_exchange(mocker, default_conf) exchange = get_patched_exchange(mocker, default_conf)
kraken = get_patched_exchange(mocker, default_conf, id="kraken") kraken = get_patched_exchange(mocker, default_conf, exchange="kraken")
prior_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc) - timedelta(hours=1)) prior_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc) - timedelta(hours=1))
trade_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc)) trade_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc))
funding_rates = DataFrame( funding_rates = DataFrame(
@@ -5247,7 +5247,7 @@ def test__fetch_and_calculate_funding_fees(
type(api_mock).has = PropertyMock(return_value={"fetchOHLCV": True}) type(api_mock).has = PropertyMock(return_value={"fetchOHLCV": True})
type(api_mock).has = PropertyMock(return_value={"fetchFundingRateHistory": True}) type(api_mock).has = PropertyMock(return_value={"fetchFundingRateHistory": True})
ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) ex = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange)
mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["1h", "4h", "8h"])) mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["1h", "4h", "8h"]))
funding_fees = ex._fetch_and_calculate_funding_fees( funding_fees = ex._fetch_and_calculate_funding_fees(
pair="ADA/USDT:USDT", amount=amount, is_short=True, open_date=d1, close_date=d2 pair="ADA/USDT:USDT", amount=amount, is_short=True, open_date=d1, close_date=d2
@@ -5261,7 +5261,7 @@ def test__fetch_and_calculate_funding_fees(
# Return empty "refresh_latest" # Return empty "refresh_latest"
mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value={}) mocker.patch(f"{EXMS}.refresh_latest_ohlcv", return_value={})
ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) ex = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange)
with pytest.raises(ExchangeError, match="Could not find funding rates."): with pytest.raises(ExchangeError, match="Could not find funding rates."):
ex._fetch_and_calculate_funding_fees( ex._fetch_and_calculate_funding_fees(
pair="ADA/USDT:USDT", amount=amount, is_short=False, open_date=d1, close_date=d2 pair="ADA/USDT:USDT", amount=amount, is_short=False, open_date=d1, close_date=d2
@@ -5292,7 +5292,7 @@ def test__fetch_and_calculate_funding_fees_datetime_called(
type(api_mock).has = PropertyMock(return_value={"fetchOHLCV": True}) type(api_mock).has = PropertyMock(return_value={"fetchOHLCV": True})
type(api_mock).has = PropertyMock(return_value={"fetchFundingRateHistory": True}) type(api_mock).has = PropertyMock(return_value={"fetchFundingRateHistory": True})
mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["4h", "8h"])) mocker.patch(f"{EXMS}.timeframes", PropertyMock(return_value=["4h", "8h"]))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange)
d1 = datetime.strptime("2021-08-31 23:00:01 +0000", "%Y-%m-%d %H:%M:%S %z") d1 = datetime.strptime("2021-08-31 23:00:01 +0000", "%Y-%m-%d %H:%M:%S %z")
time_machine.move_to("2021-09-01 08:00:00 +00:00") time_machine.move_to("2021-09-01 08:00:00 +00:00")
@@ -5609,7 +5609,7 @@ def test_liquidation_price_is_none(
): ):
default_conf["trading_mode"] = trading_mode default_conf["trading_mode"] = trading_mode
default_conf["margin_mode"] = margin_mode default_conf["margin_mode"] = margin_mode
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
assert ( assert (
exchange.get_liquidation_price( exchange.get_liquidation_price(
pair="DOGE/USDT", pair="DOGE/USDT",
@@ -5708,7 +5708,7 @@ def test_liquidation_price_binance(
default_conf["trading_mode"] = trading_mode default_conf["trading_mode"] = trading_mode
default_conf["margin_mode"] = margin_mode default_conf["margin_mode"] = margin_mode
default_conf["liquidation_buffer"] = 0.0 default_conf["liquidation_buffer"] = 0.0
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt)) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(mm_ratio, maintenance_amt))
assert ( assert (
pytest.approx( pytest.approx(
@@ -5858,7 +5858,7 @@ def test_load_leverage_tiers(mocker, default_conf, exchange_name):
) )
# SPOT # SPOT
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.load_leverage_tiers() == {} assert exchange.load_leverage_tiers() == {}
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
@@ -5867,12 +5867,12 @@ def test_load_leverage_tiers(mocker, default_conf, exchange_name):
if exchange_name != "binance": if exchange_name != "binance":
# FUTURES has.fetchLeverageTiers == False # FUTURES has.fetchLeverageTiers == False
type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": False}) type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": False})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.load_leverage_tiers() == {} assert exchange.load_leverage_tiers() == {}
# FUTURES regular # FUTURES regular
type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": True}) type(api_mock).has = PropertyMock(return_value={"fetchLeverageTiers": True})
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
assert exchange.load_leverage_tiers() == { assert exchange.load_leverage_tiers() == {
"ADA/USDT:USDT": [ "ADA/USDT:USDT": [
{ {
@@ -6024,13 +6024,13 @@ def test_get_maintenance_ratio_and_amt(
def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers): def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
# Test Spot # Test Spot
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
assert exchange.get_max_leverage("BNB/USDT", 100.0) == 1.0 assert exchange.get_max_leverage("BNB/USDT", 100.0) == 1.0
# Test Futures # Test Futures
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, id="binance") exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
exchange._leverage_tiers = leverage_tiers exchange._leverage_tiers = leverage_tiers
@@ -6054,7 +6054,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
def test__get_params(mocker, default_conf, exchange_name): def test__get_params(mocker, default_conf, exchange_name):
api_mock = MagicMock() api_mock = MagicMock()
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange._params = {"test": True} exchange._params = {"test": True}
params1 = {"test": True} params1 = {"test": True}
@@ -6109,7 +6109,7 @@ def test__get_params(mocker, default_conf, exchange_name):
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange=exchange_name)
exchange._params = {"test": True} exchange._params = {"test": True}
assert ( assert (
@@ -6326,7 +6326,7 @@ def test_get_liquidation_price(
default_conf_usdt["exchange"]["name"] = exchange_name default_conf_usdt["exchange"]["name"] = exchange_name
default_conf_usdt["margin_mode"] = margin_mode default_conf_usdt["margin_mode"] = margin_mode
mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes") mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes")
exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name)
exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01))
exchange.name = exchange_name exchange.name = exchange_name
+2 -2
View File
@@ -56,11 +56,11 @@ async def test_exchangews_ohlcv(mocker):
assert exchange_ws._klines_scheduled == set() assert exchange_ws._klines_scheduled == set()
exchange_ws.schedule_ohlcv("ETH/BTC", "1m", CandleType.SPOT) exchange_ws.schedule_ohlcv("ETH/BTC", "1m", CandleType.SPOT)
asyncio.sleep(0.5) await asyncio.sleep(0.5)
assert exchange_ws._klines_watching == {("ETH/BTC", "1m", CandleType.SPOT)} assert exchange_ws._klines_watching == {("ETH/BTC", "1m", CandleType.SPOT)}
assert exchange_ws._klines_scheduled == {("ETH/BTC", "1m", CandleType.SPOT)} assert exchange_ws._klines_scheduled == {("ETH/BTC", "1m", CandleType.SPOT)}
asyncio.sleep(0.1) await asyncio.sleep(0.1)
assert ccxt_object.watch_ohlcv.call_count == 1 assert ccxt_object.watch_ohlcv.call_count == 1
except Exception as e: except Exception as e:
print(e) print(e)
+5 -5
View File
@@ -9,7 +9,7 @@ from tests.conftest import EXMS, get_patched_exchange
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_fetch_stoploss_order_gate(default_conf, mocker): def test_fetch_stoploss_order_gate(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id="gate") exchange = get_patched_exchange(mocker, default_conf, exchange="gate")
fetch_order_mock = MagicMock() fetch_order_mock = MagicMock()
exchange.fetch_order = fetch_order_mock exchange.fetch_order = fetch_order_mock
@@ -23,7 +23,7 @@ def test_fetch_stoploss_order_gate(default_conf, mocker):
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, id="gate") exchange = get_patched_exchange(mocker, default_conf, exchange="gate")
exchange.fetch_order = MagicMock( exchange.fetch_order = MagicMock(
return_value={ return_value={
@@ -41,7 +41,7 @@ def test_fetch_stoploss_order_gate(default_conf, mocker):
def test_cancel_stoploss_order_gate(default_conf, mocker): def test_cancel_stoploss_order_gate(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id="gate") exchange = get_patched_exchange(mocker, default_conf, exchange="gate")
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
exchange.cancel_order = cancel_order_mock exchange.cancel_order = cancel_order_mock
@@ -57,7 +57,7 @@ def test_cancel_stoploss_order_gate(default_conf, mocker):
"sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")] "sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")]
) )
def test_stoploss_adjust_gate(mocker, default_conf, sl1, sl2, sl3, side): def test_stoploss_adjust_gate(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id="gate") exchange = get_patched_exchange(mocker, default_conf, exchange="gate")
order = { order = {
"price": 1500, "price": 1500,
"stopPrice": 1500, "stopPrice": 1500,
@@ -111,7 +111,7 @@ def test_fetch_my_trades_gate(mocker, default_conf, takerormaker, rate, cost):
} }
] ]
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, id="gate") exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock, exchange="gate")
exchange._trading_fees = tick exchange._trading_fees = tick
trades = exchange.get_trades_for_order("22255", "ETH/USDT:USDT", datetime.now(timezone.utc)) trades = exchange.get_trades_for_order("22255", "ETH/USDT:USDT", datetime.now(timezone.utc))
trade = trades[0] trade = trades[0]
+1 -1
View File
@@ -128,7 +128,7 @@ def test_create_stoploss_order_dry_run_htx(default_conf, mocker):
def test_stoploss_adjust_htx(mocker, default_conf): def test_stoploss_adjust_htx(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id="htx") exchange = get_patched_exchange(mocker, default_conf, exchange="htx")
order = { order = {
"type": "stop", "type": "stop",
"price": 1500, "price": 1500,
+4 -4
View File
@@ -32,7 +32,7 @@ def test_kraken_trading_agreement(default_conf, mocker, order_type, time_in_forc
mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y)
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y, **kwargs: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="kraken")
order = exchange.create_order( order = exchange.create_order(
pair="ETH/BTC", pair="ETH/BTC",
@@ -121,7 +121,7 @@ def test_get_balances_prod(default_conf, mocker):
] ]
api_mock.fetch_open_orders = MagicMock(return_value=kraken_open_orders) api_mock.fetch_open_orders = MagicMock(return_value=kraken_open_orders)
default_conf["dry_run"] = False default_conf["dry_run"] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="kraken")
balances = exchange.get_balances() balances = exchange.get_balances()
assert len(balances) == 6 assert len(balances) == 6
@@ -256,7 +256,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
"sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")] "sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")]
) )
def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side): def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id="kraken") exchange = get_patched_exchange(mocker, default_conf, exchange="kraken")
order = { order = {
"type": "market", "type": "market",
"stopLossPrice": 1500, "stopLossPrice": 1500,
@@ -278,5 +278,5 @@ def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
], ],
) )
def test__valid_trade_pagination_id_kraken(mocker, default_conf_usdt, trade_id, expected): def test__valid_trade_pagination_id_kraken(mocker, default_conf_usdt, trade_id, expected):
exchange = get_patched_exchange(mocker, default_conf_usdt, id="kraken") exchange = get_patched_exchange(mocker, default_conf_usdt, exchange="kraken")
assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected assert exchange._valid_trade_pagination_id("XRP/USDT", trade_id) == expected
+2 -2
View File
@@ -134,7 +134,7 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
def test_stoploss_adjust_kucoin(mocker, default_conf): def test_stoploss_adjust_kucoin(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id="kucoin") exchange = get_patched_exchange(mocker, default_conf, exchange="kucoin")
order = { order = {
"type": "limit", "type": "limit",
"price": 1500, "price": 1500,
@@ -161,7 +161,7 @@ def test_kucoin_create_order(default_conf, mocker, side, ordertype, rate):
default_conf["dry_run"] = False default_conf["dry_run"] = False
mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.amount_to_precision", lambda s, x, y: y)
mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y) mocker.patch(f"{EXMS}.price_to_precision", lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="kucoin")
exchange._set_leverage = MagicMock() exchange._set_leverage = MagicMock()
exchange.set_margin_mode = MagicMock() exchange.set_margin_mode = MagicMock()
+15 -15
View File
@@ -12,7 +12,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
def test_okx_ohlcv_candle_limit(default_conf, mocker): def test_okx_ohlcv_candle_limit(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
timeframes = ("1m", "5m", "1h") timeframes = ("1m", "5m", "1h")
start_time = int(datetime(2021, 1, 1, tzinfo=timezone.utc).timestamp() * 1000) start_time = int(datetime(2021, 1, 1, tzinfo=timezone.utc).timestamp() * 1000)
@@ -188,7 +188,7 @@ def test_get_maintenance_ratio_and_amt_okx(
} }
), ),
) )
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx")
assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 2000) == (0.01, None) assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 2000) == (0.01, None)
assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 2001) == (0.015, None) assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 2001) == (0.015, None)
assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 4001) == (0.02, None) assert exchange.get_maintenance_ratio_and_amt("ETH/USDT:USDT", 4001) == (0.02, None)
@@ -199,12 +199,12 @@ def test_get_maintenance_ratio_and_amt_okx(
def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers): def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers):
exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
assert exchange.get_max_pair_stake_amount("BNB/BUSD", 1.0) == float("inf") assert exchange.get_max_pair_stake_amount("BNB/BUSD", 1.0) == float("inf")
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
exchange._leverage_tiers = leverage_tiers exchange._leverage_tiers = leverage_tiers
assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 1.0) == 30000000 assert exchange.get_max_pair_stake_amount("XRP/USDT:USDT", 1.0) == 30000000
@@ -229,7 +229,7 @@ def test_get_max_pair_stake_amount_okx(default_conf, mocker, leverage_tiers):
], ],
) )
def test__get_posSide(default_conf, mocker, mode, side, reduceonly, result): def test__get_posSide(default_conf, mocker, mode, side, reduceonly, result):
exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
exchange.net_only = mode == "net" exchange.net_only = mode == "net"
assert exchange._get_posSide(side, reduceonly) == result assert exchange._get_posSide(side, reduceonly) == result
@@ -257,7 +257,7 @@ def test_additional_exchange_init_okx(default_conf, mocker):
] ]
) )
default_conf["dry_run"] = False default_conf["dry_run"] = False
exchange = get_patched_exchange(mocker, default_conf, id="okx", api_mock=api_mock) exchange = get_patched_exchange(mocker, default_conf, exchange="okx", api_mock=api_mock)
assert api_mock.fetch_accounts.call_count == 0 assert api_mock.fetch_accounts.call_count == 0
exchange.trading_mode = TradingMode.FUTURES exchange.trading_mode = TradingMode.FUTURES
# Default to netOnly # Default to netOnly
@@ -438,7 +438,7 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets, tmp_path, caplog
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
default_conf["stake_currency"] = "USDT" default_conf["stake_currency"] = "USDT"
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx")
exchange.trading_mode = TradingMode.FUTURES exchange.trading_mode = TradingMode.FUTURES
exchange.margin_mode = MarginMode.ISOLATED exchange.margin_mode = MarginMode.ISOLATED
exchange.markets = markets exchange.markets = markets
@@ -520,7 +520,7 @@ def test__set_leverage_okx(mocker, default_conf):
default_conf["trading_mode"] = TradingMode.FUTURES default_conf["trading_mode"] = TradingMode.FUTURES
default_conf["margin_mode"] = MarginMode.ISOLATED default_conf["margin_mode"] = MarginMode.ISOLATED
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx")
exchange._lev_prep("BTC/USDT:USDT", 3.2, "buy") exchange._lev_prep("BTC/USDT:USDT", 3.2, "buy")
assert api_mock.set_leverage.call_count == 1 assert api_mock.set_leverage.call_count == 1
# Leverage is rounded to 3. # Leverage is rounded to 3.
@@ -554,7 +554,7 @@ def test_fetch_stoploss_order_okx(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_order = MagicMock() api_mock.fetch_order = MagicMock()
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx")
exchange.fetch_stoploss_order("1234", "ETH/BTC") exchange.fetch_stoploss_order("1234", "ETH/BTC")
assert api_mock.fetch_order.call_count == 1 assert api_mock.fetch_order.call_count == 1
@@ -594,7 +594,7 @@ def test_fetch_stoploss_order_okx(default_conf, mocker):
assert resp["type"] == "stoploss" assert resp["type"] == "stoploss"
default_conf["dry_run"] = True default_conf["dry_run"] = True
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx")
dro_mock = mocker.patch(f"{EXMS}.fetch_dry_run_order", MagicMock(return_value={"id": "123455"})) dro_mock = mocker.patch(f"{EXMS}.fetch_dry_run_order", MagicMock(return_value={"id": "123455"}))
api_mock.fetch_order.reset_mock() api_mock.fetch_order.reset_mock()
@@ -614,7 +614,7 @@ def test_fetch_stoploss_order_okx(default_conf, mocker):
"sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")] "sl1,sl2,sl3,side", [(1501, 1499, 1501, "sell"), (1499, 1501, 1499, "buy")]
) )
def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side): def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side):
exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
order = { order = {
"type": "stoploss", "type": "stoploss",
"price": 1500, "price": 1500,
@@ -625,7 +625,7 @@ def test_stoploss_adjust_okx(mocker, default_conf, sl1, sl2, sl3, side):
def test_stoploss_cancel_okx(mocker, default_conf): def test_stoploss_cancel_okx(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
exchange.cancel_order = MagicMock() exchange.cancel_order = MagicMock()
@@ -639,7 +639,7 @@ def test_stoploss_cancel_okx(mocker, default_conf):
def test__get_stop_params_okx(mocker, default_conf): def test__get_stop_params_okx(mocker, default_conf):
default_conf["trading_mode"] = "futures" default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated" default_conf["margin_mode"] = "isolated"
exchange = get_patched_exchange(mocker, default_conf, id="okx") exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
params = exchange._get_stop_params("ETH/USDT:USDT", 1500, "sell") params = exchange._get_stop_params("ETH/USDT:USDT", 1500, "sell")
assert params["tdMode"] == "isolated" assert params["tdMode"] == "isolated"
@@ -660,13 +660,13 @@ def test_fetch_orders_okx(default_conf, mocker, limit_order):
mocker.patch(f"{EXMS}.exchange_has", return_value=True) mocker.patch(f"{EXMS}.exchange_has", return_value=True)
start_time = datetime.now(timezone.utc) - timedelta(days=20) start_time = datetime.now(timezone.utc) - timedelta(days=20)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx")
# Not available in dry-run # Not available in dry-run
assert exchange.fetch_orders("mocked", start_time) == [] assert exchange.fetch_orders("mocked", start_time) == []
assert api_mock.fetch_orders.call_count == 0 assert api_mock.fetch_orders.call_count == 0
default_conf["dry_run"] = False default_conf["dry_run"] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="okx") exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="okx")
def has_resp(_, endpoint): def has_resp(_, endpoint):
if endpoint == "fetchOrders": if endpoint == "fetchOrders":
+2 -2
View File
@@ -21,8 +21,8 @@ from tests.exchange_online.conftest import EXCHANGE_WS_FIXTURE_TYPE
@pytest.mark.longrun @pytest.mark.longrun
@pytest.mark.timeout(3 * 60) @pytest.mark.timeout(3 * 60)
class TestCCXTExchangeWs: class TestCCXTExchangeWs:
def test_ccxt_ohlcv(self, exchange_ws: EXCHANGE_WS_FIXTURE_TYPE, caplog, mocker): def test_ccxt_watch_ohlcv(self, exchange_ws: EXCHANGE_WS_FIXTURE_TYPE, caplog, mocker):
exch, exchangename, pair = exchange_ws exch, _exchangename, pair = exchange_ws
assert exch._ws_async is not None assert exch._ws_async is not None
timeframe = "1m" timeframe = "1m"
+4 -1
View File
@@ -915,7 +915,7 @@ def test_execute_entry(
default_conf_usdt["margin_mode"] = margin_mode default_conf_usdt["margin_mode"] = margin_mode
mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes") mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes")
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker, id=exchange_name) patch_exchange(mocker, exchange=exchange_name)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
freqtrade.strategy.leverage = MagicMock(return_value=leverage) freqtrade.strategy.leverage = MagicMock(return_value=leverage)
@@ -3810,6 +3810,9 @@ def test_get_real_amount_quote_dust(
def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mocker, fee): def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mocker, fee):
mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[]) mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[])
# Invalid nested trade object
buy_order_fee["trades"] = [{"amount": None, "cost": 22}]
amount = buy_order_fee["amount"] amount = buy_order_fee["amount"]
trade = Trade( trade = Trade(
pair="LTC/ETH", pair="LTC/ETH",
+6 -6
View File
@@ -4,7 +4,7 @@ from freqtrade.enums import CandleType
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input,expected", "candle_type,expected",
[ [
("", CandleType.SPOT), ("", CandleType.SPOT),
("spot", CandleType.SPOT), ("spot", CandleType.SPOT),
@@ -17,17 +17,17 @@ from freqtrade.enums import CandleType
("premiumIndex", CandleType.PREMIUMINDEX), ("premiumIndex", CandleType.PREMIUMINDEX),
], ],
) )
def test_CandleType_from_string(input, expected): def test_CandleType_from_string(candle_type, expected):
assert CandleType.from_string(input) == expected assert CandleType.from_string(candle_type) == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input,expected", "candle_type,expected",
[ [
("futures", CandleType.FUTURES), ("futures", CandleType.FUTURES),
("spot", CandleType.SPOT), ("spot", CandleType.SPOT),
("margin", CandleType.SPOT), ("margin", CandleType.SPOT),
], ],
) )
def test_CandleType_get_default(input, expected): def test_CandleType_get_default(candle_type, expected):
assert CandleType.get_default(input) == expected assert CandleType.get_default(candle_type) == expected
+2 -1
View File
@@ -291,9 +291,10 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
"is_best": True, "is_best": True,
} }
) )
hyperopt._hyper_out.print()
out, _err = capsys.readouterr() out, _err = capsys.readouterr()
assert all( assert all(
x in out for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "00:20:00"] x in out for x in ["Best", "2/2", "1", "0.10%", "0.00100000 BTC (1.00%)", "0:20:00"]
) )
+5 -5
View File
@@ -147,7 +147,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
instance = LookaheadAnalysis(lookahead_conf, strategy_obj) instance = LookaheadAnalysis(lookahead_conf, strategy_obj)
instance.current_analysis = analysis instance.current_analysis = analysis
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
lookahead_conf, [instance] lookahead_conf, [instance]
) )
@@ -163,14 +163,14 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
analysis.false_exit_signals = 10 analysis.false_exit_signals = 10
instance = LookaheadAnalysis(lookahead_conf, strategy_obj) instance = LookaheadAnalysis(lookahead_conf, strategy_obj)
instance.current_analysis = analysis instance.current_analysis = analysis
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
lookahead_conf, [instance] lookahead_conf, [instance]
) )
assert data[0][2].__contains__("error") assert data[0][2].__contains__("error")
# edit it into not showing an error # edit it into not showing an error
instance.failed_bias_check = False instance.failed_bias_check = False
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
lookahead_conf, [instance] lookahead_conf, [instance]
) )
assert data[0][0] == "strategy_test_v3_with_lookahead_bias.py" assert data[0][0] == "strategy_test_v3_with_lookahead_bias.py"
@@ -183,7 +183,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
analysis.false_indicators.append("falseIndicator1") analysis.false_indicators.append("falseIndicator1")
analysis.false_indicators.append("falseIndicator2") analysis.false_indicators.append("falseIndicator2")
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
lookahead_conf, [instance] lookahead_conf, [instance]
) )
@@ -193,7 +193,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
assert len(data) == 1 assert len(data) == 1
# check amount of multiple rows # check amount of multiple rows
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances( data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
lookahead_conf, [instance, instance, instance] lookahead_conf, [instance, instance, instance]
) )
assert len(data) == 3 assert len(data) == 3
+60 -50
View File
@@ -59,7 +59,7 @@ def _backup_file(file: Path, copy_file: bool = False) -> None:
copyfile(file_swp, file) copyfile(file_swp, file)
def test_text_table_bt_results(): def test_text_table_bt_results(capsys):
results = pd.DataFrame( results = pd.DataFrame(
{ {
"pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"], "pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"],
@@ -69,21 +69,23 @@ def test_text_table_bt_results():
} }
) )
result_str = (
"| Pair | Trades | Avg Profit % | Tot Profit BTC | "
"Tot Profit % | Avg Duration | Win Draw Loss Win% |\n"
"|---------+----------+----------------+------------------+"
"----------------+----------------+-------------------------|\n"
"| ETH/BTC | 3 | 8.33 | 0.50000000 | "
"12.50 | 0:20:00 | 2 0 1 66.7 |\n"
"| TOTAL | 3 | 8.33 | 0.50000000 | "
"12.50 | 0:20:00 | 2 0 1 66.7 |"
)
pair_results = generate_pair_metrics( pair_results = generate_pair_metrics(
["ETH/BTC"], stake_currency="BTC", starting_balance=4, results=results ["ETH/BTC"], stake_currency="BTC", starting_balance=4, results=results
) )
assert text_table_bt_results(pair_results, stake_currency="BTC") == result_str text_table_bt_results(pair_results, stake_currency="BTC", title="title")
text = capsys.readouterr().out
re.search(
r".* Pair .* Trades .* Avg Profit % .* Tot Profit BTC .* Tot Profit % .* "
r"Avg Duration .* Win Draw Loss Win% .*",
text,
)
re.search(
r".* ETH/BTC .* 3 .* 8.33 .* 0.50000000 .* 12.50 .* 0:20:00 .* 2 0 1 66.7 .*",
text,
)
re.search(
r".* TOTAL .* 3 .* 8.33 .* 0.50000000 .* 12.50 .* 0:20:00 .* 2 0 1 66.7 .*", text
)
def test_generate_backtest_stats(default_conf, testdatadir, tmp_path): def test_generate_backtest_stats(default_conf, testdatadir, tmp_path):
@@ -434,7 +436,7 @@ def test_calc_streak(testdatadir):
assert calc_streak(bt_data) == (7, 18) assert calc_streak(bt_data) == (7, 18)
def test_text_table_exit_reason(): def test_text_table_exit_reason(capsys):
results = pd.DataFrame( results = pd.DataFrame(
{ {
"pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"], "pair": ["ETH/BTC", "ETH/BTC", "ETH/BTC"],
@@ -448,23 +450,28 @@ def test_text_table_exit_reason():
} }
) )
result_str = (
"| Exit Reason | Exits | Avg Profit % | Tot Profit BTC | Tot Profit % |"
" Avg Duration | Win Draw Loss Win% |\n"
"|---------------+---------+----------------+------------------+----------------+"
"----------------+-------------------------|\n"
"| roi | 2 | 15.00 | 0.60000000 | 2.73 |"
" 0:20:00 | 2 0 0 100 |\n"
"| stop_loss | 1 | -10.00 | -0.20000000 | -0.91 |"
" 0:10:00 | 0 0 1 0 |\n"
"| TOTAL | 3 | 6.67 | 0.40000000 | 1.82 |"
" 0:17:00 | 2 0 1 66.7 |"
)
exit_reason_stats = generate_tag_metrics( exit_reason_stats = generate_tag_metrics(
"exit_reason", starting_balance=22, results=results, skip_nan=False "exit_reason", starting_balance=22, results=results, skip_nan=False
) )
assert text_table_tags("exit_tag", exit_reason_stats, "BTC") == result_str text_table_tags("exit_tag", exit_reason_stats, "BTC")
text = capsys.readouterr().out
assert re.search(
r".* Exit Reason .* Exits .* Avg Profit % .* Tot Profit BTC .* Tot Profit % .* "
r"Avg Duration .* Win Draw Loss Win% .*",
text,
)
assert re.search(
r".* roi .* 2 .* 15.0 .* 0.60000000 .* 2.73 .* 0:20:00 .* 2 0 0 100 .*",
text,
)
assert re.search(
r".* stop_loss .* 1 .* -10.0 .* -0.20000000 .* -0.91 .* 0:10:00 .* 0 0 1 0 .*",
text,
)
assert re.search(
r".* TOTAL .* 3 .* 6.67 .* 0.40000000 .* 1.82 .* 0:17:00 .* 2 0 1 66.7 .*", text
)
def test_generate_sell_reason_stats(): def test_generate_sell_reason_stats():
@@ -502,39 +509,42 @@ def test_generate_sell_reason_stats():
assert stop_result["profit_mean_pct"] == round(stop_result["profit_mean"] * 100, 2) assert stop_result["profit_mean_pct"] == round(stop_result["profit_mean"] * 100, 2)
def test_text_table_strategy(testdatadir): def test_text_table_strategy(testdatadir, capsys):
filename = testdatadir / "backtest_results/backtest-result_multistrat.json" filename = testdatadir / "backtest_results/backtest-result_multistrat.json"
bt_res_data = load_backtest_stats(filename) bt_res_data = load_backtest_stats(filename)
bt_res_data_comparison = bt_res_data.pop("strategy_comparison") bt_res_data_comparison = bt_res_data.pop("strategy_comparison")
result_str = (
"| Strategy | Trades | Avg Profit % | Tot Profit BTC |"
" Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n"
"|----------------+----------+----------------+------------------+"
"----------------+----------------+-------------------------+-----------------------|\n"
"| StrategyTestV2 | 179 | 0.08 | 0.02608550 |"
" 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n"
"| TestStrategy | 179 | 0.08 | 0.02608550 |"
" 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |"
)
strategy_results = generate_strategy_comparison(bt_stats=bt_res_data["strategy"]) strategy_results = generate_strategy_comparison(bt_stats=bt_res_data["strategy"])
assert strategy_results == bt_res_data_comparison assert strategy_results == bt_res_data_comparison
assert text_table_strategy(strategy_results, "BTC") == result_str text_table_strategy(strategy_results, "BTC", "STRATEGY SUMMARY")
captured = capsys.readouterr()
text = captured.out
assert re.search(
r".* Strategy .* Trades .* Avg Profit % .* Tot Profit BTC .* Tot Profit % .* "
r"Avg Duration .* Win Draw Loss Win% .* Drawdown .*",
text,
)
assert re.search(
r".*StrategyTestV2 .* 179 .* 0.08 .* 0.02608550 .* "
r"260.85 .* 3:40:00 .* 170 0 9 95.0 .* 0.00308222 BTC 8.67%.*",
text,
)
assert re.search(
r".*TestStrategy .* 179 .* 0.08 .* 0.02608550 .* "
r"260.85 .* 3:40:00 .* 170 0 9 95.0 .* 0.00308222 BTC 8.67%.*",
text,
)
def test_generate_edge_table(): def test_generate_edge_table(capsys):
results = {} results = {}
results["ETH/BTC"] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60) results["ETH/BTC"] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
assert generate_edge_table(results).count("+") == 7 generate_edge_table(results)
assert generate_edge_table(results).count("| ETH/BTC |") == 1 text = capsys.readouterr().out
assert ( assert re.search(r".* ETH/BTC .*", text)
generate_edge_table(results).count( assert re.search(r".* Risk Reward Ratio .* Required Risk Reward .* Expectancy .*", text)
"| Risk Reward Ratio | Required Risk Reward | Expectancy |"
)
== 1
)
def test_generate_periodic_breakdown_stats(testdatadir): def test_generate_periodic_breakdown_stats(testdatadir):
+2 -6
View File
@@ -105,9 +105,7 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf
instance = RecursiveAnalysis(recursive_conf, strategy_obj) instance = RecursiveAnalysis(recursive_conf, strategy_obj)
instance.dict_recursive = dict_diff instance.dict_recursive = dict_diff
_table, _headers, data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances( data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances([instance])
[instance]
)
# check row contents for a try that has too few signals # check row contents for a try that has too few signals
assert data[0][0] == "rsi" assert data[0][0] == "rsi"
@@ -118,9 +116,7 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf
dict_diff = dict() dict_diff = dict()
instance = RecursiveAnalysis(recursive_conf, strategy_obj) instance = RecursiveAnalysis(recursive_conf, strategy_obj)
instance.dict_recursive = dict_diff instance.dict_recursive = dict_diff
_table, _headers, data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances( data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances([instance])
[instance]
)
assert len(data) == 0 assert len(data) == 0