Merge branch 'freqtrade:develop' into develop

This commit is contained in:
hippocritical 2023-05-26 08:38:32 +02:00 committed by GitHub
commit 9366c77e42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 790 additions and 662 deletions

View File

@ -18,7 +18,7 @@ repos:
- types-requests==2.30.0.0 - types-requests==2.30.0.0
- types-tabulate==0.9.0.2 - types-tabulate==0.9.0.2
- types-python-dateutil==2.8.19.13 - types-python-dateutil==2.8.19.13
- SQLAlchemy==2.0.13 - SQLAlchemy==2.0.15
# stages: [push] # stages: [push]
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort

View File

@ -327,18 +327,18 @@ To check how the new exchange behaves, you can use the following snippet:
``` python ``` python
import ccxt import ccxt
from datetime import datetime from datetime import datetime, timezone
from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.data.converter import ohlcv_to_dataframe
ct = ccxt.binance() ct = ccxt.binance() # Use the exchange you're testing
timeframe = "1d" timeframe = "1d"
pair = "XLM/BTC" # Make sure to use a pair that exists on that exchange! pair = "BTC/USDT" # Make sure to use a pair that exists on that exchange!
raw = ct.fetch_ohlcv(pair, timeframe=timeframe) raw = ct.fetch_ohlcv(pair, timeframe=timeframe)
# convert to dataframe # convert to dataframe
df1 = ohlcv_to_dataframe(raw, timeframe, pair=pair, drop_incomplete=False) df1 = ohlcv_to_dataframe(raw, timeframe, pair=pair, drop_incomplete=False)
print(df1.tail(1)) print(df1.tail(1))
print(datetime.utcnow()) print(datetime.now(timezone.utc))
``` ```
``` output ``` output

View File

@ -1,6 +1,6 @@
markdown==3.3.7 markdown==3.3.7
mkdocs==1.4.3 mkdocs==1.4.3
mkdocs-material==9.1.12 mkdocs-material==9.1.14
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==10.0.1 pymdown-extensions==10.0.1
jinja2==3.1.2 jinja2==3.1.2

View File

@ -6,8 +6,6 @@ import re
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Optional from typing import Optional
import arrow
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -139,7 +137,8 @@ class TimeRange:
if stype[0]: if stype[0]:
starts = rvals[index] starts = rvals[index]
if stype[0] == 'date' and len(starts) == 8: if stype[0] == 'date' and len(starts) == 8:
start = arrow.get(starts, 'YYYYMMDD').int_timestamp start = int(datetime.strptime(starts, '%Y%m%d').replace(
tzinfo=timezone.utc).timestamp())
elif len(starts) == 13: elif len(starts) == 13:
start = int(starts) // 1000 start = int(starts) // 1000
else: else:
@ -148,7 +147,8 @@ class TimeRange:
if stype[1]: if stype[1]:
stops = rvals[index] stops = rvals[index]
if stype[1] == 'date' and len(stops) == 8: if stype[1] == 'date' and len(stops) == 8:
stop = arrow.get(stops, 'YYYYMMDD').int_timestamp stop = int(datetime.strptime(stops, '%Y%m%d').replace(
tzinfo=timezone.utc).timestamp())
elif len(stops) == 13: elif len(stops) == 13:
stop = int(stops) // 1000 stop = int(stops) // 1000
else: else:

View File

@ -1,10 +1,9 @@
import logging import logging
import operator import operator
from datetime import datetime from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
import arrow
from pandas import DataFrame, concat from pandas import DataFrame, concat
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
@ -236,8 +235,8 @@ def _download_pair_history(pair: str, *,
new_data = exchange.get_historic_ohlcv(pair=pair, new_data = exchange.get_historic_ohlcv(pair=pair,
timeframe=timeframe, timeframe=timeframe,
since_ms=since_ms if since_ms else since_ms=since_ms if since_ms else
arrow.utcnow().shift( int((datetime.now() - timedelta(days=new_pairs_days)
days=-new_pairs_days).int_timestamp * 1000, ).timestamp()) * 1000,
is_new_pair=data.empty, is_new_pair=data.empty,
candle_type=candle_type, candle_type=candle_type,
until_ms=until_ms if until_ms else None until_ms=until_ms if until_ms else None
@ -349,7 +348,7 @@ def _download_trades_history(exchange: Exchange,
trades = [] trades = []
if not since: if not since:
since = arrow.utcnow().shift(days=-new_pairs_days).int_timestamp * 1000 since = int((datetime.now() - timedelta(days=-new_pairs_days)).timestamp()) * 1000
from_id = trades[-1][1] if trades else None from_id = trades[-1][1] if trades else None
if trades and since < trades[-1][0]: if trades and since < trades[-1][0]:

View File

@ -3,9 +3,9 @@
import logging import logging
from collections import defaultdict from collections import defaultdict
from copy import deepcopy from copy import deepcopy
from datetime import timedelta
from typing import Any, Dict, List, NamedTuple from typing import Any, Dict, List, NamedTuple
import arrow
import numpy as np import numpy as np
import utils_find_1st as utf1st import utils_find_1st as utf1st
from pandas import DataFrame from pandas import DataFrame
@ -18,6 +18,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_seconds from freqtrade.exchange import timeframe_to_seconds
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
from freqtrade.util import dt_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -79,8 +80,8 @@ class Edge:
self._stoploss_range_step self._stoploss_range_step
) )
self._timerange: TimeRange = TimeRange.parse_timerange("%s-" % arrow.now().shift( self._timerange: TimeRange = TimeRange.parse_timerange(
days=-1 * self._since_number_of_days).format('YYYYMMDD')) f"{(dt_now() - timedelta(days=self._since_number_of_days)).strftime('%Y%m%d')}-")
if config.get('fee'): if config.get('fee'):
self.fee = config['fee'] self.fee = config['fee']
else: else:
@ -97,7 +98,7 @@ class Edge:
heartbeat = self.edge_config.get('process_throttle_secs') heartbeat = self.edge_config.get('process_throttle_secs')
if (self._last_updated > 0) and ( if (self._last_updated > 0) and (
self._last_updated + heartbeat > arrow.utcnow().int_timestamp): self._last_updated + heartbeat > int(dt_now().timestamp())):
return False return False
data: Dict[str, Any] = {} data: Dict[str, Any] = {}
@ -189,7 +190,7 @@ class Edge:
# Fill missing, calculable columns, profit, duration , abs etc. # Fill missing, calculable columns, profit, duration , abs etc.
trades_df = self._fill_calculable_fields(DataFrame(trades)) trades_df = self._fill_calculable_fields(DataFrame(trades))
self._cached_pairs = self._process_expectancy(trades_df) self._cached_pairs = self._process_expectancy(trades_df)
self._last_updated = arrow.utcnow().int_timestamp self._last_updated = int(dt_now().timestamp())
return True return True

View File

@ -1,10 +1,9 @@
""" Binance exchange subclass """ """ Binance exchange subclass """
import logging import logging
from datetime import datetime from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
import arrow
import ccxt import ccxt
from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode
@ -66,7 +65,7 @@ class Binance(Exchange):
""" """
try: try:
if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']: if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']:
position_side = self._api.fapiPrivateGetPositionsideDual() position_side = self._api.fapiPrivateGetPositionSideDual()
self._log_exchange_response('position_side_setting', position_side) self._log_exchange_response('position_side_setting', position_side)
assets_margin = self._api.fapiPrivateGetMultiAssetsMargin() assets_margin = self._api.fapiPrivateGetMultiAssetsMargin()
self._log_exchange_response('multi_asset_margin', assets_margin) self._log_exchange_response('multi_asset_margin', assets_margin)
@ -105,8 +104,9 @@ class Binance(Exchange):
if x and x[3] and x[3][0] and x[3][0][0] > since_ms: if x and x[3] and x[3][0] and x[3][0][0] > since_ms:
# Set starting date to first available candle. # Set starting date to first available candle.
since_ms = x[3][0][0] since_ms = x[3][0][0]
logger.info(f"Candle-data for {pair} available starting with " logger.info(
f"{arrow.get(since_ms // 1000).isoformat()}.") f"Candle-data for {pair} available starting with "
f"{datetime.fromtimestamp(since_ms // 1000, tz=timezone.utc).isoformat()}.")
return await super()._async_get_historic_ohlcv( return await super()._async_get_historic_ohlcv(
pair=pair, pair=pair,

View File

@ -11,7 +11,6 @@ from math import floor
from threading import Lock from threading import Lock
from typing import Any, Coroutine, Dict, List, Literal, Optional, Tuple, Union from typing import Any, Coroutine, Dict, List, Literal, Optional, Tuple, Union
import arrow
import ccxt import ccxt
import ccxt.async_support as ccxt_async import ccxt.async_support as ccxt_async
from cachetools import TTLCache from cachetools import TTLCache
@ -42,6 +41,8 @@ from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers
from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json, from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json,
safe_value_fallback2) safe_value_fallback2)
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.util import dt_from_ts, dt_now
from freqtrade.util.datetime_helpers import dt_humanize, dt_ts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -490,7 +491,7 @@ class Exchange:
try: try:
self._markets = self._api.load_markets(params={}) self._markets = self._api.load_markets(params={})
self._load_async_markets() self._load_async_markets()
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = dt_ts()
if self._ft_has['needs_trading_fees']: if self._ft_has['needs_trading_fees']:
self._trading_fees = self.fetch_trading_fees() self._trading_fees = self.fetch_trading_fees()
@ -501,15 +502,14 @@ class Exchange:
"""Reload markets both sync and async if refresh interval has passed """ """Reload markets both sync and async if refresh interval has passed """
# Check whether markets have to be reloaded # Check whether markets have to be reloaded
if (self._last_markets_refresh > 0) and ( if (self._last_markets_refresh > 0) and (
self._last_markets_refresh + self.markets_refresh_interval self._last_markets_refresh + self.markets_refresh_interval > dt_ts()):
> arrow.utcnow().int_timestamp):
return None return None
logger.debug("Performing scheduled market reload..") logger.debug("Performing scheduled market reload..")
try: try:
self._markets = self._api.load_markets(reload=True, params={}) self._markets = self._api.load_markets(reload=True, params={})
# Also reload async markets to avoid issues with newly listed pairs # Also reload async markets to avoid issues with newly listed pairs
self._load_async_markets(reload=True) self._load_async_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = dt_ts()
self.fill_leverage_tiers() self.fill_leverage_tiers()
except ccxt.BaseError: except ccxt.BaseError:
logger.exception("Could not reload markets.") logger.exception("Could not reload markets.")
@ -843,7 +843,8 @@ class Exchange:
def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, leverage: float, params: Dict = {}, rate: float, leverage: float, params: Dict = {},
stop_loss: bool = False) -> Dict[str, Any]: stop_loss: bool = False) -> Dict[str, Any]:
order_id = f'dry_run_{side}_{datetime.now().timestamp()}' now = dt_now()
order_id = f'dry_run_{side}_{now.timestamp()}'
# Rounding here must respect to contract sizes # Rounding here must respect to contract sizes
_amount = self._contracts_to_amount( _amount = self._contracts_to_amount(
pair, self.amount_to_precision(pair, self._amount_to_contracts(pair, amount))) pair, self.amount_to_precision(pair, self._amount_to_contracts(pair, amount)))
@ -858,8 +859,8 @@ class Exchange:
'side': side, 'side': side,
'filled': 0, 'filled': 0,
'remaining': _amount, 'remaining': _amount,
'datetime': arrow.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 'datetime': now.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': dt_ts(now),
'status': "open", 'status': "open",
'fee': None, 'fee': None,
'info': {}, 'info': {},
@ -1930,11 +1931,11 @@ class Exchange:
logger.debug( logger.debug(
"one_call: %s msecs (%s)", "one_call: %s msecs (%s)",
one_call, one_call,
arrow.utcnow().shift(seconds=one_call // 1000).humanize(only_distance=True) dt_humanize(dt_now() - timedelta(milliseconds=one_call), only_distance=True)
) )
input_coroutines = [self._async_get_candle_history( input_coroutines = [self._async_get_candle_history(
pair, timeframe, candle_type, since) for since in pair, timeframe, candle_type, since) for since in
range(since_ms, until_ms or (arrow.utcnow().int_timestamp * 1000), one_call)] range(since_ms, until_ms or dt_ts(), one_call)]
data: List = [] data: List = []
# Chunk requests into batches of 100 to avoid overwelming ccxt Throttling # Chunk requests into batches of 100 to avoid overwelming ccxt Throttling
@ -2117,7 +2118,7 @@ class Exchange:
""" """
try: try:
# Fetch OHLCV asynchronously # Fetch OHLCV asynchronously
s = '(' + arrow.get(since_ms // 1000).isoformat() + ') ' if since_ms is not None else '' s = '(' + dt_from_ts(since_ms).isoformat() + ') ' if since_ms is not None else ''
logger.debug( logger.debug(
"Fetching pair %s, %s, interval %s, since %s %s...", "Fetching pair %s, %s, interval %s, since %s %s...",
pair, candle_type, timeframe, since_ms, s pair, candle_type, timeframe, since_ms, s
@ -2207,7 +2208,7 @@ class Exchange:
logger.debug( logger.debug(
"Fetching trades for pair %s, since %s %s...", "Fetching trades for pair %s, since %s %s...",
pair, since, pair, since,
'(' + arrow.get(since // 1000).isoformat() + ') ' if since is not None else '' '(' + dt_from_ts(since).isoformat() + ') ' if since is not None else ''
) )
trades = await self._api_async.fetch_trades(pair, since=since, limit=1000) trades = await self._api_async.fetch_trades(pair, since=since, limit=1000)
trades = self._trades_contracts_to_amount(trades) trades = self._trades_contracts_to_amount(trades)

View File

@ -11,6 +11,7 @@ from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGIT
from freqtrade.exchange.common import BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED from freqtrade.exchange.common import BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED
from freqtrade.util import FtPrecise from freqtrade.util import FtPrecise
from freqtrade.util.datetime_helpers import dt_from_ts, dt_ts
CcxtModuleType = Any CcxtModuleType = Any
@ -99,9 +100,8 @@ def timeframe_to_prev_date(timeframe: str, date: Optional[datetime] = None) -> d
if not date: if not date:
date = datetime.now(timezone.utc) date = datetime.now(timezone.utc)
new_timestamp = ccxt.Exchange.round_timeframe(timeframe, date.timestamp() * 1000, new_timestamp = ccxt.Exchange.round_timeframe(timeframe, dt_ts(date), ROUND_DOWN) // 1000
ROUND_DOWN) // 1000 return dt_from_ts(new_timestamp)
return datetime.fromtimestamp(new_timestamp, tz=timezone.utc)
def timeframe_to_next_date(timeframe: str, date: Optional[datetime] = None) -> datetime: def timeframe_to_next_date(timeframe: str, date: Optional[datetime] = None) -> datetime:
@ -113,9 +113,8 @@ def timeframe_to_next_date(timeframe: str, date: Optional[datetime] = None) -> d
""" """
if not date: if not date:
date = datetime.now(timezone.utc) date = datetime.now(timezone.utc)
new_timestamp = ccxt.Exchange.round_timeframe(timeframe, date.timestamp() * 1000, new_timestamp = ccxt.Exchange.round_timeframe(timeframe, dt_ts(date), ROUND_UP) // 1000
ROUND_UP) // 1000 return dt_from_ts(new_timestamp)
return datetime.fromtimestamp(new_timestamp, tz=timezone.utc)
def date_minus_candles( def date_minus_candles(

View File

@ -169,6 +169,22 @@ class Okx(Exchange):
params['posSide'] = self._get_posSide(side, True) params['posSide'] = self._get_posSide(side, True)
return params return params
def _convert_stop_order(self, pair: str, order_id: str, order: Dict) -> Dict:
if (
order['status'] == 'closed'
and (real_order_id := order.get('info', {}).get('ordId')) is not None
):
# Once a order triggered, we fetch the regular followup order.
order_reg = self.fetch_order(real_order_id, pair)
self._log_exchange_response('fetch_stoploss_order1', order_reg)
order_reg['id_stop'] = order_reg['id']
order_reg['id'] = order_id
order_reg['type'] = 'stoploss'
order_reg['status_stop'] = 'triggered'
return order_reg
order['type'] = 'stoploss'
return order
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict: def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
if self._config['dry_run']: if self._config['dry_run']:
return self.fetch_dry_run_order(order_id) return self.fetch_dry_run_order(order_id)
@ -177,7 +193,7 @@ class Okx(Exchange):
params1 = {'stop': True} params1 = {'stop': True}
order_reg = self._api.fetch_order(order_id, pair, params=params1) order_reg = self._api.fetch_order(order_id, pair, params=params1)
self._log_exchange_response('fetch_stoploss_order', order_reg) self._log_exchange_response('fetch_stoploss_order', order_reg)
return order_reg return self._convert_stop_order(pair, order_id, order_reg)
except ccxt.OrderNotFound: except ccxt.OrderNotFound:
pass pass
params2 = {'stop': True, 'ordType': 'conditional'} params2 = {'stop': True, 'ordType': 'conditional'}
@ -188,18 +204,7 @@ class Okx(Exchange):
orders_f = [order for order in orders if order['id'] == order_id] orders_f = [order for order in orders if order['id'] == order_id]
if orders_f: if orders_f:
order = orders_f[0] order = orders_f[0]
if (order['status'] == 'closed' return self._convert_stop_order(pair, order_id, order)
and (real_order_id := order.get('info', {}).get('ordId')) is not None):
# Once a order triggered, we fetch the regular followup order.
order_reg = self.fetch_order(real_order_id, pair)
self._log_exchange_response('fetch_stoploss_order1', order_reg)
order_reg['id_stop'] = order_reg['id']
order_reg['id'] = order_id
order_reg['type'] = 'stoploss'
order_reg['status_stop'] = 'triggered'
return order_reg
order['type'] = 'stoploss'
return order
except ccxt.BaseError: except ccxt.BaseError:
pass pass
raise RetryableOrderError( raise RetryableOrderError(

View File

@ -180,7 +180,7 @@ class BaseEnvironment(gym.Env):
def reset_tensorboard_log(self): def reset_tensorboard_log(self):
self.tensorboard_metrics = {} self.tensorboard_metrics = {}
def reset(self): def reset(self, seed=None):
""" """
Reset is called at the beginning of every episode Reset is called at the beginning of every episode
""" """

View File

@ -10,8 +10,7 @@ logger = logging.getLogger(__name__)
class BaseTensorboardLogger: class BaseTensorboardLogger:
def __init__(self, logdir: Path, activate: bool = True): def __init__(self, logdir: Path, activate: bool = True):
logger.warning("Tensorboard is not installed, no logs will be written." pass
"Ensure torch is installed, or use the torch/RL docker images")
def log_scalar(self, tag: str, scalar_value: Any, step: int): def log_scalar(self, tag: str, scalar_value: Any, step: int):
return return
@ -23,8 +22,7 @@ class BaseTensorboardLogger:
class BaseTensorBoardCallback(TrainingCallback): class BaseTensorBoardCallback(TrainingCallback):
def __init__(self, logdir: Path, activate: bool = True): def __init__(self, logdir: Path, activate: bool = True):
logger.warning("Tensorboard is not installed, no logs will be written." pass
"Ensure torch is installed, or use the torch/RL docker images")
def after_iteration( def after_iteration(
self, model, epoch: int, evals_log: TrainingCallback.EvalsLog self, model, epoch: int, evals_log: TrainingCallback.EvalsLog

View File

@ -1,7 +1,7 @@
import math import math
import torch import torch
import torch.nn as nn from torch import nn
""" """
@ -68,7 +68,7 @@ class PyTorchTransformerModel(nn.Module):
return x return x
class PositionalEncoding(torch.nn.Module): class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000): def __init__(self, d_model, max_len=5000):
""" """
Args Args

View File

@ -1075,7 +1075,7 @@ class FreqtradeBot(LoggingMixin):
trades_closed = 0 trades_closed = 0
for trade in trades: for trade in trades:
if not self.wallets.check_exit_amount(trade): if trade.open_order_id is None and not self.wallets.check_exit_amount(trade):
logger.warning( logger.warning(
f'Not enough {trade.safe_base_currency} in wallet to exit {trade}. ' f'Not enough {trade.safe_base_currency} in wallet to exit {trade}. '
'Trying to recover.') 'Trying to recover.')

View File

@ -3,13 +3,11 @@ Various tool function for Freqtrade and scripts
""" """
import gzip import gzip
import logging import logging
import re
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Iterator, List, Mapping, Optional, TextIO, Union from typing import Any, Dict, Iterator, List, Mapping, Optional, TextIO, Union
from urllib.parse import urlparse from urllib.parse import urlparse
import orjson
import pandas as pd import pandas as pd
import rapidjson import rapidjson
@ -48,18 +46,6 @@ def round_coin_value(
return val return val
def shorten_date(_date: str) -> str:
"""
Trim the date so it fits on small screens
"""
new_date = re.sub('seconds?', 'sec', _date)
new_date = re.sub('minutes?', 'min', new_date)
new_date = re.sub('hours?', 'h', new_date)
new_date = re.sub('days?', 'd', new_date)
new_date = re.sub('^an?', '1', new_date)
return new_date
def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = True) -> None: def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = True) -> None:
""" """
Dump JSON data into a file Dump JSON data into a file
@ -262,17 +248,7 @@ def dataframe_to_json(dataframe: pd.DataFrame) -> str:
:param dataframe: A pandas DataFrame :param dataframe: A pandas DataFrame
:returns: A JSON string of the pandas DataFrame :returns: A JSON string of the pandas DataFrame
""" """
# https://github.com/pandas-dev/pandas/issues/24889 return dataframe.to_json(orient='split')
# https://github.com/pandas-dev/pandas/issues/40443
# We need to convert to a dict to avoid mem leak
def default(z):
if isinstance(z, pd.Timestamp):
return z.timestamp() * 1e3
if z is pd.NaT:
return 'NaT'
raise TypeError
return str(orjson.dumps(dataframe.to_dict(orient='split'), default=default), 'utf-8')
def json_to_dataframe(data: str) -> pd.DataFrame: def json_to_dataframe(data: str) -> pd.DataFrame:

View File

@ -19,7 +19,7 @@ from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, amount_to_contract_precisi
price_to_precision) price_to_precision)
from freqtrade.leverage import interest from freqtrade.leverage import interest
from freqtrade.persistence.base import ModelBase, SessionType from freqtrade.persistence.base import ModelBase, SessionType
from freqtrade.util import FtPrecise from freqtrade.util import FtPrecise, dt_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -68,7 +68,7 @@ class Order(ModelBase):
remaining: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) remaining: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
stop_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) stop_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
order_date: Mapped[datetime] = mapped_column(nullable=True, default=datetime.utcnow) order_date: Mapped[datetime] = mapped_column(nullable=True, default=dt_now)
order_filled_date: Mapped[Optional[datetime]] = mapped_column(nullable=True) order_filled_date: Mapped[Optional[datetime]] = mapped_column(nullable=True)
order_update_date: Mapped[Optional[datetime]] = mapped_column(nullable=True) order_update_date: Mapped[Optional[datetime]] = mapped_column(nullable=True)
funding_fee: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) funding_fee: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)

View File

@ -3,9 +3,9 @@ Minimum age (days listed) pair list filter
""" """
import logging import logging
from copy import deepcopy from copy import deepcopy
from datetime import timedelta
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config, ListPairsWithTimeframes from freqtrade.constants import Config, ListPairsWithTimeframes
@ -13,7 +13,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange.types import Tickers from freqtrade.exchange.types import Tickers
from freqtrade.misc import plural from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
from freqtrade.util import PeriodicCache from freqtrade.util import PeriodicCache, dt_floor_day, dt_now, dt_ts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -84,10 +84,7 @@ class AgeFilter(IPairList):
since_days = -( since_days = -(
self._max_days_listed if self._max_days_listed else self._min_days_listed self._max_days_listed if self._max_days_listed else self._min_days_listed
) - 1 ) - 1
since_ms = int(arrow.utcnow() since_ms = dt_ts(dt_floor_day(dt_now()) + timedelta(days=since_days))
.floor('day')
.shift(days=since_days)
.float_timestamp) * 1000
candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, cache=False) candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, cache=False)
if self._enabled: if self._enabled:
for p in deepcopy(pairlist): for p in deepcopy(pairlist):
@ -116,7 +113,7 @@ class AgeFilter(IPairList):
): ):
# We have fetched at least the minimum required number of daily candles # We have fetched at least the minimum required number of daily candles
# Add to cache, store the time we last checked this symbol # Add to cache, store the time we last checked this symbol
self._symbolsChecked[pair] = arrow.utcnow().int_timestamp * 1000 self._symbolsChecked[pair] = dt_ts()
return True return True
else: else:
self.log_once(( self.log_once((
@ -127,6 +124,6 @@ class AgeFilter(IPairList):
" or more than " " or more than "
f"{self._max_days_listed} {plural(self._max_days_listed, 'day')}" f"{self._max_days_listed} {plural(self._max_days_listed, 'day')}"
) if self._max_days_listed else ''), logger.info) ) if self._max_days_listed else ''), logger.info)
self._symbolsCheckFailed[pair] = arrow.utcnow().int_timestamp * 1000 self._symbolsCheckFailed[pair] = dt_ts()
return False return False
return False return False

View File

@ -4,9 +4,9 @@ Volatility pairlist filter
import logging import logging
import sys import sys
from copy import deepcopy from copy import deepcopy
from datetime import timedelta
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import arrow
import numpy as np import numpy as np
from cachetools import TTLCache from cachetools import TTLCache
from pandas import DataFrame from pandas import DataFrame
@ -16,6 +16,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange.types import Tickers from freqtrade.exchange.types import Tickers
from freqtrade.misc import plural from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
from freqtrade.util import dt_floor_day, dt_now, dt_ts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -73,10 +74,7 @@ class VolatilityFilter(IPairList):
needed_pairs: ListPairsWithTimeframes = [ needed_pairs: ListPairsWithTimeframes = [
(p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache] (p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache]
since_ms = (arrow.utcnow() since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days - 1))
.floor('day')
.shift(days=-self._days - 1)
.int_timestamp) * 1000
# Get all candles # Get all candles
candles = {} candles = {}
if needed_pairs: if needed_pairs:

View File

@ -4,7 +4,7 @@ Volume PairList provider
Provides dynamic pair list based on trade volumes Provides dynamic pair list based on trade volumes
""" """
import logging import logging
from datetime import datetime, timedelta, timezone from datetime import timedelta
from typing import Any, Dict, List, Literal from typing import Any, Dict, List, Literal
from cachetools import TTLCache from cachetools import TTLCache
@ -15,6 +15,7 @@ from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.exchange.types import Tickers from freqtrade.exchange.types import Tickers
from freqtrade.misc import format_ms_time from freqtrade.misc import format_ms_time
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
from freqtrade.util import dt_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -161,13 +162,13 @@ class VolumePairList(IPairList):
# get lookback period in ms, for exchange ohlcv fetch # get lookback period in ms, for exchange ohlcv fetch
since_ms = int(timeframe_to_prev_date( since_ms = int(timeframe_to_prev_date(
self._lookback_timeframe, self._lookback_timeframe,
datetime.now(timezone.utc) + timedelta( dt_now() + timedelta(
minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min) minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min)
).timestamp()) * 1000 ).timestamp()) * 1000
to_ms = int(timeframe_to_prev_date( to_ms = int(timeframe_to_prev_date(
self._lookback_timeframe, self._lookback_timeframe,
datetime.now(timezone.utc) - timedelta(minutes=self._tf_in_min) dt_now() - timedelta(minutes=self._tf_in_min)
).timestamp()) * 1000 ).timestamp()) * 1000
# todo: utc date output for starting date # todo: utc date output for starting date

View File

@ -3,9 +3,9 @@ Rate of change pairlist filter
""" """
import logging import logging
from copy import deepcopy from copy import deepcopy
from datetime import timedelta
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import arrow
from cachetools import TTLCache from cachetools import TTLCache
from pandas import DataFrame from pandas import DataFrame
@ -14,6 +14,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.exchange.types import Tickers from freqtrade.exchange.types import Tickers
from freqtrade.misc import plural from freqtrade.misc import plural
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
from freqtrade.util import dt_floor_day, dt_now, dt_ts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -71,10 +72,7 @@ class RangeStabilityFilter(IPairList):
needed_pairs: ListPairsWithTimeframes = [ needed_pairs: ListPairsWithTimeframes = [
(p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache] (p, '1d', self._def_candletype) for p in pairlist if p not in self._pair_cache]
since_ms = (arrow.utcnow() since_ms = dt_ts(dt_floor_day(dt_now()) - timedelta(days=self._days - 1))
.floor('day')
.shift(days=-self._days - 1)
.int_timestamp) * 1000
# Get all candles # Get all candles
candles = {} candles = {}
if needed_pairs: if needed_pairs:

View File

@ -8,6 +8,7 @@ from fastapi import APIRouter, BackgroundTasks, Depends
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from freqtrade.configuration.config_validation import validate_config_consistency from freqtrade.configuration.config_validation import validate_config_consistency
from freqtrade.constants import Config
from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result
from freqtrade.enums import BacktestState from freqtrade.enums import BacktestState
from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exceptions import DependencyException, OperationalException
@ -16,7 +17,7 @@ from freqtrade.misc import deep_merge_dicts
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest,
BacktestResponse) BacktestResponse)
from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode
from freqtrade.rpc.api_server.webserver import ApiServer from freqtrade.rpc.api_server.webserver_bgwork import ApiBG
from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.rpc import RPCException
@ -26,13 +27,85 @@ logger = logging.getLogger(__name__)
router = APIRouter() router = APIRouter()
def __run_backtest_bg(btconfig: Config):
from freqtrade.optimize.optimize_reports import generate_backtest_stats, store_backtest_stats
from freqtrade.resolvers import StrategyResolver
asyncio.set_event_loop(asyncio.new_event_loop())
try:
# Reload strategy
lastconfig = ApiBG.bt['last_config']
strat = StrategyResolver.load_strategy(btconfig)
validate_config_consistency(btconfig)
if (
not ApiBG.bt['bt']
or lastconfig.get('timeframe') != strat.timeframe
or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail')
or lastconfig.get('timerange') != btconfig['timerange']
):
from freqtrade.optimize.backtesting import Backtesting
ApiBG.bt['bt'] = Backtesting(btconfig)
ApiBG.bt['bt'].load_bt_data_detail()
else:
ApiBG.bt['bt'].config = btconfig
ApiBG.bt['bt'].init_backtest()
# Only reload data if timeframe changed.
if (
not ApiBG.bt['data']
or not ApiBG.bt['timerange']
or lastconfig.get('timeframe') != strat.timeframe
or lastconfig.get('timerange') != btconfig['timerange']
):
ApiBG.bt['data'], ApiBG.bt['timerange'] = ApiBG.bt[
'bt'].load_bt_data()
lastconfig['timerange'] = btconfig['timerange']
lastconfig['timeframe'] = strat.timeframe
lastconfig['protections'] = btconfig.get('protections', [])
lastconfig['enable_protections'] = btconfig.get('enable_protections')
lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet')
ApiBG.bt['bt'].enable_protections = btconfig.get('enable_protections', False)
ApiBG.bt['bt'].strategylist = [strat]
ApiBG.bt['bt'].results = {}
ApiBG.bt['bt'].load_prior_backtest()
ApiBG.bt['bt'].abort = False
if (ApiBG.bt['bt'].results and
strat.get_strategy_name() in ApiBG.bt['bt'].results['strategy']):
# When previous result hash matches - reuse that result and skip backtesting.
logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}')
else:
min_date, max_date = ApiBG.bt['bt'].backtest_one_strategy(
strat, ApiBG.bt['data'], ApiBG.bt['timerange'])
ApiBG.bt['bt'].results = generate_backtest_stats(
ApiBG.bt['data'], ApiBG.bt['bt'].all_results,
min_date=min_date, max_date=max_date)
if btconfig.get('export', 'none') == 'trades':
store_backtest_stats(
btconfig['exportfilename'], ApiBG.bt['bt'].results,
datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
)
logger.info("Backtest finished.")
except (Exception, OperationalException, DependencyException) as e:
logger.exception(f"Backtesting caused an error: {e}")
ApiBG.bt['bt_error'] = str(e)
pass
finally:
ApiBG.bgtask_running = False
@router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) @router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest'])
async def api_start_backtest( # noqa: C901 async def api_start_backtest(
bt_settings: BacktestRequest, background_tasks: BackgroundTasks, bt_settings: BacktestRequest, background_tasks: BackgroundTasks,
config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): config=Depends(get_config), ws_mode=Depends(is_webserver_mode)):
ApiServer._bt['bt_error'] = None ApiBG.bt['bt_error'] = None
"""Start backtesting if not done so already""" """Start backtesting if not done so already"""
if ApiServer._bgtask_running: if ApiBG.bgtask_running:
raise RPCException('Bot Background task already running') raise RPCException('Bot Background task already running')
if ':' in bt_settings.strategy: if ':' in bt_settings.strategy:
@ -56,80 +129,9 @@ async def api_start_backtest( # noqa: C901
# Start backtesting # Start backtesting
# Initialize backtesting object # Initialize backtesting object
def run_backtest():
from freqtrade.optimize.optimize_reports import (generate_backtest_stats,
store_backtest_stats)
from freqtrade.resolvers import StrategyResolver
asyncio.set_event_loop(asyncio.new_event_loop())
try:
# Reload strategy
lastconfig = ApiServer._bt['last_config']
strat = StrategyResolver.load_strategy(btconfig)
validate_config_consistency(btconfig)
if ( background_tasks.add_task(__run_backtest_bg, btconfig=btconfig)
not ApiServer._bt['bt'] ApiBG.bgtask_running = True
or lastconfig.get('timeframe') != strat.timeframe
or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail')
or lastconfig.get('timerange') != btconfig['timerange']
):
from freqtrade.optimize.backtesting import Backtesting
ApiServer._bt['bt'] = Backtesting(btconfig)
ApiServer._bt['bt'].load_bt_data_detail()
else:
ApiServer._bt['bt'].config = btconfig
ApiServer._bt['bt'].init_backtest()
# Only reload data if timeframe changed.
if (
not ApiServer._bt['data']
or not ApiServer._bt['timerange']
or lastconfig.get('timeframe') != strat.timeframe
or lastconfig.get('timerange') != btconfig['timerange']
):
ApiServer._bt['data'], ApiServer._bt['timerange'] = ApiServer._bt[
'bt'].load_bt_data()
lastconfig['timerange'] = btconfig['timerange']
lastconfig['timeframe'] = strat.timeframe
lastconfig['protections'] = btconfig.get('protections', [])
lastconfig['enable_protections'] = btconfig.get('enable_protections')
lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet')
ApiServer._bt['bt'].enable_protections = btconfig.get('enable_protections', False)
ApiServer._bt['bt'].strategylist = [strat]
ApiServer._bt['bt'].results = {}
ApiServer._bt['bt'].load_prior_backtest()
ApiServer._bt['bt'].abort = False
if (ApiServer._bt['bt'].results and
strat.get_strategy_name() in ApiServer._bt['bt'].results['strategy']):
# When previous result hash matches - reuse that result and skip backtesting.
logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}')
else:
min_date, max_date = ApiServer._bt['bt'].backtest_one_strategy(
strat, ApiServer._bt['data'], ApiServer._bt['timerange'])
ApiServer._bt['bt'].results = generate_backtest_stats(
ApiServer._bt['data'], ApiServer._bt['bt'].all_results,
min_date=min_date, max_date=max_date)
if btconfig.get('export', 'none') == 'trades':
store_backtest_stats(
btconfig['exportfilename'], ApiServer._bt['bt'].results,
datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
)
logger.info("Backtest finished.")
except (Exception, OperationalException, DependencyException) as e:
logger.exception(f"Backtesting caused an error: {e}")
ApiServer._bt['bt_error'] = str(e)
pass
finally:
ApiServer._bgtask_running = False
background_tasks.add_task(run_backtest)
ApiServer._bgtask_running = True
return { return {
"status": "running", "status": "running",
@ -147,18 +149,18 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)):
Returns Result after backtesting has been ran. Returns Result after backtesting has been ran.
""" """
from freqtrade.persistence import LocalTrade from freqtrade.persistence import LocalTrade
if ApiServer._bgtask_running: if ApiBG.bgtask_running:
return { return {
"status": "running", "status": "running",
"running": True, "running": True,
"step": (ApiServer._bt['bt'].progress.action if ApiServer._bt['bt'] "step": (ApiBG.bt['bt'].progress.action if ApiBG.bt['bt']
else str(BacktestState.STARTUP)), else str(BacktestState.STARTUP)),
"progress": ApiServer._bt['bt'].progress.progress if ApiServer._bt['bt'] else 0, "progress": ApiBG.bt['bt'].progress.progress if ApiBG.bt['bt'] else 0,
"trade_count": len(LocalTrade.trades), "trade_count": len(LocalTrade.trades),
"status_msg": "Backtest running", "status_msg": "Backtest running",
} }
if not ApiServer._bt['bt']: if not ApiBG.bt['bt']:
return { return {
"status": "not_started", "status": "not_started",
"running": False, "running": False,
@ -166,13 +168,13 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)):
"progress": 0, "progress": 0,
"status_msg": "Backtest not yet executed" "status_msg": "Backtest not yet executed"
} }
if ApiServer._bt['bt_error']: if ApiBG.bt['bt_error']:
return { return {
"status": "error", "status": "error",
"running": False, "running": False,
"step": "", "step": "",
"progress": 0, "progress": 0,
"status_msg": f"Backtest failed with {ApiServer._bt['bt_error']}" "status_msg": f"Backtest failed with {ApiBG.bt['bt_error']}"
} }
return { return {
@ -181,14 +183,14 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)):
"status_msg": "Backtest ended", "status_msg": "Backtest ended",
"step": "finished", "step": "finished",
"progress": 1, "progress": 1,
"backtest_result": ApiServer._bt['bt'].results, "backtest_result": ApiBG.bt['bt'].results,
} }
@router.delete('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) @router.delete('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest'])
def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): def api_delete_backtest(ws_mode=Depends(is_webserver_mode)):
"""Reset backtesting""" """Reset backtesting"""
if ApiServer._bgtask_running: if ApiBG.bgtask_running:
return { return {
"status": "running", "status": "running",
"running": True, "running": True,
@ -196,12 +198,12 @@ def api_delete_backtest(ws_mode=Depends(is_webserver_mode)):
"progress": 0, "progress": 0,
"status_msg": "Backtest running", "status_msg": "Backtest running",
} }
if ApiServer._bt['bt']: if ApiBG.bt['bt']:
ApiServer._bt['bt'].cleanup() ApiBG.bt['bt'].cleanup()
del ApiServer._bt['bt'] del ApiBG.bt['bt']
ApiServer._bt['bt'] = None ApiBG.bt['bt'] = None
del ApiServer._bt['data'] del ApiBG.bt['data']
ApiServer._bt['data'] = None ApiBG.bt['data'] = None
logger.info("Backtesting reset") logger.info("Backtesting reset")
return { return {
"status": "reset", "status": "reset",
@ -214,7 +216,7 @@ def api_delete_backtest(ws_mode=Depends(is_webserver_mode)):
@router.get('/backtest/abort', response_model=BacktestResponse, tags=['webserver', 'backtest']) @router.get('/backtest/abort', response_model=BacktestResponse, tags=['webserver', 'backtest'])
def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): def api_backtest_abort(ws_mode=Depends(is_webserver_mode)):
if not ApiServer._bgtask_running: if not ApiBG.bgtask_running:
return { return {
"status": "not_running", "status": "not_running",
"running": False, "running": False,
@ -222,7 +224,7 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)):
"progress": 0, "progress": 0,
"status_msg": "Backtest ended", "status_msg": "Backtest ended",
} }
ApiServer._bt['bt'].abort = True ApiBG.bt['bt'].abort = True
return { return {
"status": "stopping", "status": "stopping",
"running": False, "running": False,

View File

@ -6,6 +6,7 @@ from fastapi import Depends
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.persistence.models import _request_id_ctx_var from freqtrade.persistence.models import _request_id_ctx_var
from freqtrade.rpc.api_server.webserver_bgwork import ApiBG
from freqtrade.rpc.rpc import RPC, RPCException from freqtrade.rpc.rpc import RPC, RPCException
from .webserver import ApiServer from .webserver import ApiServer
@ -43,11 +44,11 @@ def get_api_config() -> Dict[str, Any]:
def get_exchange(config=Depends(get_config)): def get_exchange(config=Depends(get_config)):
if not ApiServer._exchange: if not ApiBG.exchange:
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver
ApiServer._exchange = ExchangeResolver.load_exchange( ApiBG.exchange = ExchangeResolver.load_exchange(
config, load_leverage_tiers=False) config, load_leverage_tiers=False)
return ApiServer._exchange return ApiBG.exchange
def get_message_stream(): def get_message_stream():

View File

@ -1,6 +1,6 @@
import logging import logging
from ipaddress import IPv4Address from ipaddress import IPv4Address
from typing import Any, Dict, Optional from typing import Any, Optional
import orjson import orjson
import uvicorn import uvicorn
@ -36,19 +36,8 @@ class ApiServer(RPCHandler):
__initialized = False __initialized = False
_rpc: RPC _rpc: RPC
# Backtesting type: Backtesting
_bt: Dict[str, Any] = {
'bt': None,
'data': None,
'timerange': None,
'last_config': {},
'bt_error': None,
}
_has_rpc: bool = False _has_rpc: bool = False
_bgtask_running: bool = False
_config: Config = {} _config: Config = {}
# Exchange - only available in webserver mode.
_exchange = None
# websocket message stuff # websocket message stuff
_message_stream: Optional[MessageStream] = None _message_stream: Optional[MessageStream] = None
@ -85,7 +74,7 @@ class ApiServer(RPCHandler):
""" """
Attach rpc handler Attach rpc handler
""" """
if not self._has_rpc: if not ApiServer._has_rpc:
ApiServer._rpc = rpc ApiServer._rpc = rpc
ApiServer._has_rpc = True ApiServer._has_rpc = True
else: else:

View File

@ -0,0 +1,16 @@
from typing import Any, Dict
class ApiBG():
# Backtesting type: Backtesting
bt: Dict[str, Any] = {
'bt': None,
'data': None,
'timerange': None,
'last_config': {},
'bt_error': None,
}
bgtask_running: bool = False
# Exchange - only available in webserver mode.
exchange = None

View File

@ -7,7 +7,6 @@ from datetime import date, datetime, timedelta, timezone
from math import isnan from math import isnan
from typing import Any, Dict, Generator, List, Optional, Sequence, Tuple, Union from typing import Any, Dict, Generator, List, Optional, Sequence, Tuple, Union
import arrow
import psutil import psutil
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from dateutil.tz import tzlocal from dateutil.tz import tzlocal
@ -26,12 +25,13 @@ 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.exchange.types import Tickers from freqtrade.exchange.types import Tickers
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
from freqtrade.persistence import KeyStoreKeys, KeyValueStore, Order, PairLocks, Trade from freqtrade.persistence import KeyStoreKeys, KeyValueStore, 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
from freqtrade.rpc.rpc_types import RPCSendMsg from freqtrade.rpc.rpc_types import RPCSendMsg
from freqtrade.util import dt_humanize, dt_now, shorten_date
from freqtrade.wallets import PositionWallet, Wallet from freqtrade.wallets import PositionWallet, Wallet
@ -292,7 +292,7 @@ class RPC:
and open_order.ft_order_side == trade.entry_side) else '') and open_order.ft_order_side == trade.entry_side) else '')
+ ('**' if (open_order and + ('**' if (open_order and
open_order.ft_order_side == trade.exit_side is not None) else ''), open_order.ft_order_side == trade.exit_side is not None) else ''),
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), shorten_date(dt_humanize(trade.open_date, only_distance=True)),
profit_str profit_str
] ]
if self._config.get('position_adjustment_enable', False): if self._config.get('position_adjustment_enable', False):
@ -564,10 +564,10 @@ class RPC:
'trade_count': len(trades), 'trade_count': len(trades),
'closed_trade_count': len([t for t in trades if not t.is_open]), 'closed_trade_count': len([t for t in trades if not t.is_open]),
'first_trade_date': first_date.strftime(DATETIME_PRINT_FORMAT) if first_date else '', 'first_trade_date': first_date.strftime(DATETIME_PRINT_FORMAT) if first_date else '',
'first_trade_humanized': arrow.get(first_date).humanize() if first_date else '', 'first_trade_humanized': dt_humanize(first_date) if first_date else '',
'first_trade_timestamp': int(first_date.timestamp() * 1000) if first_date else 0, 'first_trade_timestamp': int(first_date.timestamp() * 1000) if first_date else 0,
'latest_trade_date': last_date.strftime(DATETIME_PRINT_FORMAT) if last_date else '', 'latest_trade_date': last_date.strftime(DATETIME_PRINT_FORMAT) if last_date else '',
'latest_trade_humanized': arrow.get(last_date).humanize() if last_date else '', 'latest_trade_humanized': dt_humanize(last_date) if last_date else '',
'latest_trade_timestamp': int(last_date.timestamp() * 1000) if last_date else 0, 'latest_trade_timestamp': int(last_date.timestamp() * 1000) if last_date else 0,
'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0], 'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0],
'best_pair': best_pair[0] if best_pair else '', 'best_pair': best_pair[0] if best_pair else '',
@ -1252,7 +1252,7 @@ class RPC:
df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair}) df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair})
return RPC._convert_dataframe_to_dict(strategy.get_strategy_name(), pair, timeframe, return RPC._convert_dataframe_to_dict(strategy.get_strategy_name(), pair, timeframe,
df_analyzed, arrow.Arrow.utcnow().datetime) df_analyzed, dt_now())
def _rpc_plot_config(self) -> Dict[str, Any]: def _rpc_plot_config(self) -> Dict[str, Any]:
if (self._freqtrade.strategy.plot_config and if (self._freqtrade.strategy.plot_config and

View File

@ -17,7 +17,6 @@ from math import isnan
from threading import Thread from threading import Thread
from typing import Any, Callable, Coroutine, Dict, List, Optional, Union from typing import Any, Callable, Coroutine, Dict, List, Optional, Union
import arrow
from tabulate import tabulate from tabulate import tabulate
from telegram import (CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, from telegram import (CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton,
ReplyKeyboardMarkup, Update) ReplyKeyboardMarkup, Update)
@ -34,6 +33,7 @@ from freqtrade.misc import chunks, plural, round_coin_value
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException, RPCHandler from freqtrade.rpc import RPC, RPCException, RPCHandler
from freqtrade.rpc.rpc_types import RPCSendMsg from freqtrade.rpc.rpc_types import RPCSendMsg
from freqtrade.util import dt_humanize
MAX_MESSAGE_LENGTH = MessageLimit.MAX_TEXT_LENGTH MAX_MESSAGE_LENGTH = MessageLimit.MAX_TEXT_LENGTH
@ -528,7 +528,6 @@ class Telegram(RPCHandler):
order_nr += 1 order_nr += 1
wording = 'Entry' if order['ft_is_entry'] else 'Exit' wording = 'Entry' if order['ft_is_entry'] else 'Exit'
cur_entry_datetime = arrow.get(order["order_filled_date"])
cur_entry_amount = order["filled"] or order["amount"] cur_entry_amount = order["filled"] or order["amount"]
cur_entry_average = order["safe_price"] cur_entry_average = order["safe_price"]
lines.append(" ") lines.append(" ")
@ -559,22 +558,14 @@ class Telegram(RPCHandler):
lines.append(f"*{wording} #{order_nr}:* at {minus_on_entry:.2%} avg Profit") lines.append(f"*{wording} #{order_nr}:* at {minus_on_entry:.2%} avg Profit")
if is_open: if is_open:
lines.append("({})".format(cur_entry_datetime lines.append("({})".format(dt_humanize(order["order_filled_date"],
.humanize(granularity=["day", "hour", "minute"]))) granularity=["day", "hour", "minute"])))
lines.append(f"*Amount:* {cur_entry_amount} " lines.append(f"*Amount:* {cur_entry_amount} "
f"({round_coin_value(order['cost'], quote_currency)})") f"({round_coin_value(order['cost'], quote_currency)})")
lines.append(f"*Average {wording} Price:* {cur_entry_average} " lines.append(f"*Average {wording} Price:* {cur_entry_average} "
f"({price_to_1st_entry:.2%} from 1st entry Rate)") f"({price_to_1st_entry:.2%} from 1st entry Rate)")
lines.append(f"*Order filled:* {order['order_filled_date']}") lines.append(f"*Order filled:* {order['order_filled_date']}")
# TODO: is this really useful?
# dur_entry = cur_entry_datetime - arrow.get(
# filled_orders[x - 1]["order_filled_date"])
# days = dur_entry.days
# hours, remainder = divmod(dur_entry.seconds, 3600)
# minutes, seconds = divmod(remainder, 60)
# lines.append(
# f"({days}d {hours}h {minutes}m {seconds}s from previous {wording.lower()})")
lines_detail.append("\n".join(lines)) lines_detail.append("\n".join(lines))
return lines_detail return lines_detail
@ -610,7 +601,7 @@ class Telegram(RPCHandler):
position_adjust = self._config.get('position_adjustment_enable', False) position_adjust = self._config.get('position_adjustment_enable', False)
max_entries = self._config.get('max_entry_position_adjustment', -1) max_entries = self._config.get('max_entry_position_adjustment', -1)
for r in results: for r in results:
r['open_date_hum'] = arrow.get(r['open_date']).humanize() r['open_date_hum'] = dt_humanize(r['open_date'])
r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']]) r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']])
r['num_exits'] = len([o for o in r['orders'] if not o['ft_is_entry'] r['num_exits'] = len([o for o in r['orders'] if not o['ft_is_entry']
and not o['ft_order_side'] == 'stoploss']) and not o['ft_order_side'] == 'stoploss'])
@ -1219,7 +1210,7 @@ class Telegram(RPCHandler):
nrecent nrecent
) )
trades_tab = tabulate( trades_tab = tabulate(
[[arrow.get(trade['close_date']).humanize(), [[dt_humanize(trade['close_date']),
trade['pair'] + " (#" + str(trade['trade_id']) + ")", trade['pair'] + " (#" + str(trade['trade_id']) + ")",
f"{(trade['close_profit']):.2%} ({trade['close_profit_abs']})"] f"{(trade['close_profit']):.2%} ({trade['close_profit_abs']})"]
for trade in trades['trades']], for trade in trades['trades']],

View File

@ -7,7 +7,6 @@ from abc import ABC, abstractmethod
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional, Tuple, Union from typing import Dict, List, Optional, Tuple, Union
import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, Config, IntOrInf, ListPairsWithTimeframes from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, Config, IntOrInf, ListPairsWithTimeframes
@ -23,6 +22,7 @@ from freqtrade.strategy.informative_decorator import (InformativeData, PopulateI
_create_and_merge_informative_pair, _create_and_merge_informative_pair,
_format_pair_name) _format_pair_name)
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.util import dt_now
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
@ -938,7 +938,7 @@ class IStrategy(ABC, HyperStrategyMixin):
pair: str, pair: str,
timeframe: str, timeframe: str,
dataframe: DataFrame, dataframe: DataFrame,
) -> Tuple[Optional[DataFrame], Optional[arrow.Arrow]]: ) -> Tuple[Optional[DataFrame], Optional[datetime]]:
""" """
Calculates current signal based based on the entry order or exit order Calculates current signal based based on the entry order or exit order
columns of the dataframe. columns of the dataframe.
@ -954,16 +954,16 @@ class IStrategy(ABC, HyperStrategyMixin):
latest_date = dataframe['date'].max() latest_date = dataframe['date'].max()
latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1] latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
# Explicitly convert to arrow object to ensure the below comparison does not fail # Explicitly convert to datetime object to ensure the below comparison does not fail
latest_date = arrow.get(latest_date) latest_date = latest_date.to_pydatetime()
# Check if dataframe is out of date # Check if dataframe is out of date
timeframe_minutes = timeframe_to_minutes(timeframe) timeframe_minutes = timeframe_to_minutes(timeframe)
offset = self.config.get('exchange', {}).get('outdated_offset', 5) offset = self.config.get('exchange', {}).get('outdated_offset', 5)
if latest_date < (arrow.utcnow().shift(minutes=-(timeframe_minutes * 2 + offset))): if latest_date < (dt_now() - timedelta(minutes=timeframe_minutes * 2 + offset)):
logger.warning( logger.warning(
'Outdated history for pair %s. Last tick is %s minutes old', 'Outdated history for pair %s. Last tick is %s minutes old',
pair, int((arrow.utcnow() - latest_date).total_seconds() // 60) pair, int((dt_now() - latest_date).total_seconds() // 60)
) )
return None, None return None, None
return latest, latest_date return latest, latest_date
@ -1046,8 +1046,8 @@ class IStrategy(ABC, HyperStrategyMixin):
timeframe_seconds = timeframe_to_seconds(timeframe) timeframe_seconds = timeframe_to_seconds(timeframe)
if self.ignore_expired_candle( if self.ignore_expired_candle(
latest_date=latest_date.datetime, latest_date=latest_date,
current_time=datetime.now(timezone.utc), current_time=dt_now(),
timeframe_seconds=timeframe_seconds, timeframe_seconds=timeframe_seconds,
enter=bool(enter_signal) enter=bool(enter_signal)
): ):

View File

@ -1,2 +1,17 @@
from freqtrade.util.ft_precise import FtPrecise # noqa: F401 from freqtrade.util.datetime_helpers import (dt_floor_day, dt_from_ts, dt_humanize, dt_now, dt_ts,
from freqtrade.util.periodic_cache import PeriodicCache # noqa: F401 dt_utc, shorten_date)
from freqtrade.util.ft_precise import FtPrecise
from freqtrade.util.periodic_cache import PeriodicCache
__all__ = [
'dt_floor_day',
'dt_from_ts',
'dt_now',
'dt_ts',
'dt_utc',
'dt_humanize',
'shorten_date',
'FtPrecise',
'PeriodicCache',
]

View File

@ -0,0 +1,63 @@
import re
from datetime import datetime, timezone
from typing import Optional
import arrow
def dt_now() -> datetime:
"""Return the current datetime in UTC."""
return datetime.now(timezone.utc)
def dt_utc(year: int, month: int, day: int, hour: int = 0, minute: int = 0, second: int = 0,
microsecond: int = 0) -> datetime:
"""Return a datetime in UTC."""
return datetime(year, month, day, hour, minute, second, microsecond, tzinfo=timezone.utc)
def dt_ts(dt: Optional[datetime] = None) -> int:
"""
Return dt in ms as a timestamp in UTC.
If dt is None, return the current datetime in UTC.
"""
if dt:
return int(dt.timestamp() * 1000)
return int(dt_now().timestamp() * 1000)
def dt_floor_day(dt: datetime) -> datetime:
"""Return the floor of the day for the given datetime."""
return dt.replace(hour=0, minute=0, second=0, microsecond=0)
def dt_from_ts(timestamp: float) -> datetime:
"""
Return a datetime from a timestamp.
:param timestamp: timestamp in seconds or milliseconds
"""
if timestamp > 1e10:
# Timezone in ms - convert to seconds
timestamp /= 1000
return datetime.fromtimestamp(timestamp, tz=timezone.utc)
def shorten_date(_date: str) -> str:
"""
Trim the date so it fits on small screens
"""
new_date = re.sub('seconds?', 'sec', _date)
new_date = re.sub('minutes?', 'min', new_date)
new_date = re.sub('hours?', 'h', new_date)
new_date = re.sub('days?', 'd', new_date)
new_date = re.sub('^an?', '1', new_date)
return new_date
def dt_humanize(dt: datetime, **kwargs) -> str:
"""
Return a humanized string for the given datetime.
:param dt: datetime to humanize
:param kwargs: kwargs to pass to arrow's humanize()
"""
return arrow.get(dt).humanize(**kwargs)

View File

@ -3,16 +3,16 @@
import logging import logging
from copy import deepcopy from copy import deepcopy
from datetime import datetime, timedelta
from typing import Dict, NamedTuple, Optional from typing import Dict, NamedTuple, Optional
import arrow
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.enums import RunMode, TradingMode from freqtrade.enums import RunMode, TradingMode
from freqtrade.exceptions import DependencyException from freqtrade.exceptions import DependencyException
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.misc import safe_value_fallback from freqtrade.misc import safe_value_fallback
from freqtrade.persistence import LocalTrade, Trade from freqtrade.persistence import LocalTrade, Trade
from freqtrade.util.datetime_helpers import dt_now
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -43,7 +43,7 @@ class Wallets:
self._wallets: Dict[str, Wallet] = {} self._wallets: Dict[str, Wallet] = {}
self._positions: Dict[str, PositionWallet] = {} self._positions: Dict[str, PositionWallet] = {}
self.start_cap = config['dry_run_wallet'] self.start_cap = config['dry_run_wallet']
self._last_wallet_refresh = 0 self._last_wallet_refresh: Optional[datetime] = None
self.update() self.update()
def get_free(self, currency: str) -> float: def get_free(self, currency: str) -> float:
@ -166,14 +166,19 @@ class Wallets:
for trading operations, the latest balance is needed. for trading operations, the latest balance is needed.
:param require_update: Allow skipping an update if balances were recently refreshed :param require_update: Allow skipping an update if balances were recently refreshed
""" """
if (require_update or (self._last_wallet_refresh + 3600 < arrow.utcnow().int_timestamp)): now = dt_now()
if (
require_update
or self._last_wallet_refresh is None
or (self._last_wallet_refresh + timedelta(seconds=3600) < now)
):
if (not self._config['dry_run'] or self._config.get('runmode') == RunMode.LIVE): if (not self._config['dry_run'] or self._config.get('runmode') == RunMode.LIVE):
self._update_live() self._update_live()
else: else:
self._update_dry() self._update_dry()
if self._log: if self._log:
logger.info('Wallets synced.') logger.info('Wallets synced.')
self._last_wallet_refresh = arrow.utcnow().int_timestamp self._last_wallet_refresh = dt_now()
def get_all_balances(self) -> Dict[str, Wallet]: def get_all_balances(self) -> Dict[str, Wallet]:
return self._wallets return self._wallets

View File

@ -7,9 +7,9 @@
-r docs/requirements-docs.txt -r docs/requirements-docs.txt
coveralls==3.3.1 coveralls==3.3.1
ruff==0.0.267 ruff==0.0.269
mypy==1.3.0 mypy==1.3.0
pre-commit==3.3.1 pre-commit==3.3.2
pytest==7.3.1 pytest==7.3.1
pytest-asyncio==0.21.0 pytest-asyncio==0.21.0
pytest-cov==4.0.0 pytest-cov==4.0.0

View File

@ -5,7 +5,7 @@
torch==2.0.1 torch==2.0.1
#until these branches will be released we can use this #until these branches will be released we can use this
gymnasium==0.28.1 gymnasium==0.28.1
stable_baselines3==2.0.0a5 stable_baselines3==2.0.0a10
sb3_contrib>=2.0.0a4 sb3_contrib>=2.0.0a9
# Progress bar for stable-baselines3 and sb3-contrib # Progress bar for stable-baselines3 and sb3-contrib
tqdm==4.65.0 tqdm==4.65.0

View File

@ -2,17 +2,17 @@ numpy==1.24.3
pandas==2.0.1 pandas==2.0.1
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==3.0.103 ccxt==3.1.5
cryptography==40.0.2; platform_machine != 'armv7l' cryptography==40.0.2; platform_machine != 'armv7l'
cryptography==40.0.1; platform_machine == 'armv7l' cryptography==40.0.1; platform_machine == 'armv7l'
aiohttp==3.8.4 aiohttp==3.8.4
SQLAlchemy==2.0.13 SQLAlchemy==2.0.15
python-telegram-bot==20.3 python-telegram-bot==20.3
# can't be hard-pinned due to telegram-bot pinning httpx with ~ # can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.23.3 httpx>=0.23.3
arrow==1.2.3 arrow==1.2.3
cachetools==4.2.2 cachetools==5.3.0
requests==2.30.0 requests==2.31.0
urllib3==2.0.2 urllib3==2.0.2
jsonschema==4.17.3 jsonschema==4.17.3
TA-Lib==0.4.26 TA-Lib==0.4.26
@ -38,7 +38,7 @@ orjson==3.8.12
sdnotify==0.3.2 sdnotify==0.3.2
# API Server # API Server
fastapi==0.95.1 fastapi==0.95.2
pydantic==1.10.7 pydantic==1.10.7
uvicorn==0.22.0 uvicorn==0.22.0
pyjwt==2.7.0 pyjwt==2.7.0

View File

@ -279,8 +279,9 @@ class FtRestClient():
""" """
data = {"pair": pair, data = {"pair": pair,
"side": side, "side": side,
"price": price,
} }
if price:
data['price'] = price
return self._post("forceenter", data=data) return self._post("forceenter", data=data)
def forceexit(self, tradeid, ordertype=None, amount=None): def forceexit(self, tradeid, ordertype=None, amount=None):

View File

@ -72,7 +72,7 @@ setup(
'ccxt>=3.0.0', 'ccxt>=3.0.0',
'SQLAlchemy>=2.0.6', 'SQLAlchemy>=2.0.6',
'python-telegram-bot>=20.1', 'python-telegram-bot>=20.1',
'arrow>=0.17.0', 'arrow>=1.0.0',
'cachetools', 'cachetools',
'requests', 'requests',
'urllib3', 'urllib3',

View File

@ -1,12 +1,11 @@
import json import json
import re import re
from datetime import datetime from datetime import datetime, timedelta
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock, PropertyMock
from zipfile import ZipFile from zipfile import ZipFile
import arrow
import pytest import pytest
from freqtrade.commands import (start_backtesting_show, start_convert_data, start_convert_trades, from freqtrade.commands import (start_backtesting_show, start_convert_data, start_convert_trades,
@ -25,6 +24,7 @@ from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
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.util import dt_floor_day, dt_now, dt_utc
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_args, log_has, from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_args, log_has,
log_has_re, patch_exchange, patched_configuration_load_config_file) log_has_re, patch_exchange, patched_configuration_load_config_file)
from tests.conftest_trades import MOCK_TRADE_COUNT from tests.conftest_trades import MOCK_TRADE_COUNT
@ -689,7 +689,7 @@ def test_download_data_timerange(mocker, markets):
start_download_data(pargs) start_download_data(pargs)
assert dl_mock.call_count == 1 assert dl_mock.call_count == 1
# 20days ago # 20days ago
days_ago = arrow.get(arrow.now().shift(days=-20).date()).int_timestamp days_ago = dt_floor_day(dt_now() - timedelta(days=20)).timestamp()
assert dl_mock.call_args_list[0][1]['timerange'].startts == days_ago assert dl_mock.call_args_list[0][1]['timerange'].startts == days_ago
dl_mock.reset_mock() dl_mock.reset_mock()
@ -704,8 +704,7 @@ def test_download_data_timerange(mocker, markets):
start_download_data(pargs) start_download_data(pargs)
assert dl_mock.call_count == 1 assert dl_mock.call_count == 1
assert dl_mock.call_args_list[0][1]['timerange'].startts == arrow.Arrow( assert dl_mock.call_args_list[0][1]['timerange'].startts == int(dt_utc(2020, 1, 1).timestamp())
2020, 1, 1).int_timestamp
def test_download_data_no_markets(mocker, caplog): def test_download_data_no_markets(mocker, caplog):

View File

@ -8,7 +8,6 @@ from pathlib import Path
from typing import Optional from typing import Optional
from unittest.mock import MagicMock, Mock, PropertyMock from unittest.mock import MagicMock, Mock, PropertyMock
import arrow
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import pytest import pytest
@ -23,6 +22,8 @@ from freqtrade.exchange.exchange import timeframe_to_minutes
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import LocalTrade, Order, Trade, init_db from freqtrade.persistence import LocalTrade, Order, Trade, init_db
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import dt_ts
from freqtrade.util.datetime_helpers import dt_now
from freqtrade.worker import Worker from freqtrade.worker import Worker
from tests.conftest_trades import (leverage_trade, mock_trade_1, mock_trade_2, mock_trade_3, from tests.conftest_trades import (leverage_trade, mock_trade_1, mock_trade_2, mock_trade_3,
mock_trade_4, mock_trade_5, mock_trade_6, short_trade) mock_trade_4, mock_trade_5, mock_trade_6, short_trade)
@ -1663,8 +1664,8 @@ def limit_buy_order_open():
'type': 'limit', 'type': 'limit',
'side': 'buy', 'side': 'buy',
'symbol': 'mocked', 'symbol': 'mocked',
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': dt_ts(),
'datetime': arrow.utcnow().isoformat(), 'datetime': dt_now().isoformat(),
'price': 0.00001099, 'price': 0.00001099,
'average': 0.00001099, 'average': 0.00001099,
'amount': 90.99181073, 'amount': 90.99181073,
@ -1691,8 +1692,8 @@ def limit_buy_order_old():
'type': 'limit', 'type': 'limit',
'side': 'buy', 'side': 'buy',
'symbol': 'mocked', 'symbol': 'mocked',
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'datetime': (dt_now() - timedelta(minutes=601)).isoformat(),
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'timestamp': dt_ts(dt_now() - timedelta(minutes=601)),
'price': 0.00001099, 'price': 0.00001099,
'amount': 90.99181073, 'amount': 90.99181073,
'filled': 0.0, 'filled': 0.0,
@ -1708,8 +1709,8 @@ def limit_sell_order_old():
'type': 'limit', 'type': 'limit',
'side': 'sell', 'side': 'sell',
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'timestamp': dt_ts(dt_now() - timedelta(minutes=601)),
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'datetime': (dt_now() - timedelta(minutes=601)).isoformat(),
'price': 0.00001099, 'price': 0.00001099,
'amount': 90.99181073, 'amount': 90.99181073,
'filled': 0.0, 'filled': 0.0,
@ -1725,8 +1726,8 @@ def limit_buy_order_old_partial():
'type': 'limit', 'type': 'limit',
'side': 'buy', 'side': 'buy',
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'timestamp': dt_ts(dt_now() - timedelta(minutes=601)),
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'datetime': (dt_now() - timedelta(minutes=601)).isoformat(),
'price': 0.00001099, 'price': 0.00001099,
'amount': 90.99181073, 'amount': 90.99181073,
'filled': 23.0, 'filled': 23.0,
@ -1756,8 +1757,8 @@ def limit_buy_order_canceled_empty(request):
'info': {}, 'info': {},
'id': 'AZNPFF-4AC4N-7MKTAT', 'id': 'AZNPFF-4AC4N-7MKTAT',
'clientOrderId': None, 'clientOrderId': None,
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'timestamp': dt_ts(dt_now() - timedelta(minutes=601)),
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'datetime': (dt_now() - timedelta(minutes=601)).isoformat(),
'lastTradeTimestamp': None, 'lastTradeTimestamp': None,
'status': 'canceled', 'status': 'canceled',
'symbol': 'LTC/USDT', 'symbol': 'LTC/USDT',
@ -1777,8 +1778,8 @@ def limit_buy_order_canceled_empty(request):
'info': {}, 'info': {},
'id': '1234512345', 'id': '1234512345',
'clientOrderId': 'alb1234123', 'clientOrderId': 'alb1234123',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'timestamp': dt_ts(dt_now() - timedelta(minutes=601)),
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'datetime': (dt_now() - timedelta(minutes=601)).isoformat(),
'lastTradeTimestamp': None, 'lastTradeTimestamp': None,
'symbol': 'LTC/USDT', 'symbol': 'LTC/USDT',
'type': 'limit', 'type': 'limit',
@ -1798,8 +1799,8 @@ def limit_buy_order_canceled_empty(request):
'info': {}, 'info': {},
'id': '1234512345', 'id': '1234512345',
'clientOrderId': 'alb1234123', 'clientOrderId': 'alb1234123',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'timestamp': dt_ts(dt_now() - timedelta(minutes=601)),
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'datetime': (dt_now() - timedelta(minutes=601)).isoformat(),
'lastTradeTimestamp': None, 'lastTradeTimestamp': None,
'symbol': 'LTC/USDT', 'symbol': 'LTC/USDT',
'type': 'limit', 'type': 'limit',
@ -1823,8 +1824,8 @@ def limit_sell_order_open():
'type': 'limit', 'type': 'limit',
'side': 'sell', 'side': 'sell',
'symbol': 'mocked', 'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(), 'datetime': dt_now().isoformat(),
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': dt_ts(),
'price': 0.00001173, 'price': 0.00001173,
'amount': 90.99181073, 'amount': 90.99181073,
'filled': 0.0, 'filled': 0.0,
@ -2486,8 +2487,8 @@ def buy_order_fee():
'type': 'limit', 'type': 'limit',
'side': 'buy', 'side': 'buy',
'symbol': 'mocked', 'symbol': 'mocked',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'timestamp': dt_ts(dt_now() - timedelta(minutes=601)),
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'datetime': (dt_now() - timedelta(minutes=601)).isoformat(),
'price': 0.245441, 'price': 0.245441,
'amount': 8.0, 'amount': 8.0,
'cost': 1.963528, 'cost': 1.963528,
@ -2596,7 +2597,7 @@ def open_trade():
fee_open=0.0, fee_open=0.0,
fee_close=0.0, fee_close=0.0,
stake_amount=1, stake_amount=1,
open_date=arrow.utcnow().shift(minutes=-601).datetime, open_date=dt_now() - timedelta(minutes=601),
is_open=True is_open=True
) )
trade.orders = [ trade.orders = [
@ -2634,7 +2635,7 @@ def open_trade_usdt():
fee_open=0.0, fee_open=0.0,
fee_close=0.0, fee_close=0.0,
stake_amount=60.0, stake_amount=60.0,
open_date=arrow.utcnow().shift(minutes=-601).datetime, open_date=dt_now() - timedelta(minutes=601),
is_open=True is_open=True
) )
trade.orders = [ trade.orders = [
@ -2838,8 +2839,8 @@ def limit_buy_order_usdt_open():
'type': 'limit', 'type': 'limit',
'side': 'buy', 'side': 'buy',
'symbol': 'mocked', 'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(), 'datetime': dt_now().isoformat(),
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': dt_ts(),
'price': 2.00, 'price': 2.00,
'average': 2.00, 'average': 2.00,
'amount': 30.0, 'amount': 30.0,
@ -2866,8 +2867,8 @@ def limit_sell_order_usdt_open():
'type': 'limit', 'type': 'limit',
'side': 'sell', 'side': 'sell',
'symbol': 'mocked', 'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(), 'datetime': dt_now().isoformat(),
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': dt_ts(),
'price': 2.20, 'price': 2.20,
'amount': 30.0, 'amount': 30.0,
'cost': 66.0, 'cost': 66.0,
@ -2893,8 +2894,8 @@ def market_buy_order_usdt():
'type': 'market', 'type': 'market',
'side': 'buy', 'side': 'buy',
'symbol': 'mocked', 'symbol': 'mocked',
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': dt_ts(),
'datetime': arrow.utcnow().isoformat(), 'datetime': dt_now().isoformat(),
'price': 2.00, 'price': 2.00,
'amount': 30.0, 'amount': 30.0,
'filled': 30.0, 'filled': 30.0,
@ -2950,8 +2951,8 @@ def market_sell_order_usdt():
'type': 'market', 'type': 'market',
'side': 'sell', 'side': 'sell',
'symbol': 'mocked', 'symbol': 'mocked',
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': dt_ts(),
'datetime': arrow.utcnow().isoformat(), 'datetime': dt_now().isoformat(),
'price': 2.20, 'price': 2.20,
'amount': 30.0, 'amount': 30.0,
'filled': 30.0, 'filled': 30.0,

View File

@ -1,8 +1,8 @@
from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from arrow import Arrow
from pandas import DataFrame, DateOffset, Timestamp, to_datetime from pandas import DataFrame, DateOffset, Timestamp, to_datetime
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
@ -18,6 +18,7 @@ from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_
calculate_underwater, combine_dataframes_with_mean, calculate_underwater, combine_dataframes_with_mean,
create_cum_profit) create_cum_profit)
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.util import dt_utc
from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades
from tests.conftest_trades import MOCK_TRADE_COUNT from tests.conftest_trades import MOCK_TRADE_COUNT
@ -162,25 +163,25 @@ def test_extract_trades_of_period(testdatadir):
{'pair': [pair, pair, pair, pair], {'pair': [pair, pair, pair, pair],
'profit_ratio': [0.0, 0.1, -0.2, -0.5], 'profit_ratio': [0.0, 0.1, -0.2, -0.5],
'profit_abs': [0.0, 1, -2, -5], 'profit_abs': [0.0, 1, -2, -5],
'open_date': to_datetime([Arrow(2017, 11, 13, 15, 40, 0).datetime, 'open_date': to_datetime([datetime(2017, 11, 13, 15, 40, 0, tzinfo=timezone.utc),
Arrow(2017, 11, 14, 9, 41, 0).datetime, datetime(2017, 11, 14, 9, 41, 0, tzinfo=timezone.utc),
Arrow(2017, 11, 14, 14, 20, 0).datetime, datetime(2017, 11, 14, 14, 20, 0, tzinfo=timezone.utc),
Arrow(2017, 11, 15, 3, 40, 0).datetime, datetime(2017, 11, 15, 3, 40, 0, tzinfo=timezone.utc),
], utc=True ], utc=True
), ),
'close_date': to_datetime([Arrow(2017, 11, 13, 16, 40, 0).datetime, 'close_date': to_datetime([datetime(2017, 11, 13, 16, 40, 0, tzinfo=timezone.utc),
Arrow(2017, 11, 14, 10, 41, 0).datetime, datetime(2017, 11, 14, 10, 41, 0, tzinfo=timezone.utc),
Arrow(2017, 11, 14, 15, 25, 0).datetime, datetime(2017, 11, 14, 15, 25, 0, tzinfo=timezone.utc),
Arrow(2017, 11, 15, 3, 55, 0).datetime, datetime(2017, 11, 15, 3, 55, 0, tzinfo=timezone.utc),
], utc=True) ], utc=True)
}) })
trades1 = extract_trades_of_period(data, trades) trades1 = extract_trades_of_period(data, trades)
# First and last trade are dropped as they are out of range # First and last trade are dropped as they are out of range
assert len(trades1) == 2 assert len(trades1) == 2
assert trades1.iloc[0].open_date == Arrow(2017, 11, 14, 9, 41, 0).datetime assert trades1.iloc[0].open_date == datetime(2017, 11, 14, 9, 41, 0, tzinfo=timezone.utc)
assert trades1.iloc[0].close_date == Arrow(2017, 11, 14, 10, 41, 0).datetime assert trades1.iloc[0].close_date == datetime(2017, 11, 14, 10, 41, 0, tzinfo=timezone.utc)
assert trades1.iloc[-1].open_date == Arrow(2017, 11, 14, 14, 20, 0).datetime assert trades1.iloc[-1].open_date == datetime(2017, 11, 14, 14, 20, 0, tzinfo=timezone.utc)
assert trades1.iloc[-1].close_date == Arrow(2017, 11, 14, 15, 25, 0).datetime assert trades1.iloc[-1].close_date == datetime(2017, 11, 14, 15, 25, 0, tzinfo=timezone.utc)
def test_analyze_trade_parallelism(testdatadir): def test_analyze_trade_parallelism(testdatadir):
@ -420,7 +421,7 @@ def test_calculate_max_drawdown2():
-0.025782, 0.010400, 0.012374, 0.012467, 0.114741, 0.010303, 0.010088, -0.025782, 0.010400, 0.012374, 0.012467, 0.114741, 0.010303, 0.010088,
-0.033961, 0.010680, 0.010886, -0.029274, 0.011178, 0.010693, 0.010711] -0.033961, 0.010680, 0.010886, -0.029274, 0.011178, 0.010693, 0.010711]
dates = [Arrow(2020, 1, 1).shift(days=i) for i in range(len(values))] dates = [dt_utc(2020, 1, 1) + timedelta(days=i) for i in range(len(values))]
df = DataFrame(zip(values, dates), columns=['profit', 'open_date']) df = DataFrame(zip(values, dates), columns=['profit', 'open_date'])
# sort by profit and reset index # sort by profit and reset index
df = df.sort_values('profit').reset_index(drop=True) df = df.sort_values('profit').reset_index(drop=True)
@ -454,8 +455,8 @@ def test_calculate_max_drawdown_abs(profits, relative, highd, lowd, result, resu
[1000, 500, 1000, 11000, 10000] # absolute results [1000, 500, 1000, 11000, 10000] # absolute results
[1000, 50%, 0%, 0%, ~9%] # Relative drawdowns [1000, 50%, 0%, 0%, ~9%] # Relative drawdowns
""" """
init_date = Arrow(2020, 1, 1) init_date = datetime(2020, 1, 1, tzinfo=timezone.utc)
dates = [init_date.shift(days=i) for i in range(len(profits))] dates = [init_date + timedelta(days=i) for i in range(len(profits))]
df = DataFrame(zip(profits, dates), columns=['profit_abs', 'open_date']) df = DataFrame(zip(profits, dates), columns=['profit_abs', 'open_date'])
# sort by profit and reset index # sort by profit and reset index
df = df.sort_values('profit_abs').reset_index(drop=True) df = df.sort_values('profit_abs').reset_index(drop=True)
@ -467,8 +468,8 @@ def test_calculate_max_drawdown_abs(profits, relative, highd, lowd, result, resu
assert isinstance(drawdown, float) assert isinstance(drawdown, float)
assert isinstance(drawdown_rel, float) assert isinstance(drawdown_rel, float)
assert hdate == init_date.shift(days=highd) assert hdate == init_date + timedelta(days=highd)
assert ldate == init_date.shift(days=lowd) assert ldate == init_date + timedelta(days=lowd)
# High must be before low # High must be before low
assert hdate < ldate assert hdate < ldate

View File

@ -6,7 +6,6 @@ from pathlib import Path
from shutil import copyfile from shutil import copyfile
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock, PropertyMock
import arrow
import pytest import pytest
from pandas import DataFrame from pandas import DataFrame
from pandas.testing import assert_frame_equal from pandas.testing import assert_frame_equal
@ -26,6 +25,7 @@ from freqtrade.enums import CandleType
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import file_dump_json from freqtrade.misc import file_dump_json
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.util import dt_utc
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_patched_exchange, log_has, log_has_re, from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_patched_exchange, log_has, log_has_re,
patch_exchange) patch_exchange)
@ -198,7 +198,6 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
fill_missing=False, drop_incomplete=False) fill_missing=False, drop_incomplete=False)
# now = last cached item + 1 hour # now = last cached item + 1 hour
now_ts = test_data[-1][0] / 1000 + 60 * 60 now_ts = test_data[-1][0] / 1000 + 60 * 60
mocker.patch('arrow.utcnow', return_value=arrow.get(now_ts))
# timeframe starts earlier than the cached data # timeframe starts earlier than the cached data
# should fully update data # should fully update data
@ -353,10 +352,10 @@ def test_download_backtesting_data_exception(mocker, caplog, default_conf, tmpdi
def test_load_partial_missing(testdatadir, caplog) -> None: def test_load_partial_missing(testdatadir, caplog) -> None:
# Make sure we start fresh - test missing data at start # Make sure we start fresh - test missing data at start
start = arrow.get('2018-01-01T00:00:00') start = dt_utc(2018, 1, 1)
end = arrow.get('2018-01-11T00:00:00') end = dt_utc(2018, 1, 11)
data = load_data(testdatadir, '5m', ['UNITTEST/BTC'], startup_candles=20, data = load_data(testdatadir, '5m', ['UNITTEST/BTC'], startup_candles=20,
timerange=TimeRange('date', 'date', start.int_timestamp, end.int_timestamp)) timerange=TimeRange('date', 'date', start.timestamp(), end.timestamp()))
assert log_has( assert log_has(
'Using indicator startup period: 20 ...', caplog 'Using indicator startup period: 20 ...', caplog
) )
@ -369,16 +368,16 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
caplog) caplog)
# Make sure we start fresh - test missing data at end # Make sure we start fresh - test missing data at end
caplog.clear() caplog.clear()
start = arrow.get('2018-01-10T00:00:00') start = dt_utc(2018, 1, 10)
end = arrow.get('2018-02-20T00:00:00') end = dt_utc(2018, 2, 20)
data = load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], data = load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=TimeRange('date', 'date', start.int_timestamp, end.int_timestamp)) timerange=TimeRange('date', 'date', start.timestamp(), end.timestamp()))
# timedifference in 5 minutes # timedifference in 5 minutes
td = ((end - start).total_seconds() // 60 // 5) + 1 td = ((end - start).total_seconds() // 60 // 5) + 1
assert td != len(data['UNITTEST/BTC']) assert td != len(data['UNITTEST/BTC'])
# Shift endtime with +5 # Shift endtime with +5
end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]) end_real = data['UNITTEST/BTC'].iloc[-1, 0].to_pydatetime()
assert log_has(f'UNITTEST/BTC, spot, 5m, ' assert log_has(f'UNITTEST/BTC, spot, 5m, '
f'data ends at {end_real.strftime(DATETIME_PRINT_FORMAT)}', f'data ends at {end_real.strftime(DATETIME_PRINT_FORMAT)}',
caplog) caplog)

View File

@ -3,9 +3,9 @@
import logging import logging
import math import math
from datetime import timedelta
from unittest.mock import MagicMock from unittest.mock import MagicMock
import arrow
import numpy as np import numpy as np
import pytest import pytest
from pandas import DataFrame from pandas import DataFrame
@ -14,6 +14,7 @@ from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.edge import Edge, PairInfo from freqtrade.edge import Edge, PairInfo
from freqtrade.enums import ExitType from freqtrade.enums import ExitType
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.util.datetime_helpers import dt_ts, dt_utc
from tests.conftest import EXMS, get_patched_freqtradebot, log_has from tests.conftest import EXMS, get_patched_freqtradebot, log_has
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
_get_frame_time_from_offset) _get_frame_time_from_offset)
@ -27,7 +28,7 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
# 5) Stoploss and sell are hit. should sell on stoploss # 5) Stoploss and sell are hit. should sell on stoploss
#################################################################### ####################################################################
tests_start_time = arrow.get(2018, 10, 3) tests_start_time = dt_utc(2018, 10, 3)
timeframe_in_minute = 60 timeframe_in_minute = 60
# End helper functions # End helper functions
@ -220,7 +221,7 @@ def test_edge_heartbeat_calculate(mocker, edge_conf):
heartbeat = edge_conf['edge']['process_throttle_secs'] heartbeat = edge_conf['edge']['process_throttle_secs']
# should not recalculate if heartbeat not reached # should not recalculate if heartbeat not reached
edge._last_updated = arrow.utcnow().int_timestamp - heartbeat + 1 edge._last_updated = dt_ts() - heartbeat + 1
assert edge.calculate(edge_conf['exchange']['pair_whitelist']) is False assert edge.calculate(edge_conf['exchange']['pair_whitelist']) is False
@ -232,7 +233,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m',
NEOBTC = [ NEOBTC = [
[ [
tests_start_time.shift(minutes=(x * timeframe_in_minute)).int_timestamp * 1000, dt_ts(tests_start_time + timedelta(minutes=(x * timeframe_in_minute))),
math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base,
math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base + 0.0001,
math.sin(x * hz) / 1000 + base - 0.0001, math.sin(x * hz) / 1000 + base - 0.0001,
@ -244,7 +245,7 @@ def mocked_load_data(datadir, pairs=[], timeframe='0m',
base = 0.002 base = 0.002
LTCBTC = [ LTCBTC = [
[ [
tests_start_time.shift(minutes=(x * timeframe_in_minute)).int_timestamp * 1000, dt_ts(tests_start_time + timedelta(minutes=(x * timeframe_in_minute))),
math.sin(x * hz) / 1000 + base, math.sin(x * hz) / 1000 + base,
math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base + 0.0001,
math.sin(x * hz) / 1000 + base - 0.0001, math.sin(x * hz) / 1000 + base - 0.0001,
@ -268,7 +269,7 @@ def test_edge_process_downloaded_data(mocker, edge_conf):
assert edge.calculate(edge_conf['exchange']['pair_whitelist']) assert edge.calculate(edge_conf['exchange']['pair_whitelist'])
assert len(edge._cached_pairs) == 2 assert len(edge._cached_pairs) == 2
assert edge._last_updated <= arrow.utcnow().int_timestamp + 2 assert edge._last_updated <= dt_ts() + 2
def test_edge_process_no_data(mocker, edge_conf, caplog): def test_edge_process_no_data(mocker, edge_conf, caplog):

View File

@ -514,7 +514,7 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers
def test_additional_exchange_init_binance(default_conf, mocker): def test_additional_exchange_init_binance(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fapiPrivateGetPositionsideDual = MagicMock(return_value={"dualSidePosition": True}) api_mock.fapiPrivateGetPositionSideDual = MagicMock(return_value={"dualSidePosition": True})
api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": True}) api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": True})
default_conf['dry_run'] = False default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES default_conf['trading_mode'] = TradingMode.FUTURES
@ -522,12 +522,12 @@ def test_additional_exchange_init_binance(default_conf, mocker):
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r"Hedge Mode is not supported.*\nMulti-Asset Mode is not supported.*"): match=r"Hedge Mode is not supported.*\nMulti-Asset Mode is not supported.*"):
get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock) get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock)
api_mock.fapiPrivateGetPositionsideDual = MagicMock(return_value={"dualSidePosition": False}) api_mock.fapiPrivateGetPositionSideDual = MagicMock(return_value={"dualSidePosition": False})
api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": False}) api_mock.fapiPrivateGetMultiAssetsMargin = MagicMock(return_value={"multiAssetsMargin": False})
exchange = get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock) exchange = get_patched_exchange(mocker, default_conf, id="binance", api_mock=api_mock)
assert exchange assert exchange
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'binance', ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'binance',
"additional_exchange_init", "fapiPrivateGetPositionsideDual") "additional_exchange_init", "fapiPrivateGetPositionSideDual")
def test__set_leverage_binance(mocker, default_conf): def test__set_leverage_binance(mocker, default_conf):

View File

@ -43,6 +43,10 @@ EXCHANGES = {
'hasQuoteVolumeFutures': True, 'hasQuoteVolumeFutures': True,
'leverage_tiers_public': False, 'leverage_tiers_public': False,
'leverage_in_spot_market': False, 'leverage_in_spot_market': False,
'private_methods': [
'fapiPrivateGetPositionSideDual',
'fapiPrivateGetMultiAssetsMargin'
],
'sample_order': [{ 'sample_order': [{
"symbol": "SOLUSDT", "symbol": "SOLUSDT",
"orderId": 3551312894, "orderId": 3551312894,
@ -221,11 +225,13 @@ EXCHANGES = {
'hasQuoteVolumeFutures': False, 'hasQuoteVolumeFutures': False,
'leverage_tiers_public': True, 'leverage_tiers_public': True,
'leverage_in_spot_market': True, 'leverage_in_spot_market': True,
'private_methods': ['fetch_accounts'],
}, },
'bybit': { 'bybit': {
'pair': 'BTC/USDT', 'pair': 'BTC/USDT',
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'hasQuoteVolume': True, 'hasQuoteVolume': True,
'use_ci_proxy': True,
'timeframe': '1h', 'timeframe': '1h',
'futures_pair': 'BTC/USDT:USDT', 'futures_pair': 'BTC/USDT:USDT',
'futures': True, 'futures': True,
@ -755,3 +761,8 @@ class TestCCXTExchange():
max_stake_amount = futures.get_max_pair_stake_amount(futures_pair, 40000) max_stake_amount = futures.get_max_pair_stake_amount(futures_pair, 40000)
assert (isinstance(max_stake_amount, float)) assert (isinstance(max_stake_amount, float))
assert max_stake_amount >= 0.0 assert max_stake_amount >= 0.0
def test_private_method_presence(self, exchange: EXCHANGE_FIXTURE_TYPE):
exch, exchangename = exchange
for method in EXCHANGES[exchangename].get('private_methods', []):
assert hasattr(exch._api, method)

View File

@ -5,7 +5,6 @@ from datetime import datetime, timedelta, timezone
from random import randint from random import randint
from unittest.mock import MagicMock, Mock, PropertyMock, patch from unittest.mock import MagicMock, Mock, PropertyMock, patch
import arrow
import ccxt import ccxt
import pytest import pytest
from ccxt import DECIMAL_PLACES, ROUND, ROUND_UP, TICK_SIZE, TRUNCATE from ccxt import DECIMAL_PLACES, ROUND, ROUND_UP, TICK_SIZE, TRUNCATE
@ -23,6 +22,7 @@ from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_CO
calculate_backoff, remove_exchange_credentials) calculate_backoff, remove_exchange_credentials)
from freqtrade.exchange.exchange import amount_to_contract_precision from freqtrade.exchange.exchange import amount_to_contract_precision
from freqtrade.resolvers.exchange_resolver import ExchangeResolver from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from freqtrade.util import dt_now, dt_ts
from tests.conftest import (EXMS, generate_test_data_raw, get_mock_coro, get_patched_exchange, from tests.conftest import (EXMS, generate_test_data_raw, get_mock_coro, get_patched_exchange,
log_has, log_has_re, num_log_has_re) log_has, log_has_re, num_log_has_re)
@ -644,7 +644,7 @@ def test_reload_markets(default_conf, mocker, caplog):
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance", exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance",
mock_markets=False) mock_markets=False)
exchange._load_async_markets = MagicMock() exchange._load_async_markets = MagicMock()
exchange._last_markets_refresh = arrow.utcnow().int_timestamp exchange._last_markets_refresh = dt_ts()
assert exchange.markets == initial_markets assert exchange.markets == initial_markets
@ -655,7 +655,7 @@ def test_reload_markets(default_conf, mocker, caplog):
api_mock.load_markets = MagicMock(return_value=updated_markets) api_mock.load_markets = MagicMock(return_value=updated_markets)
# more than 10 minutes have passed, reload is executed # more than 10 minutes have passed, reload is executed
exchange._last_markets_refresh = arrow.utcnow().int_timestamp - 15 * 60 exchange._last_markets_refresh = dt_ts(dt_now() - timedelta(minutes=15))
exchange.reload_markets() exchange.reload_markets()
assert exchange.markets == updated_markets assert exchange.markets == updated_markets
assert exchange._load_async_markets.call_count == 1 assert exchange._load_async_markets.call_count == 1
@ -2076,7 +2076,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
ohlcv = [ ohlcv = [
[ [
arrow.utcnow().int_timestamp * 1000, # unix timestamp ms dt_ts(), # unix timestamp ms
1, # open 1, # open
2, # high 2, # high
3, # low 3, # low
@ -2096,7 +2096,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_
ret = exchange.get_historic_ohlcv( ret = exchange.get_historic_ohlcv(
pair, pair,
"5m", "5m",
int((arrow.utcnow().int_timestamp - since) * 1000), dt_ts(dt_now() - timedelta(seconds=since)),
candle_type=candle_type candle_type=candle_type
) )
@ -2114,7 +2114,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_
ret = exchange.get_historic_ohlcv( ret = exchange.get_historic_ohlcv(
pair, pair,
"5m", "5m",
int((arrow.utcnow().int_timestamp - since) * 1000), dt_ts(dt_now() - timedelta(seconds=since)),
candle_type=candle_type candle_type=candle_type
) )
assert log_has_re(r"Async code raised an exception: .*", caplog) assert log_has_re(r"Async code raised an exception: .*", caplog)
@ -2166,7 +2166,7 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_
def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None: def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None:
ohlcv = [ ohlcv = [
[ [
(arrow.utcnow().shift(minutes=-5).int_timestamp) * 1000, # unix timestamp ms dt_ts(dt_now() - timedelta(minutes=5)), # unix timestamp ms
1, # open 1, # open
2, # high 2, # high
3, # low 3, # low
@ -2174,7 +2174,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None
5, # volume (in quote currency) 5, # volume (in quote currency)
], ],
[ [
arrow.utcnow().int_timestamp * 1000, # unix timestamp ms dt_ts(), # unix timestamp ms
3, # open 3, # open
1, # high 1, # high
4, # low 4, # low
@ -2364,7 +2364,7 @@ def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_mach
async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name): async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name):
ohlcv = [ ohlcv = [
[ [
arrow.utcnow().int_timestamp * 1000, # unix timestamp ms dt_ts(), # unix timestamp ms
1, # open 1, # open
2, # high 2, # high
3, # low 3, # low
@ -2401,7 +2401,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error")) api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT, await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT,
(arrow.utcnow().int_timestamp - 2000) * 1000) dt_ts(dt_now() - timedelta(seconds=2000)))
exchange.close() exchange.close()
@ -2410,7 +2410,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported")) api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT, await exchange._async_get_candle_history(pair, "5m", CandleType.SPOT,
(arrow.utcnow().int_timestamp - 2000) * 1000) dt_ts(dt_now() - timedelta(seconds=2000)))
exchange.close() exchange.close()
@ -2433,7 +2433,7 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
with pytest.raises(DDosProtection, match=r'429 Too Many Requests'): with pytest.raises(DDosProtection, match=r'429 Too Many Requests'):
await exchange._async_get_candle_history( await exchange._async_get_candle_history(
"ETH/BTC", "5m", CandleType.SPOT, "ETH/BTC", "5m", CandleType.SPOT,
since_ms=(arrow.utcnow().int_timestamp - 2000) * 1000, count=3) since_ms=dt_ts(dt_now() - timedelta(seconds=2000)), count=3)
assert num_log_has_re(msg, caplog) == 3 assert num_log_has_re(msg, caplog) == 3
caplog.clear() caplog.clear()
@ -2450,7 +2450,7 @@ async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog):
with pytest.raises(DDosProtection, match=r'429 Too Many Requests'): with pytest.raises(DDosProtection, match=r'429 Too Many Requests'):
await exchange._async_get_candle_history( await exchange._async_get_candle_history(
"ETH/BTC", "5m", CandleType.SPOT, "ETH/BTC", "5m", CandleType.SPOT,
(arrow.utcnow().int_timestamp - 2000) * 1000, count=3) dt_ts(dt_now() - timedelta(seconds=2000)), count=3)
# Expect the "returned exception" message 12 times (4 retries * 3 (loop)) # Expect the "returned exception" message 12 times (4 retries * 3 (loop))
assert num_log_has_re(msg, caplog) == 12 assert num_log_has_re(msg, caplog) == 12
assert num_log_has_re(msg2, caplog) == 9 assert num_log_has_re(msg2, caplog) == 9
@ -2908,14 +2908,14 @@ async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
with pytest.raises(OperationalException, match=r'Could not fetch trade data*'): with pytest.raises(OperationalException, match=r'Could not fetch trade data*'):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error")) api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000) await exchange._async_fetch_trades(pair, since=dt_ts(dt_now() - timedelta(seconds=2000)))
exchange.close() exchange.close()
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching ' with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
r'historical trade data\..*'): r'historical trade data\..*'):
api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported")) api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().int_timestamp - 2000) * 1000) await exchange._async_fetch_trades(pair, since=dt_ts(dt_now() - timedelta(seconds=2000)))
exchange.close() exchange.close()

View File

@ -1,13 +1,14 @@
from datetime import timedelta
from typing import Dict, List, NamedTuple, Optional from typing import Dict, List, NamedTuple, Optional
import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade.enums import ExitType from freqtrade.enums import ExitType
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.util.datetime_helpers import dt_utc
tests_start_time = arrow.get(2018, 10, 3) tests_start_time = dt_utc(2018, 10, 3)
tests_timeframe = '1h' tests_timeframe = '1h'
@ -46,7 +47,7 @@ class BTContainer(NamedTuple):
def _get_frame_time_from_offset(offset): def _get_frame_time_from_offset(offset):
minutes = offset * timeframe_to_minutes(tests_timeframe) minutes = offset * timeframe_to_minutes(tests_timeframe)
return tests_start_time.shift(minutes=minutes).datetime return tests_start_time + timedelta(minutes=minutes)
def _build_backtest_dataframe(data): def _build_backtest_dataframe(data):

View File

@ -9,7 +9,6 @@ from unittest.mock import MagicMock, PropertyMock
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import pytest import pytest
from arrow import Arrow
from freqtrade import constants from freqtrade import constants
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting
@ -26,6 +25,7 @@ from freqtrade.optimize.backtest_caching import get_strategy_run_id
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import LocalTrade, Trade from freqtrade.persistence import LocalTrade, Trade
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.util.datetime_helpers import dt_utc
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, log_has, log_has_re, from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, log_has, log_has_re,
patch_exchange, patched_configuration_load_config_file) patch_exchange, patched_configuration_load_config_file)
@ -346,7 +346,7 @@ def test_backtest_abort(default_conf, mocker, testdatadir) -> None:
def test_backtesting_start(default_conf, mocker, caplog) -> None: def test_backtesting_start(default_conf, mocker, caplog) -> None:
def get_timerange(input1): def get_timerange(input1):
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59) return dt_utc(2017, 11, 14, 21, 17), dt_utc(2017, 11, 14, 22, 59)
mocker.patch('freqtrade.data.history.get_timerange', get_timerange) mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
patch_exchange(mocker) patch_exchange(mocker)
@ -385,7 +385,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> None: def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> None:
def get_timerange(input1): def get_timerange(input1):
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59) return dt_utc(2017, 11, 14, 21, 17), dt_utc(2017, 11, 14, 22, 59)
mocker.patch('freqtrade.data.history.history_utils.load_pair_history', mocker.patch('freqtrade.data.history.history_utils.load_pair_history',
MagicMock(return_value=pd.DataFrame())) MagicMock(return_value=pd.DataFrame()))
@ -710,11 +710,11 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'stake_amount': [0.001, 0.001], 'stake_amount': [0.001, 0.001],
'max_stake_amount': [0.001, 0.001], 'max_stake_amount': [0.001, 0.001],
'amount': [0.00957442, 0.0097064], 'amount': [0.00957442, 0.0097064],
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime, 'open_date': pd.to_datetime([dt_utc(2018, 1, 29, 18, 40, 0),
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True dt_utc(2018, 1, 30, 3, 30, 0)], utc=True
), ),
'close_date': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime, 'close_date': pd.to_datetime([dt_utc(2018, 1, 29, 22, 35, 0),
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True), dt_utc(2018, 1, 30, 4, 10, 0)], utc=True),
'open_rate': [0.104445, 0.10302485], 'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541], 'close_rate': [0.104969, 0.103541],
'fee_open': [0.0025, 0.0025], 'fee_open': [0.0025, 0.0025],

View File

@ -5,13 +5,13 @@ from unittest.mock import MagicMock
import pandas as pd import pandas as pd
import pytest import pytest
from arrow import Arrow
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.history import get_timerange from freqtrade.data.history import get_timerange
from freqtrade.enums import ExitType, TradingMode from freqtrade.enums import ExitType, TradingMode
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
from freqtrade.util.datetime_helpers import dt_utc
from tests.conftest import EXMS, patch_exchange from tests.conftest import EXMS, patch_exchange
@ -52,11 +52,11 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
'stake_amount': [500.0, 100.0], 'stake_amount': [500.0, 100.0],
'max_stake_amount': [500.0, 100], 'max_stake_amount': [500.0, 100],
'amount': [4806.87657523, 970.63960782], 'amount': [4806.87657523, 970.63960782],
'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime, 'open_date': pd.to_datetime([dt_utc(2018, 1, 29, 18, 40, 0),
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True dt_utc(2018, 1, 30, 3, 30, 0)], utc=True
), ),
'close_date': pd.to_datetime([Arrow(2018, 1, 29, 22, 00, 0).datetime, 'close_date': pd.to_datetime([dt_utc(2018, 1, 29, 22, 00, 0),
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True), dt_utc(2018, 1, 30, 4, 10, 0)], utc=True),
'open_rate': [0.10401764894444211, 0.10302485], 'open_rate': [0.10401764894444211, 0.10302485],
'close_rate': [0.10453904066847439, 0.103541], 'close_rate': [0.10453904066847439, 0.103541],
'fee_open': [0.0025, 0.0025], 'fee_open': [0.0025, 0.0025],

View File

@ -6,7 +6,6 @@ from unittest.mock import ANY, MagicMock, PropertyMock
import pandas as pd import pandas as pd
import pytest import pytest
from arrow import Arrow
from filelock import Timeout from filelock import Timeout
from skopt.space import Integer from skopt.space import Integer
@ -20,6 +19,7 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
from freqtrade.strategy import IntParameter from freqtrade.strategy import IntParameter
from freqtrade.util import dt_utc
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, get_markets, log_has, log_has_re, from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, get_args, get_markets, log_has, log_has_re,
patch_exchange, patched_configuration_load_config_file) patch_exchange, patched_configuration_load_config_file)
@ -349,14 +349,14 @@ def test_hyperopt_format_results(hyperopt):
"UNITTEST/BTC", "UNITTEST/BTC"], "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780], "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, "open_date": [dt_utc(2017, 11, 14, 19, 32, 00),
Arrow(2017, 11, 14, 21, 36, 00).datetime, dt_utc(2017, 11, 14, 21, 36, 00),
Arrow(2017, 11, 14, 22, 12, 00).datetime, dt_utc(2017, 11, 14, 22, 12, 00),
Arrow(2017, 11, 14, 22, 44, 00).datetime], dt_utc(2017, 11, 14, 22, 44, 00)],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, "close_date": [dt_utc(2017, 11, 14, 21, 35, 00),
Arrow(2017, 11, 14, 22, 10, 00).datetime, dt_utc(2017, 11, 14, 22, 10, 00),
Arrow(2017, 11, 14, 22, 43, 00).datetime, dt_utc(2017, 11, 14, 22, 43, 00),
Arrow(2017, 11, 14, 22, 58, 00).datetime], dt_utc(2017, 11, 14, 22, 58, 00)],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214], "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
@ -379,8 +379,8 @@ def test_hyperopt_format_results(hyperopt):
'backtest_end_time': 1619718665, 'backtest_end_time': 1619718665,
} }
results_metrics = generate_strategy_stats(['XRP/BTC'], '', bt_result, results_metrics = generate_strategy_stats(['XRP/BTC'], '', bt_result,
Arrow(2017, 11, 14, 19, 32, 00), dt_utc(2017, 11, 14, 19, 32, 00),
Arrow(2017, 12, 14, 19, 32, 00), market_change=0) dt_utc(2017, 12, 14, 19, 32, 00), market_change=0)
results_explanation = HyperoptTools.format_results_explanation_string(results_metrics, 'BTC') results_explanation = HyperoptTools.format_results_explanation_string(results_metrics, 'BTC')
total_profit = results_metrics['profit_total_abs'] total_profit = results_metrics['profit_total_abs']
@ -423,14 +423,14 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
"UNITTEST/BTC", "UNITTEST/BTC"], "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780], "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, "open_date": [dt_utc(2017, 11, 14, 19, 32, 00),
Arrow(2017, 11, 14, 21, 36, 00).datetime, dt_utc(2017, 11, 14, 21, 36, 00),
Arrow(2017, 11, 14, 22, 12, 00).datetime, dt_utc(2017, 11, 14, 22, 12, 00),
Arrow(2017, 11, 14, 22, 44, 00).datetime], dt_utc(2017, 11, 14, 22, 44, 00)],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, "close_date": [dt_utc(2017, 11, 14, 21, 35, 00),
Arrow(2017, 11, 14, 22, 10, 00).datetime, dt_utc(2017, 11, 14, 22, 10, 00),
Arrow(2017, 11, 14, 22, 43, 00).datetime, dt_utc(2017, 11, 14, 22, 43, 00),
Arrow(2017, 11, 14, 22, 58, 00).datetime], dt_utc(2017, 11, 14, 22, 58, 00)],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214], "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
@ -453,7 +453,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
mocker.patch('freqtrade.optimize.hyperopt.Backtesting.backtest', return_value=backtest_result) mocker.patch('freqtrade.optimize.hyperopt.Backtesting.backtest', return_value=backtest_result)
mocker.patch('freqtrade.optimize.hyperopt.get_timerange', mocker.patch('freqtrade.optimize.hyperopt.get_timerange',
return_value=(Arrow(2017, 12, 10), Arrow(2017, 12, 13))) return_value=(dt_utc(2017, 12, 10), dt_utc(2017, 12, 13)))
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.object(Path, 'open') mocker.patch.object(Path, 'open')
mocker.patch('freqtrade.configuration.config_validation.validate_config_schema') mocker.patch('freqtrade.configuration.config_validation.validate_config_schema')
@ -513,8 +513,8 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
} }
hyperopt = Hyperopt(hyperopt_conf) hyperopt = Hyperopt(hyperopt_conf)
hyperopt.min_date = Arrow(2017, 12, 10) hyperopt.min_date = dt_utc(2017, 12, 10)
hyperopt.max_date = Arrow(2017, 12, 13) hyperopt.max_date = dt_utc(2017, 12, 13)
hyperopt.init_spaces() hyperopt.init_spaces()
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values())) generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
assert generate_optimizer_value == response_expected assert generate_optimizer_value == response_expected

View File

@ -6,7 +6,6 @@ from shutil import copyfile
import joblib import joblib
import pandas as pd import pandas as pd
import pytest import pytest
from arrow import Arrow
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
@ -25,6 +24,8 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene
store_backtest_stats, text_table_bt_results, store_backtest_stats, text_table_bt_results,
text_table_exit_reason, text_table_strategy) text_table_exit_reason, text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver from freqtrade.resolvers.strategy_resolver import StrategyResolver
from freqtrade.util import dt_ts
from freqtrade.util.datetime_helpers import dt_from_ts, dt_utc
from tests.conftest import CURRENT_TEST_STRATEGY from tests.conftest import CURRENT_TEST_STRATEGY
from tests.data.test_history import _clean_test_file from tests.data.test_history import _clean_test_file
@ -80,14 +81,14 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"UNITTEST/BTC", "UNITTEST/BTC"], "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780], "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, "open_date": [dt_utc(2017, 11, 14, 19, 32, 00),
Arrow(2017, 11, 14, 21, 36, 00).datetime, dt_utc(2017, 11, 14, 21, 36, 00),
Arrow(2017, 11, 14, 22, 12, 00).datetime, dt_utc(2017, 11, 14, 22, 12, 00),
Arrow(2017, 11, 14, 22, 44, 00).datetime], dt_utc(2017, 11, 14, 22, 44, 00)],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, "close_date": [dt_utc(2017, 11, 14, 21, 35, 00),
Arrow(2017, 11, 14, 22, 10, 00).datetime, dt_utc(2017, 11, 14, 22, 10, 00),
Arrow(2017, 11, 14, 22, 43, 00).datetime, dt_utc(2017, 11, 14, 22, 43, 00),
Arrow(2017, 11, 14, 22, 58, 00).datetime], dt_utc(2017, 11, 14, 22, 58, 00)],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214], "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
@ -106,14 +107,14 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
'canceled_trade_entries': 0, 'canceled_trade_entries': 0,
'canceled_entry_orders': 0, 'canceled_entry_orders': 0,
'replaced_entry_orders': 0, 'replaced_entry_orders': 0,
'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_start_time': dt_ts() // 1000,
'backtest_end_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': dt_ts() // 1000,
'run_id': '123', 'run_id': '123',
} }
} }
timerange = TimeRange.parse_timerange('1510688220-1510700340') timerange = TimeRange.parse_timerange('1510688220-1510700340')
min_date = Arrow.fromtimestamp(1510688220) min_date = dt_from_ts(1510688220)
max_date = Arrow.fromtimestamp(1510700340) max_date = dt_from_ts(1510700340)
btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True) fill_up_missing=True)
@ -135,14 +136,14 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"], {"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_ratio": [0.003312, 0.010801, -0.013803, 0.002780], "profit_ratio": [0.003312, 0.010801, -0.013803, 0.002780],
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003], "profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, "open_date": [dt_utc(2017, 11, 14, 19, 32, 00),
Arrow(2017, 11, 14, 21, 36, 00).datetime, dt_utc(2017, 11, 14, 21, 36, 00),
Arrow(2017, 11, 14, 22, 12, 00).datetime, dt_utc(2017, 11, 14, 22, 12, 00),
Arrow(2017, 11, 14, 22, 44, 00).datetime], dt_utc(2017, 11, 14, 22, 44, 00)],
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, "close_date": [dt_utc(2017, 11, 14, 21, 35, 00),
Arrow(2017, 11, 14, 22, 10, 00).datetime, dt_utc(2017, 11, 14, 22, 10, 00),
Arrow(2017, 11, 14, 22, 43, 00).datetime, dt_utc(2017, 11, 14, 22, 43, 00),
Arrow(2017, 11, 14, 22, 58, 00).datetime], dt_utc(2017, 11, 14, 22, 58, 00)],
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214], "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217], "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
@ -161,8 +162,8 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
'canceled_trade_entries': 0, 'canceled_trade_entries': 0,
'canceled_entry_orders': 0, 'canceled_entry_orders': 0,
'replaced_entry_orders': 0, 'replaced_entry_orders': 0,
'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_start_time': dt_ts() // 1000,
'backtest_end_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': dt_ts() // 1000,
'run_id': '124', 'run_id': '124',
} }
} }

View File

@ -2,7 +2,6 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from types import FunctionType from types import FunctionType
import arrow
import pytest import pytest
from sqlalchemy import select from sqlalchemy import select
@ -10,6 +9,7 @@ from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, DATETIME_PRINT_FORMAT
from freqtrade.enums import TradingMode from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException from freqtrade.exceptions import DependencyException
from freqtrade.persistence import LocalTrade, Order, Trade, init_db from freqtrade.persistence import LocalTrade, Order, Trade, init_db
from freqtrade.util import dt_now
from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re
@ -27,7 +27,7 @@ def test_enter_exit_side(fee, is_short):
open_rate=0.01, open_rate=0.01,
amount=5, amount=5,
is_open=True, is_open=True,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -49,7 +49,7 @@ def test_set_stop_loss_liquidation(fee):
open_rate=2.0, open_rate=2.0,
amount=30.0, amount=30.0,
is_open=True, is_open=True,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -329,7 +329,7 @@ def test_borrowed(fee, is_short, lev, borrowed, trading_mode):
open_rate=2.0, open_rate=2.0,
amount=30.0, amount=30.0,
is_open=True, is_open=True,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -428,7 +428,7 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
open_rate=open_rate, open_rate=open_rate,
amount=30.0, amount=30.0,
is_open=True, is_open=True,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -485,7 +485,7 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
is_open=True, is_open=True,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
exchange='binance', exchange='binance',
trading_mode=margin, trading_mode=margin,
leverage=1.0, leverage=1.0,
@ -635,7 +635,7 @@ def test_trade_close(fee):
assert pytest.approx(trade.close_profit) == 0.094513715 assert pytest.approx(trade.close_profit) == 0.094513715
assert trade.close_date is not None assert trade.close_date is not None
new_date = arrow.Arrow(2020, 2, 2, 15, 6, 1).datetime, new_date = datetime(2020, 2, 2, 15, 6, 1),
assert trade.close_date != new_date assert trade.close_date != new_date
# Close should NOT update close_date if the trade has been closed already # Close should NOT update close_date if the trade has been closed already
assert trade.is_open is False assert trade.is_open is False
@ -1326,7 +1326,7 @@ def test_to_json(fee):
amount_requested=123.0, amount_requested=123.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
open_rate=0.123, open_rate=0.123,
exchange='binance', exchange='binance',
enter_tag=None, enter_tag=None,
@ -1411,8 +1411,8 @@ def test_to_json(fee):
amount_requested=101.0, amount_requested=101.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
close_date=arrow.utcnow().shift(hours=-1).datetime, close_date=dt_now() - timedelta(hours=1),
open_rate=0.123, open_rate=0.123,
close_rate=0.125, close_rate=0.125,
enter_tag='buys_signal_001', enter_tag='buys_signal_001',
@ -1496,7 +1496,7 @@ def test_stoploss_reinitialization(default_conf, fee):
pair='ADA/USDT', pair='ADA/USDT',
stake_amount=30.0, stake_amount=30.0,
fee_open=fee.return_value, fee_open=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
amount=30.0, amount=30.0,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -1557,7 +1557,7 @@ def test_stoploss_reinitialization_leverage(default_conf, fee):
pair='ADA/USDT', pair='ADA/USDT',
stake_amount=30.0, stake_amount=30.0,
fee_open=fee.return_value, fee_open=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
amount=30.0, amount=30.0,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -1619,7 +1619,7 @@ def test_stoploss_reinitialization_short(default_conf, fee):
pair='ADA/USDT', pair='ADA/USDT',
stake_amount=0.001, stake_amount=0.001,
fee_open=fee.return_value, fee_open=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
amount=10, amount=10,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -1678,7 +1678,7 @@ def test_update_fee(fee):
pair='ADA/USDT', pair='ADA/USDT',
stake_amount=30.0, stake_amount=30.0,
fee_open=fee.return_value, fee_open=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
amount=30.0, amount=30.0,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -1717,7 +1717,7 @@ def test_fee_updated(fee):
pair='ADA/USDT', pair='ADA/USDT',
stake_amount=30.0, stake_amount=30.0,
fee_open=fee.return_value, fee_open=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
amount=30.0, amount=30.0,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -2092,7 +2092,7 @@ def test_recalc_trade_from_orders(fee):
trade = Trade( trade = Trade(
pair='ADA/USDT', pair='ADA/USDT',
stake_amount=o1_cost, stake_amount=o1_cost,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
amount=o1_amount, amount=o1_amount,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
@ -2167,8 +2167,8 @@ def test_recalc_trade_from_orders(fee):
filled=o2_amount, filled=o2_amount,
remaining=0, remaining=0,
cost=o2_cost, cost=o2_cost,
order_date=arrow.utcnow().shift(hours=-1).datetime, order_date=dt_now() - timedelta(hours=1),
order_filled_date=arrow.utcnow().shift(hours=-1).datetime, order_filled_date=dt_now() - timedelta(hours=1),
) )
trade.orders.append(order2) trade.orders.append(order2)
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()
@ -2201,8 +2201,8 @@ def test_recalc_trade_from_orders(fee):
filled=o3_amount, filled=o3_amount,
remaining=0, remaining=0,
cost=o3_cost, cost=o3_cost,
order_date=arrow.utcnow().shift(hours=-1).datetime, order_date=dt_now() - timedelta(hours=1),
order_filled_date=arrow.utcnow().shift(hours=-1).datetime, order_filled_date=dt_now() - timedelta(hours=1),
) )
trade.orders.append(order3) trade.orders.append(order3)
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()
@ -2257,7 +2257,7 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
trade = Trade( trade = Trade(
pair='ADA/USDT', pair='ADA/USDT',
stake_amount=o1_cost, stake_amount=o1_cost,
open_date=arrow.utcnow().shift(hours=-2).datetime, open_date=dt_now() - timedelta(hours=2),
amount=o1_amount, amount=o1_amount,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
@ -2309,8 +2309,8 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
filled=o1_amount, filled=o1_amount,
remaining=0, remaining=0,
cost=o1_cost, cost=o1_cost,
order_date=arrow.utcnow().shift(hours=-1).datetime, order_date=dt_now() - timedelta(hours=1),
order_filled_date=arrow.utcnow().shift(hours=-1).datetime, order_filled_date=dt_now() - timedelta(hours=1),
) )
trade.orders.append(order2) trade.orders.append(order2)
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()
@ -2337,8 +2337,8 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
filled=0, filled=0,
remaining=4, remaining=4,
cost=5, cost=5,
order_date=arrow.utcnow().shift(hours=-1).datetime, order_date=dt_now() - timedelta(hours=1),
order_filled_date=arrow.utcnow().shift(hours=-1).datetime, order_filled_date=dt_now() - timedelta(hours=1),
) )
trade.orders.append(order3) trade.orders.append(order3)
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()
@ -2364,8 +2364,8 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee, is_short):
filled=o1_amount, filled=o1_amount,
remaining=0, remaining=0,
cost=o1_cost, cost=o1_cost,
order_date=arrow.utcnow().shift(hours=-1).datetime, order_date=dt_now() - timedelta(hours=1),
order_filled_date=arrow.utcnow().shift(hours=-1).datetime, order_filled_date=dt_now() - timedelta(hours=1),
) )
trade.orders.append(order4) trade.orders.append(order4)
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()
@ -2592,7 +2592,7 @@ def test_recalc_trade_from_orders_dca(data) -> None:
open_rate=data['orders'][0][0][2], open_rate=data['orders'][0][0][2],
amount=data['orders'][0][0][1], amount=data['orders'][0][0][1],
is_open=True, is_open=True,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
fee_open=data['fee'], fee_open=data['fee'],
fee_close=data['fee'], fee_close=data['fee'],
exchange='binance', exchange='binance',
@ -2622,8 +2622,8 @@ def test_recalc_trade_from_orders_dca(data) -> None:
filled=amount, filled=amount,
remaining=0, remaining=0,
cost=amount * price, cost=amount * price,
order_date=arrow.utcnow().shift(hours=-10 + idx).datetime, order_date=dt_now() - timedelta(hours=10 + idx),
order_filled_date=arrow.utcnow().shift(hours=-10 + idx).datetime, order_filled_date=dt_now() - timedelta(hours=10 + idx),
) )
trade.orders.append(order_obj) trade.orders.append(order_obj)
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()

View File

@ -1,10 +1,10 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import arrow
import pytest import pytest
from freqtrade.persistence import PairLocks from freqtrade.persistence import PairLocks
from freqtrade.persistence.models import PairLock from freqtrade.persistence.models import PairLock
from freqtrade.util import dt_now
@pytest.mark.parametrize('use_db', (False, True)) @pytest.mark.parametrize('use_db', (False, True))
@ -20,20 +20,20 @@ def test_PairLocks(use_db):
pair = 'ETH/BTC' pair = 'ETH/BTC'
assert not PairLocks.is_pair_locked(pair) assert not PairLocks.is_pair_locked(pair)
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime) PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4))
# ETH/BTC locked for 4 minutes (on both sides) # ETH/BTC locked for 4 minutes (on both sides)
assert PairLocks.is_pair_locked(pair) assert PairLocks.is_pair_locked(pair)
assert PairLocks.is_pair_locked(pair, side='long') assert PairLocks.is_pair_locked(pair, side='long')
assert PairLocks.is_pair_locked(pair, side='short') assert PairLocks.is_pair_locked(pair, side='short')
pair = 'BNB/BTC' pair = 'BNB/BTC'
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime, side='long') PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4), side='long')
assert not PairLocks.is_pair_locked(pair) assert not PairLocks.is_pair_locked(pair)
assert PairLocks.is_pair_locked(pair, side='long') assert PairLocks.is_pair_locked(pair, side='long')
assert not PairLocks.is_pair_locked(pair, side='short') assert not PairLocks.is_pair_locked(pair, side='short')
pair = 'BNB/USDT' pair = 'BNB/USDT'
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime, side='short') PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4), side='short')
assert not PairLocks.is_pair_locked(pair) assert not PairLocks.is_pair_locked(pair)
assert not PairLocks.is_pair_locked(pair, side='long') assert not PairLocks.is_pair_locked(pair, side='long')
assert PairLocks.is_pair_locked(pair, side='short') assert PairLocks.is_pair_locked(pair, side='short')
@ -44,7 +44,7 @@ def test_PairLocks(use_db):
# Unlocking a pair that's not locked should not raise an error # Unlocking a pair that's not locked should not raise an error
PairLocks.unlock_pair(pair) PairLocks.unlock_pair(pair)
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime) PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4))
assert PairLocks.is_pair_locked(pair) assert PairLocks.is_pair_locked(pair)
# Get both locks from above # Get both locks from above
@ -113,20 +113,20 @@ def test_PairLocks_getlongestlock(use_db):
pair = 'ETH/BTC' pair = 'ETH/BTC'
assert not PairLocks.is_pair_locked(pair) assert not PairLocks.is_pair_locked(pair)
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime) PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=4))
# ETH/BTC locked for 4 minutes # ETH/BTC locked for 4 minutes
assert PairLocks.is_pair_locked(pair) assert PairLocks.is_pair_locked(pair)
lock = PairLocks.get_pair_longest_lock(pair) lock = PairLocks.get_pair_longest_lock(pair)
assert lock.lock_end_time.replace(tzinfo=timezone.utc) > arrow.utcnow().shift(minutes=3) assert lock.lock_end_time.replace(tzinfo=timezone.utc) > dt_now() + timedelta(minutes=3)
assert lock.lock_end_time.replace(tzinfo=timezone.utc) < arrow.utcnow().shift(minutes=14) assert lock.lock_end_time.replace(tzinfo=timezone.utc) < dt_now() + timedelta(minutes=14)
PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=15).datetime) PairLocks.lock_pair(pair, dt_now() + timedelta(minutes=15))
assert PairLocks.is_pair_locked(pair) assert PairLocks.is_pair_locked(pair)
lock = PairLocks.get_pair_longest_lock(pair) lock = PairLocks.get_pair_longest_lock(pair)
# Must be longer than above # Must be longer than above
assert lock.lock_end_time.replace(tzinfo=timezone.utc) > arrow.utcnow().shift(minutes=14) assert lock.lock_end_time.replace(tzinfo=timezone.utc) > dt_now() + timedelta(minutes=14)
PairLocks.reset_locks() PairLocks.reset_locks()
PairLocks.use_db = True PairLocks.use_db = True
@ -143,8 +143,8 @@ def test_PairLocks_reason(use_db):
assert PairLocks.use_db == use_db assert PairLocks.use_db == use_db
PairLocks.lock_pair('XRP/USDT', arrow.utcnow().shift(minutes=4).datetime, 'TestLock1') PairLocks.lock_pair('XRP/USDT', dt_now() + timedelta(minutes=4), 'TestLock1')
PairLocks.lock_pair('ETH/USDT', arrow.utcnow().shift(minutes=4).datetime, 'TestLock2') PairLocks.lock_pair('ETH/USDT', dt_now() + timedelta(minutes=4), 'TestLock2')
assert PairLocks.is_pair_locked('XRP/USDT') assert PairLocks.is_pair_locked('XRP/USDT')
assert PairLocks.is_pair_locked('ETH/USDT') assert PairLocks.is_pair_locked('ETH/USDT')

View File

@ -21,11 +21,13 @@ from freqtrade.__init__ import __version__
from freqtrade.enums import CandleType, RunMode, State, TradingMode from freqtrade.enums import CandleType, RunMode, State, TradingMode
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
from freqtrade.loggers import setup_logging, setup_logging_pre from freqtrade.loggers import setup_logging, setup_logging_pre
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.rpc import RPC from freqtrade.rpc import RPC
from freqtrade.rpc.api_server import ApiServer from freqtrade.rpc.api_server import ApiServer
from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
from freqtrade.rpc.api_server.webserver_bgwork import ApiBG
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_mock_coro, from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_mock_coro,
get_patched_freqtradebot, log_has, log_has_re, patch_get_signal) get_patched_freqtradebot, log_has, log_has_re, patch_get_signal)
@ -1665,137 +1667,140 @@ def test_sysinfo(botclient):
def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
ftbot, client = botclient try:
mocker.patch(f'{EXMS}.get_fee', fee) ftbot, client = botclient
mocker.patch(f'{EXMS}.get_fee', fee)
rc = client_get(client, f"{BASE_URI}/backtest") rc = client_get(client, f"{BASE_URI}/backtest")
# Backtest prevented in default mode # Backtest prevented in default mode
assert_response(rc, 502) assert_response(rc, 502)
ftbot.config['runmode'] = RunMode.WEBSERVER ftbot.config['runmode'] = RunMode.WEBSERVER
# Backtesting not started yet # Backtesting not started yet
rc = client_get(client, f"{BASE_URI}/backtest") rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'not_started' assert result['status'] == 'not_started'
assert not result['running'] assert not result['running']
assert result['status_msg'] == 'Backtest not yet executed' assert result['status_msg'] == 'Backtest not yet executed'
assert result['progress'] == 0 assert result['progress'] == 0
# Reset backtesting # Reset backtesting
rc = client_delete(client, f"{BASE_URI}/backtest") rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'reset' assert result['status'] == 'reset'
assert not result['running'] assert not result['running']
assert result['status_msg'] == 'Backtest reset' assert result['status_msg'] == 'Backtest reset'
ftbot.config['export'] = 'trades' ftbot.config['export'] = 'trades'
ftbot.config['backtest_cache'] = 'day' ftbot.config['backtest_cache'] = 'day'
ftbot.config['user_data_dir'] = Path(tmpdir) ftbot.config['user_data_dir'] = Path(tmpdir)
ftbot.config['exportfilename'] = Path(tmpdir) / "backtest_results" ftbot.config['exportfilename'] = Path(tmpdir) / "backtest_results"
ftbot.config['exportfilename'].mkdir() ftbot.config['exportfilename'].mkdir()
# start backtesting # start backtesting
data = { data = {
"strategy": CURRENT_TEST_STRATEGY, "strategy": CURRENT_TEST_STRATEGY,
"timeframe": "5m", "timeframe": "5m",
"timerange": "20180110-20180111", "timerange": "20180110-20180111",
"max_open_trades": 3, "max_open_trades": 3,
"stake_amount": 100, "stake_amount": 100,
"dry_run_wallet": 1000, "dry_run_wallet": 1000,
"enable_protections": False "enable_protections": False
} }
rc = client_post(client, f"{BASE_URI}/backtest", data=data) rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'running' assert result['status'] == 'running'
assert result['progress'] == 0 assert result['progress'] == 0
assert result['running'] assert result['running']
assert result['status_msg'] == 'Backtest started' assert result['status_msg'] == 'Backtest started'
rc = client_get(client, f"{BASE_URI}/backtest") rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'ended' assert result['status'] == 'ended'
assert not result['running'] assert not result['running']
assert result['status_msg'] == 'Backtest ended' assert result['status_msg'] == 'Backtest ended'
assert result['progress'] == 1 assert result['progress'] == 1
assert result['backtest_result'] assert result['backtest_result']
rc = client_get(client, f"{BASE_URI}/backtest/abort") rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'not_running' assert result['status'] == 'not_running'
assert not result['running'] assert not result['running']
assert result['status_msg'] == 'Backtest ended' assert result['status_msg'] == 'Backtest ended'
# Simulate running backtest # Simulate running backtest
ApiServer._bgtask_running = True ApiBG.bgtask_running = True
rc = client_get(client, f"{BASE_URI}/backtest/abort") rc = client_get(client, f"{BASE_URI}/backtest/abort")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'stopping' assert result['status'] == 'stopping'
assert not result['running'] assert not result['running']
assert result['status_msg'] == 'Backtest ended' assert result['status_msg'] == 'Backtest ended'
# Get running backtest... # Get running backtest...
rc = client_get(client, f"{BASE_URI}/backtest") rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'running' assert result['status'] == 'running'
assert result['running'] assert result['running']
assert result['step'] == "backtest" assert result['step'] == "backtest"
assert result['status_msg'] == "Backtest running" assert result['status_msg'] == "Backtest running"
# Try delete with task still running # Try delete with task still running
rc = client_delete(client, f"{BASE_URI}/backtest") rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'running' assert result['status'] == 'running'
# Post to backtest that's still running # Post to backtest that's still running
rc = client_post(client, f"{BASE_URI}/backtest", data=data) rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 502) assert_response(rc, 502)
result = rc.json() result = rc.json()
assert 'Bot Background task already running' in result['error'] assert 'Bot Background task already running' in result['error']
ApiServer._bgtask_running = False ApiBG.bgtask_running = False
# Rerun backtest (should get previous result) # Rerun backtest (should get previous result)
rc = client_post(client, f"{BASE_URI}/backtest", data=data) rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert log_has_re('Reusing result of previous backtest.*', caplog) assert log_has_re('Reusing result of previous backtest.*', caplog)
data['stake_amount'] = 101 data['stake_amount'] = 101
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy',
side_effect=DependencyException('DeadBeef')) side_effect=DependencyException('DeadBeef'))
rc = client_post(client, f"{BASE_URI}/backtest", data=data) rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert log_has("Backtesting caused an error: DeadBeef", caplog) assert log_has("Backtesting caused an error: DeadBeef", caplog)
rc = client_get(client, f"{BASE_URI}/backtest") rc = client_get(client, f"{BASE_URI}/backtest")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'error' assert result['status'] == 'error'
assert 'Backtest failed' in result['status_msg'] assert 'Backtest failed' in result['status_msg']
# Delete backtesting to avoid leakage since the backtest-object may stick around. # Delete backtesting to avoid leakage since the backtest-object may stick around.
rc = client_delete(client, f"{BASE_URI}/backtest") rc = client_delete(client, f"{BASE_URI}/backtest")
assert_response(rc) assert_response(rc)
result = rc.json() result = rc.json()
assert result['status'] == 'reset' assert result['status'] == 'reset'
assert not result['running'] assert not result['running']
assert result['status_msg'] == 'Backtest reset' assert result['status_msg'] == 'Backtest reset'
# Disallow base64 strategies # Disallow base64 strategies
data['strategy'] = "xx:cHJpbnQoImhlbGxvIHdvcmxkIik=" data['strategy'] = "xx:cHJpbnQoImhlbGxvIHdvcmxkIik="
rc = client_post(client, f"{BASE_URI}/backtest", data=data) rc = client_post(client, f"{BASE_URI}/backtest", data=data)
assert_response(rc, 500) assert_response(rc, 500)
finally:
Backtesting.cleanup()
def test_api_backtest_history(botclient, mocker, testdatadir): def test_api_backtest_history(botclient, mocker, testdatadir):

View File

@ -12,7 +12,6 @@ from random import choice, randint
from string import ascii_uppercase from string import ascii_uppercase
from unittest.mock import ANY, AsyncMock, MagicMock from unittest.mock import ANY, AsyncMock, MagicMock
import arrow
import pytest import pytest
import time_machine import time_machine
from pandas import DataFrame from pandas import DataFrame
@ -33,6 +32,7 @@ from freqtrade.persistence.models import Order
from freqtrade.rpc import RPC from freqtrade.rpc import RPC
from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.rpc import RPCException
from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.rpc.telegram import Telegram, authorized_only
from freqtrade.util.datetime_helpers import dt_now
from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades,
create_mock_trades_usdt, get_patched_freqtradebot, log_has, log_has_re, create_mock_trades_usdt, get_patched_freqtradebot, log_has, log_has_re,
patch_exchange, patch_get_signal, patch_whitelist) patch_exchange, patch_get_signal, patch_whitelist)
@ -259,7 +259,7 @@ async def test_telegram_status(default_conf, update, mocker) -> None:
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'base_currency': 'ETH', 'base_currency': 'ETH',
'quote_currency': 'BTC', 'quote_currency': 'BTC',
'open_date': arrow.utcnow(), 'open_date': dt_now(),
'close_date': None, 'close_date': None,
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'close_rate': None, 'close_rate': None,
@ -1518,8 +1518,8 @@ async def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -
msg_mock.reset_mock() msg_mock.reset_mock()
PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason') PairLocks.lock_pair('ETH/BTC', dt_now() + timedelta(minutes=4), 'randreason')
PairLocks.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef') PairLocks.lock_pair('XRP/BTC', dt_now() + timedelta(minutes=20), 'deadbeef')
await telegram._locks(update=update, context=MagicMock()) await telegram._locks(update=update, context=MagicMock())
@ -1898,7 +1898,7 @@ def test_send_msg_enter_notification(default_conf, mocker, caplog, message_type,
'current_rate': 1.099e-05, 'current_rate': 1.099e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'analyzed_candle': {'open': 1.1, 'high': 2.2, 'low': 1.0, 'close': 1.5}, 'analyzed_candle': {'open': 1.1, 'high': 2.2, 'low': 1.0, 'close': 1.5},
'open_date': arrow.utcnow().shift(hours=-1) 'open_date': dt_now() + timedelta(hours=-1)
} }
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
@ -1959,7 +1959,7 @@ def test_send_msg_protection_notification(default_conf, mocker, time_machine) ->
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
time_machine.move_to("2021-09-01 05:00:00 +00:00") time_machine.move_to("2021-09-01 05:00:00 +00:00")
lock = PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=6).datetime, 'randreason') lock = PairLocks.lock_pair('ETH/BTC', dt_now() + timedelta(minutes=6), 'randreason')
msg = { msg = {
'type': RPCMessageType.PROTECTION_TRIGGER, 'type': RPCMessageType.PROTECTION_TRIGGER,
} }
@ -1974,7 +1974,7 @@ def test_send_msg_protection_notification(default_conf, mocker, time_machine) ->
msg = { msg = {
'type': RPCMessageType.PROTECTION_TRIGGER_GLOBAL, 'type': RPCMessageType.PROTECTION_TRIGGER_GLOBAL,
} }
lock = PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=100).datetime, 'randreason') lock = PairLocks.lock_pair('*', dt_now() + timedelta(minutes=100), 'randreason')
msg.update(lock.to_json()) msg.update(lock.to_json())
telegram.send_msg(msg) telegram.send_msg(msg)
assert (msg_mock.call_args[0][0] == "*Protection* triggered due to randreason. " assert (msg_mock.call_args[0][0] == "*Protection* triggered due to randreason. "
@ -2005,7 +2005,7 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1) 'open_date': dt_now() - timedelta(hours=1)
}) })
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else '' leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == ( assert msg_mock.call_args[0][0] == (
@ -2032,7 +2032,7 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1) 'open_date': dt_now() - timedelta(hours=1)
}) })
assert msg_mock.call_args[0][0] == ( assert msg_mock.call_args[0][0] == (
@ -2071,8 +2071,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'enter_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'exit_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1), 'open_date': dt_now() - timedelta(hours=1),
'close_date': arrow.utcnow(), 'close_date': dt_now(),
}) })
assert msg_mock.call_args[0][0] == ( assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n' '\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
@ -2107,8 +2107,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'enter_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'exit_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
'close_date': arrow.utcnow(), 'close_date': dt_now(),
'stake_amount': 0.01, 'stake_amount': 0.01,
'sub_trade': True, 'sub_trade': True,
}) })
@ -2144,8 +2144,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'enter_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'exit_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
'close_date': arrow.utcnow(), 'close_date': dt_now(),
}) })
assert msg_mock.call_args[0][0] == ( assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n' '\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
@ -2226,8 +2226,8 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'enter_tag': enter_signal, 'enter_tag': enter_signal,
'exit_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
'close_date': arrow.utcnow(), 'close_date': dt_now(),
}) })
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else '' leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
@ -2317,7 +2317,7 @@ def test_send_msg_buy_notification_no_fiat(
'fiat_currency': None, 'fiat_currency': None,
'current_rate': 1.099e-05, 'current_rate': 1.099e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1) 'open_date': dt_now() - timedelta(hours=1)
}) })
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else '' leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
@ -2363,8 +2363,8 @@ def test_send_msg_sell_notification_no_fiat(
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'enter_tag': enter_signal, 'enter_tag': enter_signal,
'exit_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), 'open_date': dt_now() - timedelta(hours=2, minutes=35, seconds=3),
'close_date': arrow.utcnow(), 'close_date': dt_now(),
}) })
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else '' leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''

View File

@ -4,7 +4,6 @@ from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock from unittest.mock import MagicMock
import arrow
import pytest import pytest
from pandas import DataFrame from pandas import DataFrame
@ -22,6 +21,7 @@ from freqtrade.strategy.hyper import detect_parameters
from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter, from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter,
DecimalParameter, IntParameter, RealParameter) DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.util import dt_now
from tests.conftest import (CURRENT_TEST_STRATEGY, TRADE_SIDES, create_mock_trades, log_has, from tests.conftest import (CURRENT_TEST_STRATEGY, TRADE_SIDES, create_mock_trades, log_has,
log_has_re) log_has_re)
@ -34,7 +34,7 @@ _STRATEGY.dp = DataProvider({}, None, None)
def test_returns_latest_signal(ohlcv_history): def test_returns_latest_signal(ohlcv_history):
ohlcv_history.loc[1, 'date'] = arrow.utcnow() ohlcv_history.loc[1, 'date'] = dt_now()
# Take a copy to correctly modify the call # Take a copy to correctly modify the call
mocked_history = ohlcv_history.copy() mocked_history = ohlcv_history.copy()
mocked_history['enter_long'] = 0 mocked_history['enter_long'] = 0
@ -159,7 +159,7 @@ def test_get_signal_exception_valueerror(mocker, caplog, ohlcv_history):
def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
# default_conf defines a 5m interval. we check interval * 2 + 5m # default_conf defines a 5m interval. we check interval * 2 + 5m
# this is necessary as the last candle is removed (partial candles) by default # this is necessary as the last candle is removed (partial candles) by default
ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16) ohlcv_history.loc[1, 'date'] = dt_now() - timedelta(minutes=16)
# Take a copy to correctly modify the call # Take a copy to correctly modify the call
mocked_history = ohlcv_history.copy() mocked_history = ohlcv_history.copy()
mocked_history['exit_long'] = 0 mocked_history['exit_long'] = 0
@ -180,7 +180,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
# default_conf defines a 5m interval. we check interval * 2 + 5m # default_conf defines a 5m interval. we check interval * 2 + 5m
# this is necessary as the last candle is removed (partial candles) by default # this is necessary as the last candle is removed (partial candles) by default
ohlcv_history.loc[1, 'date'] = arrow.utcnow() ohlcv_history.loc[1, 'date'] = dt_now()
# Take a copy to correctly modify the call # Take a copy to correctly modify the call
mocked_history = ohlcv_history.copy() mocked_history = ohlcv_history.copy()
# Intentionally don't set sell column # Intentionally don't set sell column
@ -224,7 +224,7 @@ def test_ignore_expired_candle(default_conf):
def test_assert_df_raise(mocker, caplog, ohlcv_history): def test_assert_df_raise(mocker, caplog, ohlcv_history):
ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16) ohlcv_history.loc[1, 'date'] = dt_now() - timedelta(minutes=16)
# Take a copy to correctly modify the call # Take a copy to correctly modify the call
mocked_history = ohlcv_history.copy() mocked_history = ohlcv_history.copy()
mocked_history['sell'] = 0 mocked_history['sell'] = 0
@ -323,21 +323,21 @@ def test_min_roi_reached(default_conf, fee) -> None:
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=5, amount=5,
open_date=arrow.utcnow().shift(hours=-1).datetime, open_date=dt_now() - timedelta(hours=1),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
open_rate=1, open_rate=1,
) )
assert not strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-56).datetime) assert not strategy.min_roi_reached(trade, 0.02, dt_now() - timedelta(minutes=56))
assert strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-56).datetime) assert strategy.min_roi_reached(trade, 0.12, dt_now() - timedelta(minutes=56))
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-39).datetime) assert not strategy.min_roi_reached(trade, 0.04, dt_now() - timedelta(minutes=39))
assert strategy.min_roi_reached(trade, 0.06, arrow.utcnow().shift(minutes=-39).datetime) assert strategy.min_roi_reached(trade, 0.06, dt_now() - timedelta(minutes=39))
assert not strategy.min_roi_reached(trade, -0.01, arrow.utcnow().shift(minutes=-1).datetime) assert not strategy.min_roi_reached(trade, -0.01, dt_now() - timedelta(minutes=1))
assert strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-1).datetime) assert strategy.min_roi_reached(trade, 0.02, dt_now() - timedelta(minutes=1))
def test_min_roi_reached2(default_conf, fee) -> None: def test_min_roi_reached2(default_conf, fee) -> None:
@ -361,25 +361,25 @@ def test_min_roi_reached2(default_conf, fee) -> None:
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=5, amount=5,
open_date=arrow.utcnow().shift(hours=-1).datetime, open_date=dt_now() - timedelta(hours=1),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
open_rate=1, open_rate=1,
) )
assert not strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-56).datetime) assert not strategy.min_roi_reached(trade, 0.02, dt_now() - timedelta(minutes=56))
assert strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-56).datetime) assert strategy.min_roi_reached(trade, 0.12, dt_now() - timedelta(minutes=56))
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-39).datetime) assert not strategy.min_roi_reached(trade, 0.04, dt_now() - timedelta(minutes=39))
assert strategy.min_roi_reached(trade, 0.071, arrow.utcnow().shift(minutes=-39).datetime) assert strategy.min_roi_reached(trade, 0.071, dt_now() - timedelta(minutes=39))
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-26).datetime) assert not strategy.min_roi_reached(trade, 0.04, dt_now() - timedelta(minutes=26))
assert strategy.min_roi_reached(trade, 0.06, arrow.utcnow().shift(minutes=-26).datetime) assert strategy.min_roi_reached(trade, 0.06, dt_now() - timedelta(minutes=26))
# Should not trigger with 20% profit since after 55 minutes only 30% is active. # Should not trigger with 20% profit since after 55 minutes only 30% is active.
assert not strategy.min_roi_reached(trade, 0.20, arrow.utcnow().shift(minutes=-2).datetime) assert not strategy.min_roi_reached(trade, 0.20, dt_now() - timedelta(minutes=2))
assert strategy.min_roi_reached(trade, 0.31, arrow.utcnow().shift(minutes=-2).datetime) assert strategy.min_roi_reached(trade, 0.31, dt_now() - timedelta(minutes=2))
def test_min_roi_reached3(default_conf, fee) -> None: def test_min_roi_reached3(default_conf, fee) -> None:
@ -395,25 +395,25 @@ def test_min_roi_reached3(default_conf, fee) -> None:
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.001, stake_amount=0.001,
amount=5, amount=5,
open_date=arrow.utcnow().shift(hours=-1).datetime, open_date=dt_now() - timedelta(hours=1),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
open_rate=1, open_rate=1,
) )
assert not strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-56).datetime) assert not strategy.min_roi_reached(trade, 0.02, dt_now() - timedelta(minutes=56))
assert not strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-56).datetime) assert not strategy.min_roi_reached(trade, 0.12, dt_now() - timedelta(minutes=56))
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-39).datetime) assert not strategy.min_roi_reached(trade, 0.04, dt_now() - timedelta(minutes=39))
assert strategy.min_roi_reached(trade, 0.071, arrow.utcnow().shift(minutes=-39).datetime) assert strategy.min_roi_reached(trade, 0.071, dt_now() - timedelta(minutes=39))
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-26).datetime) assert not strategy.min_roi_reached(trade, 0.04, dt_now() - timedelta(minutes=26))
assert strategy.min_roi_reached(trade, 0.06, arrow.utcnow().shift(minutes=-26).datetime) assert strategy.min_roi_reached(trade, 0.06, dt_now() - timedelta(minutes=26))
# Should not trigger with 20% profit since after 55 minutes only 30% is active. # Should not trigger with 20% profit since after 55 minutes only 30% is active.
assert not strategy.min_roi_reached(trade, 0.20, arrow.utcnow().shift(minutes=-2).datetime) assert not strategy.min_roi_reached(trade, 0.20, dt_now() - timedelta(minutes=2))
assert strategy.min_roi_reached(trade, 0.31, arrow.utcnow().shift(minutes=-2).datetime) assert strategy.min_roi_reached(trade, 0.31, dt_now() - timedelta(minutes=2))
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -449,7 +449,7 @@ def test_ft_stoploss_reached(default_conf, fee, profit, adjusted, expected, liq,
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.01, stake_amount=0.01,
amount=1, amount=1,
open_date=arrow.utcnow().shift(hours=-1).datetime, open_date=dt_now() - timedelta(hours=1),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
@ -464,7 +464,7 @@ def test_ft_stoploss_reached(default_conf, fee, profit, adjusted, expected, liq,
if custom_stop: if custom_stop:
strategy.custom_stoploss = custom_stop strategy.custom_stoploss = custom_stop
now = arrow.utcnow().datetime now = dt_now()
current_rate = trade.open_rate * (1 + profit) current_rate = trade.open_rate * (1 + profit)
sl_flag = strategy.ft_stoploss_reached(current_rate=current_rate, trade=trade, sl_flag = strategy.ft_stoploss_reached(current_rate=current_rate, trade=trade,
current_time=now, current_profit=profit, current_time=now, current_profit=profit,
@ -498,14 +498,14 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.01, stake_amount=0.01,
amount=1, amount=1,
open_date=arrow.utcnow().shift(hours=-1).datetime, open_date=dt_now() - timedelta(hours=1),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
open_rate=1, open_rate=1,
) )
now = arrow.utcnow().datetime now = dt_now()
res = strategy.should_exit(trade, 1, now, res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False, enter=False, exit_=False,
low=None, high=None) low=None, high=None)
@ -547,13 +547,13 @@ def test_should_sell(default_conf, fee) -> None:
pair='ETH/BTC', pair='ETH/BTC',
stake_amount=0.01, stake_amount=0.01,
amount=1, amount=1,
open_date=arrow.utcnow().shift(hours=-1).datetime, open_date=dt_now() - timedelta(hours=1),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
exchange='binance', exchange='binance',
open_rate=1, open_rate=1,
) )
now = arrow.utcnow().datetime now = dt_now()
res = strategy.should_exit(trade, 1, now, res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False, enter=False, exit_=False,
low=None, high=None) low=None, high=None)
@ -728,7 +728,7 @@ def test_is_pair_locked(default_conf):
pair = 'ETH/BTC' pair = 'ETH/BTC'
assert not strategy.is_pair_locked(pair) assert not strategy.is_pair_locked(pair)
strategy.lock_pair(pair, arrow.now(timezone.utc).shift(minutes=4).datetime) strategy.lock_pair(pair, dt_now() + timedelta(minutes=4))
# ETH/BTC locked for 4 minutes # ETH/BTC locked for 4 minutes
assert strategy.is_pair_locked(pair) assert strategy.is_pair_locked(pair)
@ -746,7 +746,7 @@ def test_is_pair_locked(default_conf):
# Lock with reason # Lock with reason
reason = "TestLockR" reason = "TestLockR"
strategy.lock_pair(pair, arrow.now(timezone.utc).shift(minutes=4).datetime, reason) strategy.lock_pair(pair, dt_now() + timedelta(minutes=4), reason)
assert strategy.is_pair_locked(pair) assert strategy.is_pair_locked(pair)
strategy.unlock_reason(reason) strategy.unlock_reason(reason)
assert not strategy.is_pair_locked(pair) assert not strategy.is_pair_locked(pair)

View File

@ -4,10 +4,10 @@
import logging import logging
import time import time
from copy import deepcopy from copy import deepcopy
from datetime import timedelta
from typing import List from typing import List
from unittest.mock import ANY, MagicMock, PropertyMock, patch from unittest.mock import ANY, MagicMock, PropertyMock, patch
import arrow
import pytest import pytest
from pandas import DataFrame from pandas import DataFrame
from sqlalchemy import select from sqlalchemy import select
@ -22,6 +22,7 @@ from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import 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.protections.iprotection import ProtectionReturn from freqtrade.plugins.protections.iprotection import ProtectionReturn
from freqtrade.util.datetime_helpers import dt_now, dt_utc
from freqtrade.worker import Worker from freqtrade.worker import Worker
from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt, from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt,
get_patched_freqtradebot, get_patched_worker, log_has, log_has_re, get_patched_freqtradebot, get_patched_worker, log_has, log_has_re,
@ -473,7 +474,7 @@ def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_b
assert not log_has_re(message, caplog) assert not log_has_re(message, caplog)
caplog.clear() caplog.clear()
PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because', side='*') PairLocks.lock_pair('*', dt_now() + timedelta(minutes=20), 'Just because', side='*')
n = freqtrade.enter_positions() n = freqtrade.enter_positions()
assert n == 0 assert n == 0
assert log_has_re(message, caplog) assert log_has_re(message, caplog)
@ -494,7 +495,7 @@ def test_handle_protections(mocker, default_conf_usdt, fee, is_short):
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.protections._protection_handlers[1].global_stop = MagicMock( freqtrade.protections._protection_handlers[1].global_stop = MagicMock(
return_value=ProtectionReturn(True, arrow.utcnow().shift(hours=1).datetime, "asdf")) return_value=ProtectionReturn(True, dt_now() + timedelta(hours=1), "asdf"))
create_mock_trades(fee, is_short) create_mock_trades(fee, is_short)
freqtrade.handle_protections('ETC/BTC', '*') freqtrade.handle_protections('ETC/BTC', '*')
send_msg_mock = freqtrade.rpc.send_msg send_msg_mock = freqtrade.rpc.send_msg
@ -1290,7 +1291,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
}]) }])
trade.stoploss_order_id = "107" trade.stoploss_order_id = "107"
trade.is_open = True trade.is_open = True
trade.stoploss_last_update = arrow.utcnow().shift(hours=-1).datetime trade.stoploss_last_update = dt_now() - timedelta(hours=1)
trade.stop_loss = 24 trade.stop_loss = 24
trade.exit_reason = None trade.exit_reason = None
trade.orders.append( trade.orders.append(
@ -1439,7 +1440,7 @@ def test_handle_stoploss_on_exchange_partial_cancel_here(
}) })
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit) mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel) mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel)
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-10).datetime trade.stoploss_last_update = dt_now() - timedelta(minutes=10)
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
# Canceled Stoploss filled partially ... # Canceled Stoploss filled partially ...
@ -1659,7 +1660,7 @@ def test_handle_stoploss_on_exchange_trailing(
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
trade.stoploss_order_id = '100' trade.stoploss_order_id = '100'
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-20).datetime trade.stoploss_last_update = dt_now() - timedelta(minutes=20)
trade.orders.append( trade.orders.append(
Order( Order(
ft_order_side='stoploss', ft_order_side='stoploss',
@ -1790,7 +1791,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
trade.open_order_id = None trade.open_order_id = None
trade.stoploss_order_id = "abcd" trade.stoploss_order_id = "abcd"
trade.stop_loss = 0.2 trade.stop_loss = 0.2
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime.replace(tzinfo=None) trade.stoploss_last_update = (dt_now() - timedelta(minutes=601)).replace(tzinfo=None)
trade.is_short = is_short trade.is_short = is_short
stoploss_order_hanging = { stoploss_order_hanging = {
@ -1814,7 +1815,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
assert stoploss.call_count == 1 assert stoploss.call_count == 1
# Fail creating stoploss order # Fail creating stoploss order
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime trade.stoploss_last_update = dt_now() - timedelta(minutes=601)
caplog.clear() caplog.clear()
cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order') cancel_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order')
mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError()) mocker.patch(f'{EXMS}.create_stoploss', side_effect=ExchangeError())
@ -1903,7 +1904,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
trade.stoploss_order_id = '100' trade.stoploss_order_id = '100'
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime trade.stoploss_last_update = dt_now() - timedelta(minutes=601)
trade.orders.append( trade.orders.append(
Order( Order(
ft_order_side='stoploss', ft_order_side='stoploss',
@ -2041,7 +2042,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
trade.stoploss_order_id = '100' trade.stoploss_order_id = '100'
trade.stoploss_last_update = arrow.utcnow().datetime trade.stoploss_last_update = dt_now()
trade.orders.append( trade.orders.append(
Order( Order(
ft_order_side='stoploss', ft_order_side='stoploss',
@ -2151,7 +2152,7 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
fee_open=0.001, fee_open=0.001,
fee_close=0.001, fee_close=0.001,
open_rate=0.01, open_rate=0.01,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
stake_amount=0.01, stake_amount=0.01,
amount=11, amount=11,
exchange="binance", exchange="binance",
@ -2197,7 +2198,7 @@ def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog
fee_open=0.001, fee_open=0.001,
fee_close=0.001, fee_close=0.001,
open_rate=0.01, open_rate=0.01,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
stake_amount=0.01, stake_amount=0.01,
amount=11, amount=11,
exchange="binance", exchange="binance",
@ -2246,7 +2247,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_order, is_short, ca
fee_open=0.001, fee_open=0.001,
fee_close=0.001, fee_close=0.001,
open_rate=0.01, open_rate=0.01,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
amount=11, amount=11,
exchange="binance", exchange="binance",
is_short=is_short, is_short=is_short,
@ -2319,7 +2320,7 @@ def test_update_trade_state_withorderdict(
amount=amount, amount=amount,
exchange='binance', exchange='binance',
open_rate=2.0, open_rate=2.0,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_order_id=order_id, open_order_id=order_id,
@ -2406,7 +2407,7 @@ def test_update_trade_state_sell(
open_rate=0.245441, open_rate=0.245441,
fee_open=0.0025, fee_open=0.0025,
fee_close=0.0025, fee_close=0.0025,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
open_order_id=open_order['id'], open_order_id=open_order['id'],
is_open=True, is_open=True,
interest_rate=0.0005, interest_rate=0.0005,
@ -2992,8 +2993,8 @@ def test_manage_open_orders_exit_usercustom(
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime open_trade_usdt.open_date = dt_now() - timedelta(hours=5)
open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime open_trade_usdt.close_date = dt_now() - timedelta(minutes=601)
open_trade_usdt.close_profit_abs = 0.001 open_trade_usdt.close_profit_abs = 0.001
Trade.session.add(open_trade_usdt) Trade.session.add(open_trade_usdt)
@ -3074,8 +3075,8 @@ def test_manage_open_orders_exit(
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime open_trade_usdt.open_date = dt_now() - timedelta(hours=5)
open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime open_trade_usdt.close_date = dt_now() - timedelta(minutes=601)
open_trade_usdt.close_profit_abs = 0.001 open_trade_usdt.close_profit_abs = 0.001
open_trade_usdt.is_short = is_short open_trade_usdt.is_short = is_short
@ -3115,8 +3116,8 @@ def test_check_handle_cancelled_exit(
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
open_trade_usdt.open_date = arrow.utcnow().shift(hours=-5).datetime open_trade_usdt.open_date = dt_now() - timedelta(hours=5)
open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime open_trade_usdt.close_date = dt_now() - timedelta(minutes=601)
open_trade_usdt.is_short = is_short open_trade_usdt.is_short = is_short
Trade.session.add(open_trade_usdt) Trade.session.add(open_trade_usdt)
@ -3444,11 +3445,11 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None:
exchange='binance', exchange='binance',
open_rate=0.245441, open_rate=0.245441,
open_order_id="sell_123456", open_order_id="sell_123456",
open_date=arrow.utcnow().shift(days=-2).datetime, open_date=dt_now() - timedelta(days=2),
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
close_rate=0.555, close_rate=0.555,
close_date=arrow.utcnow().datetime, close_date=dt_now(),
exit_reason="sell_reason_whatever", exit_reason="sell_reason_whatever",
stake_amount=0.245441 * 2, stake_amount=0.245441 * 2,
) )
@ -5465,7 +5466,7 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh
stake_amount=60.0, stake_amount=60.0,
fee_open=fee.return_value, fee_open=fee.return_value,
fee_close=fee.return_value, fee_close=fee.return_value,
open_date=arrow.utcnow().datetime, open_date=dt_now(),
is_open=True, is_open=True,
amount=30, amount=30,
open_rate=2.0, open_rate=2.0,
@ -5601,7 +5602,7 @@ def test_handle_onexchange_order(mocker, default_conf_usdt, limit_order, is_shor
fee_open=0.001, fee_open=0.001,
fee_close=0.001, fee_close=0.001,
open_rate=entry_order['price'], open_rate=entry_order['price'],
open_date=arrow.utcnow().datetime, open_date=dt_now(),
stake_amount=entry_order['cost'], stake_amount=entry_order['cost'],
amount=entry_order['amount'], amount=entry_order['amount'],
exchange="binance", exchange="binance",
@ -5739,9 +5740,9 @@ def test_update_funding_fees(
default_conf['trading_mode'] = 'futures' default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated' default_conf['margin_mode'] = 'isolated'
date_midnight = arrow.get('2021-09-01 00:00:00').datetime date_midnight = dt_utc(2021, 9, 1)
date_eight = arrow.get('2021-09-01 08:00:00').datetime date_eight = dt_utc(2021, 9, 1, 8)
date_sixteen = arrow.get('2021-09-01 16:00:00').datetime date_sixteen = dt_utc(2021, 9, 1, 16)
columns = ['date', 'open', 'high', 'low', 'close', 'volume'] columns = ['date', 'open', 'high', 'low', 'close', 'volume']
# 16:00 entry is actually never used # 16:00 entry is actually never used
# But should be kept in the test to ensure we're filtering correctly. # But should be kept in the test to ensure we're filtering correctly.
@ -6030,7 +6031,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
'ft_is_open': False, 'ft_is_open': False,
'id': '651', 'id': '651',
'order_id': '651', 'order_id': '651',
'datetime': arrow.utcnow().isoformat(), 'datetime': dt_now().isoformat(),
} }
mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_dca_order_1)) mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=closed_dca_order_1))

View File

@ -12,7 +12,7 @@ from freqtrade.misc import (dataframe_to_json, decimals_per_coin, deep_merge_dic
file_load_json, format_ms_time, json_to_dataframe, pair_to_filename, file_load_json, format_ms_time, json_to_dataframe, pair_to_filename,
parse_db_uri_for_logging, plural, render_template, parse_db_uri_for_logging, plural, render_template,
render_template_with_fallback, round_coin_value, safe_value_fallback, render_template_with_fallback, round_coin_value, safe_value_fallback,
safe_value_fallback2, shorten_date) safe_value_fallback2)
def test_decimals_per_coin(): def test_decimals_per_coin():
@ -39,12 +39,6 @@ def test_round_coin_value():
assert round_coin_value(222.2, 'USDT', False, True) == '222.200' assert round_coin_value(222.2, 'USDT', False, True) == '222.200'
def test_shorten_date() -> None:
str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago'
str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago'
assert shorten_date(str_data) == str_shorten_data
def test_file_dump_json(mocker) -> None: def test_file_dump_json(mocker) -> None:
file_open = mocker.patch('freqtrade.misc.Path.open', MagicMock()) file_open = mocker.patch('freqtrade.misc.Path.open', MagicMock())
json_dump = mocker.patch('rapidjson.dump', MagicMock()) json_dump = mocker.patch('rapidjson.dump', MagicMock())

View File

@ -1,7 +1,6 @@
# pragma pylint: disable=missing-docstring, C0103 # pragma pylint: disable=missing-docstring, C0103
from datetime import datetime, timezone from datetime import datetime, timezone
import arrow
import pytest import pytest
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
@ -69,7 +68,7 @@ def test_subtract_start():
def test_adjust_start_if_necessary(): def test_adjust_start_if_necessary():
min_date = arrow.Arrow(2017, 11, 14, 21, 15, 00) min_date = datetime(2017, 11, 14, 21, 15, 00, tzinfo=timezone.utc)
x = TimeRange('date', 'date', 1510694100, 1510780500) x = TimeRange('date', 'date', 1510694100, 1510780500)
# Adjust by 20 candles - min_date == startts # Adjust by 20 candles - min_date == startts

View File

@ -45,7 +45,7 @@ def test_sync_wallet_at_boot(mocker, default_conf):
assert freqtrade.wallets._wallets['GAS'].total == 0.260739 assert freqtrade.wallets._wallets['GAS'].total == 0.260739
assert freqtrade.wallets.get_free('BNT') == 1.0 assert freqtrade.wallets.get_free('BNT') == 1.0
assert 'USDT' in freqtrade.wallets._wallets assert 'USDT' in freqtrade.wallets._wallets
assert freqtrade.wallets._last_wallet_refresh > 0 assert freqtrade.wallets._last_wallet_refresh is not None
mocker.patch.multiple( mocker.patch.multiple(
EXMS, EXMS,
get_balances=MagicMock(return_value={ get_balances=MagicMock(return_value={
@ -332,7 +332,7 @@ def test_sync_wallet_futures_live(mocker, default_conf):
assert 'USDT' in freqtrade.wallets._wallets assert 'USDT' in freqtrade.wallets._wallets
assert 'ETH/USDT:USDT' in freqtrade.wallets._positions assert 'ETH/USDT:USDT' in freqtrade.wallets._positions
assert freqtrade.wallets._last_wallet_refresh > 0 assert freqtrade.wallets._last_wallet_refresh is not None
# Remove ETH/USDT:USDT position # Remove ETH/USDT:USDT position
del mock_result[0] del mock_result[0]

View File

@ -0,0 +1,59 @@
from datetime import datetime, timedelta, timezone
import pytest
import time_machine
from freqtrade.util import dt_floor_day, dt_from_ts, dt_now, dt_ts, dt_utc, shorten_date
from freqtrade.util.datetime_helpers import dt_humanize
def test_dt_now():
with time_machine.travel("2021-09-01 05:01:00 +00:00", tick=False) as t:
now = datetime.now(timezone.utc)
assert dt_now() == now
assert dt_ts() == int(now.timestamp() * 1000)
assert dt_ts(now) == int(now.timestamp() * 1000)
t.shift(timedelta(hours=5))
assert dt_now() >= now
assert dt_now() == datetime.now(timezone.utc)
assert dt_ts() == int(dt_now().timestamp() * 1000)
# Test with different time than now
assert dt_ts(now) == int(now.timestamp() * 1000)
def test_dt_utc():
assert dt_utc(2023, 5, 5) == datetime(2023, 5, 5, tzinfo=timezone.utc)
assert dt_utc(2023, 5, 5, 0, 0, 0, 555500) == datetime(2023, 5, 5, 0, 0, 0, 555500,
tzinfo=timezone.utc)
@pytest.mark.parametrize('as_ms', [True, False])
def test_dt_from_ts(as_ms):
multi = 1000 if as_ms else 1
assert dt_from_ts(1683244800.0 * multi) == datetime(2023, 5, 5, tzinfo=timezone.utc)
assert dt_from_ts(1683244800.5555 * multi) == datetime(2023, 5, 5, 0, 0, 0, 555500,
tzinfo=timezone.utc)
# As int
assert dt_from_ts(1683244800 * multi) == datetime(2023, 5, 5, tzinfo=timezone.utc)
# As milliseconds
assert dt_from_ts(1683244800 * multi) == datetime(2023, 5, 5, tzinfo=timezone.utc)
assert dt_from_ts(1683242400 * multi) == datetime(2023, 5, 4, 23, 20, tzinfo=timezone.utc)
def test_dt_floor_day():
now = datetime(2023, 9, 1, 5, 2, 3, 455555, tzinfo=timezone.utc)
assert dt_floor_day(now) == datetime(2023, 9, 1, tzinfo=timezone.utc)
def test_shorten_date() -> None:
str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago'
str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago'
assert shorten_date(str_data) == str_shorten_data
def test_dt_humanize() -> None:
assert dt_humanize(dt_now()) == 'just now'
assert dt_humanize(dt_now(), only_distance=True) == 'instantly'
assert dt_humanize(dt_now() - timedelta(hours=16), only_distance=True) == '16 hours'