2020-01-26 12:08:58 +00:00
|
|
|
import csv
|
|
|
|
import logging
|
|
|
|
import sys
|
2024-07-07 06:36:51 +00:00
|
|
|
from typing import Any, Dict, List, Union
|
2020-01-26 12:08:58 +00:00
|
|
|
|
|
|
|
import rapidjson
|
2024-07-06 10:59:50 +00:00
|
|
|
from rich.console import Console
|
|
|
|
from rich.table import Table
|
|
|
|
from rich.text import Text
|
2020-01-26 12:08:58 +00:00
|
|
|
|
2020-01-26 12:55:48 +00:00
|
|
|
from freqtrade.configuration import setup_utils_configuration
|
2021-06-08 19:20:35 +00:00
|
|
|
from freqtrade.enums import RunMode
|
2024-03-19 06:06:43 +00:00
|
|
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
2023-06-03 06:36:14 +00:00
|
|
|
from freqtrade.exchange import list_available_exchanges, market_is_active
|
2024-09-04 04:44:48 +00:00
|
|
|
from freqtrade.ft_types import ValidExchangesType
|
2021-07-12 12:08:01 +00:00
|
|
|
from freqtrade.misc import parse_db_uri_for_logging, plural
|
2020-01-26 12:08:58 +00:00
|
|
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
2024-07-06 16:26:36 +00:00
|
|
|
from freqtrade.util import print_rich_table
|
2020-01-26 12:08:58 +00:00
|
|
|
|
2020-09-28 17:39:41 +00:00
|
|
|
|
2020-01-26 12:08:58 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
def start_list_exchanges(args: Dict[str, Any]) -> None:
|
|
|
|
"""
|
|
|
|
Print available exchanges
|
|
|
|
:param args: Cli args from Arguments()
|
|
|
|
:return: None
|
|
|
|
"""
|
2024-07-06 10:59:50 +00:00
|
|
|
available_exchanges: List[ValidExchangesType] = list_available_exchanges(
|
|
|
|
args["list_exchanges_all"]
|
|
|
|
)
|
2021-04-06 05:47:44 +00:00
|
|
|
|
2024-05-12 14:27:03 +00:00
|
|
|
if args["print_one_column"]:
|
2024-08-03 16:30:21 +00:00
|
|
|
print("\n".join([e["classname"] for e in available_exchanges]))
|
2020-01-26 12:08:58 +00:00
|
|
|
else:
|
2024-07-06 10:59:50 +00:00
|
|
|
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):"
|
2023-06-03 06:28:44 +00:00
|
|
|
|
2024-07-06 10:59:50 +00:00
|
|
|
table = Table(title=title)
|
2023-06-03 06:28:44 +00:00
|
|
|
|
2024-07-06 10:59:50 +00:00
|
|
|
table.add_column("Exchange Name")
|
2024-08-18 09:36:34 +00:00
|
|
|
table.add_column("Class Name")
|
2024-07-06 10:59:50 +00:00
|
|
|
table.add_column("Markets")
|
|
|
|
table.add_column("Reason")
|
2023-06-03 06:28:44 +00:00
|
|
|
|
2024-07-06 10:59:50 +00:00
|
|
|
for exchange in available_exchanges:
|
2024-08-18 09:36:34 +00:00
|
|
|
name = Text(exchange["name"])
|
2024-07-06 10:59:50 +00:00
|
|
|
if exchange["supported"]:
|
2024-08-18 09:36:34 +00:00
|
|
|
name.append(" (Supported)", style="italic")
|
2024-07-06 10:59:50 +00:00
|
|
|
name.stylize("green bold")
|
2024-08-18 09:36:34 +00:00
|
|
|
classname = Text(exchange["classname"])
|
|
|
|
if exchange["is_alias"]:
|
|
|
|
name.stylize("strike")
|
|
|
|
classname.stylize("strike")
|
|
|
|
classname.append(f" (use {exchange['alias_for']})", style="italic")
|
2024-07-06 10:59:50 +00:00
|
|
|
|
|
|
|
trade_modes = Text(
|
|
|
|
", ".join(
|
2024-07-06 16:09:03 +00:00
|
|
|
(f"{a.get('margin_mode', '')} {a['trading_mode']}").lstrip()
|
2024-07-06 10:59:50 +00:00
|
|
|
for a in exchange["trade_modes"]
|
|
|
|
),
|
|
|
|
style="",
|
|
|
|
)
|
|
|
|
if exchange["dex"]:
|
|
|
|
trade_modes = Text("DEX: ") + trade_modes
|
|
|
|
trade_modes.stylize("bold", 0, 3)
|
2021-04-06 05:47:44 +00:00
|
|
|
|
2024-07-06 10:59:50 +00:00
|
|
|
table.add_row(
|
|
|
|
name,
|
2024-08-18 09:36:34 +00:00
|
|
|
classname,
|
2024-07-06 10:59:50 +00:00
|
|
|
trade_modes,
|
|
|
|
exchange["comment"],
|
|
|
|
style=None if exchange["valid"] else "red",
|
2024-05-12 14:27:03 +00:00
|
|
|
)
|
2024-07-06 10:59:50 +00:00
|
|
|
# table.add_row(*[exchange[header] for header in headers])
|
|
|
|
|
|
|
|
console = Console()
|
|
|
|
console.print(table)
|
2020-01-26 12:08:58 +00:00
|
|
|
|
|
|
|
|
2022-10-14 14:32:30 +00:00
|
|
|
def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
|
2024-05-12 14:27:03 +00:00
|
|
|
names = [s["name"] for s in objs]
|
2024-07-07 06:36:51 +00:00
|
|
|
objs_to_print: List[Dict[str, Union[Text, str]]] = [
|
2024-05-12 14:27:03 +00:00
|
|
|
{
|
2024-07-06 16:09:03 +00:00
|
|
|
"name": Text(s["name"] if s["name"] else "--"),
|
2024-07-07 06:36:51 +00:00
|
|
|
"location": s["location_rel"],
|
2024-05-12 14:27:03 +00:00
|
|
|
"status": (
|
2024-07-06 16:09:03 +00:00
|
|
|
Text("LOAD FAILED", style="bold red")
|
2024-05-12 14:27:03 +00:00
|
|
|
if s["class"] is None
|
2024-07-06 16:09:03 +00:00
|
|
|
else Text("OK", style="bold green")
|
2024-05-12 14:27:03 +00:00
|
|
|
if names.count(s["name"]) == 1
|
2024-07-06 16:09:03 +00:00
|
|
|
else Text("DUPLICATE NAME", style="bold yellow")
|
2024-05-12 14:27:03 +00:00
|
|
|
),
|
|
|
|
}
|
|
|
|
for s in objs
|
|
|
|
]
|
2021-05-29 11:02:18 +00:00
|
|
|
for idx, s in enumerate(objs):
|
2024-05-12 14:27:03 +00:00
|
|
|
if "hyperoptable" in s:
|
|
|
|
objs_to_print[idx].update(
|
|
|
|
{
|
|
|
|
"hyperoptable": "Yes" if s["hyperoptable"]["count"] > 0 else "No",
|
2024-07-06 16:09:03 +00:00
|
|
|
"buy-Params": str(len(s["hyperoptable"].get("buy", []))),
|
|
|
|
"sell-Params": str(len(s["hyperoptable"].get("sell", []))),
|
2024-05-12 14:27:03 +00:00
|
|
|
}
|
|
|
|
)
|
2024-07-07 06:36:51 +00:00
|
|
|
table = Table()
|
2024-07-06 16:09:03 +00:00
|
|
|
|
|
|
|
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)
|
2020-02-14 18:24:30 +00:00
|
|
|
|
|
|
|
|
2020-01-26 12:08:58 +00:00
|
|
|
def start_list_strategies(args: Dict[str, Any]) -> None:
|
|
|
|
"""
|
2020-02-02 15:48:29 +00:00
|
|
|
Print files with Strategy custom classes available in the directory
|
2020-01-26 12:08:58 +00:00
|
|
|
"""
|
|
|
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
|
|
|
|
2022-04-23 07:11:50 +00:00
|
|
|
strategy_objs = StrategyResolver.search_all_objects(
|
2024-05-12 14:27:03 +00:00
|
|
|
config, not args["print_one_column"], config.get("recursive_strategy_search", False)
|
|
|
|
)
|
2020-01-26 12:08:58 +00:00
|
|
|
# Sort alphabetically
|
2024-05-12 14:27:03 +00:00
|
|
|
strategy_objs = sorted(strategy_objs, key=lambda x: x["name"])
|
2021-05-29 11:02:18 +00:00
|
|
|
for obj in strategy_objs:
|
2024-05-12 14:27:03 +00:00
|
|
|
if obj["class"]:
|
|
|
|
obj["hyperoptable"] = obj["class"].detect_all_parameters()
|
2021-05-29 11:02:18 +00:00
|
|
|
else:
|
2024-05-12 14:27:03 +00:00
|
|
|
obj["hyperoptable"] = {"count": 0}
|
2020-01-26 12:08:58 +00:00
|
|
|
|
2024-05-12 14:27:03 +00:00
|
|
|
if args["print_one_column"]:
|
|
|
|
print("\n".join([s["name"] for s in strategy_objs]))
|
2020-01-26 12:08:58 +00:00
|
|
|
else:
|
2024-05-12 14:27:03 +00:00
|
|
|
_print_objs_tabular(strategy_objs, config.get("print_colorized", False))
|
2020-01-26 12:08:58 +00:00
|
|
|
|
|
|
|
|
2022-10-14 16:20:49 +00:00
|
|
|
def start_list_freqAI_models(args: Dict[str, Any]) -> None:
|
|
|
|
"""
|
|
|
|
Print files with FreqAI models custom classes available in the directory
|
|
|
|
"""
|
|
|
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
|
|
|
from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
|
2024-05-12 14:27:03 +00:00
|
|
|
|
|
|
|
model_objs = FreqaiModelResolver.search_all_objects(config, not args["print_one_column"])
|
2022-10-14 16:20:49 +00:00
|
|
|
# Sort alphabetically
|
2024-05-12 14:27:03 +00:00
|
|
|
model_objs = sorted(model_objs, key=lambda x: x["name"])
|
|
|
|
if args["print_one_column"]:
|
|
|
|
print("\n".join([s["name"] for s in model_objs]))
|
2022-10-14 16:20:49 +00:00
|
|
|
else:
|
2024-05-12 14:27:03 +00:00
|
|
|
_print_objs_tabular(model_objs, config.get("print_colorized", False))
|
2022-10-14 16:20:49 +00:00
|
|
|
|
|
|
|
|
2020-01-26 12:08:58 +00:00
|
|
|
def start_list_timeframes(args: Dict[str, Any]) -> None:
|
|
|
|
"""
|
2021-04-03 14:54:47 +00:00
|
|
|
Print timeframes available on Exchange
|
2020-01-26 12:08:58 +00:00
|
|
|
"""
|
|
|
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
2020-06-01 18:33:26 +00:00
|
|
|
# Do not use timeframe set in the config
|
2024-05-12 14:27:03 +00:00
|
|
|
config["timeframe"] = None
|
2020-01-26 12:08:58 +00:00
|
|
|
|
|
|
|
# Init exchange
|
2023-05-13 06:27:27 +00:00
|
|
|
exchange = ExchangeResolver.load_exchange(config, validate=False)
|
2020-01-26 12:08:58 +00:00
|
|
|
|
2024-05-12 14:27:03 +00:00
|
|
|
if args["print_one_column"]:
|
|
|
|
print("\n".join(exchange.timeframes))
|
2020-01-26 12:08:58 +00:00
|
|
|
else:
|
2024-05-12 14:27:03 +00:00
|
|
|
print(
|
|
|
|
f"Timeframes available for the exchange `{exchange.name}`: "
|
|
|
|
f"{', '.join(exchange.timeframes)}"
|
|
|
|
)
|
2020-01-26 12:08:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
|
|
|
|
"""
|
|
|
|
Print pairs/markets on the exchange
|
|
|
|
:param args: Cli args from Arguments()
|
|
|
|
:param pairs_only: if True print only pairs, otherwise print all instruments (markets)
|
|
|
|
:return: None
|
|
|
|
"""
|
|
|
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
|
|
|
|
|
|
|
# Init exchange
|
2023-05-13 06:27:27 +00:00
|
|
|
exchange = ExchangeResolver.load_exchange(config, validate=False)
|
2020-01-26 12:08:58 +00:00
|
|
|
|
|
|
|
# By default only active pairs/markets are to be shown
|
2024-05-12 14:27:03 +00:00
|
|
|
active_only = not args.get("list_pairs_all", False)
|
2020-01-26 12:08:58 +00:00
|
|
|
|
2024-05-12 14:27:03 +00:00
|
|
|
base_currencies = args.get("base_currencies", [])
|
|
|
|
quote_currencies = args.get("quote_currencies", [])
|
2020-01-26 12:08:58 +00:00
|
|
|
|
|
|
|
try:
|
2024-05-12 14:27:03 +00:00
|
|
|
pairs = exchange.get_markets(
|
|
|
|
base_currencies=base_currencies,
|
|
|
|
quote_currencies=quote_currencies,
|
|
|
|
tradable_only=pairs_only,
|
|
|
|
active_only=active_only,
|
|
|
|
)
|
2020-01-26 12:08:58 +00:00
|
|
|
# Sort the pairs/markets by symbol
|
2021-06-13 09:45:23 +00:00
|
|
|
pairs = dict(sorted(pairs.items()))
|
2020-01-26 12:08:58 +00:00
|
|
|
except Exception as e:
|
|
|
|
raise OperationalException(f"Cannot get markets. Reason: {e}") from e
|
|
|
|
|
|
|
|
else:
|
2024-05-12 14:27:03 +00:00
|
|
|
summary_str = (
|
|
|
|
(f"Exchange {exchange.name} has {len(pairs)} ")
|
|
|
|
+ ("active " if active_only else "")
|
|
|
|
+ (plural(len(pairs), "pair" if pairs_only else "market"))
|
|
|
|
+ (
|
|
|
|
f" with {', '.join(base_currencies)} as base "
|
|
|
|
f"{plural(len(base_currencies), 'currency', 'currencies')}"
|
|
|
|
if base_currencies
|
|
|
|
else ""
|
|
|
|
)
|
|
|
|
+ (" and" if base_currencies and quote_currencies else "")
|
|
|
|
+ (
|
|
|
|
f" with {', '.join(quote_currencies)} as quote "
|
|
|
|
f"{plural(len(quote_currencies), 'currency', 'currencies')}"
|
|
|
|
if quote_currencies
|
|
|
|
else ""
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
headers = [
|
|
|
|
"Id",
|
|
|
|
"Symbol",
|
|
|
|
"Base",
|
|
|
|
"Quote",
|
|
|
|
"Active",
|
|
|
|
"Spot",
|
|
|
|
"Margin",
|
|
|
|
"Future",
|
|
|
|
"Leverage",
|
|
|
|
]
|
|
|
|
|
|
|
|
tabular_data = [
|
|
|
|
{
|
|
|
|
"Id": v["id"],
|
|
|
|
"Symbol": v["symbol"],
|
|
|
|
"Base": v["base"],
|
|
|
|
"Quote": v["quote"],
|
|
|
|
"Active": market_is_active(v),
|
|
|
|
"Spot": "Spot" if exchange.market_is_spot(v) else "",
|
|
|
|
"Margin": "Margin" if exchange.market_is_margin(v) else "",
|
|
|
|
"Future": "Future" if exchange.market_is_future(v) else "",
|
|
|
|
"Leverage": exchange.get_max_leverage(v["symbol"], 20),
|
|
|
|
}
|
|
|
|
for _, v in pairs.items()
|
|
|
|
]
|
|
|
|
|
|
|
|
if (
|
|
|
|
args.get("print_one_column", False)
|
|
|
|
or args.get("list_pairs_print_json", False)
|
|
|
|
or args.get("print_csv", False)
|
|
|
|
):
|
2020-01-26 12:08:58 +00:00
|
|
|
# Print summary string in the log in case of machine-readable
|
|
|
|
# regular formats.
|
|
|
|
logger.info(f"{summary_str}.")
|
|
|
|
else:
|
|
|
|
# Print empty string separating leading logs and output in case of
|
|
|
|
# human-readable formats.
|
|
|
|
print()
|
|
|
|
|
2021-03-21 11:44:32 +00:00
|
|
|
if pairs:
|
2024-05-12 14:27:03 +00:00
|
|
|
if args.get("print_list", False):
|
2020-01-26 12:08:58 +00:00
|
|
|
# print data as a list, with human-readable summary
|
|
|
|
print(f"{summary_str}: {', '.join(pairs.keys())}.")
|
2024-05-12 14:27:03 +00:00
|
|
|
elif args.get("print_one_column", False):
|
|
|
|
print("\n".join(pairs.keys()))
|
|
|
|
elif args.get("list_pairs_print_json", False):
|
2020-01-26 12:08:58 +00:00
|
|
|
print(rapidjson.dumps(list(pairs.keys()), default=str))
|
2024-05-12 14:27:03 +00:00
|
|
|
elif args.get("print_csv", False):
|
2020-01-26 12:08:58 +00:00
|
|
|
writer = csv.DictWriter(sys.stdout, fieldnames=headers)
|
|
|
|
writer.writeheader()
|
|
|
|
writer.writerows(tabular_data)
|
|
|
|
else:
|
2024-07-06 16:30:48 +00:00
|
|
|
print_rich_table(tabular_data, headers, summary_str)
|
2024-05-12 14:27:03 +00:00
|
|
|
elif not (
|
|
|
|
args.get("print_one_column", False)
|
|
|
|
or args.get("list_pairs_print_json", False)
|
|
|
|
or args.get("print_csv", False)
|
|
|
|
):
|
2020-01-26 12:08:58 +00:00
|
|
|
print(f"{summary_str}.")
|
2020-05-02 09:26:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
def start_show_trades(args: Dict[str, Any]) -> None:
|
|
|
|
"""
|
|
|
|
Show trades
|
|
|
|
"""
|
|
|
|
import json
|
2020-09-28 17:39:41 +00:00
|
|
|
|
2020-10-16 05:39:12 +00:00
|
|
|
from freqtrade.persistence import Trade, init_db
|
2024-05-12 14:27:03 +00:00
|
|
|
|
2020-05-02 09:26:12 +00:00
|
|
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
2020-05-05 17:48:28 +00:00
|
|
|
|
2024-05-12 14:27:03 +00:00
|
|
|
if "db_url" not in config:
|
2024-03-19 06:06:43 +00:00
|
|
|
raise ConfigurationError("--db-url is required for this command.")
|
2020-05-05 17:48:28 +00:00
|
|
|
|
2021-07-12 12:08:01 +00:00
|
|
|
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
|
2024-05-12 14:27:03 +00:00
|
|
|
init_db(config["db_url"])
|
2020-05-02 09:26:12 +00:00
|
|
|
tfilter = []
|
|
|
|
|
2024-05-12 14:27:03 +00:00
|
|
|
if config.get("trade_ids"):
|
|
|
|
tfilter.append(Trade.id.in_(config["trade_ids"]))
|
2020-05-02 09:26:12 +00:00
|
|
|
|
2020-05-02 09:44:18 +00:00
|
|
|
trades = Trade.get_trades(tfilter).all()
|
|
|
|
logger.info(f"Printing {len(trades)} Trades: ")
|
2024-05-12 14:27:03 +00:00
|
|
|
if config.get("print_json", False):
|
2020-05-02 09:26:12 +00:00
|
|
|
print(json.dumps([trade.to_json() for trade in trades], indent=4))
|
|
|
|
else:
|
|
|
|
for trade in trades:
|
|
|
|
print(trade)
|