Merge branch 'develop' into no-ticker-2
This commit is contained in:
commit
51f52c8609
@ -23,7 +23,7 @@
|
|||||||
"ask_strategy":{
|
"ask_strategy":{
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
"order_book_min": 1,
|
"order_book_min": 1,
|
||||||
"order_book_max": 9,
|
"order_book_max": 1,
|
||||||
"use_sell_signal": true,
|
"use_sell_signal": true,
|
||||||
"sell_profit_only": false,
|
"sell_profit_only": false,
|
||||||
"ignore_roi_if_buy_signal": false
|
"ignore_roi_if_buy_signal": false
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"ask_strategy":{
|
"ask_strategy":{
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
"order_book_min": 1,
|
"order_book_min": 1,
|
||||||
"order_book_max": 9,
|
"order_book_max": 1,
|
||||||
"use_sell_signal": true,
|
"use_sell_signal": true,
|
||||||
"sell_profit_only": false,
|
"sell_profit_only": false,
|
||||||
"ignore_roi_if_buy_signal": false
|
"ignore_roi_if_buy_signal": false
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
"price_side": "ask",
|
"price_side": "ask",
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
"order_book_min": 1,
|
"order_book_min": 1,
|
||||||
"order_book_max": 9,
|
"order_book_max": 1,
|
||||||
"use_sell_signal": true,
|
"use_sell_signal": true,
|
||||||
"sell_profit_only": false,
|
"sell_profit_only": false,
|
||||||
"ignore_roi_if_buy_signal": false
|
"ignore_roi_if_buy_signal": false
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"ask_strategy":{
|
"ask_strategy":{
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
"order_book_min": 1,
|
"order_book_min": 1,
|
||||||
"order_book_max": 9,
|
"order_book_max": 1,
|
||||||
"use_sell_signal": true,
|
"use_sell_signal": true,
|
||||||
"sell_profit_only": false,
|
"sell_profit_only": false,
|
||||||
"ignore_roi_if_buy_signal": false
|
"ignore_roi_if_buy_signal": false
|
||||||
|
@ -536,8 +536,14 @@ The idea here is to place the sell order early, to be ahead in the queue.
|
|||||||
|
|
||||||
A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number.
|
A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number.
|
||||||
|
|
||||||
!!! Warning "Orderbook and stoploss_on_exchange"
|
!!! Warning "Order_book_max > 1 - increased risks for stoplosses!"
|
||||||
Using `ask_strategy.order_book_max` higher than 1 may increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed.
|
Using `ask_strategy.order_book_max` higher than 1 will increase the risk the stoploss on exchange is cancelled too early, since an eventual [stoploss on exchange](#understand-order_types) will be cancelled as soon as the order is placed.
|
||||||
|
Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss on exchange).
|
||||||
|
|
||||||
|
!!! Warning "Order_book_max > 1 in dry-run"
|
||||||
|
Using `ask_strategy.order_book_max` higher than 1 will result in improper dry-run results (significantly better than real orders executed on exchange), since dry-run assumes orders to be filled almost instantly.
|
||||||
|
It is therefore advised to not use this setting for dry-runs.
|
||||||
|
|
||||||
|
|
||||||
#### Sell price without Orderbook enabled
|
#### Sell price without Orderbook enabled
|
||||||
|
|
||||||
|
@ -160,6 +160,9 @@ So let's write the buy strategy using these values:
|
|||||||
dataframe['macd'], dataframe['macdsignal']
|
dataframe['macd'], dataframe['macdsignal']
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Check that volume is not 0
|
||||||
|
conditions.append(dataframe['volume'] > 0)
|
||||||
|
|
||||||
if conditions:
|
if conditions:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
reduce(lambda x, y: x & y, conditions),
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
@ -429,6 +429,7 @@ usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[--min-total-profit FLOAT]
|
[--min-total-profit FLOAT]
|
||||||
[--max-total-profit FLOAT] [--no-color]
|
[--max-total-profit FLOAT] [--no-color]
|
||||||
[--print-json] [--no-details]
|
[--print-json] [--no-details]
|
||||||
|
[--export-csv FILE]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@ -450,6 +451,8 @@ optional arguments:
|
|||||||
useful if you are redirecting output to a file.
|
useful if you are redirecting output to a file.
|
||||||
--print-json Print best result detailization in JSON format.
|
--print-json Print best result detailization in JSON format.
|
||||||
--no-details Do not print best epoch details.
|
--no-details Do not print best epoch details.
|
||||||
|
--export-csv FILE Export to CSV-File. This will disable table print.
|
||||||
|
Example: --export-csv hyperopt.csv
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
@ -458,9 +461,10 @@ Common arguments:
|
|||||||
details.
|
details.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
Specify configuration file (default: `config.json`).
|
Specify configuration file (default:
|
||||||
Multiple --config options may be used. Can be set to
|
`userdir/config.json` or `config.json` whichever
|
||||||
`-` to read config from stdin.
|
exists). Multiple --config options may be used. Can be
|
||||||
|
set to `-` to read config from stdin.
|
||||||
-d PATH, --datadir PATH
|
-d PATH, --datadir PATH
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
@ -69,7 +69,8 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
|
|||||||
"hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time",
|
"hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time",
|
||||||
"hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit",
|
"hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit",
|
||||||
"hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit",
|
"hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit",
|
||||||
"print_colorized", "print_json", "hyperopt_list_no_details"]
|
"print_colorized", "print_json", "hyperopt_list_no_details",
|
||||||
|
"export_csv"]
|
||||||
|
|
||||||
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
|
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
|
||||||
"print_json", "hyperopt_show_no_header"]
|
"print_json", "hyperopt_show_no_header"]
|
||||||
|
@ -221,6 +221,13 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
),
|
),
|
||||||
|
"export_csv": Arg(
|
||||||
|
'--export-csv',
|
||||||
|
help='Export to CSV-File.'
|
||||||
|
' This will disable table print.'
|
||||||
|
' Example: --export-csv hyperopt.csv',
|
||||||
|
metavar='FILE',
|
||||||
|
),
|
||||||
"hyperopt_jobs": Arg(
|
"hyperopt_jobs": Arg(
|
||||||
'-j', '--job-workers',
|
'-j', '--job-workers',
|
||||||
help='The number of concurrently running jobs for hyperoptimization '
|
help='The number of concurrently running jobs for hyperoptimization '
|
||||||
|
@ -21,6 +21,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
print_colorized = config.get('print_colorized', False)
|
print_colorized = config.get('print_colorized', False)
|
||||||
print_json = config.get('print_json', False)
|
print_json = config.get('print_json', False)
|
||||||
|
export_csv = config.get('export_csv', None)
|
||||||
no_details = config.get('hyperopt_list_no_details', False)
|
no_details = config.get('hyperopt_list_no_details', False)
|
||||||
no_header = False
|
no_header = False
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
|||||||
if print_colorized:
|
if print_colorized:
|
||||||
colorama_init(autoreset=True)
|
colorama_init(autoreset=True)
|
||||||
|
|
||||||
|
if not export_csv:
|
||||||
try:
|
try:
|
||||||
Hyperopt.print_result_table(config, trials, total_epochs,
|
Hyperopt.print_result_table(config, trials, total_epochs,
|
||||||
not filteroptions['only_best'], print_colorized, 0)
|
not filteroptions['only_best'], print_colorized, 0)
|
||||||
@ -60,6 +62,11 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
|||||||
results = sorted_trials[0]
|
results = sorted_trials[0]
|
||||||
Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header)
|
Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header)
|
||||||
|
|
||||||
|
if trials and export_csv:
|
||||||
|
Hyperopt.export_csv_file(
|
||||||
|
config, trials, total_epochs, not filteroptions['only_best'], export_csv
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -17,10 +17,15 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
|
|||||||
"""
|
"""
|
||||||
config = setup_utils_configuration(args, method)
|
config = setup_utils_configuration(args, method)
|
||||||
|
|
||||||
if method == RunMode.BACKTEST:
|
no_unlimited_runmodes = {
|
||||||
if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT:
|
RunMode.BACKTEST: 'backtesting',
|
||||||
raise DependencyException('stake amount could not be "%s" for backtesting' %
|
RunMode.HYPEROPT: 'hyperoptimization',
|
||||||
constants.UNLIMITED_STAKE_AMOUNT)
|
}
|
||||||
|
if (method in no_unlimited_runmodes.keys() and
|
||||||
|
config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT):
|
||||||
|
raise DependencyException(
|
||||||
|
f'The value of `stake_amount` cannot be set as "{constants.UNLIMITED_STAKE_AMOUNT}" '
|
||||||
|
f'for {no_unlimited_runmodes[method]}')
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -282,6 +282,9 @@ class Configuration:
|
|||||||
self._args_to_config(config, argname='print_json',
|
self._args_to_config(config, argname='print_json',
|
||||||
logstring='Parameter --print-json detected ...')
|
logstring='Parameter --print-json detected ...')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='export_csv',
|
||||||
|
logstring='Parameter --export-csv detected: {}')
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_jobs',
|
self._args_to_config(config, argname='hyperopt_jobs',
|
||||||
logstring='Parameter -j/--job-workers detected: {}')
|
logstring='Parameter -j/--job-workers detected: {}')
|
||||||
|
|
||||||
|
@ -145,27 +145,40 @@ class IDataHandler(ABC):
|
|||||||
if startup_candles > 0 and timerange_startup:
|
if startup_candles > 0 and timerange_startup:
|
||||||
timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles)
|
timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles)
|
||||||
|
|
||||||
df = self._ohlcv_load(pair, timeframe, timerange=timerange_startup)
|
pairdf = self._ohlcv_load(pair, timeframe,
|
||||||
if df.empty:
|
timerange=timerange_startup)
|
||||||
|
if self._check_empty_df(pairdf, pair, timeframe, warn_no_data):
|
||||||
|
return pairdf
|
||||||
|
else:
|
||||||
|
enddate = df.iloc[-1]['date']
|
||||||
|
|
||||||
|
if timerange_startup:
|
||||||
|
self._validate_pairdata(pair, pairdf, timerange_startup)
|
||||||
|
pairdf = trim_dataframe(pairdf, timerange_startup)
|
||||||
|
if self._check_empty_df(pairdf, pair, timeframe, warn_no_data):
|
||||||
|
return pairdf
|
||||||
|
|
||||||
|
# incomplete candles should only be dropped if we didn't trim the end beforehand.
|
||||||
|
pairdf = clean_ohlcv_dataframe(pairdf, timeframe,
|
||||||
|
pair=pair,
|
||||||
|
fill_missing=fill_missing,
|
||||||
|
drop_incomplete=(drop_incomplete and
|
||||||
|
enddate == pairdf.iloc[-1]['date']))
|
||||||
|
self._check_empty_df(pairdf, pair, timeframe, warn_no_data)
|
||||||
|
return pairdf
|
||||||
|
|
||||||
|
def _check_empty_df(self, pairdf: DataFrame, pair: str, timeframe: str, warn_no_data: bool):
|
||||||
|
"""
|
||||||
|
Warn on empty dataframe
|
||||||
|
"""
|
||||||
|
if pairdf.empty:
|
||||||
if warn_no_data:
|
if warn_no_data:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'No history data for pair: "{pair}", timeframe: {timeframe}. '
|
f'No history data for pair: "{pair}", timeframe: {timeframe}. '
|
||||||
'Use `freqtrade download-data` to download the data'
|
'Use `freqtrade download-data` to download the data'
|
||||||
)
|
)
|
||||||
return df
|
return True
|
||||||
else:
|
return False
|
||||||
enddate = df.iloc[-1]['date']
|
|
||||||
|
|
||||||
if timerange_startup:
|
|
||||||
self._validate_pairdata(pair, df, timerange_startup)
|
|
||||||
df = trim_dataframe(df, timerange_startup)
|
|
||||||
|
|
||||||
# incomplete candles should only be dropped if we didn't trim the end beforehand.
|
|
||||||
return clean_ohlcv_dataframe(df, timeframe,
|
|
||||||
pair=pair,
|
|
||||||
fill_missing=fill_missing,
|
|
||||||
drop_incomplete=(drop_incomplete and
|
|
||||||
enddate == df.iloc[-1]['date']))
|
|
||||||
|
|
||||||
def _validate_pairdata(self, pair, pairdata: DataFrame, timerange: TimeRange):
|
def _validate_pairdata(self, pair, pairdata: DataFrame, timerange: TimeRange):
|
||||||
"""
|
"""
|
||||||
|
@ -23,6 +23,8 @@ from joblib import (Parallel, cpu_count, delayed, dump, load,
|
|||||||
wrap_non_picklable_objects)
|
wrap_non_picklable_objects)
|
||||||
from pandas import DataFrame, json_normalize, isna
|
from pandas import DataFrame, json_normalize, isna
|
||||||
import tabulate
|
import tabulate
|
||||||
|
from os import path
|
||||||
|
import io
|
||||||
|
|
||||||
from freqtrade.data.converter import trim_dataframe
|
from freqtrade.data.converter import trim_dataframe
|
||||||
from freqtrade.data.history import get_timerange
|
from freqtrade.data.history import get_timerange
|
||||||
@ -330,10 +332,10 @@ class Hyperopt:
|
|||||||
lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs)
|
lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs)
|
||||||
)
|
)
|
||||||
trials['Avg profit'] = trials['Avg profit'].apply(
|
trials['Avg profit'] = trials['Avg profit'].apply(
|
||||||
lambda x: ('{:,.2f}%'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')
|
lambda x: '{:,.2f}%'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')
|
||||||
)
|
)
|
||||||
trials['Avg duration'] = trials['Avg duration'].apply(
|
trials['Avg duration'] = trials['Avg duration'].apply(
|
||||||
lambda x: ('{:,.1f} m'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')
|
lambda x: '{:,.1f} m'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')
|
||||||
)
|
)
|
||||||
trials['Objective'] = trials['Objective'].apply(
|
trials['Objective'] = trials['Objective'].apply(
|
||||||
lambda x: '{:,.5f}'.format(x).rjust(8, ' ') if x != 100000 else "N/A".rjust(8, ' ')
|
lambda x: '{:,.5f}'.format(x).rjust(8, ' ') if x != 100000 else "N/A".rjust(8, ' ')
|
||||||
@ -381,6 +383,62 @@ class Hyperopt:
|
|||||||
)
|
)
|
||||||
print(table)
|
print(table)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def export_csv_file(config: dict, results: list, total_epochs: int, highlight_best: bool,
|
||||||
|
csv_file: str) -> None:
|
||||||
|
"""
|
||||||
|
Log result to csv-file
|
||||||
|
"""
|
||||||
|
if not results:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Verification for overwrite
|
||||||
|
if path.isfile(csv_file):
|
||||||
|
logger.error("CSV-File already exists!")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
io.open(csv_file, 'w+').close()
|
||||||
|
except IOError:
|
||||||
|
logger.error("Filed to create CSV-File!")
|
||||||
|
return
|
||||||
|
|
||||||
|
trials = json_normalize(results, max_level=1)
|
||||||
|
trials['Best'] = ''
|
||||||
|
trials['Stake currency'] = config['stake_currency']
|
||||||
|
trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count',
|
||||||
|
'results_metrics.avg_profit', 'results_metrics.total_profit',
|
||||||
|
'Stake currency', 'results_metrics.profit', 'results_metrics.duration',
|
||||||
|
'loss', 'is_initial_point', 'is_best']]
|
||||||
|
trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', 'Stake currency',
|
||||||
|
'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best']
|
||||||
|
trials['is_profit'] = False
|
||||||
|
trials.loc[trials['is_initial_point'], 'Best'] = '*'
|
||||||
|
trials.loc[trials['is_best'], 'Best'] = 'Best'
|
||||||
|
trials.loc[trials['Total profit'] > 0, 'is_profit'] = True
|
||||||
|
trials['Epoch'] = trials['Epoch'].astype(str)
|
||||||
|
trials['Trades'] = trials['Trades'].astype(str)
|
||||||
|
|
||||||
|
trials['Total profit'] = trials['Total profit'].apply(
|
||||||
|
lambda x: '{:,.8f}'.format(x) if x != 0.0 else ""
|
||||||
|
)
|
||||||
|
trials['Profit'] = trials['Profit'].apply(
|
||||||
|
lambda x: '{:,.2f}'.format(x) if not isna(x) else ""
|
||||||
|
)
|
||||||
|
trials['Avg profit'] = trials['Avg profit'].apply(
|
||||||
|
lambda x: '{:,.2f}%'.format(x) if not isna(x) else ""
|
||||||
|
)
|
||||||
|
trials['Avg duration'] = trials['Avg duration'].apply(
|
||||||
|
lambda x: '{:,.1f} m'.format(x) if not isna(x) else ""
|
||||||
|
)
|
||||||
|
trials['Objective'] = trials['Objective'].apply(
|
||||||
|
lambda x: '{:,.5f}'.format(x) if x != 100000 else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit'])
|
||||||
|
trials.to_csv(csv_file, index=False, header=True, mode='w', encoding='UTF-8')
|
||||||
|
print("CSV-File created!")
|
||||||
|
|
||||||
def has_space(self, space: str) -> bool:
|
def has_space(self, space: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Tell if the space value is contained in the configuration
|
Tell if the space value is contained in the configuration
|
||||||
|
@ -36,7 +36,7 @@ class SharpeHyperOptLoss(IHyperOptLoss):
|
|||||||
expected_returns_mean = total_profit.sum() / days_period
|
expected_returns_mean = total_profit.sum() / days_period
|
||||||
up_stdev = np.std(total_profit)
|
up_stdev = np.std(total_profit)
|
||||||
|
|
||||||
if (np.std(total_profit) != 0.):
|
if up_stdev != 0:
|
||||||
sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365)
|
sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365)
|
||||||
else:
|
else:
|
||||||
# Define high (negative) sharpe ratio to be clear that this is NOT optimal.
|
# Define high (negative) sharpe ratio to be clear that this is NOT optimal.
|
||||||
|
@ -51,7 +51,7 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
|
|||||||
expected_returns_mean = total_profit.mean()
|
expected_returns_mean = total_profit.mean()
|
||||||
up_stdev = total_profit.std()
|
up_stdev = total_profit.std()
|
||||||
|
|
||||||
if (up_stdev != 0.):
|
if up_stdev != 0:
|
||||||
sharp_ratio = expected_returns_mean / up_stdev * math.sqrt(days_in_year)
|
sharp_ratio = expected_returns_mean / up_stdev * math.sqrt(days_in_year)
|
||||||
else:
|
else:
|
||||||
# Define high (negative) sharpe ratio to be clear that this is NOT optimal.
|
# Define high (negative) sharpe ratio to be clear that this is NOT optimal.
|
||||||
|
@ -39,7 +39,7 @@ class SortinoHyperOptLoss(IHyperOptLoss):
|
|||||||
results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent']
|
results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent']
|
||||||
down_stdev = np.std(results['downside_returns'])
|
down_stdev = np.std(results['downside_returns'])
|
||||||
|
|
||||||
if np.std(total_profit) != 0.0:
|
if down_stdev != 0:
|
||||||
sortino_ratio = expected_returns_mean / down_stdev * np.sqrt(365)
|
sortino_ratio = expected_returns_mean / down_stdev * np.sqrt(365)
|
||||||
else:
|
else:
|
||||||
# Define high (negative) sortino ratio to be clear that this is NOT optimal.
|
# Define high (negative) sortino ratio to be clear that this is NOT optimal.
|
||||||
|
@ -59,7 +59,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
|
|||||||
# where P = sum_daily["profit_percent_after_slippage"]
|
# where P = sum_daily["profit_percent_after_slippage"]
|
||||||
down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside))
|
down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside))
|
||||||
|
|
||||||
if (down_stdev != 0.):
|
if down_stdev != 0:
|
||||||
sortino_ratio = expected_returns_mean / down_stdev * math.sqrt(days_in_year)
|
sortino_ratio = expected_returns_mean / down_stdev * math.sqrt(days_in_year)
|
||||||
else:
|
else:
|
||||||
# Define high (negative) sortino ratio to be clear that this is NOT optimal.
|
# Define high (negative) sortino ratio to be clear that this is NOT optimal.
|
||||||
|
@ -67,21 +67,37 @@ class IPairList(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def verify_blacklist(pairlist: List[str], blacklist: List[str]) -> List[str]:
|
def verify_blacklist(pairlist: List[str], blacklist: List[str],
|
||||||
|
aswarning: bool) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Verify and remove items from pairlist - returning a filtered pairlist.
|
Verify and remove items from pairlist - returning a filtered pairlist.
|
||||||
|
Logs a warning or info depending on `aswarning`.
|
||||||
|
Pairlists explicitly using this method shall use `aswarning=False`!
|
||||||
|
:param pairlist: Pairlist to validate
|
||||||
|
:param blacklist: Blacklist to validate pairlist against
|
||||||
|
:param aswarning: Log message as Warning or info
|
||||||
|
:return: pairlist - blacklisted pairs
|
||||||
"""
|
"""
|
||||||
for pair in deepcopy(pairlist):
|
for pair in deepcopy(pairlist):
|
||||||
if pair in blacklist:
|
if pair in blacklist:
|
||||||
|
if aswarning:
|
||||||
logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...")
|
logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...")
|
||||||
|
else:
|
||||||
|
logger.info(f"Pair {pair} in your blacklist. Removing it from whitelist...")
|
||||||
pairlist.remove(pair)
|
pairlist.remove(pair)
|
||||||
return pairlist
|
return pairlist
|
||||||
|
|
||||||
def _verify_blacklist(self, pairlist: List[str]) -> List[str]:
|
def _verify_blacklist(self, pairlist: List[str], aswarning: bool = True) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Proxy method to verify_blacklist for easy access for child classes.
|
Proxy method to verify_blacklist for easy access for child classes.
|
||||||
|
Logs a warning or info depending on `aswarning`.
|
||||||
|
Pairlists explicitly using this method shall use aswarning=False!
|
||||||
|
:param pairlist: Pairlist to validate
|
||||||
|
:param aswarning: Log message as Warning or info.
|
||||||
|
:return: pairlist - blacklisted pairs
|
||||||
"""
|
"""
|
||||||
return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist)
|
return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist,
|
||||||
|
aswarning=aswarning)
|
||||||
|
|
||||||
def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]:
|
def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]:
|
||||||
"""
|
"""
|
||||||
@ -113,6 +129,5 @@ class IPairList(ABC):
|
|||||||
if pair not in sanitized_whitelist:
|
if pair not in sanitized_whitelist:
|
||||||
sanitized_whitelist.append(pair)
|
sanitized_whitelist.append(pair)
|
||||||
|
|
||||||
sanitized_whitelist = self._verify_blacklist(sanitized_whitelist)
|
|
||||||
# We need to remove pairs that are unknown
|
# We need to remove pairs that are unknown
|
||||||
return sanitized_whitelist
|
return sanitized_whitelist
|
||||||
|
@ -106,7 +106,7 @@ class VolumePairList(IPairList):
|
|||||||
|
|
||||||
# Validate whitelist to only have active market pairs
|
# Validate whitelist to only have active market pairs
|
||||||
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
|
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
|
||||||
pairs = self._verify_blacklist(pairs)
|
pairs = self._verify_blacklist(pairs, aswarning=False)
|
||||||
# Limit to X number of pairs
|
# Limit to X number of pairs
|
||||||
pairs = pairs[:self._number_pairs]
|
pairs = pairs[:self._number_pairs]
|
||||||
logger.info(f"Searching {self._number_pairs} pairs: {pairs}")
|
logger.info(f"Searching {self._number_pairs} pairs: {pairs}")
|
||||||
|
@ -91,6 +91,6 @@ class PairListManager():
|
|||||||
pairlist = pl.filter_pairlist(pairlist, tickers)
|
pairlist = pl.filter_pairlist(pairlist, tickers)
|
||||||
|
|
||||||
# Validation against blacklist happens after the pairlists to ensure blacklist is respected.
|
# Validation against blacklist happens after the pairlists to ensure blacklist is respected.
|
||||||
pairlist = IPairList.verify_blacklist(pairlist, self.blacklist)
|
pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True)
|
||||||
|
|
||||||
self._whitelist = pairlist
|
self._whitelist = pairlist
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"price_side": "ask",
|
"price_side": "ask",
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
"order_book_min": 1,
|
"order_book_min": 1,
|
||||||
"order_book_max": 9,
|
"order_book_max": 1,
|
||||||
"use_sell_signal": true,
|
"use_sell_signal": true,
|
||||||
"sell_profit_only": false,
|
"sell_profit_only": false,
|
||||||
"ignore_roi_if_buy_signal": false
|
"ignore_roi_if_buy_signal": false
|
||||||
|
@ -21,7 +21,7 @@ class {{ hyperopt }}(IHyperOpt):
|
|||||||
"""
|
"""
|
||||||
This is a Hyperopt template to get you started.
|
This is a Hyperopt template to get you started.
|
||||||
|
|
||||||
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md
|
More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/
|
||||||
|
|
||||||
You should:
|
You should:
|
||||||
- Add any lib you need to build your hyperopt.
|
- Add any lib you need to build your hyperopt.
|
||||||
@ -29,11 +29,14 @@ class {{ hyperopt }}(IHyperOpt):
|
|||||||
You must keep:
|
You must keep:
|
||||||
- The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator.
|
- The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator.
|
||||||
|
|
||||||
The roi_space, generate_roi_table, stoploss_space methods are no longer required to be
|
The methods roi_space, generate_roi_table and stoploss_space are not required
|
||||||
copied in every custom hyperopt. However, you may override them if you need the
|
and are provided by default.
|
||||||
'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade.
|
However, you may override them if you need 'roi' and 'stoploss' spaces that
|
||||||
Sample implementation of these methods can be found in
|
differ from the defaults offered by Freqtrade.
|
||||||
https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py
|
Sample implementation of these methods will be copied to `user_data/hyperopts` when
|
||||||
|
creating the user-data directory using `freqtrade create-userdir --userdir user_data`,
|
||||||
|
or is available online under the following URL:
|
||||||
|
https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -63,6 +66,9 @@ class {{ hyperopt }}(IHyperOpt):
|
|||||||
dataframe['close'], dataframe['sar']
|
dataframe['close'], dataframe['sar']
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Check that the candle had volume
|
||||||
|
conditions.append(dataframe['volume'] > 0)
|
||||||
|
|
||||||
if conditions:
|
if conditions:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
reduce(lambda x, y: x & y, conditions),
|
reduce(lambda x, y: x & y, conditions),
|
||||||
@ -108,6 +114,9 @@ class {{ hyperopt }}(IHyperOpt):
|
|||||||
dataframe['sar'], dataframe['close']
|
dataframe['sar'], dataframe['close']
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Check that the candle had volume
|
||||||
|
conditions.append(dataframe['volume'] > 0)
|
||||||
|
|
||||||
if conditions:
|
if conditions:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
reduce(lambda x, y: x & y, conditions),
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
@ -20,23 +20,28 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|||||||
class SampleHyperOpt(IHyperOpt):
|
class SampleHyperOpt(IHyperOpt):
|
||||||
"""
|
"""
|
||||||
This is a sample Hyperopt to inspire you.
|
This is a sample Hyperopt to inspire you.
|
||||||
Feel free to customize it.
|
|
||||||
|
|
||||||
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md
|
More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/
|
||||||
|
|
||||||
You should:
|
You should:
|
||||||
- Rename the class name to some unique name.
|
- Rename the class name to some unique name.
|
||||||
- Add any methods you want to build your hyperopt.
|
- Add any methods you want to build your hyperopt.
|
||||||
- Add any lib you need to build your hyperopt.
|
- Add any lib you need to build your hyperopt.
|
||||||
|
|
||||||
|
An easier way to get a new hyperopt file is by using
|
||||||
|
`freqtrade new-hyperopt --hyperopt MyCoolHyperopt`.
|
||||||
|
|
||||||
You must keep:
|
You must keep:
|
||||||
- The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator.
|
- The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator.
|
||||||
|
|
||||||
The roi_space, generate_roi_table, stoploss_space methods are no longer required to be
|
The methods roi_space, generate_roi_table and stoploss_space are not required
|
||||||
copied in every custom hyperopt. However, you may override them if you need the
|
and are provided by default.
|
||||||
'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade.
|
However, you may override them if you need 'roi' and 'stoploss' spaces that
|
||||||
Sample implementation of these methods can be found in
|
differ from the defaults offered by Freqtrade.
|
||||||
https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py
|
Sample implementation of these methods will be copied to `user_data/hyperopts` when
|
||||||
|
creating the user-data directory using `freqtrade create-userdir --userdir user_data`,
|
||||||
|
or is available online under the following URL:
|
||||||
|
https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -73,6 +78,9 @@ class SampleHyperOpt(IHyperOpt):
|
|||||||
dataframe['close'], dataframe['sar']
|
dataframe['close'], dataframe['sar']
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Check that volume is not 0
|
||||||
|
conditions.append(dataframe['volume'] > 0)
|
||||||
|
|
||||||
if conditions:
|
if conditions:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
reduce(lambda x, y: x & y, conditions),
|
reduce(lambda x, y: x & y, conditions),
|
||||||
@ -133,6 +141,9 @@ class SampleHyperOpt(IHyperOpt):
|
|||||||
dataframe['sar'], dataframe['close']
|
dataframe['sar'], dataframe['close']
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Check that volume is not 0
|
||||||
|
conditions.append(dataframe['volume'] > 0)
|
||||||
|
|
||||||
if conditions:
|
if conditions:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
reduce(lambda x, y: x & y, conditions),
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
@ -22,7 +22,7 @@ class AdvancedSampleHyperOpt(IHyperOpt):
|
|||||||
This is a sample hyperopt to inspire you.
|
This is a sample hyperopt to inspire you.
|
||||||
Feel free to customize it.
|
Feel free to customize it.
|
||||||
|
|
||||||
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md
|
More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/
|
||||||
|
|
||||||
You should:
|
You should:
|
||||||
- Rename the class name to some unique name.
|
- Rename the class name to some unique name.
|
||||||
@ -32,8 +32,9 @@ class AdvancedSampleHyperOpt(IHyperOpt):
|
|||||||
You must keep:
|
You must keep:
|
||||||
- The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator.
|
- The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator.
|
||||||
|
|
||||||
The roi_space, generate_roi_table, stoploss_space methods are no longer required to be
|
The methods roi_space, generate_roi_table and stoploss_space are not required
|
||||||
copied in every custom hyperopt. However, you may override them if you need the
|
and are provided by default.
|
||||||
|
However, you may override them if you need the
|
||||||
'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade.
|
'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade.
|
||||||
|
|
||||||
This sample illustrates how to override these methods.
|
This sample illustrates how to override these methods.
|
||||||
@ -92,6 +93,9 @@ class AdvancedSampleHyperOpt(IHyperOpt):
|
|||||||
dataframe['close'], dataframe['sar']
|
dataframe['close'], dataframe['sar']
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Check that volume is not 0
|
||||||
|
conditions.append(dataframe['volume'] > 0)
|
||||||
|
|
||||||
if conditions:
|
if conditions:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
reduce(lambda x, y: x & y, conditions),
|
reduce(lambda x, y: x & y, conditions),
|
||||||
@ -152,6 +156,9 @@ class AdvancedSampleHyperOpt(IHyperOpt):
|
|||||||
dataframe['sar'], dataframe['close']
|
dataframe['sar'], dataframe['close']
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Check that volume is not 0
|
||||||
|
conditions.append(dataframe['volume'] > 0)
|
||||||
|
|
||||||
if conditions:
|
if conditions:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
reduce(lambda x, y: x & y, conditions),
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# requirements without requirements installable via conda
|
# requirements without requirements installable via conda
|
||||||
# mainly used for Raspberry pi installs
|
# mainly used for Raspberry pi installs
|
||||||
ccxt==1.23.30
|
ccxt==1.23.81
|
||||||
SQLAlchemy==1.3.13
|
SQLAlchemy==1.3.13
|
||||||
python-telegram-bot==12.4.2
|
python-telegram-bot==12.4.2
|
||||||
arrow==0.15.5
|
arrow==0.15.5
|
||||||
cachetools==4.0.0
|
cachetools==4.0.0
|
||||||
requests==2.23.0
|
requests==2.23.0
|
||||||
urllib3==1.25.8
|
urllib3==1.25.8
|
||||||
wrapt==1.12.0
|
wrapt==1.12.1
|
||||||
jsonschema==3.2.0
|
jsonschema==3.2.0
|
||||||
TA-Lib==0.4.17
|
TA-Lib==0.4.17
|
||||||
tabulate==0.8.6
|
tabulate==0.8.6
|
||||||
@ -30,4 +30,4 @@ flask==1.1.1
|
|||||||
colorama==0.4.3
|
colorama==0.4.3
|
||||||
# Building config files interactively
|
# Building config files interactively
|
||||||
questionary==1.5.1
|
questionary==1.5.1
|
||||||
prompt-toolkit==3.0.3
|
prompt-toolkit==3.0.4
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# Required for hyperopt
|
# Required for hyperopt
|
||||||
scipy==1.4.1
|
scipy==1.4.1
|
||||||
scikit-learn==0.22.2
|
scikit-learn==0.22.2.post1
|
||||||
scikit-optimize==0.7.4
|
scikit-optimize==0.7.4
|
||||||
filelock==3.0.12
|
filelock==3.0.12
|
||||||
joblib==0.14.1
|
joblib==0.14.1
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Include all requirements to run the bot.
|
# Include all requirements to run the bot.
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
plotly==4.5.2
|
plotly==4.5.3
|
||||||
|
|
||||||
|
@ -902,6 +902,21 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results):
|
|||||||
assert all(x not in captured.out
|
assert all(x not in captured.out
|
||||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12"
|
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12"
|
||||||
" 9/12", " 10/12", " 11/12", " 12/12"])
|
" 9/12", " 10/12", " 11/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--no-details",
|
||||||
|
"--export-csv", "test_file.csv"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in ["CSV-File created!"])
|
||||||
|
f = Path("test_file.csv")
|
||||||
|
assert 'Best,1,2,-1.25%,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text()
|
||||||
|
assert f.is_file()
|
||||||
|
f.unlink()
|
||||||
|
|
||||||
|
|
||||||
def test_hyperopt_show(mocker, capsys, hyperopt_results):
|
def test_hyperopt_show(mocker, capsys, hyperopt_results):
|
||||||
|
@ -241,7 +241,7 @@ def test_setup_optimize_configuration_unlimited_stake_amount(mocker, default_con
|
|||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
]
|
]
|
||||||
|
|
||||||
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
with pytest.raises(DependencyException, match=r'.`stake_amount`.*'):
|
||||||
setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
|
setup_optimize_configuration(get_args(args), RunMode.BACKTEST)
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,10 +10,11 @@ import pytest
|
|||||||
from arrow import Arrow
|
from arrow import Arrow
|
||||||
from filelock import Timeout
|
from filelock import Timeout
|
||||||
|
|
||||||
|
from freqtrade import constants
|
||||||
from freqtrade.commands.optimize_commands import (setup_optimize_configuration,
|
from freqtrade.commands.optimize_commands import (setup_optimize_configuration,
|
||||||
start_hyperopt)
|
start_hyperopt)
|
||||||
from freqtrade.data.history import load_data
|
from freqtrade.data.history import load_data
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.optimize.default_hyperopt import DefaultHyperOpt
|
from freqtrade.optimize.default_hyperopt import DefaultHyperOpt
|
||||||
from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss
|
from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss
|
||||||
from freqtrade.optimize.hyperopt import Hyperopt
|
from freqtrade.optimize.hyperopt import Hyperopt
|
||||||
@ -158,6 +159,21 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
|||||||
assert log_has('Parameter --print-all detected ...', caplog)
|
assert log_has('Parameter --print-all detected ...', caplog)
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_hyperopt_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
|
||||||
|
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||||
|
|
||||||
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
|
args = [
|
||||||
|
'hyperopt',
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
|
]
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException, match=r'.`stake_amount`.*'):
|
||||||
|
setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
||||||
|
|
||||||
|
|
||||||
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
|
@ -240,8 +240,6 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):
|
|||||||
(['ETH/BTC', 'TKN/BTC', 'ETH/USDT'], "is not compatible with your stake currency"),
|
(['ETH/BTC', 'TKN/BTC', 'ETH/USDT'], "is not compatible with your stake currency"),
|
||||||
# BCH/BTC not available
|
# BCH/BTC not available
|
||||||
(['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"),
|
(['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"),
|
||||||
# BLK/BTC in blacklist
|
|
||||||
(['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "in your blacklist. Removing "),
|
|
||||||
# BTT/BTC is inactive
|
# BTT/BTC is inactive
|
||||||
(['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active")
|
(['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active")
|
||||||
])
|
])
|
||||||
|
Loading…
Reference in New Issue
Block a user