Merge pull request #2317 from hroff-1902/list-timeframes
Add list-timeframes subcommand
This commit is contained in:
commit
946b8c29d7
@ -12,17 +12,23 @@ This page explains the different parameters of the bot and how to run it.
|
|||||||
usage: freqtrade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
usage: freqtrade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||||
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
||||||
[--db-url PATH] [--sd-notify]
|
[--db-url PATH] [--sd-notify]
|
||||||
{backtesting,edge,hyperopt,create-userdir,list-exchanges} ...
|
{backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit}
|
||||||
|
...
|
||||||
|
|
||||||
Free, open source crypto trading bot
|
Free, open source crypto trading bot
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
{backtesting,edge,hyperopt,create-userdir,list-exchanges}
|
{backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit}
|
||||||
backtesting Backtesting module.
|
backtesting Backtesting module.
|
||||||
edge Edge module.
|
edge Edge module.
|
||||||
hyperopt Hyperopt module.
|
hyperopt Hyperopt module.
|
||||||
create-userdir Create user-data directory.
|
create-userdir Create user-data directory.
|
||||||
list-exchanges Print available exchanges.
|
list-exchanges Print available exchanges.
|
||||||
|
list-timeframes Print available ticker intervals (timeframes) for the
|
||||||
|
exchange.
|
||||||
|
download-data Download backtesting data.
|
||||||
|
plot-dataframe Plot candles with indicators.
|
||||||
|
plot-profit Generate plot showing profits.
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
|
56
docs/utils.md
Normal file
56
docs/utils.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Utility Subcommands
|
||||||
|
|
||||||
|
Besides the Live-Trade and Dry-Run run modes, the `backtesting`, `edge` and `hyperopt` optimization subcommands, and the `download-data` subcommand which prepares historical data, the bot contains a number of utility subcommands. They are described in this section.
|
||||||
|
|
||||||
|
## List Exchanges
|
||||||
|
|
||||||
|
Use the `list-exchanges` subcommand to see the exchanges available for the bot.
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: freqtrade list-exchanges [-h] [-1] [-a]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-1, --one-column Print exchanges in one column.
|
||||||
|
-a, --all Print all exchanges known to the ccxt library.
|
||||||
|
```
|
||||||
|
|
||||||
|
* Example: see exchanges available for the bot:
|
||||||
|
```
|
||||||
|
$ freqtrade list-exchanges
|
||||||
|
Exchanges available for Freqtrade: _1btcxe, acx, allcoin, bequant, bibox, binance, binanceje, binanceus, bitbank, bitfinex, bitfinex2, bitkk, bitlish, bitmart, bittrex, bitz, bleutrade, btcalpha, btcmarkets, btcturk, buda, cex, cobinhood, coinbaseprime, coinbasepro, coinex, cointiger, coss, crex24, digifinex, dsx, dx, ethfinex, fcoin, fcoinjp, gateio, gdax, gemini, hitbtc2, huobipro, huobiru, idex, kkex, kraken, kucoin, kucoin2, kuna, lbank, mandala, mercado, oceanex, okcoincny, okcoinusd, okex, okex3, poloniex, rightbtc, theocean, tidebit, upbit, zb
|
||||||
|
```
|
||||||
|
|
||||||
|
* Example: see all exchanges supported by the ccxt library (including 'bad' ones, i.e. those that are known to not work with Freqtrade):
|
||||||
|
```
|
||||||
|
$ freqtrade list-exchanges -a
|
||||||
|
All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpro, bcex, bequant, bibox, bigone, binance, binanceje, binanceus, bit2c, bitbank, bitbay, bitfinex, bitfinex2, bitflyer, bitforex, bithumb, bitkk, bitlish, bitmart, bitmex, bitso, bitstamp, bitstamp1, bittrex, bitz, bl3p, bleutrade, braziliex, btcalpha, btcbox, btcchina, btcmarkets, btctradeim, btctradeua, btcturk, buda, bxinth, cex, chilebit, cobinhood, coinbase, coinbaseprime, coinbasepro, coincheck, coinegg, coinex, coinexchange, coinfalcon, coinfloor, coingi, coinmarketcap, coinmate, coinone, coinspot, cointiger, coolcoin, coss, crex24, crypton, deribit, digifinex, dsx, dx, ethfinex, exmo, exx, fcoin, fcoinjp, flowbtc, foxbit, fybse, gateio, gdax, gemini, hitbtc, hitbtc2, huobipro, huobiru, ice3x, idex, independentreserve, indodax, itbit, kkex, kraken, kucoin, kucoin2, kuna, lakebtc, latoken, lbank, liquid, livecoin, luno, lykke, mandala, mercado, mixcoins, negociecoins, nova, oceanex, okcoincny, okcoinusd, okex, okex3, paymium, poloniex, rightbtc, southxchange, stronghold, surbitcoin, theocean, therock, tidebit, tidex, upbit, vaultoro, vbtc, virwox, xbtce, yobit, zaif, zb
|
||||||
|
```
|
||||||
|
|
||||||
|
## List Timeframes
|
||||||
|
|
||||||
|
Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange.
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: freqtrade list-timeframes [-h] [--exchange EXCHANGE] [-1]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
|
||||||
|
config is provided.
|
||||||
|
-1, --one-column Print exchanges in one column.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
* Example: see the timeframes for the 'binance' exchange, set in the configuration file:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ freqtrade -c config_binance.json list-timeframes
|
||||||
|
...
|
||||||
|
Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
|
||||||
|
```
|
||||||
|
|
||||||
|
* Example: enumerate exchanges available for Freqtrade and print timeframes supported by each of them:
|
||||||
|
```
|
||||||
|
$ for i in `freqtrade list-exchanges -1`; do freqtrade list-timeframes --exchange $i; done
|
||||||
|
```
|
@ -31,6 +31,8 @@ ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
|
|||||||
|
|
||||||
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]
|
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]
|
||||||
|
|
||||||
|
ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"]
|
||||||
|
|
||||||
ARGS_CREATE_USERDIR = ["user_data_dir"]
|
ARGS_CREATE_USERDIR = ["user_data_dir"]
|
||||||
|
|
||||||
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "exchange", "timeframes", "erase"]
|
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "exchange", "timeframes", "erase"]
|
||||||
@ -41,7 +43,7 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_
|
|||||||
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
||||||
"trade_source", "ticker_interval"]
|
"trade_source", "ticker_interval"]
|
||||||
|
|
||||||
NO_CONF_REQURIED = ["download-data", "plot-dataframe", "plot-profit"]
|
NO_CONF_REQURIED = ["download-data", "list-timeframes", "plot-dataframe", "plot-profit"]
|
||||||
|
|
||||||
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges"]
|
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges"]
|
||||||
|
|
||||||
@ -103,7 +105,8 @@ class Arguments:
|
|||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
|
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
|
||||||
from freqtrade.utils import start_create_userdir, start_download_data, start_list_exchanges
|
from freqtrade.utils import (start_create_userdir, start_download_data,
|
||||||
|
start_list_exchanges, start_list_timeframes)
|
||||||
|
|
||||||
subparsers = self.parser.add_subparsers(dest='subparser')
|
subparsers = self.parser.add_subparsers(dest='subparser')
|
||||||
|
|
||||||
@ -136,6 +139,14 @@ class Arguments:
|
|||||||
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
|
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
|
||||||
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
|
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
|
||||||
|
|
||||||
|
# Add list-timeframes subcommand
|
||||||
|
list_timeframes_cmd = subparsers.add_parser(
|
||||||
|
'list-timeframes',
|
||||||
|
help='Print available ticker intervals (timeframes) for the exchange.'
|
||||||
|
)
|
||||||
|
list_timeframes_cmd.set_defaults(func=start_list_timeframes)
|
||||||
|
self._build_args(optionlist=ARGS_LIST_TIMEFRAMES, parser=list_timeframes_cmd)
|
||||||
|
|
||||||
# Add download-data subcommand
|
# Add download-data subcommand
|
||||||
download_data_cmd = subparsers.add_parser(
|
download_data_cmd = subparsers.add_parser(
|
||||||
'download-data',
|
'download-data',
|
||||||
|
@ -203,6 +203,9 @@ class Exchange:
|
|||||||
|
|
||||||
logger.info('Using Exchange "%s"', self.name)
|
logger.info('Using Exchange "%s"', self.name)
|
||||||
|
|
||||||
|
# Check if timeframe is available
|
||||||
|
self.validate_timeframes(config.get('ticker_interval'))
|
||||||
|
|
||||||
# Converts the interval provided in minutes in config to seconds
|
# Converts the interval provided in minutes in config to seconds
|
||||||
self.markets_refresh_interval: int = exchange_config.get(
|
self.markets_refresh_interval: int = exchange_config.get(
|
||||||
"markets_refresh_interval", 60) * 60
|
"markets_refresh_interval", 60) * 60
|
||||||
@ -214,10 +217,6 @@ class Exchange:
|
|||||||
self.validate_ordertypes(config.get('order_types', {}))
|
self.validate_ordertypes(config.get('order_types', {}))
|
||||||
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
|
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
|
||||||
|
|
||||||
if config.get('ticker_interval'):
|
|
||||||
# Check if timeframe is available
|
|
||||||
self.validate_timeframes(config['ticker_interval'])
|
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""
|
"""
|
||||||
Destructor - clean up async stuff
|
Destructor - clean up async stuff
|
||||||
@ -269,6 +268,10 @@ class Exchange:
|
|||||||
"""exchange ccxt id"""
|
"""exchange ccxt id"""
|
||||||
return self._api.id
|
return self._api.id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timeframes(self) -> List[str]:
|
||||||
|
return list((self._api.timeframes or {}).keys())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def markets(self) -> Dict:
|
def markets(self) -> Dict:
|
||||||
"""exchange ccxt markets"""
|
"""exchange ccxt markets"""
|
||||||
@ -361,7 +364,7 @@ class Exchange:
|
|||||||
return pair
|
return pair
|
||||||
raise DependencyException(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
|
raise DependencyException(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
|
||||||
|
|
||||||
def validate_timeframes(self, timeframe: List[str]) -> None:
|
def validate_timeframes(self, timeframe: Optional[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Checks if ticker interval from config is a supported timeframe on the exchange
|
Checks if ticker interval from config is a supported timeframe on the exchange
|
||||||
"""
|
"""
|
||||||
@ -374,10 +377,9 @@ class Exchange:
|
|||||||
f"for the exchange \"{self.name}\" and this exchange "
|
f"for the exchange \"{self.name}\" and this exchange "
|
||||||
f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}")
|
f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}")
|
||||||
|
|
||||||
timeframes = self._api.timeframes
|
if timeframe and (timeframe not in self.timeframes):
|
||||||
if timeframe not in timeframes:
|
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'Invalid ticker {timeframe}, this Exchange supports {timeframes}')
|
f"Invalid ticker interval '{timeframe}'. This exchange supports: {self.timeframes}")
|
||||||
|
|
||||||
def validate_ordertypes(self, order_types: Dict) -> None:
|
def validate_ordertypes(self, order_types: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -99,3 +99,21 @@ def start_download_data(args: Dict[str, Any]) -> None:
|
|||||||
if pairs_not_available:
|
if pairs_not_available:
|
||||||
logger.info(f"Pairs [{','.join(pairs_not_available)}] not available "
|
logger.info(f"Pairs [{','.join(pairs_not_available)}] not available "
|
||||||
f"on exchange {config['exchange']['name']}.")
|
f"on exchange {config['exchange']['name']}.")
|
||||||
|
|
||||||
|
|
||||||
|
def start_list_timeframes(args: Dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
Print ticker intervals (timeframes) available on Exchange
|
||||||
|
"""
|
||||||
|
config = setup_utils_configuration(args, RunMode.OTHER)
|
||||||
|
# Do not use ticker_interval set in the config
|
||||||
|
config['ticker_interval'] = None
|
||||||
|
|
||||||
|
# Init exchange
|
||||||
|
exchange = ExchangeResolver(config['exchange']['name'], config).exchange
|
||||||
|
|
||||||
|
if args['print_one_column']:
|
||||||
|
print('\n'.join(exchange.timeframes))
|
||||||
|
else:
|
||||||
|
print(f"Timeframes available for the exchange `{config['exchange']['name']}`: "
|
||||||
|
f"{', '.join(exchange.timeframes)}")
|
||||||
|
@ -14,16 +14,17 @@ nav:
|
|||||||
- Data Downloading: data-download.md
|
- Data Downloading: data-download.md
|
||||||
- Backtesting: backtesting.md
|
- Backtesting: backtesting.md
|
||||||
- Hyperopt: hyperopt.md
|
- Hyperopt: hyperopt.md
|
||||||
- Edge positioning: edge.md
|
- Edge Positioning: edge.md
|
||||||
|
- Utility Subcommands: utils.md
|
||||||
- FAQ: faq.md
|
- FAQ: faq.md
|
||||||
- Data Analysis:
|
- Data Analysis:
|
||||||
- Jupyter Notebooks: data-analysis.md
|
- Jupyter Notebooks: data-analysis.md
|
||||||
- Strategy analysis: strategy_analysis_example.md
|
- Strategy analysis: strategy_analysis_example.md
|
||||||
- Plotting: plotting.md
|
- Plotting: plotting.md
|
||||||
- SQL Cheatsheet: sql_cheatsheet.md
|
- SQL Cheatsheet: sql_cheatsheet.md
|
||||||
- Sandbox testing: sandbox-testing.md
|
- Sandbox Testing: sandbox-testing.md
|
||||||
- Deprecated features: deprecated.md
|
- Deprecated Features: deprecated.md
|
||||||
- Contributors guide: developer.md
|
- Contributors Guide: developer.md
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
logo: 'images/logo.png'
|
logo: 'images/logo.png'
|
||||||
|
@ -409,7 +409,8 @@ def test_validate_timeframes_failed(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
|
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
|
||||||
with pytest.raises(OperationalException, match=r'Invalid ticker 3m, this Exchange supports.*'):
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"Invalid ticker interval '3m'. This exchange supports.*"):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ import pytest
|
|||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from freqtrade.utils import (setup_utils_configuration, start_create_userdir,
|
from freqtrade.utils import (setup_utils_configuration, start_create_userdir,
|
||||||
start_download_data, start_list_exchanges)
|
start_download_data, start_list_exchanges,
|
||||||
|
start_list_timeframes)
|
||||||
from tests.conftest import get_args, log_has, patch_exchange
|
from tests.conftest import get_args, log_has, patch_exchange
|
||||||
|
|
||||||
|
|
||||||
@ -73,6 +74,96 @@ def test_list_exchanges(capsys):
|
|||||||
assert re.search(r"^bitmex$", captured.out, re.MULTILINE)
|
assert re.search(r"^bitmex$", captured.out, re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_timeframes(mocker, capsys):
|
||||||
|
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.timeframes = {'1m': 'oneMin',
|
||||||
|
'5m': 'fiveMin',
|
||||||
|
'30m': 'thirtyMin',
|
||||||
|
'1h': 'hour',
|
||||||
|
'1d': 'day',
|
||||||
|
}
|
||||||
|
patch_exchange(mocker, api_mock=api_mock)
|
||||||
|
args = [
|
||||||
|
"list-timeframes",
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r"This command requires a configured exchange.*"):
|
||||||
|
start_list_timeframes(pargs)
|
||||||
|
|
||||||
|
# Test with --config config.json.example
|
||||||
|
args = [
|
||||||
|
'--config', 'config.json.example',
|
||||||
|
"list-timeframes",
|
||||||
|
]
|
||||||
|
start_list_timeframes(get_args(args))
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert re.match("Timeframes available for the exchange `bittrex`: "
|
||||||
|
"1m, 5m, 30m, 1h, 1d",
|
||||||
|
captured.out)
|
||||||
|
|
||||||
|
# Test with --exchange bittrex
|
||||||
|
args = [
|
||||||
|
"list-timeframes",
|
||||||
|
"--exchange", "bittrex",
|
||||||
|
]
|
||||||
|
start_list_timeframes(get_args(args))
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert re.match("Timeframes available for the exchange `bittrex`: "
|
||||||
|
"1m, 5m, 30m, 1h, 1d",
|
||||||
|
captured.out)
|
||||||
|
|
||||||
|
api_mock.timeframes = {'1m': '1m',
|
||||||
|
'5m': '5m',
|
||||||
|
'15m': '15m',
|
||||||
|
'30m': '30m',
|
||||||
|
'1h': '1h',
|
||||||
|
'6h': '6h',
|
||||||
|
'12h': '12h',
|
||||||
|
'1d': '1d',
|
||||||
|
'3d': '3d',
|
||||||
|
}
|
||||||
|
patch_exchange(mocker, api_mock=api_mock)
|
||||||
|
# Test with --exchange binance
|
||||||
|
args = [
|
||||||
|
"list-timeframes",
|
||||||
|
"--exchange", "binance",
|
||||||
|
]
|
||||||
|
start_list_timeframes(get_args(args))
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert re.match("Timeframes available for the exchange `binance`: "
|
||||||
|
"1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d, 3d",
|
||||||
|
captured.out)
|
||||||
|
|
||||||
|
# Test with --one-column
|
||||||
|
args = [
|
||||||
|
'--config', 'config.json.example',
|
||||||
|
"list-timeframes",
|
||||||
|
"--one-column",
|
||||||
|
]
|
||||||
|
start_list_timeframes(get_args(args))
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert re.search(r"^1m$", captured.out, re.MULTILINE)
|
||||||
|
assert re.search(r"^5m$", captured.out, re.MULTILINE)
|
||||||
|
assert re.search(r"^1h$", captured.out, re.MULTILINE)
|
||||||
|
assert re.search(r"^1d$", captured.out, re.MULTILINE)
|
||||||
|
|
||||||
|
# Test with --exchange binance --one-column
|
||||||
|
args = [
|
||||||
|
"list-timeframes",
|
||||||
|
"--exchange", "binance",
|
||||||
|
"--one-column",
|
||||||
|
]
|
||||||
|
start_list_timeframes(get_args(args))
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert re.search(r"^1m$", captured.out, re.MULTILINE)
|
||||||
|
assert re.search(r"^5m$", captured.out, re.MULTILINE)
|
||||||
|
assert re.search(r"^1h$", captured.out, re.MULTILINE)
|
||||||
|
assert re.search(r"^1d$", captured.out, re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
def test_create_datadir_failed(caplog):
|
def test_create_datadir_failed(caplog):
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
Loading…
Reference in New Issue
Block a user