mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-12-15 20:31:43 +00:00
Merge branch 'develop' into feature/fetch-public-trades
This commit is contained in:
@@ -31,7 +31,7 @@ repos:
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.5.0'
|
||||
rev: 'v0.5.1'
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ This will spin up a local server (usually on port 8000) so you can see if everyt
|
||||
## 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]? ".
|
||||
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`.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
markdown==3.6
|
||||
mkdocs==1.6.0
|
||||
mkdocs-material==9.5.27
|
||||
mkdocs-material==9.5.28
|
||||
mdx_truly_sane_lists==1.3
|
||||
pymdown-extensions==10.8.1
|
||||
jinja2==3.1.4
|
||||
|
||||
@@ -6,7 +6,7 @@ if "dev" in __version__:
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
import subprocess # noqa: S404
|
||||
|
||||
freqtrade_basedir = Path(__file__).parent
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from freqtrade.exceptions import ConfigurationError
|
||||
from freqtrade.exchange import timeframe_to_minutes
|
||||
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
|
||||
from freqtrade.resolvers import ExchangeResolver
|
||||
from freqtrade.util import print_rich_table
|
||||
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)
|
||||
|
||||
from tabulate import tabulate
|
||||
|
||||
from freqtrade.data.history import get_datahandler
|
||||
|
||||
dhc = get_datahandler(config["datadir"], config["dataformat_ohlcv"])
|
||||
@@ -131,8 +130,7 @@ def start_list_data(args: Dict[str, Any]) -> None:
|
||||
|
||||
if args["pairs"]:
|
||||
paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]]
|
||||
|
||||
print(f"Found {len(paircombs)} pair / timeframe combinations.")
|
||||
title = f"Found {len(paircombs)} pair / timeframe combinations."
|
||||
if not config.get("show_timerange"):
|
||||
groupedpair = defaultdict(list)
|
||||
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)
|
||||
|
||||
if groupedpair:
|
||||
print(
|
||||
tabulate(
|
||||
[
|
||||
(pair, ", ".join(timeframes), candle_type)
|
||||
for (pair, candle_type), timeframes in groupedpair.items()
|
||||
],
|
||||
headers=("Pair", "Timeframe", "Type"),
|
||||
tablefmt="psql",
|
||||
stralign="right",
|
||||
)
|
||||
print_rich_table(
|
||||
[
|
||||
(pair, ", ".join(timeframes), candle_type)
|
||||
for (pair, candle_type), timeframes in groupedpair.items()
|
||||
],
|
||||
("Pair", "Timeframe", "Type"),
|
||||
title,
|
||||
table_kwargs={"min_width": 50},
|
||||
)
|
||||
else:
|
||||
paircombs1 = [
|
||||
(pair, timeframe, candle_type, *dhc.ohlcv_data_min_max(pair, timeframe, candle_type))
|
||||
for pair, timeframe, candle_type in paircombs
|
||||
]
|
||||
|
||||
print(
|
||||
tabulate(
|
||||
[
|
||||
(
|
||||
pair,
|
||||
timeframe,
|
||||
candle_type,
|
||||
start.strftime(DATETIME_PRINT_FORMAT),
|
||||
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])
|
||||
)
|
||||
],
|
||||
headers=("Pair", "Timeframe", "Type", "From", "To", "Candles"),
|
||||
tablefmt="psql",
|
||||
stralign="right",
|
||||
)
|
||||
print_rich_table(
|
||||
[
|
||||
(
|
||||
pair,
|
||||
timeframe,
|
||||
candle_type,
|
||||
start.strftime(DATETIME_PRINT_FORMAT),
|
||||
end.strftime(DATETIME_PRINT_FORMAT),
|
||||
str(length),
|
||||
)
|
||||
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,
|
||||
table_kwargs={"min_width": 50},
|
||||
)
|
||||
|
||||
@@ -2,8 +2,6 @@ import logging
|
||||
from operator import itemgetter
|
||||
from typing import Any, Dict
|
||||
|
||||
from colorama import init as colorama_init
|
||||
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
from freqtrade.data.btanalysis import get_latest_hyperopt_file
|
||||
from freqtrade.enums import RunMode
|
||||
@@ -18,6 +16,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
||||
"""
|
||||
List hyperopt epochs previously evaluated
|
||||
"""
|
||||
from freqtrade.optimize.hyperopt_output import HyperoptOutput
|
||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||
|
||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||
@@ -35,21 +34,17 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
||||
# Previous evaluations
|
||||
epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config)
|
||||
|
||||
if print_colorized:
|
||||
colorama_init(autoreset=True)
|
||||
|
||||
if not export_csv:
|
||||
try:
|
||||
print(
|
||||
HyperoptTools.get_result_table(
|
||||
config,
|
||||
epochs,
|
||||
total_epochs,
|
||||
not config.get("hyperopt_list_best", False),
|
||||
print_colorized,
|
||||
0,
|
||||
)
|
||||
h_out = HyperoptOutput()
|
||||
h_out.add_data(
|
||||
config,
|
||||
epochs,
|
||||
total_epochs,
|
||||
not config.get("hyperopt_list_best", False),
|
||||
)
|
||||
h_out.print(print_colorized=print_colorized)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("User interrupted..")
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import sys
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
import rapidjson
|
||||
from colorama import Fore, Style
|
||||
from colorama import init as colorama_init
|
||||
from tabulate import tabulate
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
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.misc import parse_db_uri_for_logging, plural
|
||||
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__)
|
||||
@@ -26,72 +27,69 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
|
||||
:param args: Cli args from Arguments()
|
||||
: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"]:
|
||||
print("\n".join([e["name"] for e in exchanges]))
|
||||
print("\n".join([e["name"] for e in available_exchanges]))
|
||||
else:
|
||||
headers = {
|
||||
"name": "Exchange name",
|
||||
"supported": "Supported",
|
||||
"trade_modes": "Markets",
|
||||
"comment": "Reason",
|
||||
}
|
||||
headers.update({"valid": "Valid"} if args["list_exchanges_all"] else {})
|
||||
if args["list_exchanges_all"]:
|
||||
title = (
|
||||
f"All exchanges supported by the ccxt library "
|
||||
f"({len(available_exchanges)} exchanges):"
|
||||
)
|
||||
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):
|
||||
valid_entry = {"valid": exchange["valid"]} if valid else {}
|
||||
result: Dict[str, Union[str, bool]] = {
|
||||
"name": exchange["name"],
|
||||
**valid_entry,
|
||||
"supported": "Official" if exchange["supported"] else "",
|
||||
"trade_modes": ("DEX: " if exchange["dex"] else "")
|
||||
+ ", ".join(
|
||||
(f"{a['margin_mode']} " if a["margin_mode"] else "") + a["trading_mode"]
|
||||
table = Table(title=title)
|
||||
|
||||
table.add_column("Exchange Name")
|
||||
table.add_column("Markets")
|
||||
table.add_column("Reason")
|
||||
|
||||
for exchange in available_exchanges:
|
||||
name = Text(exchange["name"])
|
||||
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"]
|
||||
),
|
||||
"comment": exchange["comment"],
|
||||
}
|
||||
|
||||
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,
|
||||
style="",
|
||||
)
|
||||
)
|
||||
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:
|
||||
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]
|
||||
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"],
|
||||
"status": (
|
||||
red + "LOAD FAILED" + reset
|
||||
Text("LOAD FAILED", style="bold red")
|
||||
if s["class"] is None
|
||||
else "OK"
|
||||
else Text("OK", style="bold green")
|
||||
if names.count(s["name"]) == 1
|
||||
else yellow + "DUPLICATE NAME" + reset
|
||||
else Text("DUPLICATE NAME", style="bold yellow")
|
||||
),
|
||||
}
|
||||
for s in objs
|
||||
@@ -101,11 +99,23 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
|
||||
objs_to_print[idx].update(
|
||||
{
|
||||
"hyperoptable": "Yes" if s["hyperoptable"]["count"] > 0 else "No",
|
||||
"buy-Params": len(s["hyperoptable"].get("buy", [])),
|
||||
"sell-Params": len(s["hyperoptable"].get("sell", [])),
|
||||
"buy-Params": str(len(s["hyperoptable"].get("buy", []))),
|
||||
"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:
|
||||
@@ -270,9 +280,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
|
||||
writer.writeheader()
|
||||
writer.writerows(tabular_data)
|
||||
else:
|
||||
# print data as a table, with the human-readable summary
|
||||
print(f"{summary_str}:")
|
||||
print(tabulate(tabular_data, headers="keys", tablefmt="psql", stralign="right"))
|
||||
print_rich_table(tabular_data, headers, summary_str)
|
||||
elif not (
|
||||
args.get("print_one_column", False)
|
||||
or args.get("list_pairs_print_json", False)
|
||||
|
||||
@@ -38,7 +38,7 @@ def chown_user_directory(directory: Path) -> None:
|
||||
"""
|
||||
if running_in_docker():
|
||||
try:
|
||||
import subprocess
|
||||
import subprocess # noqa: S404
|
||||
|
||||
subprocess.check_output(["sudo", "chown", "-R", "ftuser:", str(directory.resolve())])
|
||||
except Exception:
|
||||
|
||||
@@ -4,7 +4,6 @@ from typing import List
|
||||
|
||||
import joblib
|
||||
import pandas as pd
|
||||
from tabulate import tabulate
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import Config
|
||||
@@ -14,6 +13,7 @@ from freqtrade.data.btanalysis import (
|
||||
load_backtest_stats,
|
||||
)
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.util import print_df_rich_table
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -307,7 +307,7 @@ def _print_table(
|
||||
if name is not None:
|
||||
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):
|
||||
|
||||
@@ -65,6 +65,7 @@ SUPPORTED_EXCHANGES = [
|
||||
EXCHANGE_HAS_REQUIRED: Dict[str, List[str]] = {
|
||||
# Required / private
|
||||
"fetchOrder": ["fetchOpenOrder", "fetchClosedOrder"],
|
||||
"fetchL2OrderBook": ["fetchTicker"],
|
||||
"cancelOrder": [],
|
||||
"createOrder": [],
|
||||
"fetchBalance": [],
|
||||
|
||||
@@ -624,9 +624,12 @@ class Exchange:
|
||||
# Reload async markets, then assign them to sync api
|
||||
self._markets = self._load_async_markets(reload=True)
|
||||
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:
|
||||
# Set markets to avoid reloading on websocket api
|
||||
self._ws_async.set_markets(self._api.markets, self._api.currencies)
|
||||
self._ws_async.options = self._api.options
|
||||
self._last_markets_refresh = dt_ts()
|
||||
|
||||
if is_initial and self._ft_has["needs_trading_fees"]:
|
||||
@@ -2084,7 +2087,7 @@ class Exchange:
|
||||
def get_fee(
|
||||
self,
|
||||
symbol: str,
|
||||
type: str = "",
|
||||
order_type: str = "",
|
||||
side: str = "",
|
||||
amount: float = 1,
|
||||
price: float = 1,
|
||||
@@ -2093,13 +2096,13 @@ class Exchange:
|
||||
"""
|
||||
Retrieve fee from exchange
|
||||
: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 amount: Amount of order
|
||||
:param price: Price of order
|
||||
: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"
|
||||
try:
|
||||
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(
|
||||
symbol=symbol,
|
||||
type=type,
|
||||
type=order_type,
|
||||
side=side,
|
||||
amount=amount,
|
||||
price=price,
|
||||
|
||||
@@ -52,7 +52,7 @@ class BaseEnvironment(gym.Env):
|
||||
reward_kwargs: dict = {},
|
||||
window_size=10,
|
||||
starting_point=True,
|
||||
id: str = "baseenv-1",
|
||||
id: str = "baseenv-1", # noqa: A002
|
||||
seed: int = 1,
|
||||
config: dict = {},
|
||||
live: bool = False,
|
||||
|
||||
@@ -238,9 +238,9 @@ class FreqaiDataDrawer:
|
||||
metadata, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE
|
||||
)
|
||||
|
||||
def np_encoder(self, object):
|
||||
if isinstance(object, np.generic):
|
||||
return object.item()
|
||||
def np_encoder(self, obj):
|
||||
if isinstance(obj, np.generic):
|
||||
return obj.item()
|
||||
|
||||
def get_pair_dict_info(self, pair: str) -> Tuple[str, int]:
|
||||
"""
|
||||
@@ -448,8 +448,8 @@ class FreqaiDataDrawer:
|
||||
|
||||
delete_dict: Dict[str, Any] = {}
|
||||
|
||||
for dir in model_folders:
|
||||
result = pattern.match(str(dir.name))
|
||||
for directory in model_folders:
|
||||
result = pattern.match(str(directory.name))
|
||||
if result is None:
|
||||
continue
|
||||
coin = result.group(1)
|
||||
@@ -458,10 +458,10 @@ class FreqaiDataDrawer:
|
||||
if coin not in delete_dict:
|
||||
delete_dict[coin] = {}
|
||||
delete_dict[coin]["num_folders"] = 1
|
||||
delete_dict[coin]["timestamps"] = {int(timestamp): dir}
|
||||
delete_dict[coin]["timestamps"] = {int(timestamp): directory}
|
||||
else:
|
||||
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:
|
||||
if delete_dict[coin]["num_folders"] > num_keep:
|
||||
@@ -612,9 +612,9 @@ class FreqaiDataDrawer:
|
||||
elif self.model_type == "pytorch":
|
||||
import torch
|
||||
|
||||
zip = torch.load(dk.data_path / f"{dk.model_filename}_model.zip")
|
||||
model = zip["pytrainer"]
|
||||
model = model.load_from_checkpoint(zip)
|
||||
zipfile = torch.load(dk.data_path / f"{dk.model_filename}_model.zip")
|
||||
model = zipfile["pytrainer"]
|
||||
model = model.load_from_checkpoint(zipfile)
|
||||
|
||||
if not model:
|
||||
raise OperationalException(
|
||||
|
||||
@@ -45,10 +45,10 @@ class TensorBoardCallback(BaseTensorBoardCallback):
|
||||
return False
|
||||
|
||||
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():
|
||||
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
|
||||
|
||||
|
||||
@@ -2370,6 +2370,18 @@ class FreqtradeBot(LoggingMixin):
|
||||
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(
|
||||
self, trade: Trade, order: Dict, order_obj: Order, order_amount: float, trades: List
|
||||
) -> Optional[float]:
|
||||
@@ -2377,7 +2389,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
fee-detection fallback to Trades.
|
||||
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(
|
||||
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
|
||||
|
||||
import pandas as pd
|
||||
from rich.text import Text
|
||||
|
||||
from freqtrade.constants import Config
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.optimize.analysis.lookahead import LookaheadAnalysis
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.util import print_rich_table
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -53,18 +55,18 @@ class LookaheadAnalysisSubFunctions:
|
||||
[
|
||||
inst.strategy_obj["location"].parts[-1],
|
||||
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.false_entry_signals,
|
||||
inst.current_analysis.false_exit_signals,
|
||||
", ".join(inst.current_analysis.false_indicators),
|
||||
]
|
||||
)
|
||||
from tabulate import tabulate
|
||||
|
||||
table = tabulate(data, headers=headers, tablefmt="orgtbl")
|
||||
print(table)
|
||||
return table, headers, data
|
||||
print_rich_table(data, headers, summary="Lookahead Analysis")
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
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.optimize.analysis.recursive import RecursiveAnalysis
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.util import print_rich_table
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -16,9 +17,9 @@ class RecursiveAnalysisSubFunctions:
|
||||
@staticmethod
|
||||
def text_table_recursive_analysis_instances(recursive_instances: List[RecursiveAnalysis]):
|
||||
startups = recursive_instances[0]._startup_candle
|
||||
headers = ["indicators"]
|
||||
headers = ["Indicators"]
|
||||
for candle in startups:
|
||||
headers.append(candle)
|
||||
headers.append(str(candle))
|
||||
|
||||
data = []
|
||||
for inst in recursive_instances:
|
||||
@@ -30,13 +31,11 @@ class RecursiveAnalysisSubFunctions:
|
||||
data.append(temp_data)
|
||||
|
||||
if len(data) > 0:
|
||||
from tabulate import tabulate
|
||||
print_rich_table(data, headers, summary="Recursive Analysis")
|
||||
|
||||
table = tabulate(data, headers=headers, tablefmt="orgtbl")
|
||||
print(table)
|
||||
return table, headers, data
|
||||
return data
|
||||
|
||||
return None, None, data
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def calculate_config_overrides(config: Config):
|
||||
|
||||
@@ -465,25 +465,25 @@ class Backtesting:
|
||||
return data
|
||||
|
||||
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:
|
||||
"""
|
||||
Get close rate for backtesting result
|
||||
"""
|
||||
# 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.TRAILING_STOP_LOSS,
|
||||
ExitType.LIQUIDATION,
|
||||
):
|
||||
return self._get_close_rate_for_stoploss(row, trade, exit, trade_dur)
|
||||
elif exit.exit_type == (ExitType.ROI):
|
||||
return self._get_close_rate_for_roi(row, trade, exit, trade_dur)
|
||||
return self._get_close_rate_for_stoploss(row, trade, exit_, trade_dur)
|
||||
elif exit_.exit_type == (ExitType.ROI):
|
||||
return self._get_close_rate_for_roi(row, trade, exit_, trade_dur)
|
||||
else:
|
||||
return row[OPEN_IDX]
|
||||
|
||||
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:
|
||||
# our stoploss was already lower than candle high,
|
||||
# possibly due to a cancelled trade exit.
|
||||
@@ -491,7 +491,7 @@ class Backtesting:
|
||||
is_short = trade.is_short or False
|
||||
leverage = trade.leverage or 1.0
|
||||
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
|
||||
else:
|
||||
stoploss_value = trade.stop_loss
|
||||
@@ -506,7 +506,7 @@ class Backtesting:
|
||||
# Special case: trailing triggers within same candle as trade opened. Assume most
|
||||
# pessimistic price movement, which is moving just enough to arm stoploss and
|
||||
# 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 (
|
||||
not self.strategy.use_custom_stoploss
|
||||
and self.strategy.trailing_stop
|
||||
@@ -537,7 +537,7 @@ class Backtesting:
|
||||
return stoploss_value
|
||||
|
||||
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:
|
||||
is_short = trade.is_short or False
|
||||
leverage = trade.leverage or 1.0
|
||||
|
||||
@@ -52,4 +52,4 @@ class EdgeCli:
|
||||
result = self.edge.calculate(self.config["exchange"]["pair_whitelist"])
|
||||
if result:
|
||||
print("") # blank line for readability
|
||||
print(generate_edge_table(self.edge._cached_pairs))
|
||||
generate_edge_table(self.edge._cached_pairs)
|
||||
|
||||
@@ -14,14 +14,14 @@ from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import rapidjson
|
||||
from colorama import init as colorama_init
|
||||
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
|
||||
from joblib.externals import cloudpickle
|
||||
from pandas import DataFrame
|
||||
from rich.align import Align
|
||||
from rich.console import Console
|
||||
from rich.progress import (
|
||||
BarColumn,
|
||||
MofNCompleteColumn,
|
||||
Progress,
|
||||
TaskProgressColumn,
|
||||
TextColumn,
|
||||
TimeElapsedColumn,
|
||||
@@ -40,6 +40,7 @@ from freqtrade.optimize.backtesting import Backtesting
|
||||
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
|
||||
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
|
||||
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
|
||||
from freqtrade.optimize.hyperopt_output import HyperoptOutput
|
||||
from freqtrade.optimize.hyperopt_tools import (
|
||||
HyperoptStateContainer,
|
||||
HyperoptTools,
|
||||
@@ -47,6 +48,7 @@ from freqtrade.optimize.hyperopt_tools import (
|
||||
)
|
||||
from freqtrade.optimize.optimize_reports import generate_strategy_stats
|
||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
|
||||
from freqtrade.util import CustomProgress
|
||||
|
||||
|
||||
# Suppress scikit-learn FutureWarnings from skopt
|
||||
@@ -86,6 +88,8 @@ class Hyperopt:
|
||||
self.max_open_trades_space: List[Dimension] = []
|
||||
self.dimensions: List[Dimension] = []
|
||||
|
||||
self._hyper_out: HyperoptOutput = HyperoptOutput()
|
||||
|
||||
self.config = config
|
||||
self.min_date: datetime
|
||||
self.max_date: datetime
|
||||
@@ -260,7 +264,7 @@ class Hyperopt:
|
||||
result["max_open_trades"] = {"max_open_trades": strategy.max_open_trades}
|
||||
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
|
||||
TODO: this should be moved to HyperoptTools too
|
||||
@@ -268,17 +272,12 @@ class Hyperopt:
|
||||
is_best = results["is_best"]
|
||||
|
||||
if self.print_all or is_best:
|
||||
print(
|
||||
HyperoptTools.get_result_table(
|
||||
self.config,
|
||||
results,
|
||||
self.total_epochs,
|
||||
self.print_all,
|
||||
self.print_colorized,
|
||||
self.hyperopt_table_header,
|
||||
)
|
||||
self._hyper_out.add_data(
|
||||
self.config,
|
||||
[results],
|
||||
self.total_epochs,
|
||||
self.print_all,
|
||||
)
|
||||
self.hyperopt_table_header = 2
|
||||
|
||||
def init_spaces(self):
|
||||
"""
|
||||
@@ -626,16 +625,16 @@ class Hyperopt:
|
||||
|
||||
self.opt = self.get_optimizer(self.dimensions, config_jobs)
|
||||
|
||||
if self.print_colorized:
|
||||
colorama_init(autoreset=True)
|
||||
|
||||
try:
|
||||
with Parallel(n_jobs=config_jobs) as parallel:
|
||||
jobs = parallel._effective_n_jobs()
|
||||
logger.info(f"Effective number of parallel workers used: {jobs}")
|
||||
console = Console(
|
||||
color_system="auto" if self.print_colorized else None,
|
||||
)
|
||||
|
||||
# Define progressbar
|
||||
with Progress(
|
||||
with CustomProgress(
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(bar_width=None),
|
||||
MofNCompleteColumn(),
|
||||
@@ -645,6 +644,8 @@ class Hyperopt:
|
||||
"•",
|
||||
TimeRemainingColumn(),
|
||||
expand=True,
|
||||
console=console,
|
||||
cust_objs=[Align.center(self._hyper_out.table)],
|
||||
) as pbar:
|
||||
task = pbar.add_task("Epochs", total=self.total_epochs)
|
||||
|
||||
|
||||
123
freqtrade/optimize/hyperopt_output.py
Normal file
123
freqtrade/optimize/hyperopt_output.py
Normal 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 "",
|
||||
]
|
||||
),
|
||||
)
|
||||
@@ -5,10 +5,7 @@ from pathlib import Path
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import rapidjson
|
||||
import tabulate
|
||||
from colorama import Fore, Style
|
||||
from pandas import isna, json_normalize
|
||||
|
||||
from freqtrade.constants import FTHYPT_FILEVERSION, Config
|
||||
@@ -16,8 +13,6 @@ from freqtrade.enums import HyperoptState
|
||||
from freqtrade.exceptions import OperationalException
|
||||
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.optimize_reports import generate_wins_draws_losses
|
||||
from freqtrade.util import fmt_coin
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -357,175 +352,6 @@ class HyperoptTools:
|
||||
+ 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
|
||||
def export_csv_file(config: Config, results: list, csv_file: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import logging
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from tabulate import tabulate
|
||||
from typing import Any, Dict, List, Literal, Union
|
||||
|
||||
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config
|
||||
from freqtrade.optimize.optimize_reports.optimize_reports import generate_periodic_breakdown_stats
|
||||
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__)
|
||||
@@ -46,22 +44,23 @@ def generate_wins_draws_losses(wins, draws, losses):
|
||||
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
|
||||
:param pair_results: List of Dictionaries - one entry per pair + final TOTAL row
|
||||
: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")
|
||||
floatfmt = _get_line_floatfmt(stake_currency)
|
||||
output = [
|
||||
[
|
||||
t["key"],
|
||||
t["trades"],
|
||||
t["profit_mean_pct"],
|
||||
t["profit_total_abs"],
|
||||
f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}",
|
||||
t["profit_total_pct"],
|
||||
t["duration_avg"],
|
||||
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
|
||||
]
|
||||
# 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
|
||||
:param pair_results: List of Dictionaries - one entry per pair + final TOTAL row
|
||||
:param stake_currency: stake-currency - used to correctly name headers
|
||||
:return: pretty printed table with tabulate as string
|
||||
"""
|
||||
floatfmt = _get_line_floatfmt(stake_currency)
|
||||
fallback: str = ""
|
||||
is_list = False
|
||||
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":
|
||||
headers = _get_line_header("Exit Reason", stake_currency, "Exits")
|
||||
title = "Exit Reason"
|
||||
headers = _get_line_header(title, stake_currency, "Exits")
|
||||
fallback = "exit_reason"
|
||||
else:
|
||||
# Mix tag
|
||||
title = "Mixed Tag"
|
||||
headers = _get_line_header(["Enter Tag", "Exit Reason"], stake_currency, "Trades")
|
||||
floatfmt.insert(0, "s")
|
||||
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["profit_mean_pct"],
|
||||
t["profit_total_abs"],
|
||||
f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}",
|
||||
t["profit_total_pct"],
|
||||
t.get("duration_avg"),
|
||||
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
|
||||
]
|
||||
# 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(
|
||||
days_breakdown_stats: List[Dict[str, Any]], stake_currency: str, period: str
|
||||
) -> str:
|
||||
) -> None:
|
||||
"""
|
||||
Generate small table with Backtest results by days
|
||||
:param days_breakdown_stats: Days breakdown metrics
|
||||
:param stake_currency: Stakecurrency used
|
||||
:return: pretty printed table with tabulate as string
|
||||
"""
|
||||
headers = [
|
||||
period.capitalize(),
|
||||
@@ -143,17 +147,15 @@ def text_table_periodic_breakdown(
|
||||
]
|
||||
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
|
||||
:param strategy_results: Dict of <Strategyname: DataFrame> containing results for all strategies
|
||||
: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")
|
||||
# _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.
|
||||
@@ -177,8 +179,8 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
|
||||
[
|
||||
t["key"],
|
||||
t["trades"],
|
||||
t["profit_mean_pct"],
|
||||
t["profit_total_abs"],
|
||||
f"{t['profit_mean_pct']:.2f}",
|
||||
f"{t['profit_total_abs']:.{decimals_per_coin(stake_currency)}f}",
|
||||
t["profit_total_pct"],
|
||||
t["duration_avg"],
|
||||
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)
|
||||
]
|
||||
# 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_add_metrics(strat_results: Dict) -> str:
|
||||
def text_table_add_metrics(strat_results: Dict) -> None:
|
||||
if len(strat_results["trades"]) > 0:
|
||||
best_trade = max(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,
|
||||
("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:
|
||||
start_balance = fmt_coin(strat_results["starting_balance"], strat_results["stake_currency"])
|
||||
stake_amount = (
|
||||
@@ -387,7 +388,7 @@ def text_table_add_metrics(strat_results: Dict) -> str:
|
||||
f"Your starting balance was {start_balance}, "
|
||||
f"and your stake was {stake_amount}."
|
||||
)
|
||||
return message
|
||||
print(message)
|
||||
|
||||
|
||||
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)
|
||||
"""
|
||||
if (enter_tags := results.get("results_per_enter_tag")) is not None:
|
||||
table = 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)
|
||||
text_table_tags("enter_tag", enter_tags, stake_currency)
|
||||
|
||||
if (exit_reasons := results.get("exit_reason_summary")) is not None:
|
||||
table = 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)
|
||||
text_table_tags("exit_tag", exit_reasons, stake_currency)
|
||||
|
||||
if (mix_tag := results.get("mix_tag_stats")) is not None:
|
||||
table = 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)
|
||||
text_table_tags("mix_tag", mix_tag, stake_currency)
|
||||
|
||||
|
||||
def show_backtest_result(
|
||||
@@ -424,15 +413,12 @@ def show_backtest_result(
|
||||
"""
|
||||
# Print results
|
||||
print(f"Result for strategy {strategy}")
|
||||
table = text_table_bt_results(results["results_per_pair"], stake_currency=stake_currency)
|
||||
if isinstance(table, str):
|
||||
print(" BACKTESTING REPORT ".center(len(table.splitlines()[0]), "="))
|
||||
print(table)
|
||||
|
||||
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)
|
||||
text_table_bt_results(
|
||||
results["results_per_pair"], stake_currency=stake_currency, title="BACKTESTING REPORT"
|
||||
)
|
||||
text_table_bt_results(
|
||||
results["left_open_trades"], stake_currency=stake_currency, title="LEFT OPEN TRADES REPORT"
|
||||
)
|
||||
|
||||
_show_tag_subresults(results, stake_currency)
|
||||
|
||||
@@ -443,20 +429,11 @@ def show_backtest_result(
|
||||
days_breakdown_stats = generate_periodic_breakdown_stats(
|
||||
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
|
||||
)
|
||||
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)
|
||||
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]))
|
||||
text_table_add_metrics(results)
|
||||
|
||||
print()
|
||||
|
||||
@@ -472,15 +449,13 @@ def show_backtest_results(config: Config, backtest_stats: BacktestResultType):
|
||||
if len(backtest_stats["strategy"]) > 0:
|
||||
# Print Strategy summary table
|
||||
|
||||
table = text_table_strategy(backtest_stats["strategy_comparison"], stake_currency)
|
||||
print(
|
||||
f"Backtested {results['backtest_start']} -> {results['backtest_end']} |"
|
||||
f" Max open trades : {results['max_open_trades']}"
|
||||
)
|
||||
print(" STRATEGY SUMMARY ".center(len(table.splitlines()[0]), "="))
|
||||
print(table)
|
||||
print("=" * len(table.splitlines()[0]))
|
||||
print("\nFor more details, please look at the detail tables above")
|
||||
text_table_strategy(
|
||||
backtest_stats["strategy_comparison"], stake_currency, "STRATEGY SUMMARY"
|
||||
)
|
||||
|
||||
|
||||
def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType):
|
||||
@@ -493,8 +468,7 @@ def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType):
|
||||
print("]")
|
||||
|
||||
|
||||
def generate_edge_table(results: dict) -> str:
|
||||
floatfmt = ("s", ".10g", ".2f", ".2f", ".2f", ".2f", "d", "d", "d")
|
||||
def generate_edge_table(results: dict) -> None:
|
||||
tabular_data = []
|
||||
headers = [
|
||||
"Pair",
|
||||
@@ -512,17 +486,13 @@ def generate_edge_table(results: dict) -> str:
|
||||
tabular_data.append(
|
||||
[
|
||||
result[0],
|
||||
result[1].stoploss,
|
||||
result[1].winrate,
|
||||
result[1].risk_reward_ratio,
|
||||
result[1].required_risk_reward,
|
||||
result[1].expectancy,
|
||||
f"{result[1].stoploss:.10g}",
|
||||
f"{result[1].winrate:.2f}",
|
||||
f"{result[1].risk_reward_ratio:.2f}",
|
||||
f"{result[1].required_risk_reward:.2f}",
|
||||
f"{result[1].expectancy:.2f}",
|
||||
result[1].nb_trades,
|
||||
round(result[1].avg_trade_duration),
|
||||
]
|
||||
)
|
||||
|
||||
# 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"
|
||||
)
|
||||
print_rich_table(tabular_data, headers, summary="EDGE TABLE")
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
id = _request_id_ctx_var.get()
|
||||
if id is None:
|
||||
request_id = _request_id_ctx_var.get()
|
||||
if request_id is None:
|
||||
# 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"
|
||||
|
||||
@@ -2012,7 +2012,7 @@ class Trade(ModelBase, LocalTrade):
|
||||
).all()
|
||||
|
||||
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"
|
||||
exit_reason = exit_reason if exit_reason is not None else "Other"
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ class IResolver:
|
||||
|
||||
# Add extra directory to the top of the search paths
|
||||
if extra_dirs:
|
||||
for dir in extra_dirs:
|
||||
abs_paths.insert(0, Path(dir).resolve())
|
||||
for directory in extra_dirs:
|
||||
abs_paths.insert(0, Path(directory).resolve())
|
||||
|
||||
if cls.extra_path and (extra := config.get(cls.extra_path)):
|
||||
abs_paths.insert(0, Path(extra).resolve())
|
||||
|
||||
@@ -311,9 +311,9 @@ def warn_deprecated_setting(strategy: IStrategy, old: str, new: str, error=False
|
||||
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.
|
||||
:returns: True if the object is overridden.
|
||||
"""
|
||||
return getattr(type(object), attribute) != getattr(parentclass, attribute)
|
||||
return getattr(type(obj), attribute) != getattr(parentclass, attribute)
|
||||
|
||||
@@ -11,7 +11,7 @@ from typing import Any, Dict, Generator, List, Optional, Sequence, Tuple, Union
|
||||
import psutil
|
||||
from dateutil.relativedelta import relativedelta
|
||||
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 sqlalchemy import func, select
|
||||
|
||||
@@ -204,9 +204,9 @@ class RPC:
|
||||
trade.pair, side="exit", is_short=trade.is_short, refresh=False
|
||||
)
|
||||
except (ExchangeError, PricingError):
|
||||
current_rate = NAN
|
||||
current_rate = nan
|
||||
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):
|
||||
prof = trade.calculate_profit(current_rate)
|
||||
current_profit = prof.profit_ratio
|
||||
@@ -277,7 +277,7 @@ class RPC:
|
||||
raise RPCException("no active trade")
|
||||
else:
|
||||
trades_list = []
|
||||
fiat_profit_sum = NAN
|
||||
fiat_profit_sum = nan
|
||||
for trade in trades:
|
||||
# calculate profit and send message to user
|
||||
try:
|
||||
@@ -285,9 +285,9 @@ class RPC:
|
||||
trade.pair, side="exit", is_short=trade.is_short, refresh=False
|
||||
)
|
||||
except (PricingError, ExchangeError):
|
||||
current_rate = NAN
|
||||
trade_profit = NAN
|
||||
profit_str = f"{NAN:.2%}"
|
||||
current_rate = nan
|
||||
trade_profit = nan
|
||||
profit_str = f"{nan:.2%}"
|
||||
else:
|
||||
if trade.nr_of_successful_entries > 0:
|
||||
profit = trade.calculate_profit(current_rate)
|
||||
@@ -533,9 +533,9 @@ class RPC:
|
||||
trade.pair, side="exit", is_short=trade.is_short, refresh=False
|
||||
)
|
||||
except (PricingError, ExchangeError):
|
||||
current_rate = NAN
|
||||
profit_ratio = NAN
|
||||
profit_abs = NAN
|
||||
current_rate = nan
|
||||
profit_ratio = nan
|
||||
profit_abs = nan
|
||||
else:
|
||||
_profit = trade.calculate_profit(trade.close_rate or current_rate)
|
||||
|
||||
@@ -1321,7 +1321,7 @@ class RPC:
|
||||
dataframe = dataframe.drop(
|
||||
["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 = {
|
||||
"pair": pair,
|
||||
|
||||
@@ -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.measure_time import MeasureTime
|
||||
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
|
||||
|
||||
|
||||
@@ -36,4 +38,7 @@ __all__ = [
|
||||
"round_value",
|
||||
"fmt_coin",
|
||||
"MeasureTime",
|
||||
"print_rich_table",
|
||||
"print_df_rich_table",
|
||||
"CustomProgress",
|
||||
]
|
||||
|
||||
14
freqtrade/util/rich_progress.py
Normal file
14
freqtrade/util/rich_progress.py
Normal 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
freqtrade/util/rich_tables.py
Normal file
77
freqtrade/util/rich_tables.py
Normal 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)
|
||||
@@ -7,7 +7,7 @@ if "dev" in __version__:
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
import subprocess # noqa: S404
|
||||
|
||||
freqtrade_basedir = Path(__file__).parent
|
||||
|
||||
|
||||
@@ -133,8 +133,8 @@ def test_FtRestClient_call_invalid(caplog):
|
||||
)
|
||||
def test_FtRestClient_call_explicit_methods(method, args, kwargs):
|
||||
client, mock = get_rest_client()
|
||||
exec = getattr(client, method)
|
||||
exec(*args, **kwargs)
|
||||
executor = getattr(client, method)
|
||||
executor(*args, **kwargs)
|
||||
assert mock.call_count == 1
|
||||
|
||||
|
||||
|
||||
@@ -134,17 +134,19 @@ extend-select = [
|
||||
"W", # pycodestyle
|
||||
"UP", # pyupgrade
|
||||
"I", # isort
|
||||
"A", # flake8-builtins
|
||||
"TID", # flake8-tidy-imports
|
||||
# "EXE", # flake8-executable
|
||||
# "C4", # flake8-comprehensions
|
||||
"YTT", # flake8-2020
|
||||
"S", # flake8-bandit
|
||||
"S", # flake8-bandit
|
||||
# "DTZ", # flake8-datetimez
|
||||
# "RSE", # flake8-raise
|
||||
# "TCH", # flake8-type-checking
|
||||
"PTH", # flake8-use-pathlib
|
||||
# "RUF", # ruff
|
||||
"ASYNC", # flake8-async
|
||||
"NPY", # numpy
|
||||
]
|
||||
|
||||
extend-ignore = [
|
||||
@@ -155,6 +157,7 @@ extend-ignore = [
|
||||
"S603", # `subprocess` call: check for execution of untrusted input
|
||||
"S607", # Starting a process with a partial executable path
|
||||
"S608", # Possible SQL injection vector through string-based query construction
|
||||
"NPY002", # Numpy legacy random generator
|
||||
]
|
||||
|
||||
[tool.ruff.lint.mccabe]
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-r docs/requirements-docs.txt
|
||||
|
||||
coveralls==4.0.1
|
||||
ruff==0.5.0
|
||||
ruff==0.5.1
|
||||
mypy==1.10.1
|
||||
pre-commit==3.7.1
|
||||
pytest==8.2.2
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
-r requirements-plot.txt
|
||||
|
||||
# Required for freqai
|
||||
scikit-learn==1.5.0
|
||||
scikit-learn==1.5.1
|
||||
joblib==1.4.2
|
||||
catboost==1.2.5; 'arm' not in platform_machine
|
||||
lightgbm==4.4.0
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
# Required for hyperopt
|
||||
scipy==1.14.0; 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
|
||||
filelock==3.15.4
|
||||
|
||||
@@ -4,7 +4,7 @@ bottleneck==1.4.0
|
||||
numexpr==2.10.1
|
||||
pandas-ta==0.3.14b
|
||||
|
||||
ccxt==4.3.54
|
||||
ccxt==4.3.58
|
||||
cryptography==42.0.8
|
||||
aiohttp==3.9.5
|
||||
SQLAlchemy==2.0.31
|
||||
@@ -32,21 +32,19 @@ py_find_1st==1.1.6
|
||||
# Load ticker files 30% faster
|
||||
python-rapidjson==1.18
|
||||
# Properly format api responses
|
||||
orjson==3.10.5
|
||||
orjson==3.10.6
|
||||
|
||||
# Notify systemd
|
||||
sdnotify==0.3.2
|
||||
|
||||
# API Server
|
||||
fastapi==0.111.0
|
||||
pydantic==2.7.4
|
||||
pydantic==2.8.2
|
||||
uvicorn==0.30.1
|
||||
pyjwt==2.8.0
|
||||
aiofiles==24.1.0
|
||||
psutil==6.0.0
|
||||
|
||||
# Support for colorized terminal output
|
||||
colorama==0.4.6
|
||||
# Building config files interactively
|
||||
questionary==2.0.1
|
||||
prompt-toolkit==3.0.36
|
||||
|
||||
@@ -172,10 +172,10 @@ class ClientProtocol:
|
||||
|
||||
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)
|
||||
|
||||
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"]
|
||||
|
||||
if not df.empty:
|
||||
@@ -189,8 +189,8 @@ class ClientProtocol:
|
||||
else:
|
||||
self.logger.info("Empty DataFrame")
|
||||
|
||||
async def _handle_default(self, name, type, data):
|
||||
self.logger.info("Unknown message of type {type} received...")
|
||||
async def _handle_default(self, name, msgtype, data):
|
||||
self.logger.info("Unknown message of type {msgtype} received...")
|
||||
self.logger.info(data)
|
||||
|
||||
|
||||
|
||||
3
setup.py
3
setup.py
@@ -78,7 +78,7 @@ setup(
|
||||
"httpx>=0.24.1",
|
||||
"urllib3",
|
||||
"jsonschema",
|
||||
"numpy",
|
||||
"numpy<2.0",
|
||||
"pandas>=2.2.0,<3.0",
|
||||
"TA-Lib",
|
||||
"pandas-ta",
|
||||
@@ -88,7 +88,6 @@ setup(
|
||||
"py_find_1st",
|
||||
"python-rapidjson",
|
||||
"orjson",
|
||||
"colorama",
|
||||
"jinja2",
|
||||
"questionary",
|
||||
"prompt-toolkit",
|
||||
|
||||
@@ -116,7 +116,7 @@ def test_list_exchanges(capsys):
|
||||
|
||||
start_list_exchanges(get_args(args))
|
||||
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".*bybit.*", captured.out)
|
||||
|
||||
@@ -139,7 +139,7 @@ def test_list_exchanges(capsys):
|
||||
|
||||
start_list_exchanges(get_args(args))
|
||||
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".*bingx.*", captured.out)
|
||||
assert re.search(r".*bitmex.*", captured.out)
|
||||
@@ -167,7 +167,7 @@ def test_list_timeframes(mocker, capsys):
|
||||
"1h": "hour",
|
||||
"1d": "day",
|
||||
}
|
||||
patch_exchange(mocker, api_mock=api_mock, id="bybit")
|
||||
patch_exchange(mocker, api_mock=api_mock, exchange="bybit")
|
||||
args = [
|
||||
"list-timeframes",
|
||||
]
|
||||
@@ -213,7 +213,7 @@ def test_list_timeframes(mocker, capsys):
|
||||
"1d": "1d",
|
||||
"3d": "3d",
|
||||
}
|
||||
patch_exchange(mocker, api_mock=api_mock, id="binance")
|
||||
patch_exchange(mocker, api_mock=api_mock, exchange="binance")
|
||||
# Test with --exchange binance
|
||||
args = [
|
||||
"list-timeframes",
|
||||
@@ -258,7 +258,7 @@ def test_list_timeframes(mocker, capsys):
|
||||
|
||||
def test_list_markets(mocker, markets_static, capsys):
|
||||
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
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
args = ["list-markets", "--exchange", "binance"]
|
||||
pargs = get_args(args)
|
||||
pargs["config"] = None
|
||||
start_list_markets(pargs, False)
|
||||
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
|
||||
args = [
|
||||
"list-markets",
|
||||
@@ -491,7 +491,7 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
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
|
||||
args = [
|
||||
@@ -823,7 +823,7 @@ def test_download_data_no_markets(mocker, caplog):
|
||||
"freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data",
|
||||
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={})
|
||||
args = [
|
||||
"download-data",
|
||||
@@ -952,7 +952,7 @@ def test_download_data_trades(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={})
|
||||
args = [
|
||||
"download-data",
|
||||
@@ -1633,8 +1633,8 @@ def test_start_list_data(testdatadir, capsys):
|
||||
start_list_data(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "Found 16 pair / timeframe combinations." in captured.out
|
||||
assert "\n| Pair | Timeframe | Type |\n" in captured.out
|
||||
assert "\n| UNITTEST/BTC | 1m, 5m, 8m, 30m | spot |\n" in captured.out
|
||||
assert re.search(r".*Pair.*Timeframe.*Type.*\n", captured.out)
|
||||
assert re.search(r"\n.* UNITTEST/BTC .* 1m, 5m, 8m, 30m .* spot |\n", captured.out)
|
||||
|
||||
args = [
|
||||
"list-data",
|
||||
@@ -1650,9 +1650,9 @@ def test_start_list_data(testdatadir, capsys):
|
||||
start_list_data(pargs)
|
||||
captured = capsys.readouterr()
|
||||
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 "\n| XRP/ETH | 1m, 5m | spot |\n" in captured.out
|
||||
assert re.search(r"\n.* XRP/ETH .* 1m, 5m .* spot |\n", captured.out)
|
||||
|
||||
args = [
|
||||
"list-data",
|
||||
@@ -1667,9 +1667,9 @@ def test_start_list_data(testdatadir, capsys):
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert "Found 6 pair / timeframe combinations." in captured.out
|
||||
assert "\n| Pair | Timeframe | Type |\n" in captured.out
|
||||
assert "\n| XRP/USDT:USDT | 5m, 1h | futures |\n" in captured.out
|
||||
assert "\n| XRP/USDT:USDT | 1h, 8h | mark |\n" in captured.out
|
||||
assert re.search(r".*Pair.*Timeframe.*Type.*\n", captured.out)
|
||||
assert re.search(r"\n.* XRP/USDT:USDT .* 5m, 1h .* futures |\n", captured.out)
|
||||
assert re.search(r"\n.* XRP/USDT:USDT .* 1h, 8h .* mark |\n", captured.out)
|
||||
|
||||
args = [
|
||||
"list-data",
|
||||
@@ -1684,15 +1684,12 @@ def test_start_list_data(testdatadir, capsys):
|
||||
start_list_data(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "Found 2 pair / timeframe combinations." in captured.out
|
||||
assert (
|
||||
"\n| Pair | Timeframe | Type "
|
||||
"| From | To | Candles |\n"
|
||||
) in captured.out
|
||||
assert re.search(r".*Pair.*Timeframe.*Type.*From .* To .* Candles .*\n", captured.out)
|
||||
assert "UNITTEST/BTC" not in captured.out
|
||||
assert (
|
||||
"\n| XRP/ETH | 1m | spot | "
|
||||
"2019-10-11 00:00:00 | 2019-10-13 11:19:00 | 2469 |\n"
|
||||
) in captured.out
|
||||
assert re.search(
|
||||
r"\n.* XRP/USDT .* 1m .* spot .* 2019-10-11 00:00:00 .* 2019-10-13 11:19:00 .* 2469 |\n",
|
||||
captured.out,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
|
||||
@@ -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)
|
||||
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)
|
||||
]
|
||||
|
||||
@@ -155,7 +155,7 @@ def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days=
|
||||
df = pd.DataFrame(
|
||||
{
|
||||
"timestamp": timestamp,
|
||||
"id": id,
|
||||
"id": trade_id,
|
||||
"type": None,
|
||||
"side": side,
|
||||
"price": price,
|
||||
@@ -236,12 +236,12 @@ def patched_configuration_load_config_file(mocker, config) -> None:
|
||||
|
||||
|
||||
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:
|
||||
mocker.patch(f"{EXMS}.validate_config", MagicMock())
|
||||
mocker.patch(f"{EXMS}.validate_timeframes", MagicMock())
|
||||
mocker.patch(f"{EXMS}.id", PropertyMock(return_value=id))
|
||||
mocker.patch(f"{EXMS}.name", PropertyMock(return_value=id.title()))
|
||||
mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange))
|
||||
mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title()))
|
||||
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2))
|
||||
# Temporary patch ...
|
||||
mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers")
|
||||
@@ -254,7 +254,8 @@ def patch_exchange(
|
||||
|
||||
if mock_supported_modes:
|
||||
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(
|
||||
return_value=[
|
||||
(TradingMode.MARGIN, MarginMode.CROSS),
|
||||
@@ -274,10 +275,10 @@ def patch_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:
|
||||
patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes)
|
||||
config["exchange"]["name"] = id
|
||||
patch_exchange(mocker, api_mock, exchange, mock_markets, mock_supported_modes)
|
||||
config["exchange"]["name"] = exchange
|
||||
try:
|
||||
exchange = ExchangeResolver.load_exchange(config, load_leverage_tiers=True)
|
||||
except ImportError:
|
||||
|
||||
@@ -324,7 +324,8 @@ def hyperopt_test_result():
|
||||
"profit_mean": None,
|
||||
"profit_median": None,
|
||||
"profit_total": 0,
|
||||
"profit": 0.0,
|
||||
"max_drawdown_account": 0.0,
|
||||
"max_drawdown_abs": 0.0,
|
||||
"holding_avg": timedelta(),
|
||||
}, # noqa: E501
|
||||
"results_explanation": " 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.", # noqa: E501
|
||||
|
||||
@@ -83,7 +83,7 @@ def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected",
|
||||
"pair,expected",
|
||||
[
|
||||
("XMR_USDT", "XMR/USDT"),
|
||||
("BTC_USDT", "BTC/USDT"),
|
||||
@@ -95,8 +95,8 @@ def test_datahandler_ohlcv_regex(filename, pair, timeframe, candletype):
|
||||
("UNITTEST_USDT", "UNITTEST/USDT"),
|
||||
],
|
||||
)
|
||||
def test_rebuild_pair_from_filename(input, expected):
|
||||
assert IDataHandler.rebuild_pair_from_filename(input) == expected
|
||||
def test_rebuild_pair_from_filename(pair, expected):
|
||||
assert IDataHandler.rebuild_pair_from_filename(pair) == expected
|
||||
|
||||
|
||||
def test_datahandler_ohlcv_get_available_data(testdatadir):
|
||||
|
||||
@@ -286,7 +286,7 @@ def test_refresh(mocker, default_conf):
|
||||
refresh_mock = mocker.patch(f"{EXMS}.refresh_latest_ohlcv")
|
||||
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"]
|
||||
pairs = [("XRP/BTC", timeframe), ("UNITTEST/BTC", timeframe)]
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ def test_download_data_main_no_markets(mocker, caplog):
|
||||
"freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data",
|
||||
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={})
|
||||
config = setup_utils_configuration({"exchange": "binance"}, RunMode.UTIL_EXCHANGE)
|
||||
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):
|
||||
patch_exchange(mocker, id="kraken")
|
||||
patch_exchange(mocker, exchange="kraken")
|
||||
mocker.patch(f"{EXMS}.get_markets", return_value={})
|
||||
config = setup_utils_configuration({"exchange": "kraken"}, RunMode.UTIL_EXCHANGE)
|
||||
config.update(
|
||||
|
||||
@@ -154,10 +154,10 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, use
|
||||
assert "-3.5" in captured.out
|
||||
assert "50" 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 "0.104411" in captured.out
|
||||
assert "52.8292" in captured.out
|
||||
assert "0.104" in captured.out
|
||||
assert "52.829" in captured.out
|
||||
|
||||
# test group 1
|
||||
args = get_args(base_args + ["--analysis-groups", "1"])
|
||||
|
||||
@@ -555,7 +555,7 @@ def test_refresh_backtest_ohlcv_data(
|
||||
mocker.patch.object(Path, "unlink", MagicMock())
|
||||
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")
|
||||
refresh_backtest_ohlcv_data(
|
||||
exchange=ex,
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_import_kraken_trades_from_csv(testdatadir, tmp_path, caplog, default_co
|
||||
|
||||
default_conf_usdt["exchange"]["name"] = "kraken"
|
||||
|
||||
patch_exchange(mocker, id="kraken")
|
||||
patch_exchange(mocker, exchange="kraken")
|
||||
mocker.patch(
|
||||
f"{EXMS}.markets",
|
||||
PropertyMock(
|
||||
|
||||
@@ -12,7 +12,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"side,type,time_in_force,expected",
|
||||
"side,order_type,time_in_force,expected",
|
||||
[
|
||||
("buy", "limit", "gtc", {"timeInForce": "GTC"}),
|
||||
("buy", "limit", "IOC", {"timeInForce": "IOC"}),
|
||||
@@ -22,9 +22,9 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
("sell", "market", "PO", {}),
|
||||
],
|
||||
)
|
||||
def test__get_params_binance(default_conf, mocker, side, type, time_in_force, expected):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
||||
assert exchange._get_params(side, type, 1, False, 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, exchange="binance")
|
||||
assert exchange._get_params(side, order_type, 1, False, time_in_force) == expected
|
||||
|
||||
|
||||
@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")]
|
||||
)
|
||||
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 = {
|
||||
"type": "stop_loss_limit",
|
||||
"price": 1500,
|
||||
@@ -378,7 +378,7 @@ def test_fill_leverage_tiers_binance(default_conf, mocker):
|
||||
default_conf["dry_run"] = False
|
||||
default_conf["trading_mode"] = TradingMode.FUTURES
|
||||
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()
|
||||
|
||||
assert exchange._leverage_tiers == {
|
||||
@@ -497,7 +497,7 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers
|
||||
api_mock = MagicMock()
|
||||
default_conf["trading_mode"] = TradingMode.FUTURES
|
||||
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()
|
||||
assert len(exchange._leverage_tiers.keys()) > 100
|
||||
for key, value in leverage_tiers.items():
|
||||
@@ -518,10 +518,10 @@ def test_additional_exchange_init_binance(default_conf, mocker):
|
||||
OperationalException,
|
||||
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.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
|
||||
ccxt_exceptionhandlers(
|
||||
mocker,
|
||||
@@ -541,7 +541,7 @@ def test__set_leverage_binance(mocker, default_conf):
|
||||
default_conf["trading_mode"] = TradingMode.FUTURES
|
||||
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")
|
||||
assert api_mock.set_leverage.call_count == 1
|
||||
# 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
|
||||
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
|
||||
|
||||
@@ -620,7 +620,7 @@ def test_get_maintenance_ratio_and_amt_binance(
|
||||
amt,
|
||||
):
|
||||
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
|
||||
(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)
|
||||
|
||||
@@ -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)
|
||||
assert len(orders) == 1
|
||||
|
||||
@@ -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.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.is_unified_enabled.call_count == 1
|
||||
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.is_unified_enabled = MagicMock(return_value=[False, True])
|
||||
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)
|
||||
# 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.is_unified_enabled.call_count == 1
|
||||
# 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"
|
||||
api_mock = MagicMock()
|
||||
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
|
||||
# Test fetch_funding_rate_history (current data)
|
||||
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):
|
||||
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.get_funding_fees("BTC/USDT:USDT", 1, False, now)
|
||||
assert exchange._fetch_and_calculate_funding_fees.call_count == 0
|
||||
|
||||
default_conf["trading_mode"] = "futures"
|
||||
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.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)
|
||||
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
|
||||
assert exchange.fetch_orders("mocked", start_time) == []
|
||||
assert api_mock.fetch_orders.call_count == 0
|
||||
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)
|
||||
# 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
|
||||
@@ -136,7 +136,7 @@ def test_bybit_fetch_order_canceled_empty(default_conf_usdt, mocker):
|
||||
)
|
||||
|
||||
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")
|
||||
assert res["remaining"] is None
|
||||
|
||||
@@ -118,19 +118,19 @@ def ccxt_exceptionhandlers(
|
||||
with patch("freqtrade.exchange.common.time.sleep"):
|
||||
with pytest.raises(DDosProtection):
|
||||
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)
|
||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
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)
|
||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
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)
|
||||
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):
|
||||
caplog.set_level(logging.INFO)
|
||||
# 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 = {
|
||||
"buy": "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):
|
||||
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}.precisionMode", PropertyMock(return_value=precision_mode))
|
||||
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:
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
|
||||
stoploss = -0.05
|
||||
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"
|
||||
default_conf["trading_mode"] = "futures"
|
||||
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))
|
||||
|
||||
# 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:
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
|
||||
stoploss = -0.05
|
||||
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)
|
||||
default_conf["exchange"]["markets_refresh_interval"] = 10
|
||||
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")
|
||||
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"))
|
||||
default_conf["exchange"]["markets_refresh_interval"] = 10
|
||||
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
|
||||
@@ -1153,7 +1153,7 @@ def test_exchange_has(default_conf, mocker):
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverage):
|
||||
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(
|
||||
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,
|
||||
):
|
||||
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(
|
||||
EXMS,
|
||||
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["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(
|
||||
EXMS,
|
||||
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"
|
||||
mocker.patch(f"{EXMS}.amount_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_margin_mode = MagicMock()
|
||||
|
||||
@@ -1393,7 +1393,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
|
||||
"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._set_leverage = 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)
|
||||
def test_buy_dry_run(default_conf, mocker, exchange_name):
|
||||
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(
|
||||
pair="ETH/BTC",
|
||||
@@ -1440,7 +1440,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
default_conf["dry_run"] = False
|
||||
mocker.patch(f"{EXMS}.amount_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(
|
||||
pair="ETH/BTC",
|
||||
@@ -1484,7 +1484,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
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(
|
||||
pair="ETH/BTC",
|
||||
ordertype=order_type,
|
||||
@@ -1497,7 +1497,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
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(
|
||||
pair="ETH/BTC",
|
||||
ordertype="limit",
|
||||
@@ -1510,7 +1510,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
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(
|
||||
pair="ETH/BTC",
|
||||
ordertype="market",
|
||||
@@ -1523,7 +1523,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
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(
|
||||
pair="ETH/BTC",
|
||||
ordertype=order_type,
|
||||
@@ -1536,7 +1536,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
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(
|
||||
pair="ETH/BTC",
|
||||
ordertype=order_type,
|
||||
@@ -1559,7 +1559,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
default_conf["dry_run"] = False
|
||||
mocker.patch(f"{EXMS}.amount_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"
|
||||
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}.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(
|
||||
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
|
||||
with pytest.raises(InsufficientFundsError):
|
||||
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(
|
||||
pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
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(
|
||||
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
|
||||
with pytest.raises(DependencyException):
|
||||
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(
|
||||
pair="ETH/BTC", ordertype="market", side="sell", amount=1, rate=200, leverage=1.0
|
||||
)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
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(
|
||||
pair="ETH/BTC", ordertype=order_type, side="sell", amount=1, rate=200, leverage=1.0
|
||||
)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
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(
|
||||
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
|
||||
mocker.patch(f"{EXMS}.amount_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"
|
||||
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}
|
||||
)
|
||||
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 exchange.get_balances()["1ST"]["free"] == 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},
|
||||
]
|
||||
)
|
||||
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() == []
|
||||
default_conf["dry_run"] = False
|
||||
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()
|
||||
assert len(res) == 2
|
||||
|
||||
@@ -1831,13 +1831,13 @@ def test_fetch_orders(default_conf, mocker, exchange_name, limit_order):
|
||||
if exchange_name == "bybit":
|
||||
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
|
||||
assert exchange.fetch_orders("mocked", start_time) == []
|
||||
assert api_mock.fetch_orders.call_count == 0
|
||||
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)
|
||||
assert api_mock.fetch_orders.call_count == expected
|
||||
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
|
||||
api_mock.fetch_trading_fees = MagicMock(return_value=tick)
|
||||
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 "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={})
|
||||
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()
|
||||
mocker.patch(f"{EXMS}.exchange_has", return_value=True)
|
||||
assert exchange.fetch_trading_fees() == {}
|
||||
@@ -1978,7 +1978,7 @@ def test_fetch_bids_asks(default_conf, mocker):
|
||||
exchange_name = "binance"
|
||||
api_mock.fetch_bids_asks = MagicMock(return_value=tick)
|
||||
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
|
||||
bidsasks = exchange.fetch_bids_asks()
|
||||
|
||||
@@ -2005,11 +2005,11 @@ def test_fetch_bids_asks(default_conf, mocker):
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
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()
|
||||
|
||||
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()
|
||||
mocker.patch(f"{EXMS}.exchange_has", return_value=True)
|
||||
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)
|
||||
api_mock.fetch_tickers = MagicMock(return_value=tick)
|
||||
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
|
||||
tickers = exchange.get_tickers()
|
||||
|
||||
@@ -2065,19 +2065,19 @@ def test_get_tickers(default_conf, mocker, exchange_name, caplog):
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
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()
|
||||
|
||||
caplog.clear()
|
||||
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()
|
||||
assert x == []
|
||||
assert log_has_re(r"Could not load tickers due to BadSymbol\..*SomeSymbol", caplog)
|
||||
caplog.clear()
|
||||
|
||||
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()
|
||||
|
||||
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["margin_mode"] = MarginMode.ISOLATED
|
||||
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()
|
||||
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.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
|
||||
ticker = exchange.fetch_ticker(pair="ETH/BTC")
|
||||
|
||||
@@ -2123,7 +2123,7 @@ def test_fetch_ticker(default_conf, mocker, exchange_name):
|
||||
"last": 42,
|
||||
}
|
||||
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 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={})
|
||||
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")
|
||||
|
||||
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)
|
||||
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"
|
||||
candle_type = CandleType.SPOT
|
||||
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("candle_type", ["mark", ""])
|
||||
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 = [
|
||||
[
|
||||
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)
|
||||
]
|
||||
]
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
|
||||
# Monkey-patch async function
|
||||
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)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
|
||||
# Monkey-patch async function
|
||||
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.*"
|
||||
):
|
||||
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(
|
||||
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\..*",
|
||||
):
|
||||
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(
|
||||
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"}'
|
||||
)
|
||||
)
|
||||
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"))
|
||||
|
||||
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.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)
|
||||
assert "bids" 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()
|
||||
with pytest.raises(OperationalException):
|
||||
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)
|
||||
with pytest.raises(TemporaryError):
|
||||
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)
|
||||
with pytest.raises(OperationalException):
|
||||
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)
|
||||
|
||||
|
||||
@@ -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],
|
||||
[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)
|
||||
sort_mock = mocker.patch("freqtrade.exchange.exchange.sorted", MagicMock(side_effect=sort_data))
|
||||
# Test the OHLCV data sort
|
||||
@@ -3283,7 +3283,7 @@ async def test__async_fetch_trades(
|
||||
default_conf, mocker, caplog, exchange_name, fetch_trades_result
|
||||
):
|
||||
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
|
||||
exchange._api_async.fetch_trades = get_mock_coro(fetch_trades_result)
|
||||
|
||||
@@ -3337,7 +3337,7 @@ async def test__async_fetch_trades(
|
||||
api_mock = MagicMock()
|
||||
with pytest.raises(OperationalException, match=r"Could not fetch trade data*"):
|
||||
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)))
|
||||
exchange.close()
|
||||
|
||||
@@ -3346,7 +3346,7 @@ async def test__async_fetch_trades(
|
||||
match=r"Exchange.* does not support fetching " r"historical trade data\..*",
|
||||
):
|
||||
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)))
|
||||
exchange.close()
|
||||
|
||||
@@ -3358,7 +3358,7 @@ async def test__async_fetch_trades_contract_size(
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf["margin_mode"] = "isolated"
|
||||
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
|
||||
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(
|
||||
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":
|
||||
exchange.close()
|
||||
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):
|
||||
if exchange_name == "kraken":
|
||||
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
|
||||
|
||||
@@ -3479,7 +3479,7 @@ async def test__async_get_trade_history_time(
|
||||
return fetch_trades_result[-1:]
|
||||
|
||||
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":
|
||||
exchange.close()
|
||||
pytest.skip("Exchange does not support pagination by timestamp")
|
||||
@@ -3521,7 +3521,7 @@ async def test__async_get_trade_history_time_empty(
|
||||
return [], None
|
||||
|
||||
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
|
||||
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
pair = "ETH/BTC"
|
||||
@@ -3542,7 +3542,7 @@ async def test__async_get_trade_history_time_empty(
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history):
|
||||
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"
|
||||
|
||||
@@ -3573,7 +3573,7 @@ def test_get_historic_trades_notsupported(
|
||||
default_conf, mocker, caplog, exchange_name, trades_history
|
||||
):
|
||||
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"
|
||||
|
||||
@@ -3587,7 +3587,7 @@ def test_get_historic_trades_notsupported(
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
|
||||
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)
|
||||
assert exchange.cancel_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):
|
||||
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
|
||||
|
||||
|
||||
@@ -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):
|
||||
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
|
||||
|
||||
|
||||
@@ -3662,7 +3662,7 @@ def test_cancel_order_with_result(
|
||||
api_mock = MagicMock()
|
||||
api_mock.cancel_order = MagicMock(return_value=corder)
|
||||
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)
|
||||
assert isinstance(res, dict)
|
||||
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.cancel_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)
|
||||
assert isinstance(res, dict)
|
||||
@@ -3691,12 +3691,12 @@ def test_cancel_order(default_conf, mocker, exchange_name):
|
||||
default_conf["dry_run"] = False
|
||||
api_mock = MagicMock()
|
||||
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"}
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
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")
|
||||
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
|
||||
api_mock = MagicMock()
|
||||
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"}
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
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")
|
||||
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"
|
||||
mocker.patch(f"{EXMS}.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}
|
||||
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")
|
||||
mocker.patch(f"{EXMS}.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)
|
||||
|
||||
|
||||
@@ -3785,7 +3785,7 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
|
||||
order.symbol = "TKN/BTC"
|
||||
|
||||
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
|
||||
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
|
||||
api_mock = MagicMock()
|
||||
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 log_has(("API fetch_order: {'id': '123', 'amount': 2, 'symbol': 'TKN/BTC'}"), caplog)
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
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")
|
||||
assert api_mock.fetch_order.call_count == 1
|
||||
|
||||
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 pytest.raises(InvalidOrderException):
|
||||
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.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)
|
||||
exchange._dry_run_open_orders["X"] = order
|
||||
# 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(
|
||||
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 log_has(
|
||||
("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(
|
||||
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 log_has(
|
||||
("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):
|
||||
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"))
|
||||
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")
|
||||
assert api_mock.fetch_open_order.call_count == 1
|
||||
|
||||
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(
|
||||
mocker,
|
||||
@@ -3913,7 +3913,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
|
||||
mocker.patch(f"{EXMS}.exchange_has", return_value=True)
|
||||
order = MagicMock()
|
||||
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
|
||||
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
|
||||
api_mock = MagicMock()
|
||||
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"}
|
||||
if exchange_name == "okx":
|
||||
res = {"id": "123", "symbol": "TKN/BTC", "type": "stoploss"}
|
||||
@@ -3934,7 +3934,7 @@ def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
|
||||
return
|
||||
with pytest.raises(InvalidOrderException):
|
||||
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")
|
||||
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):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
|
||||
fetch_order_mock = MagicMock()
|
||||
fetch_stoploss_order_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@@ -3979,7 +3979,7 @@ def test_fetch_order_or_stoploss_order(default_conf, mocker):
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
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.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)
|
||||
assert len(orders) == 1
|
||||
@@ -4069,7 +4069,7 @@ def test_get_fee(default_conf, mocker, exchange_name):
|
||||
api_mock.calculate_fee = MagicMock(
|
||||
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)
|
||||
|
||||
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):
|
||||
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 .*"):
|
||||
exchange.create_stoploss(
|
||||
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):
|
||||
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}
|
||||
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):
|
||||
if exchange_name == "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")
|
||||
expected = exchange._ft_has["ohlcv_candle_limit"]
|
||||
for timeframe in timeframes:
|
||||
@@ -4538,7 +4538,7 @@ def test_market_is_tradable(
|
||||
) -> None:
|
||||
default_conf["trading_mode"] = trademode
|
||||
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 = {
|
||||
"symbol": market_symbol,
|
||||
"base": base,
|
||||
@@ -4809,7 +4809,7 @@ def test_get_funding_fees(default_conf_usdt, mocker, exchange_name, caplog):
|
||||
now = datetime.now(timezone.utc)
|
||||
default_conf_usdt["trading_mode"] = "futures"
|
||||
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)
|
||||
assert exchange.get_funding_fees("BTC/USDT:USDT", 1, False, now) == 0.0
|
||||
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})
|
||||
|
||||
# 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")
|
||||
unix_time = int(date_time.timestamp())
|
||||
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(
|
||||
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 (
|
||||
exchange._get_stake_amount_considering_leverage(stake_amount, leverage)
|
||||
== 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
|
||||
):
|
||||
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:
|
||||
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):
|
||||
default_conf["trading_mode"] = trading_mode
|
||||
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
|
||||
|
||||
|
||||
@@ -5005,7 +5005,7 @@ def test_get_max_leverage_from_margin(default_conf, mocker, pair, nominal_value,
|
||||
default_conf["margin_mode"] = "isolated"
|
||||
api_mock = MagicMock()
|
||||
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
|
||||
|
||||
|
||||
@@ -5022,7 +5022,7 @@ def test_calculate_funding_fees(
|
||||
default_conf, mocker, size, funding_rate, mark_price, funding_fee, kraken_fee, time_in_ratio
|
||||
):
|
||||
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))
|
||||
trade_date = timeframe_to_prev_date("1h", datetime.now(timezone.utc))
|
||||
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={"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"]))
|
||||
funding_fees = ex._fetch_and_calculate_funding_fees(
|
||||
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"
|
||||
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."):
|
||||
ex._fetch_and_calculate_funding_fees(
|
||||
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={"fetchFundingRateHistory": True})
|
||||
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")
|
||||
|
||||
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["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 (
|
||||
exchange.get_liquidation_price(
|
||||
pair="DOGE/USDT",
|
||||
@@ -5708,7 +5708,7 @@ def test_liquidation_price_binance(
|
||||
default_conf["trading_mode"] = trading_mode
|
||||
default_conf["margin_mode"] = margin_mode
|
||||
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))
|
||||
assert (
|
||||
pytest.approx(
|
||||
@@ -5858,7 +5858,7 @@ def test_load_leverage_tiers(mocker, default_conf, exchange_name):
|
||||
)
|
||||
|
||||
# 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() == {}
|
||||
|
||||
default_conf["trading_mode"] = "futures"
|
||||
@@ -5867,12 +5867,12 @@ def test_load_leverage_tiers(mocker, default_conf, exchange_name):
|
||||
if exchange_name != "binance":
|
||||
# FUTURES has.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() == {}
|
||||
|
||||
# FUTURES regular
|
||||
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() == {
|
||||
"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):
|
||||
# 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
|
||||
|
||||
# Test Futures
|
||||
default_conf["trading_mode"] = "futures"
|
||||
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
|
||||
|
||||
@@ -6054,7 +6054,7 @@ def test_get_max_leverage_futures(default_conf, mocker, leverage_tiers):
|
||||
def test__get_params(mocker, default_conf, exchange_name):
|
||||
api_mock = MagicMock()
|
||||
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}
|
||||
|
||||
params1 = {"test": True}
|
||||
@@ -6109,7 +6109,7 @@ def test__get_params(mocker, default_conf, exchange_name):
|
||||
|
||||
default_conf["trading_mode"] = "futures"
|
||||
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}
|
||||
|
||||
assert (
|
||||
@@ -6326,7 +6326,7 @@ def test_get_liquidation_price(
|
||||
default_conf_usdt["exchange"]["name"] = exchange_name
|
||||
default_conf_usdt["margin_mode"] = margin_mode
|
||||
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.name = exchange_name
|
||||
|
||||
@@ -56,11 +56,11 @@ async def test_exchangews_ohlcv(mocker):
|
||||
assert exchange_ws._klines_scheduled == set()
|
||||
|
||||
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_scheduled == {("ETH/BTC", "1m", CandleType.SPOT)}
|
||||
asyncio.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
assert ccxt_object.watch_ohlcv.call_count == 1
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
@@ -9,7 +9,7 @@ from tests.conftest import EXMS, get_patched_exchange
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
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()
|
||||
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["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(
|
||||
return_value={
|
||||
@@ -41,7 +41,7 @@ def test_fetch_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()
|
||||
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")]
|
||||
)
|
||||
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 = {
|
||||
"price": 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
|
||||
trades = exchange.get_trades_for_order("22255", "ETH/USDT:USDT", datetime.now(timezone.utc))
|
||||
trade = trades[0]
|
||||
|
||||
@@ -128,7 +128,7 @@ def test_create_stoploss_order_dry_run_htx(default_conf, mocker):
|
||||
|
||||
|
||||
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 = {
|
||||
"type": "stop",
|
||||
"price": 1500,
|
||||
|
||||
@@ -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}.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(
|
||||
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)
|
||||
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()
|
||||
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")]
|
||||
)
|
||||
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 = {
|
||||
"type": "market",
|
||||
"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):
|
||||
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
|
||||
|
||||
@@ -134,7 +134,7 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
|
||||
|
||||
|
||||
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 = {
|
||||
"type": "limit",
|
||||
"price": 1500,
|
||||
@@ -161,7 +161,7 @@ def test_kucoin_create_order(default_conf, mocker, side, ordertype, rate):
|
||||
default_conf["dry_run"] = False
|
||||
mocker.patch(f"{EXMS}.amount_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_margin_mode = MagicMock()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
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")
|
||||
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", 2001) == (0.015, 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):
|
||||
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")
|
||||
|
||||
default_conf["trading_mode"] = "futures"
|
||||
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
|
||||
|
||||
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):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="okx")
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
|
||||
exchange.net_only = mode == "net"
|
||||
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
|
||||
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
|
||||
exchange.trading_mode = TradingMode.FUTURES
|
||||
# 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["margin_mode"] = "isolated"
|
||||
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.margin_mode = MarginMode.ISOLATED
|
||||
exchange.markets = markets
|
||||
@@ -520,7 +520,7 @@ def test__set_leverage_okx(mocker, default_conf):
|
||||
default_conf["trading_mode"] = TradingMode.FUTURES
|
||||
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")
|
||||
assert api_mock.set_leverage.call_count == 1
|
||||
# Leverage is rounded to 3.
|
||||
@@ -554,7 +554,7 @@ def test_fetch_stoploss_order_okx(default_conf, mocker):
|
||||
api_mock = 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")
|
||||
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"
|
||||
|
||||
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"}))
|
||||
|
||||
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")]
|
||||
)
|
||||
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 = {
|
||||
"type": "stoploss",
|
||||
"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):
|
||||
exchange = get_patched_exchange(mocker, default_conf, id="okx")
|
||||
exchange = get_patched_exchange(mocker, default_conf, exchange="okx")
|
||||
|
||||
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):
|
||||
default_conf["trading_mode"] = "futures"
|
||||
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")
|
||||
|
||||
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)
|
||||
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
|
||||
assert exchange.fetch_orders("mocked", start_time) == []
|
||||
assert api_mock.fetch_orders.call_count == 0
|
||||
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):
|
||||
if endpoint == "fetchOrders":
|
||||
|
||||
@@ -21,8 +21,8 @@ from tests.exchange_online.conftest import EXCHANGE_WS_FIXTURE_TYPE
|
||||
@pytest.mark.longrun
|
||||
@pytest.mark.timeout(3 * 60)
|
||||
class TestCCXTExchangeWs:
|
||||
def test_ccxt_ohlcv(self, exchange_ws: EXCHANGE_WS_FIXTURE_TYPE, caplog, mocker):
|
||||
exch, exchangename, pair = exchange_ws
|
||||
def test_ccxt_watch_ohlcv(self, exchange_ws: EXCHANGE_WS_FIXTURE_TYPE, caplog, mocker):
|
||||
exch, _exchangename, pair = exchange_ws
|
||||
|
||||
assert exch._ws_async is not None
|
||||
timeframe = "1m"
|
||||
|
||||
@@ -915,7 +915,7 @@ def test_execute_entry(
|
||||
default_conf_usdt["margin_mode"] = margin_mode
|
||||
mocker.patch("freqtrade.exchange.gate.Gate.validate_ordertypes")
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker, id=exchange_name)
|
||||
patch_exchange(mocker, exchange=exchange_name)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
|
||||
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):
|
||||
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"]
|
||||
trade = Trade(
|
||||
pair="LTC/ETH",
|
||||
|
||||
@@ -4,7 +4,7 @@ from freqtrade.enums import CandleType
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected",
|
||||
"candle_type,expected",
|
||||
[
|
||||
("", CandleType.SPOT),
|
||||
("spot", CandleType.SPOT),
|
||||
@@ -17,17 +17,17 @@ from freqtrade.enums import CandleType
|
||||
("premiumIndex", CandleType.PREMIUMINDEX),
|
||||
],
|
||||
)
|
||||
def test_CandleType_from_string(input, expected):
|
||||
assert CandleType.from_string(input) == expected
|
||||
def test_CandleType_from_string(candle_type, expected):
|
||||
assert CandleType.from_string(candle_type) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,expected",
|
||||
"candle_type,expected",
|
||||
[
|
||||
("futures", CandleType.FUTURES),
|
||||
("spot", CandleType.SPOT),
|
||||
("margin", CandleType.SPOT),
|
||||
],
|
||||
)
|
||||
def test_CandleType_get_default(input, expected):
|
||||
assert CandleType.get_default(input) == expected
|
||||
def test_CandleType_get_default(candle_type, expected):
|
||||
assert CandleType.get_default(candle_type) == expected
|
||||
|
||||
@@ -291,9 +291,10 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
|
||||
"is_best": True,
|
||||
}
|
||||
)
|
||||
hyperopt._hyper_out.print()
|
||||
out, _err = capsys.readouterr()
|
||||
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"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||
|
||||
instance = LookaheadAnalysis(lookahead_conf, strategy_obj)
|
||||
instance.current_analysis = analysis
|
||||
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
lookahead_conf, [instance]
|
||||
)
|
||||
|
||||
@@ -163,14 +163,14 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||
analysis.false_exit_signals = 10
|
||||
instance = LookaheadAnalysis(lookahead_conf, strategy_obj)
|
||||
instance.current_analysis = analysis
|
||||
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
lookahead_conf, [instance]
|
||||
)
|
||||
assert data[0][2].__contains__("error")
|
||||
|
||||
# edit it into not showing an error
|
||||
instance.failed_bias_check = False
|
||||
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
lookahead_conf, [instance]
|
||||
)
|
||||
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("falseIndicator2")
|
||||
_table, _headers, data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
data = LookaheadAnalysisSubFunctions.text_table_lookahead_analysis_instances(
|
||||
lookahead_conf, [instance]
|
||||
)
|
||||
|
||||
@@ -193,7 +193,7 @@ def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf
|
||||
assert len(data) == 1
|
||||
|
||||
# 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]
|
||||
)
|
||||
assert len(data) == 3
|
||||
|
||||
@@ -59,7 +59,7 @@ def _backup_file(file: Path, copy_file: bool = False) -> None:
|
||||
copyfile(file_swp, file)
|
||||
|
||||
|
||||
def test_text_table_bt_results():
|
||||
def test_text_table_bt_results(capsys):
|
||||
results = pd.DataFrame(
|
||||
{
|
||||
"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(
|
||||
["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):
|
||||
@@ -434,7 +436,7 @@ def test_calc_streak(testdatadir):
|
||||
assert calc_streak(bt_data) == (7, 18)
|
||||
|
||||
|
||||
def test_text_table_exit_reason():
|
||||
def test_text_table_exit_reason(capsys):
|
||||
results = pd.DataFrame(
|
||||
{
|
||||
"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", 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():
|
||||
@@ -502,39 +509,42 @@ def test_generate_sell_reason_stats():
|
||||
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"
|
||||
bt_res_data = load_backtest_stats(filename)
|
||||
|
||||
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"])
|
||||
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["ETH/BTC"] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
|
||||
assert generate_edge_table(results).count("+") == 7
|
||||
assert generate_edge_table(results).count("| ETH/BTC |") == 1
|
||||
assert (
|
||||
generate_edge_table(results).count(
|
||||
"| Risk Reward Ratio | Required Risk Reward | Expectancy |"
|
||||
)
|
||||
== 1
|
||||
)
|
||||
generate_edge_table(results)
|
||||
text = capsys.readouterr().out
|
||||
assert re.search(r".* ETH/BTC .*", text)
|
||||
assert re.search(r".* Risk Reward Ratio .* Required Risk Reward .* Expectancy .*", text)
|
||||
|
||||
|
||||
def test_generate_periodic_breakdown_stats(testdatadir):
|
||||
|
||||
@@ -105,9 +105,7 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf
|
||||
|
||||
instance = RecursiveAnalysis(recursive_conf, strategy_obj)
|
||||
instance.dict_recursive = dict_diff
|
||||
_table, _headers, data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances(
|
||||
[instance]
|
||||
)
|
||||
data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances([instance])
|
||||
|
||||
# check row contents for a try that has too few signals
|
||||
assert data[0][0] == "rsi"
|
||||
@@ -118,9 +116,7 @@ def test_recursive_helper_text_table_recursive_analysis_instances(recursive_conf
|
||||
dict_diff = dict()
|
||||
instance = RecursiveAnalysis(recursive_conf, strategy_obj)
|
||||
instance.dict_recursive = dict_diff
|
||||
_table, _headers, data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances(
|
||||
[instance]
|
||||
)
|
||||
data = RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances([instance])
|
||||
assert len(data) == 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user