Compare commits
17 Commits
feat/kvsto
...
feat/hyper
Author | SHA1 | Date | |
---|---|---|---|
|
cf770d496b | ||
|
bfd9e35e34 | ||
|
299e788891 | ||
|
4c1de4ad56 | ||
|
ed57e7d43b | ||
|
818d18d4e0 | ||
|
b6aac5079b | ||
|
40450ebecc | ||
|
d532da9071 | ||
|
df51111c33 | ||
|
dd8900a1c6 | ||
|
9c2cdd4fb9 | ||
|
c2c97d9f78 | ||
|
355fde3bca | ||
|
3cabcabcbd | ||
|
55781e7f10 | ||
|
f1e831a7b8 |
@@ -274,19 +274,20 @@ A backtesting result will look like that:
|
|||||||
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
||||||
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
||||||
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
||||||
========================================================= EXIT REASON STATS ==========================================================
|
|
||||||
| Exit Reason | Exits | Wins | Draws | Losses |
|
|
||||||
|:-------------------|--------:|------:|-------:|--------:|
|
|
||||||
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
|
||||||
| stop_loss | 166 | 0 | 0 | 166 |
|
|
||||||
| exit_signal | 56 | 36 | 0 | 20 |
|
|
||||||
| force_exit | 2 | 0 | 0 | 2 |
|
|
||||||
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
||||||
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
||||||
|:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
|:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
||||||
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
||||||
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
||||||
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
||||||
|
==================== EXIT REASON STATS ====================
|
||||||
|
| Exit Reason | Exits | Wins | Draws | Losses |
|
||||||
|
|:-------------------|--------:|------:|-------:|--------:|
|
||||||
|
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
||||||
|
| stop_loss | 166 | 0 | 0 | 166 |
|
||||||
|
| exit_signal | 56 | 36 | 0 | 20 |
|
||||||
|
| force_exit | 2 | 0 | 0 | 2 |
|
||||||
|
|
||||||
================== SUMMARY METRICS ==================
|
================== SUMMARY METRICS ==================
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
|-----------------------------+---------------------|
|
|-----------------------------+---------------------|
|
||||||
|
@@ -279,7 +279,6 @@ Return a summary of your profit/loss and performance.
|
|||||||
> ∙ `33.095 EUR`
|
> ∙ `33.095 EUR`
|
||||||
>
|
>
|
||||||
> **Total Trade Count:** `138`
|
> **Total Trade Count:** `138`
|
||||||
> **Bot started:** `2022-07-11 18:40:44`
|
|
||||||
> **First Trade opened:** `3 days ago`
|
> **First Trade opened:** `3 days ago`
|
||||||
> **Latest Trade opened:** `2 minutes ago`
|
> **Latest Trade opened:** `2 minutes ago`
|
||||||
> **Avg. Duration:** `2:33:45`
|
> **Avg. Duration:** `2:33:45`
|
||||||
@@ -293,7 +292,6 @@ The relative profit of `15.2 Σ%` is be based on the starting capital - so in th
|
|||||||
Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
|
Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
|
||||||
Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
|
Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
|
||||||
Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
|
Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
|
||||||
Bot started date will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
|
|
||||||
|
|
||||||
### /forceexit <trade_id>
|
### /forceexit <trade_id>
|
||||||
|
|
||||||
|
@@ -1291,7 +1291,7 @@ class FreqaiDataKitchen:
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def use_strategy_to_populate_indicators(
|
def use_strategy_to_populate_indicators( # noqa: C901
|
||||||
self,
|
self,
|
||||||
strategy: IStrategy,
|
strategy: IStrategy,
|
||||||
corr_dataframes: dict = {},
|
corr_dataframes: dict = {},
|
||||||
@@ -1362,12 +1362,12 @@ class FreqaiDataKitchen:
|
|||||||
dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy,
|
dataframe = self.populate_features(dataframe.copy(), corr_pair, strategy,
|
||||||
corr_dataframes, base_dataframes, True)
|
corr_dataframes, base_dataframes, True)
|
||||||
|
|
||||||
dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata)
|
if self.live:
|
||||||
|
dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata)
|
||||||
|
dataframe = self.remove_special_chars_from_feature_names(dataframe)
|
||||||
|
|
||||||
self.get_unique_classes_from_labels(dataframe)
|
self.get_unique_classes_from_labels(dataframe)
|
||||||
|
|
||||||
dataframe = self.remove_special_chars_from_feature_names(dataframe)
|
|
||||||
|
|
||||||
if self.config.get('reduce_df_footprint', False):
|
if self.config.get('reduce_df_footprint', False):
|
||||||
dataframe = reduce_dataframe_footprint(dataframe)
|
dataframe = reduce_dataframe_footprint(dataframe)
|
||||||
|
|
||||||
|
@@ -306,7 +306,7 @@ class IFreqaiModel(ABC):
|
|||||||
if check_features:
|
if check_features:
|
||||||
self.dd.load_metadata(dk)
|
self.dd.load_metadata(dk)
|
||||||
dataframe_dummy_features = self.dk.use_strategy_to_populate_indicators(
|
dataframe_dummy_features = self.dk.use_strategy_to_populate_indicators(
|
||||||
strategy, prediction_dataframe=dataframe.tail(1), pair=metadata["pair"]
|
strategy, prediction_dataframe=dataframe.tail(1), pair=pair
|
||||||
)
|
)
|
||||||
dk.find_features(dataframe_dummy_features)
|
dk.find_features(dataframe_dummy_features)
|
||||||
self.check_if_feature_list_matches_strategy(dk)
|
self.check_if_feature_list_matches_strategy(dk)
|
||||||
@@ -316,7 +316,7 @@ class IFreqaiModel(ABC):
|
|||||||
else:
|
else:
|
||||||
if populate_indicators:
|
if populate_indicators:
|
||||||
dataframe = self.dk.use_strategy_to_populate_indicators(
|
dataframe = self.dk.use_strategy_to_populate_indicators(
|
||||||
strategy, prediction_dataframe=dataframe, pair=metadata["pair"]
|
strategy, prediction_dataframe=dataframe, pair=pair
|
||||||
)
|
)
|
||||||
populate_indicators = False
|
populate_indicators = False
|
||||||
|
|
||||||
@@ -332,6 +332,10 @@ class IFreqaiModel(ABC):
|
|||||||
dataframe_train = dk.slice_dataframe(tr_train, dataframe_base_train)
|
dataframe_train = dk.slice_dataframe(tr_train, dataframe_base_train)
|
||||||
dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe_base_backtest)
|
dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe_base_backtest)
|
||||||
|
|
||||||
|
dataframe_train = dk.remove_special_chars_from_feature_names(dataframe_train)
|
||||||
|
dataframe_backtest = dk.remove_special_chars_from_feature_names(dataframe_backtest)
|
||||||
|
dk.get_unique_classes_from_labels(dataframe_train)
|
||||||
|
|
||||||
if not self.model_exists(dk):
|
if not self.model_exists(dk):
|
||||||
dk.find_features(dataframe_train)
|
dk.find_features(dataframe_train)
|
||||||
dk.find_labels(dataframe_train)
|
dk.find_labels(dataframe_train)
|
||||||
|
@@ -26,7 +26,6 @@ from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, timeframe_to_minutes, time
|
|||||||
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
||||||
from freqtrade.mixins import LoggingMixin
|
from freqtrade.mixins import LoggingMixin
|
||||||
from freqtrade.persistence import Order, PairLocks, Trade, init_db
|
from freqtrade.persistence import Order, PairLocks, Trade, init_db
|
||||||
from freqtrade.persistence.key_value_store import set_startup_time
|
|
||||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||||
from freqtrade.plugins.protectionmanager import ProtectionManager
|
from freqtrade.plugins.protectionmanager import ProtectionManager
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
@@ -183,7 +182,6 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
performs startup tasks
|
performs startup tasks
|
||||||
"""
|
"""
|
||||||
migrate_binance_futures_names(self.config)
|
migrate_binance_futures_names(self.config)
|
||||||
set_startup_time()
|
|
||||||
|
|
||||||
self.rpc.startup_messages(self.config, self.pairlists, self.protections)
|
self.rpc.startup_messages(self.config, self.pairlists, self.protections)
|
||||||
# Update older trades with precision and precision mode
|
# Update older trades with precision and precision mode
|
||||||
|
@@ -1,24 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
from logging import Formatter
|
from logging import Formatter
|
||||||
from logging.handlers import BufferingHandler, RotatingFileHandler, SysLogHandler
|
from logging.handlers import RotatingFileHandler, SysLogHandler
|
||||||
|
|
||||||
from freqtrade.constants import Config
|
from freqtrade.constants import Config
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from freqtrade.loggers.buffering_handler import FTBufferingHandler
|
||||||
|
from freqtrade.loggers.std_err_stream_handler import FTStdErrStreamHandler
|
||||||
class FTBufferingHandler(BufferingHandler):
|
|
||||||
def flush(self):
|
|
||||||
"""
|
|
||||||
Override Flush behaviour - we keep half of the configured capacity
|
|
||||||
otherwise, we have moments with "empty" logs.
|
|
||||||
"""
|
|
||||||
self.acquire()
|
|
||||||
try:
|
|
||||||
# Keep half of the records in buffer.
|
|
||||||
self.buffer = self.buffer[-int(self.capacity / 2):]
|
|
||||||
finally:
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -69,7 +56,7 @@ def setup_logging_pre() -> None:
|
|||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format=LOGFORMAT,
|
format=LOGFORMAT,
|
||||||
handlers=[logging.StreamHandler(sys.stderr), bufferHandler]
|
handlers=[FTStdErrStreamHandler(), bufferHandler]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
15
freqtrade/loggers/buffering_handler.py
Normal file
15
freqtrade/loggers/buffering_handler.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from logging.handlers import BufferingHandler
|
||||||
|
|
||||||
|
|
||||||
|
class FTBufferingHandler(BufferingHandler):
|
||||||
|
def flush(self):
|
||||||
|
"""
|
||||||
|
Override Flush behaviour - we keep half of the configured capacity
|
||||||
|
otherwise, we have moments with "empty" logs.
|
||||||
|
"""
|
||||||
|
self.acquire()
|
||||||
|
try:
|
||||||
|
# Keep half of the records in buffer.
|
||||||
|
self.buffer = self.buffer[-int(self.capacity / 2):]
|
||||||
|
finally:
|
||||||
|
self.release()
|
26
freqtrade/loggers/std_err_stream_handler.py
Normal file
26
freqtrade/loggers/std_err_stream_handler.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import sys
|
||||||
|
from logging import Handler
|
||||||
|
|
||||||
|
|
||||||
|
class FTStdErrStreamHandler(Handler):
|
||||||
|
def flush(self):
|
||||||
|
"""
|
||||||
|
Override Flush behaviour - we keep half of the configured capacity
|
||||||
|
otherwise, we have moments with "empty" logs.
|
||||||
|
"""
|
||||||
|
self.acquire()
|
||||||
|
try:
|
||||||
|
sys.stderr.flush()
|
||||||
|
finally:
|
||||||
|
self.release()
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
try:
|
||||||
|
msg = self.format(record)
|
||||||
|
# Don't keep a reference to stderr - this can be problematic with progressbars.
|
||||||
|
sys.stderr.write(msg + '\n')
|
||||||
|
self.flush()
|
||||||
|
except RecursionError:
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
self.handleError(record)
|
@@ -13,13 +13,13 @@ from math import ceil
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import progressbar
|
|
||||||
import rapidjson
|
import rapidjson
|
||||||
from colorama import Fore, Style
|
|
||||||
from colorama import init as colorama_init
|
from colorama import init as colorama_init
|
||||||
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
|
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
|
||||||
from joblib.externals import cloudpickle
|
from joblib.externals import cloudpickle
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
from rich.progress import (BarColumn, MofNCompleteColumn, Progress, TaskProgressColumn, TextColumn,
|
||||||
|
TimeElapsedColumn, TimeRemainingColumn)
|
||||||
|
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
|
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
|
||||||
from freqtrade.data.converter import trim_dataframes
|
from freqtrade.data.converter import trim_dataframes
|
||||||
@@ -44,8 +44,6 @@ with warnings.catch_warnings():
|
|||||||
from skopt import Optimizer
|
from skopt import Optimizer
|
||||||
from skopt.space import Dimension
|
from skopt.space import Dimension
|
||||||
|
|
||||||
progressbar.streams.wrap_stderr()
|
|
||||||
progressbar.streams.wrap_stdout()
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -520,29 +518,6 @@ class Hyperopt:
|
|||||||
else:
|
else:
|
||||||
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]
|
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]
|
||||||
|
|
||||||
def get_progressbar_widgets(self):
|
|
||||||
if self.print_colorized:
|
|
||||||
widgets = [
|
|
||||||
' [Epoch ', progressbar.Counter(), ' of ', str(self.total_epochs),
|
|
||||||
' (', progressbar.Percentage(), ')] ',
|
|
||||||
progressbar.Bar(marker=progressbar.AnimatedMarker(
|
|
||||||
fill='\N{FULL BLOCK}',
|
|
||||||
fill_wrap=Fore.GREEN + '{}' + Fore.RESET,
|
|
||||||
marker_wrap=Style.BRIGHT + '{}' + Style.RESET_ALL,
|
|
||||||
)),
|
|
||||||
' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
widgets = [
|
|
||||||
' [Epoch ', progressbar.Counter(), ' of ', str(self.total_epochs),
|
|
||||||
' (', progressbar.Percentage(), ')] ',
|
|
||||||
progressbar.Bar(marker=progressbar.AnimatedMarker(
|
|
||||||
fill='\N{FULL BLOCK}',
|
|
||||||
)),
|
|
||||||
' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
|
|
||||||
]
|
|
||||||
return widgets
|
|
||||||
|
|
||||||
def evaluate_result(self, val: Dict[str, Any], current: int, is_random: bool):
|
def evaluate_result(self, val: Dict[str, Any], current: int, is_random: bool):
|
||||||
"""
|
"""
|
||||||
Evaluate results returned from generate_optimizer
|
Evaluate results returned from generate_optimizer
|
||||||
@@ -602,11 +577,19 @@ class Hyperopt:
|
|||||||
logger.info(f'Effective number of parallel workers used: {jobs}')
|
logger.info(f'Effective number of parallel workers used: {jobs}')
|
||||||
|
|
||||||
# Define progressbar
|
# Define progressbar
|
||||||
widgets = self.get_progressbar_widgets()
|
with Progress(
|
||||||
with progressbar.ProgressBar(
|
TextColumn("[progress.description]{task.description}"),
|
||||||
max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False,
|
BarColumn(bar_width=None),
|
||||||
widgets=widgets
|
MofNCompleteColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
"•",
|
||||||
|
TimeElapsedColumn(),
|
||||||
|
"•",
|
||||||
|
TimeRemainingColumn(),
|
||||||
|
expand=True,
|
||||||
) as pbar:
|
) as pbar:
|
||||||
|
task = pbar.add_task("Epochs", total=self.total_epochs)
|
||||||
|
|
||||||
start = 0
|
start = 0
|
||||||
|
|
||||||
if self.analyze_per_epoch:
|
if self.analyze_per_epoch:
|
||||||
@@ -616,7 +599,7 @@ class Hyperopt:
|
|||||||
f_val0 = self.generate_optimizer(asked[0])
|
f_val0 = self.generate_optimizer(asked[0])
|
||||||
self.opt.tell(asked, [f_val0['loss']])
|
self.opt.tell(asked, [f_val0['loss']])
|
||||||
self.evaluate_result(f_val0, 1, is_random[0])
|
self.evaluate_result(f_val0, 1, is_random[0])
|
||||||
pbar.update(1)
|
pbar.update(task, advance=1)
|
||||||
start += 1
|
start += 1
|
||||||
|
|
||||||
evals = ceil((self.total_epochs - start) / jobs)
|
evals = ceil((self.total_epochs - start) / jobs)
|
||||||
@@ -630,14 +613,12 @@ class Hyperopt:
|
|||||||
f_val = self.run_optimizer_parallel(parallel, asked)
|
f_val = self.run_optimizer_parallel(parallel, asked)
|
||||||
self.opt.tell(asked, [v['loss'] for v in f_val])
|
self.opt.tell(asked, [v['loss'] for v in f_val])
|
||||||
|
|
||||||
# Calculate progressbar outputs
|
|
||||||
for j, val in enumerate(f_val):
|
for j, val in enumerate(f_val):
|
||||||
# Use human-friendly indexes here (starting from 1)
|
# Use human-friendly indexes here (starting from 1)
|
||||||
current = i * jobs + j + 1 + start
|
current = i * jobs + j + 1 + start
|
||||||
|
|
||||||
self.evaluate_result(val, current, is_random[j])
|
self.evaluate_result(val, current, is_random[j])
|
||||||
|
pbar.update(task, advance=1)
|
||||||
pbar.update(current)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('User interrupted..')
|
print('User interrupted..')
|
||||||
|
@@ -865,6 +865,11 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
|
|||||||
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
|
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
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)
|
||||||
|
|
||||||
if (results.get('results_per_enter_tag') is not None
|
if (results.get('results_per_enter_tag') is not None
|
||||||
or results.get('results_per_buy_tag') is not None):
|
or results.get('results_per_buy_tag') is not None):
|
||||||
# results_per_buy_tag is deprecated and should be removed 2 versions after short golive.
|
# results_per_buy_tag is deprecated and should be removed 2 versions after short golive.
|
||||||
@@ -884,11 +889,6 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
|
|||||||
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
|
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
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)
|
|
||||||
|
|
||||||
for period in backtest_breakdown:
|
for period in backtest_breakdown:
|
||||||
days_breakdown_stats = generate_periodic_breakdown_stats(
|
days_breakdown_stats = generate_periodic_breakdown_stats(
|
||||||
trade_list=results['trades'], period=period)
|
trade_list=results['trades'], period=period)
|
||||||
@@ -917,11 +917,11 @@ def show_backtest_results(config: Config, backtest_stats: Dict):
|
|||||||
strategy, results, stake_currency,
|
strategy, results, stake_currency,
|
||||||
config.get('backtest_breakdown', []))
|
config.get('backtest_breakdown', []))
|
||||||
|
|
||||||
if len(backtest_stats['strategy']) > 1:
|
if len(backtest_stats['strategy']) > 0:
|
||||||
# Print Strategy summary table
|
# Print Strategy summary table
|
||||||
|
|
||||||
table = text_table_strategy(backtest_stats['strategy_comparison'], stake_currency)
|
table = text_table_strategy(backtest_stats['strategy_comparison'], stake_currency)
|
||||||
print(f"{results['backtest_start']} -> {results['backtest_end']} |"
|
print(f"Backtested {results['backtest_start']} -> {results['backtest_end']} |"
|
||||||
f" Max open trades : {results['max_open_trades']}")
|
f" Max open trades : {results['max_open_trades']}")
|
||||||
print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '='))
|
print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '='))
|
||||||
print(table)
|
print(table)
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
|
|
||||||
from freqtrade.persistence.key_value_store import KeyStoreKeys, KeyValueStore
|
|
||||||
from freqtrade.persistence.models import init_db
|
from freqtrade.persistence.models import init_db
|
||||||
from freqtrade.persistence.pairlock_middleware import PairLocks
|
from freqtrade.persistence.pairlock_middleware import PairLocks
|
||||||
from freqtrade.persistence.trade_model import LocalTrade, Order, Trade
|
from freqtrade.persistence.trade_model import LocalTrade, Order, Trade
|
||||||
|
@@ -1,179 +0,0 @@
|
|||||||
from datetime import datetime, timezone
|
|
||||||
from enum import Enum
|
|
||||||
from typing import ClassVar, Optional, Union
|
|
||||||
|
|
||||||
from sqlalchemy import String
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
|
||||||
|
|
||||||
from freqtrade.persistence.base import ModelBase, SessionType
|
|
||||||
|
|
||||||
|
|
||||||
ValueTypes = Union[str, datetime, float, int]
|
|
||||||
|
|
||||||
|
|
||||||
class ValueTypesEnum(str, Enum):
|
|
||||||
STRING = 'str'
|
|
||||||
DATETIME = 'datetime'
|
|
||||||
FLOAT = 'float'
|
|
||||||
INT = 'int'
|
|
||||||
|
|
||||||
|
|
||||||
class KeyStoreKeys(str, Enum):
|
|
||||||
BOT_START_TIME = 'bot_start_time'
|
|
||||||
STARTUP_TIME = 'startup_time'
|
|
||||||
|
|
||||||
|
|
||||||
class _KeyValueStoreModel(ModelBase):
|
|
||||||
"""
|
|
||||||
Pair Locks database model.
|
|
||||||
"""
|
|
||||||
__tablename__ = 'KeyValueStore'
|
|
||||||
session: ClassVar[SessionType]
|
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
|
||||||
|
|
||||||
key: Mapped[KeyStoreKeys] = mapped_column(String(25), nullable=False, index=True)
|
|
||||||
|
|
||||||
value_type: Mapped[ValueTypesEnum] = mapped_column(String(20), nullable=False)
|
|
||||||
|
|
||||||
string_value: Mapped[Optional[str]]
|
|
||||||
datetime_value: Mapped[Optional[datetime]]
|
|
||||||
float_value: Mapped[Optional[float]]
|
|
||||||
int_value: Mapped[Optional[int]]
|
|
||||||
|
|
||||||
|
|
||||||
class KeyValueStore():
|
|
||||||
"""
|
|
||||||
Generic bot-wide, persistent key-value store
|
|
||||||
Can be used to store generic values, e.g. very first bot startup time.
|
|
||||||
Supports the types str, datetime, float and int.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def store_value(key: KeyStoreKeys, value: ValueTypes) -> None:
|
|
||||||
"""
|
|
||||||
Store the given value for the given key.
|
|
||||||
:param key: Key to store the value for - can be used in get-value to retrieve the key
|
|
||||||
:param value: Value to store - can be str, datetime, float or int
|
|
||||||
"""
|
|
||||||
kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
|
|
||||||
_KeyValueStoreModel.key == key).first()
|
|
||||||
if kv is None:
|
|
||||||
kv = _KeyValueStoreModel(key=key)
|
|
||||||
if isinstance(value, str):
|
|
||||||
kv.value_type = ValueTypesEnum.STRING
|
|
||||||
kv.string_value = value
|
|
||||||
elif isinstance(value, datetime):
|
|
||||||
kv.value_type = ValueTypesEnum.DATETIME
|
|
||||||
kv.datetime_value = value
|
|
||||||
elif isinstance(value, float):
|
|
||||||
kv.value_type = ValueTypesEnum.FLOAT
|
|
||||||
kv.float_value = value
|
|
||||||
elif isinstance(value, int):
|
|
||||||
kv.value_type = ValueTypesEnum.INT
|
|
||||||
kv.int_value = value
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Unknown value type {kv.value_type}')
|
|
||||||
_KeyValueStoreModel.session.add(kv)
|
|
||||||
_KeyValueStoreModel.session.commit()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def delete_value(key: KeyStoreKeys) -> None:
|
|
||||||
"""
|
|
||||||
Delete the value for the given key.
|
|
||||||
:param key: Key to delete the value for
|
|
||||||
"""
|
|
||||||
kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
|
|
||||||
_KeyValueStoreModel.key == key).first()
|
|
||||||
if kv is not None:
|
|
||||||
_KeyValueStoreModel.session.delete(kv)
|
|
||||||
_KeyValueStoreModel.session.commit()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_value(key: KeyStoreKeys) -> Optional[ValueTypes]:
|
|
||||||
"""
|
|
||||||
Get the value for the given key.
|
|
||||||
:param key: Key to get the value for
|
|
||||||
"""
|
|
||||||
kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
|
|
||||||
_KeyValueStoreModel.key == key).first()
|
|
||||||
if kv is None:
|
|
||||||
return None
|
|
||||||
if kv.value_type == ValueTypesEnum.STRING:
|
|
||||||
return kv.string_value
|
|
||||||
if kv.value_type == ValueTypesEnum.DATETIME and kv.datetime_value is not None:
|
|
||||||
return kv.datetime_value.replace(tzinfo=timezone.utc)
|
|
||||||
if kv.value_type == ValueTypesEnum.FLOAT:
|
|
||||||
return kv.float_value
|
|
||||||
if kv.value_type == ValueTypesEnum.INT:
|
|
||||||
return kv.int_value
|
|
||||||
# This should never happen unless someone messed with the database manually
|
|
||||||
raise ValueError(f'Unknown value type {kv.value_type}') # pragma: no cover
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_string_value(key: KeyStoreKeys) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
Get the value for the given key.
|
|
||||||
:param key: Key to get the value for
|
|
||||||
"""
|
|
||||||
kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
|
|
||||||
_KeyValueStoreModel.key == key,
|
|
||||||
_KeyValueStoreModel.value_type == ValueTypesEnum.STRING).first()
|
|
||||||
if kv is None:
|
|
||||||
return None
|
|
||||||
return kv.string_value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_datetime_value(key: KeyStoreKeys) -> Optional[datetime]:
|
|
||||||
"""
|
|
||||||
Get the value for the given key.
|
|
||||||
:param key: Key to get the value for
|
|
||||||
"""
|
|
||||||
kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
|
|
||||||
_KeyValueStoreModel.key == key,
|
|
||||||
_KeyValueStoreModel.value_type == ValueTypesEnum.DATETIME).first()
|
|
||||||
if kv is None or kv.datetime_value is None:
|
|
||||||
return None
|
|
||||||
return kv.datetime_value.replace(tzinfo=timezone.utc)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_float_value(key: KeyStoreKeys) -> Optional[float]:
|
|
||||||
"""
|
|
||||||
Get the value for the given key.
|
|
||||||
:param key: Key to get the value for
|
|
||||||
"""
|
|
||||||
kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
|
|
||||||
_KeyValueStoreModel.key == key,
|
|
||||||
_KeyValueStoreModel.value_type == ValueTypesEnum.FLOAT).first()
|
|
||||||
if kv is None:
|
|
||||||
return None
|
|
||||||
return kv.float_value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_int_value(key: KeyStoreKeys) -> Optional[int]:
|
|
||||||
"""
|
|
||||||
Get the value for the given key.
|
|
||||||
:param key: Key to get the value for
|
|
||||||
"""
|
|
||||||
kv = _KeyValueStoreModel.session.query(_KeyValueStoreModel).filter(
|
|
||||||
_KeyValueStoreModel.key == key,
|
|
||||||
_KeyValueStoreModel.value_type == ValueTypesEnum.INT).first()
|
|
||||||
if kv is None:
|
|
||||||
return None
|
|
||||||
return kv.int_value
|
|
||||||
|
|
||||||
|
|
||||||
def set_startup_time():
|
|
||||||
"""
|
|
||||||
sets bot_start_time to the first trade open date - or "now" on new databases.
|
|
||||||
sets startup_time to "now"
|
|
||||||
"""
|
|
||||||
st = KeyValueStore.get_value('bot_start_time')
|
|
||||||
if st is None:
|
|
||||||
from freqtrade.persistence import Trade
|
|
||||||
t = Trade.session.query(Trade).order_by(Trade.open_date.asc()).first()
|
|
||||||
if t is not None:
|
|
||||||
KeyValueStore.store_value('bot_start_time', t.open_date_utc)
|
|
||||||
else:
|
|
||||||
KeyValueStore.store_value('bot_start_time', datetime.now(timezone.utc))
|
|
||||||
KeyValueStore.store_value('startup_time', datetime.now(timezone.utc))
|
|
@@ -13,7 +13,6 @@ from sqlalchemy.pool import StaticPool
|
|||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.persistence.base import ModelBase
|
from freqtrade.persistence.base import ModelBase
|
||||||
from freqtrade.persistence.key_value_store import _KeyValueStoreModel
|
|
||||||
from freqtrade.persistence.migrations import check_migrate
|
from freqtrade.persistence.migrations import check_migrate
|
||||||
from freqtrade.persistence.pairlock import PairLock
|
from freqtrade.persistence.pairlock import PairLock
|
||||||
from freqtrade.persistence.trade_model import Order, Trade
|
from freqtrade.persistence.trade_model import Order, Trade
|
||||||
@@ -77,7 +76,6 @@ def init_db(db_url: str) -> None:
|
|||||||
bind=engine, autoflush=False), scopefunc=get_request_or_thread_id)
|
bind=engine, autoflush=False), scopefunc=get_request_or_thread_id)
|
||||||
Order.session = Trade.session
|
Order.session = Trade.session
|
||||||
PairLock.session = Trade.session
|
PairLock.session = Trade.session
|
||||||
_KeyValueStoreModel.session = Trade.session
|
|
||||||
|
|
||||||
previous_tables = inspect(engine).get_table_names()
|
previous_tables = inspect(engine).get_table_names()
|
||||||
ModelBase.metadata.create_all(engine)
|
ModelBase.metadata.create_all(engine)
|
||||||
|
@@ -108,8 +108,6 @@ class Profit(BaseModel):
|
|||||||
max_drawdown: float
|
max_drawdown: float
|
||||||
max_drawdown_abs: float
|
max_drawdown_abs: float
|
||||||
trading_volume: Optional[float]
|
trading_volume: Optional[float]
|
||||||
bot_start_timestamp: int
|
|
||||||
bot_start_date: str
|
|
||||||
|
|
||||||
|
|
||||||
class SellReason(BaseModel):
|
class SellReason(BaseModel):
|
||||||
|
@@ -26,7 +26,7 @@ from freqtrade.exceptions import ExchangeError, PricingError
|
|||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
|
||||||
from freqtrade.loggers import bufferHandler
|
from freqtrade.loggers import bufferHandler
|
||||||
from freqtrade.misc import decimals_per_coin, shorten_date
|
from freqtrade.misc import decimals_per_coin, shorten_date
|
||||||
from freqtrade.persistence import KeyStoreKeys, KeyValueStore, Order, PairLocks, Trade
|
from freqtrade.persistence import Order, PairLocks, Trade
|
||||||
from freqtrade.persistence.models import PairLock
|
from freqtrade.persistence.models import PairLock
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||||
@@ -543,7 +543,6 @@ class RPC:
|
|||||||
first_date = trades[0].open_date if trades else None
|
first_date = trades[0].open_date if trades else None
|
||||||
last_date = trades[-1].open_date if trades else None
|
last_date = trades[-1].open_date if trades else None
|
||||||
num = float(len(durations) or 1)
|
num = float(len(durations) or 1)
|
||||||
bot_start = KeyValueStore.get_datetime_value(KeyStoreKeys.BOT_START_TIME)
|
|
||||||
return {
|
return {
|
||||||
'profit_closed_coin': profit_closed_coin_sum,
|
'profit_closed_coin': profit_closed_coin_sum,
|
||||||
'profit_closed_percent_mean': round(profit_closed_ratio_mean * 100, 2),
|
'profit_closed_percent_mean': round(profit_closed_ratio_mean * 100, 2),
|
||||||
@@ -577,8 +576,6 @@ class RPC:
|
|||||||
'max_drawdown': max_drawdown,
|
'max_drawdown': max_drawdown,
|
||||||
'max_drawdown_abs': max_drawdown_abs,
|
'max_drawdown_abs': max_drawdown_abs,
|
||||||
'trading_volume': trading_volume,
|
'trading_volume': trading_volume,
|
||||||
'bot_start_timestamp': int(bot_start.timestamp() * 1000) if bot_start else 0,
|
|
||||||
'bot_start_date': bot_start.strftime(DATETIME_PRINT_FORMAT) if bot_start else '',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
|
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
|
||||||
|
@@ -819,7 +819,7 @@ class Telegram(RPCHandler):
|
|||||||
best_pair = stats['best_pair']
|
best_pair = stats['best_pair']
|
||||||
best_pair_profit_ratio = stats['best_pair_profit_ratio']
|
best_pair_profit_ratio = stats['best_pair_profit_ratio']
|
||||||
if stats['trade_count'] == 0:
|
if stats['trade_count'] == 0:
|
||||||
markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`"
|
markdown_msg = 'No trades yet.'
|
||||||
else:
|
else:
|
||||||
# Message to display
|
# Message to display
|
||||||
if stats['closed_trade_count'] > 0:
|
if stats['closed_trade_count'] > 0:
|
||||||
@@ -838,7 +838,6 @@ class Telegram(RPCHandler):
|
|||||||
f"({profit_all_percent} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
f"({profit_all_percent} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
||||||
f"∙ `{round_coin_value(profit_all_fiat, fiat_disp_cur)}`\n"
|
f"∙ `{round_coin_value(profit_all_fiat, fiat_disp_cur)}`\n"
|
||||||
f"*Total Trade Count:* `{trade_count}`\n"
|
f"*Total Trade Count:* `{trade_count}`\n"
|
||||||
f"*Bot started:* `{stats['bot_start_date']}`\n"
|
|
||||||
f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* "
|
f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* "
|
||||||
f"`{first_trade_date}`\n"
|
f"`{first_trade_date}`\n"
|
||||||
f"*Latest Trade opened:* `{latest_trade_date}`\n"
|
f"*Latest Trade opened:* `{latest_trade_date}`\n"
|
||||||
|
@@ -6,4 +6,3 @@ scipy==1.10.1
|
|||||||
scikit-learn==1.1.3
|
scikit-learn==1.1.3
|
||||||
scikit-optimize==0.9.0
|
scikit-optimize==0.9.0
|
||||||
filelock==3.10.6
|
filelock==3.10.6
|
||||||
progressbar2==4.2.0
|
|
||||||
|
@@ -20,6 +20,7 @@ jinja2==3.1.2
|
|||||||
tables==3.8.0
|
tables==3.8.0
|
||||||
blosc==1.11.1
|
blosc==1.11.1
|
||||||
joblib==1.2.0
|
joblib==1.2.0
|
||||||
|
rich==13.3.3
|
||||||
pyarrow==11.0.0; platform_machine != 'armv7l'
|
pyarrow==11.0.0; platform_machine != 'armv7l'
|
||||||
|
|
||||||
# find first, C search in arrays
|
# find first, C search in arrays
|
||||||
|
2
setup.py
2
setup.py
@@ -8,7 +8,6 @@ hyperopt = [
|
|||||||
'scikit-learn',
|
'scikit-learn',
|
||||||
'scikit-optimize>=0.7.0',
|
'scikit-optimize>=0.7.0',
|
||||||
'filelock',
|
'filelock',
|
||||||
'progressbar2',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
freqai = [
|
freqai = [
|
||||||
@@ -82,6 +81,7 @@ setup(
|
|||||||
'numpy',
|
'numpy',
|
||||||
'pandas',
|
'pandas',
|
||||||
'joblib>=1.2.0',
|
'joblib>=1.2.0',
|
||||||
|
'rich',
|
||||||
'pyarrow; platform_machine != "armv7l"',
|
'pyarrow; platform_machine != "armv7l"',
|
||||||
'fastapi',
|
'fastapi',
|
||||||
'pydantic>=1.8.0',
|
'pydantic>=1.8.0',
|
||||||
|
@@ -119,6 +119,7 @@ def make_unfiltered_dataframe(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
freqai.dk.pair = "ADA/BTC"
|
freqai.dk.pair = "ADA/BTC"
|
||||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
||||||
@@ -152,6 +153,7 @@ def make_data_dictionary(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
freqai.dk.pair = "ADA/BTC"
|
freqai.dk.pair = "ADA/BTC"
|
||||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(data_load_timerange, freqai.dk)
|
||||||
|
@@ -19,6 +19,7 @@ def test_update_historic_data(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||||
|
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
@@ -41,6 +42,7 @@ def test_load_all_pairs_histories(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ def test_get_base_and_corr_dataframes(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
||||||
@@ -87,6 +90,7 @@ def test_use_strategy_to_populate_indicators(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180114")
|
timerange = TimeRange.parse_timerange("20180110-20180114")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
sub_timerange = TimeRange.parse_timerange("20180111-20180114")
|
||||||
@@ -103,8 +107,9 @@ def test_get_timerange_from_live_historic_predictions(mocker, freqai_conf):
|
|||||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = False
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = False
|
||||||
timerange = TimeRange.parse_timerange("20180126-20180130")
|
timerange = TimeRange.parse_timerange("20180126-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
sub_timerange = TimeRange.parse_timerange("20180128-20180130")
|
sub_timerange = TimeRange.parse_timerange("20180128-20180130")
|
||||||
|
@@ -180,6 +180,7 @@ def test_get_full_model_path(mocker, freqai_conf, model):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
|
@@ -87,6 +87,7 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
|||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.can_short = can_short
|
freqai.can_short = can_short
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
freqai.dk.set_paths('ADA/BTC', 10000)
|
freqai.dk.set_paths('ADA/BTC', 10000)
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
@@ -135,6 +136,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
@@ -178,6 +180,7 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
@@ -371,6 +374,9 @@ def test_backtesting_fit_live_predictions(mocker, freqai_conf, caplog):
|
|||||||
sub_timerange = TimeRange.parse_timerange("20180129-20180130")
|
sub_timerange = TimeRange.parse_timerange("20180129-20180130")
|
||||||
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC", freqai.dk)
|
corr_df, base_df = freqai.dd.get_base_and_corr_dataframes(sub_timerange, "LTC/BTC", freqai.dk)
|
||||||
df = freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, "LTC/BTC")
|
df = freqai.dk.use_strategy_to_populate_indicators(strategy, corr_df, base_df, "LTC/BTC")
|
||||||
|
df = strategy.set_freqai_targets(df.copy(), metadata={"pair": "LTC/BTC"})
|
||||||
|
df = freqai.dk.remove_special_chars_from_feature_names(df)
|
||||||
|
freqai.dk.get_unique_classes_from_labels(df)
|
||||||
freqai.dk.pair = "ADA/BTC"
|
freqai.dk.pair = "ADA/BTC"
|
||||||
freqai.dk.full_df = df.fillna(0)
|
freqai.dk.full_df = df.fillna(0)
|
||||||
freqai.dk.full_df
|
freqai.dk.full_df
|
||||||
@@ -394,6 +400,7 @@ def test_principal_component_analysis(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
@@ -425,10 +432,12 @@ def test_plot_feature_importance(mocker, freqai_conf):
|
|||||||
freqai = strategy.freqai
|
freqai = strategy.freqai
|
||||||
freqai.live = True
|
freqai.live = True
|
||||||
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
freqai.dk = FreqaiDataKitchen(freqai_conf)
|
||||||
|
freqai.dk.live = True
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180130")
|
timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
freqai.dd.load_all_pair_histories(timerange, freqai.dk)
|
||||||
|
|
||||||
freqai.dd.pair_dict = MagicMock()
|
freqai.dd.pair_dict = {"ADA/BTC": {"model_filename": "fake_name",
|
||||||
|
"trained_timestamp": 1, "data_path": "", "extras": {}}}
|
||||||
|
|
||||||
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
data_load_timerange = TimeRange.parse_timerange("20180110-20180130")
|
||||||
new_timerange = TimeRange.parse_timerange("20180120-20180130")
|
new_timerange = TimeRange.parse_timerange("20180120-20180130")
|
||||||
|
@@ -1,69 +0,0 @@
|
|||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from freqtrade.persistence.key_value_store import KeyValueStore, set_startup_time
|
|
||||||
from tests.conftest import create_mock_trades_usdt
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
|
||||||
def test_key_value_store(time_machine):
|
|
||||||
start = datetime(2023, 1, 1, 4, tzinfo=timezone.utc)
|
|
||||||
time_machine.move_to(start, tick=False)
|
|
||||||
|
|
||||||
KeyValueStore.store_value("test", "testStringValue")
|
|
||||||
KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
|
|
||||||
KeyValueStore.store_value("test_float", 22.51)
|
|
||||||
KeyValueStore.store_value("test_int", 15)
|
|
||||||
|
|
||||||
assert KeyValueStore.get_value("test") == "testStringValue"
|
|
||||||
assert KeyValueStore.get_value("test") == "testStringValue"
|
|
||||||
assert KeyValueStore.get_string_value("test") == "testStringValue"
|
|
||||||
assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
|
|
||||||
assert KeyValueStore.get_datetime_value("test_dt") == datetime.now(timezone.utc)
|
|
||||||
assert KeyValueStore.get_string_value("test_dt") is None
|
|
||||||
assert KeyValueStore.get_float_value("test_dt") is None
|
|
||||||
assert KeyValueStore.get_int_value("test_dt") is None
|
|
||||||
assert KeyValueStore.get_value("test_float") == 22.51
|
|
||||||
assert KeyValueStore.get_float_value("test_float") == 22.51
|
|
||||||
assert KeyValueStore.get_value("test_int") == 15
|
|
||||||
assert KeyValueStore.get_int_value("test_int") == 15
|
|
||||||
assert KeyValueStore.get_datetime_value("test_int") is None
|
|
||||||
|
|
||||||
time_machine.move_to(start + timedelta(days=20, hours=5), tick=False)
|
|
||||||
assert KeyValueStore.get_value("test_dt") != datetime.now(timezone.utc)
|
|
||||||
assert KeyValueStore.get_value("test_dt") == start
|
|
||||||
# Test update works
|
|
||||||
KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
|
|
||||||
assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
|
|
||||||
|
|
||||||
KeyValueStore.store_value("test_float", 23.51)
|
|
||||||
assert KeyValueStore.get_value("test_float") == 23.51
|
|
||||||
# test deleting
|
|
||||||
KeyValueStore.delete_value("test_float")
|
|
||||||
assert KeyValueStore.get_value("test_float") is None
|
|
||||||
# Delete same value again (should not fail)
|
|
||||||
KeyValueStore.delete_value("test_float")
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"Unknown value type"):
|
|
||||||
KeyValueStore.store_value("test_float", {'some': 'dict'})
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
|
||||||
def test_set_startup_time(fee, time_machine):
|
|
||||||
create_mock_trades_usdt(fee)
|
|
||||||
start = datetime.now(timezone.utc)
|
|
||||||
time_machine.move_to(start, tick=False)
|
|
||||||
set_startup_time()
|
|
||||||
|
|
||||||
assert KeyValueStore.get_value("startup_time") == start
|
|
||||||
initial_time = KeyValueStore.get_value("bot_start_time")
|
|
||||||
assert initial_time <= start
|
|
||||||
|
|
||||||
# Simulate bot restart
|
|
||||||
new_start = start + timedelta(days=5)
|
|
||||||
time_machine.move_to(new_start, tick=False)
|
|
||||||
set_startup_time()
|
|
||||||
|
|
||||||
assert KeyValueStore.get_value("startup_time") == new_start
|
|
||||||
assert KeyValueStore.get_value("bot_start_time") == initial_time
|
|
@@ -883,8 +883,6 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
|
|||||||
'max_drawdown': ANY,
|
'max_drawdown': ANY,
|
||||||
'max_drawdown_abs': ANY,
|
'max_drawdown_abs': ANY,
|
||||||
'trading_volume': expected['trading_volume'],
|
'trading_volume': expected['trading_volume'],
|
||||||
'bot_start_timestamp': 0,
|
|
||||||
'bot_start_date': '',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -23,7 +23,8 @@ from freqtrade.configuration.load_config import (load_config_file, load_file, lo
|
|||||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
|
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.loggers import FTBufferingHandler, _set_loggers, setup_logging, setup_logging_pre
|
from freqtrade.loggers import (FTBufferingHandler, FTStdErrStreamHandler, _set_loggers,
|
||||||
|
setup_logging, setup_logging_pre)
|
||||||
from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
|
from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
|
||||||
patched_configuration_load_config_file)
|
patched_configuration_load_config_file)
|
||||||
|
|
||||||
@@ -658,7 +659,7 @@ def test_set_loggers_syslog():
|
|||||||
setup_logging(config)
|
setup_logging(config)
|
||||||
assert len(logger.handlers) == 3
|
assert len(logger.handlers) == 3
|
||||||
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
|
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
|
||||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||||
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
|
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
|
||||||
# setting up logging again should NOT cause the loggers to be added a second time.
|
# setting up logging again should NOT cause the loggers to be added a second time.
|
||||||
setup_logging(config)
|
setup_logging(config)
|
||||||
@@ -681,7 +682,7 @@ def test_set_loggers_Filehandler(tmpdir):
|
|||||||
setup_logging(config)
|
setup_logging(config)
|
||||||
assert len(logger.handlers) == 3
|
assert len(logger.handlers) == 3
|
||||||
assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
|
assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
|
||||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||||
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
|
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
|
||||||
# setting up logging again should NOT cause the loggers to be added a second time.
|
# setting up logging again should NOT cause the loggers to be added a second time.
|
||||||
setup_logging(config)
|
setup_logging(config)
|
||||||
@@ -706,7 +707,7 @@ def test_set_loggers_journald(mocker):
|
|||||||
setup_logging(config)
|
setup_logging(config)
|
||||||
assert len(logger.handlers) == 3
|
assert len(logger.handlers) == 3
|
||||||
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
|
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
|
||||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
|
||||||
# reset handlers to not break pytest
|
# reset handlers to not break pytest
|
||||||
logger.handlers = orig_handlers
|
logger.handlers = orig_handlers
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user