2020-01-26 12:08:58 +00:00
|
|
|
import csv
|
|
|
|
import logging
|
|
|
|
import sys
|
2023-06-03 09:46:01 +00:00
|
|
|
from typing import Any, Dict, List, Union
|
2020-01-26 12:08:58 +00:00
|
|
|
|
|
|
|
import rapidjson
|
2020-09-28 17:39:41 +00:00
|
|
|
from colorama import Fore, Style
|
|
|
|
from colorama import init as colorama_init
|
2020-01-26 12:08:58 +00:00
|
|
|
from tabulate import tabulate
|
|
|
|
|
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
|
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
|
2023-06-03 09:46:01 +00:00
|
|
|
from freqtrade.types import ValidExchangesType
|
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-05-12 14:27:03 +00:00
|
|
|
exchanges = 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"]:
|
|
|
|
print("\n".join([e["name"] for e in exchanges]))
|
2020-01-26 12:08:58 +00:00
|
|
|
else:
|
2023-06-03 06:28:44 +00:00
|
|
|
headers = {
|
2024-05-12 14:27:03 +00:00
|
|
|
"name": "Exchange name",
|
|
|
|
"supported": "Supported",
|
|
|
|
"trade_modes": "Markets",
|
|
|
|
"comment": "Reason",
|
|
|
|
}
|
|
|
|
headers.update({"valid": "Valid"} if args["list_exchanges_all"] else {})
|
2023-06-03 06:28:44 +00:00
|
|
|
|
2023-06-03 09:46:01 +00:00
|
|
|
def build_entry(exchange: ValidExchangesType, valid: bool):
|
2024-05-12 14:27:03 +00:00
|
|
|
valid_entry = {"valid": exchange["valid"]} if valid else {}
|
2023-06-03 09:46:01 +00:00
|
|
|
result: Dict[str, Union[str, bool]] = {
|
2024-05-12 14:27:03 +00:00
|
|
|
"name": exchange["name"],
|
2023-06-03 06:28:44 +00:00
|
|
|
**valid_entry,
|
2024-05-12 14:27:03 +00:00
|
|
|
"supported": "Official" if exchange["supported"] else "",
|
|
|
|
"trade_modes": ", ".join(
|
|
|
|
(f"{a['margin_mode']} " if a["margin_mode"] else "") + a["trading_mode"]
|
|
|
|
for a in exchange["trade_modes"]
|
2023-06-03 09:46:01 +00:00
|
|
|
),
|
2024-05-12 14:27:03 +00:00
|
|
|
"comment": exchange["comment"],
|
2023-06-03 06:28:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
2024-05-12 14:27:03 +00:00
|
|
|
if args["list_exchanges_all"]:
|
2021-04-06 05:47:44 +00:00
|
|
|
print("All exchanges supported by the ccxt library:")
|
2023-06-03 06:28:44 +00:00
|
|
|
exchanges = [build_entry(e, True) for e in exchanges]
|
2020-01-26 12:08:58 +00:00
|
|
|
else:
|
2021-04-06 05:47:44 +00:00
|
|
|
print("Exchanges available for Freqtrade:")
|
2024-05-12 14:27:03 +00:00
|
|
|
exchanges = [build_entry(e, False) for e in exchanges if e["valid"] is not False]
|
2021-04-06 05:47:44 +00:00
|
|
|
|
2024-05-12 14:27:03 +00:00
|
|
|
print(
|
|
|
|
tabulate(
|
|
|
|
exchanges,
|
|
|
|
headers=headers,
|
|
|
|
)
|
|
|
|
)
|
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:
|
2020-02-14 19:28:49 +00:00
|
|
|
if print_colorized:
|
|
|
|
colorama_init(autoreset=True)
|
2020-02-15 17:43:11 +00:00
|
|
|
red = Fore.RED
|
|
|
|
yellow = Fore.YELLOW
|
|
|
|
reset = Style.RESET_ALL
|
|
|
|
else:
|
2024-05-12 14:27:03 +00:00
|
|
|
red = ""
|
|
|
|
yellow = ""
|
|
|
|
reset = ""
|
|
|
|
|
|
|
|
names = [s["name"] for s in objs]
|
|
|
|
objs_to_print = [
|
|
|
|
{
|
|
|
|
"name": s["name"] if s["name"] else "--",
|
|
|
|
"location": s["location_rel"],
|
|
|
|
"status": (
|
|
|
|
red + "LOAD FAILED" + reset
|
|
|
|
if s["class"] is None
|
|
|
|
else "OK"
|
|
|
|
if names.count(s["name"]) == 1
|
|
|
|
else yellow + "DUPLICATE NAME" + reset
|
|
|
|
),
|
|
|
|
}
|
|
|
|
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",
|
|
|
|
"buy-Params": len(s["hyperoptable"].get("buy", [])),
|
|
|
|
"sell-Params": len(s["hyperoptable"].get("sell", [])),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
print(tabulate(objs_to_print, headers="keys", tablefmt="psql", stralign="right"))
|
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:
|
|
|
|
# print data as a table, with the human-readable summary
|
|
|
|
print(f"{summary_str}:")
|
2024-05-12 14:27:03 +00:00
|
|
|
print(tabulate(tabular_data, headers="keys", tablefmt="psql", stralign="right"))
|
|
|
|
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)
|