merge dev into backtest-live-predictions
This commit is contained in:
commit
91df79ff44
@ -3,11 +3,12 @@ This module contains the argument manager class
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
|
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
|
||||||
|
|
||||||
@ -29,6 +30,52 @@ class TimeRange:
|
|||||||
self.startts: int = startts
|
self.startts: int = startts
|
||||||
self.stopts: int = stopts
|
self.stopts: int = stopts
|
||||||
|
|
||||||
|
@property
|
||||||
|
def startdt(self) -> Optional[datetime]:
|
||||||
|
if self.startts:
|
||||||
|
return datetime.fromtimestamp(self.startts, tz=timezone.utc)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stopdt(self) -> Optional[datetime]:
|
||||||
|
if self.stopts:
|
||||||
|
return datetime.fromtimestamp(self.stopts, tz=timezone.utc)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def timerange_str(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns a string representation of the timerange as used by parse_timerange.
|
||||||
|
Follows the format yyyymmdd-yyyymmdd - leaving out the parts that are not set.
|
||||||
|
"""
|
||||||
|
start = ''
|
||||||
|
stop = ''
|
||||||
|
if startdt := self.startdt:
|
||||||
|
start = startdt.strftime('%Y%m%d')
|
||||||
|
if stopdt := self.stopdt:
|
||||||
|
stop = stopdt.strftime('%Y%m%d')
|
||||||
|
return f"{start}-{stop}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def start_fmt(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns a string representation of the start date
|
||||||
|
"""
|
||||||
|
val = 'unbounded'
|
||||||
|
if (startdt := self.startdt) is not None:
|
||||||
|
val = startdt.strftime(DATETIME_PRINT_FORMAT)
|
||||||
|
return val
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stop_fmt(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns a string representation of the stop date
|
||||||
|
"""
|
||||||
|
val = 'unbounded'
|
||||||
|
if (stopdt := self.stopdt) is not None:
|
||||||
|
val = stopdt.strftime(DATETIME_PRINT_FORMAT)
|
||||||
|
return val
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Override the default Equals behavior"""
|
"""Override the default Equals behavior"""
|
||||||
return (self.starttype == other.starttype and self.stoptype == other.stoptype
|
return (self.starttype == other.starttype and self.stoptype == other.stoptype
|
||||||
|
@ -3,7 +3,6 @@ Functions to convert data from one format to another
|
|||||||
"""
|
"""
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
@ -138,11 +137,9 @@ def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date',
|
|||||||
df = df.iloc[startup_candles:, :]
|
df = df.iloc[startup_candles:, :]
|
||||||
else:
|
else:
|
||||||
if timerange.starttype == 'date':
|
if timerange.starttype == 'date':
|
||||||
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
|
df = df.loc[df[df_date_col] >= timerange.startdt, :]
|
||||||
df = df.loc[df[df_date_col] >= start, :]
|
|
||||||
if timerange.stoptype == 'date':
|
if timerange.stoptype == 'date':
|
||||||
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
|
df = df.loc[df[df_date_col] <= timerange.stopdt, :]
|
||||||
df = df.loc[df[df_date_col] <= stop, :]
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
@ -160,9 +160,9 @@ def _load_cached_data_for_updating(
|
|||||||
end = None
|
end = None
|
||||||
if timerange:
|
if timerange:
|
||||||
if timerange.starttype == 'date':
|
if timerange.starttype == 'date':
|
||||||
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
|
start = timerange.startdt
|
||||||
if timerange.stoptype == 'date':
|
if timerange.stoptype == 'date':
|
||||||
end = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
|
end = timerange.stopdt
|
||||||
|
|
||||||
# Intentionally don't pass timerange in - since we need to load the full dataset.
|
# Intentionally don't pass timerange in - since we need to load the full dataset.
|
||||||
data = data_handler.ohlcv_load(pair, timeframe=timeframe,
|
data = data_handler.ohlcv_load(pair, timeframe=timeframe,
|
||||||
|
@ -366,13 +366,11 @@ class IDataHandler(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if timerange.starttype == 'date':
|
if timerange.starttype == 'date':
|
||||||
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
|
if pairdata.iloc[0]['date'] > timerange.startdt:
|
||||||
if pairdata.iloc[0]['date'] > start:
|
|
||||||
logger.warning(f"{pair}, {candle_type}, {timeframe}, "
|
logger.warning(f"{pair}, {candle_type}, {timeframe}, "
|
||||||
f"data starts at {pairdata.iloc[0]['date']:%Y-%m-%d %H:%M:%S}")
|
f"data starts at {pairdata.iloc[0]['date']:%Y-%m-%d %H:%M:%S}")
|
||||||
if timerange.stoptype == 'date':
|
if timerange.stoptype == 'date':
|
||||||
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
|
if pairdata.iloc[-1]['date'] < timerange.stopdt:
|
||||||
if pairdata.iloc[-1]['date'] < stop:
|
|
||||||
logger.warning(f"{pair}, {candle_type}, {timeframe}, "
|
logger.warning(f"{pair}, {candle_type}, {timeframe}, "
|
||||||
f"data ends at {pairdata.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}")
|
f"data ends at {pairdata.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}")
|
||||||
|
|
||||||
|
@ -436,9 +436,7 @@ class FreqaiDataKitchen:
|
|||||||
timerange_train.stopts = timerange_train.startts + train_period_days
|
timerange_train.stopts = timerange_train.startts + train_period_days
|
||||||
|
|
||||||
first = False
|
first = False
|
||||||
start = datetime.fromtimestamp(timerange_train.startts, tz=timezone.utc)
|
tr_training_list.append(timerange_train.timerange_str)
|
||||||
stop = datetime.fromtimestamp(timerange_train.stopts, tz=timezone.utc)
|
|
||||||
tr_training_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d"))
|
|
||||||
tr_training_list_timerange.append(copy.deepcopy(timerange_train))
|
tr_training_list_timerange.append(copy.deepcopy(timerange_train))
|
||||||
|
|
||||||
# associated backtest period
|
# associated backtest period
|
||||||
@ -450,9 +448,7 @@ class FreqaiDataKitchen:
|
|||||||
if timerange_backtest.stopts > config_timerange.stopts:
|
if timerange_backtest.stopts > config_timerange.stopts:
|
||||||
timerange_backtest.stopts = config_timerange.stopts
|
timerange_backtest.stopts = config_timerange.stopts
|
||||||
|
|
||||||
start = datetime.fromtimestamp(timerange_backtest.startts, tz=timezone.utc)
|
tr_backtesting_list.append(timerange_backtest.timerange_str)
|
||||||
stop = datetime.fromtimestamp(timerange_backtest.stopts, tz=timezone.utc)
|
|
||||||
tr_backtesting_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d"))
|
|
||||||
tr_backtesting_list_timerange.append(copy.deepcopy(timerange_backtest))
|
tr_backtesting_list_timerange.append(copy.deepcopy(timerange_backtest))
|
||||||
|
|
||||||
# ensure we are predicting on exactly same amount of data as requested by user defined
|
# ensure we are predicting on exactly same amount of data as requested by user defined
|
||||||
@ -494,11 +490,9 @@ class FreqaiDataKitchen:
|
|||||||
it is sliced down to just the present training period.
|
it is sliced down to just the present training period.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
|
df = df.loc[df["date"] >= timerange.startdt, :]
|
||||||
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
|
|
||||||
df = df.loc[df["date"] >= start, :]
|
|
||||||
if not self.live:
|
if not self.live:
|
||||||
df = df.loc[df["date"] < stop, :]
|
df = df.loc[df["date"] < timerange.stopdt, :]
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
@ -1059,9 +1053,7 @@ class FreqaiDataKitchen:
|
|||||||
backtest_timerange.startts = (
|
backtest_timerange.startts = (
|
||||||
backtest_timerange.startts - backtest_period_days * SECONDS_IN_DAY
|
backtest_timerange.startts - backtest_period_days * SECONDS_IN_DAY
|
||||||
)
|
)
|
||||||
start = datetime.fromtimestamp(backtest_timerange.startts, tz=timezone.utc)
|
full_timerange = backtest_timerange.timerange_str
|
||||||
stop = datetime.fromtimestamp(backtest_timerange.stopts, tz=timezone.utc)
|
|
||||||
full_timerange = start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d")
|
|
||||||
config_path = Path(self.config["config_files"][0])
|
config_path = Path(self.config["config_files"][0])
|
||||||
|
|
||||||
if not self.full_path.is_dir():
|
if not self.full_path.is_dir():
|
||||||
|
@ -13,7 +13,7 @@ from numpy.typing import NDArray
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, Config
|
from freqtrade.constants import Config
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_seconds
|
||||||
@ -826,14 +826,8 @@ class IFreqaiModel(ABC):
|
|||||||
:return: if the data exists or not
|
:return: if the data exists or not
|
||||||
"""
|
"""
|
||||||
if self.config.get("freqai_backtest_live_models", False) and len(dataframe_backtest) == 0:
|
if self.config.get("freqai_backtest_live_models", False) and len(dataframe_backtest) == 0:
|
||||||
tr_backtest_startts_str = datetime.fromtimestamp(
|
logger.info(f"No data found for pair {pair} from "
|
||||||
tr_backtest.startts,
|
f"from { tr_backtest.start_fmt} to {tr_backtest.stop_fmt}. "
|
||||||
tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
|
|
||||||
tr_backtest_stopts_str = datetime.fromtimestamp(
|
|
||||||
tr_backtest.stopts,
|
|
||||||
tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
|
|
||||||
logger.info(f"No data found for pair {pair} from {tr_backtest_startts_str} "
|
|
||||||
f" from {tr_backtest_startts_str} to {tr_backtest_stopts_str}. "
|
|
||||||
"Probably more than one training within the same candle period.")
|
"Probably more than one training within the same candle period.")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@ -848,18 +842,11 @@ class IFreqaiModel(ABC):
|
|||||||
:param pair: the current pair
|
:param pair: the current pair
|
||||||
:param total_trains: total trains (total number of slides for the sliding window)
|
:param total_trains: total trains (total number of slides for the sliding window)
|
||||||
"""
|
"""
|
||||||
tr_train_startts_str = datetime.fromtimestamp(
|
|
||||||
tr_train.startts,
|
|
||||||
tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
|
|
||||||
tr_train_stopts_str = datetime.fromtimestamp(
|
|
||||||
tr_train.stopts,
|
|
||||||
tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
|
|
||||||
|
|
||||||
if not self.config.get("freqai_backtest_live_models", False):
|
if not self.config.get("freqai_backtest_live_models", False):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Training {pair}, {self.pair_it}/{self.total_pairs} pairs"
|
f"Training {pair}, {self.pair_it}/{self.total_pairs} pairs"
|
||||||
f" from {tr_train_startts_str} "
|
f" from {tr_train.start_fmt} "
|
||||||
f"to {tr_train_stopts_str}, {train_it}/{total_trains} "
|
f"to {tr_train.stop_fmt}, {train_it}/{total_trains} "
|
||||||
"trains"
|
"trains"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -235,7 +235,4 @@ def get_timerange_backtest_live_models(config: Config) -> str:
|
|||||||
else:
|
else:
|
||||||
timerange = dk.get_timerange_from_backtesting_live_dataframe()
|
timerange = dk.get_timerange_from_backtesting_live_dataframe()
|
||||||
|
|
||||||
start_date = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
|
return timerange.timerange_str
|
||||||
end_date = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
|
|
||||||
tr = f"{start_date.strftime('%Y%m%d')}-{end_date.strftime('%Y%m%d')}"
|
|
||||||
return tr
|
|
||||||
|
@ -1286,8 +1286,7 @@ class Backtesting:
|
|||||||
def _get_min_cached_backtest_date(self):
|
def _get_min_cached_backtest_date(self):
|
||||||
min_backtest_date = None
|
min_backtest_date = None
|
||||||
backtest_cache_age = self.config.get('backtest_cache', constants.BACKTEST_CACHE_DEFAULT)
|
backtest_cache_age = self.config.get('backtest_cache', constants.BACKTEST_CACHE_DEFAULT)
|
||||||
if self.timerange.stopts == 0 or datetime.fromtimestamp(
|
if self.timerange.stopts == 0 or self.timerange.stopdt > datetime.now(tz=timezone.utc):
|
||||||
self.timerange.stopts, tz=timezone.utc) > datetime.now(tz=timezone.utc):
|
|
||||||
logger.warning('Backtest result caching disabled due to use of open-ended timerange.')
|
logger.warning('Backtest result caching disabled due to use of open-ended timerange.')
|
||||||
elif backtest_cache_age == 'day':
|
elif backtest_cache_age == 'day':
|
||||||
min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(days=1)
|
min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(days=1)
|
||||||
|
@ -90,6 +90,13 @@ class Order(_DECL_BASE):
|
|||||||
def safe_filled(self) -> float:
|
def safe_filled(self) -> float:
|
||||||
return self.filled if self.filled is not None else self.amount or 0.0
|
return self.filled if self.filled is not None else self.amount or 0.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def safe_remaining(self) -> float:
|
||||||
|
return (
|
||||||
|
self.remaining if self.remaining is not None else
|
||||||
|
self.amount - (self.filled or 0.0)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def safe_fee_base(self) -> float:
|
def safe_fee_base(self) -> float:
|
||||||
return self.ft_fee_base or 0.0
|
return self.ft_fee_base or 0.0
|
||||||
|
@ -218,9 +218,10 @@ class RPC:
|
|||||||
stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2),
|
stoploss_current_dist_pct=round(stoploss_current_dist_ratio * 100, 2),
|
||||||
stoploss_entry_dist=stoploss_entry_dist,
|
stoploss_entry_dist=stoploss_entry_dist,
|
||||||
stoploss_entry_dist_ratio=round(stoploss_entry_dist_ratio, 8),
|
stoploss_entry_dist_ratio=round(stoploss_entry_dist_ratio, 8),
|
||||||
open_order='({} {} rem={:.8f})'.format(
|
open_order=(
|
||||||
order.order_type, order.side, order.remaining
|
f'({order.order_type} {order.side} rem={order.safe_remaining:.8f})' if
|
||||||
) if order else None,
|
order else None
|
||||||
|
),
|
||||||
))
|
))
|
||||||
results.append(trade_dict)
|
results.append(trade_dict)
|
||||||
return results
|
return results
|
||||||
|
@ -1062,7 +1062,7 @@ class Telegram(RPCHandler):
|
|||||||
self._rpc._rpc_force_entry(pair, price, order_side=order_side)
|
self._rpc._rpc_force_entry(pair, price, order_side=order_side)
|
||||||
except RPCException as e:
|
except RPCException as e:
|
||||||
logger.exception("Forcebuy error!")
|
logger.exception("Forcebuy error!")
|
||||||
self._send_msg(str(e))
|
self._send_msg(str(e), ParseMode.HTML)
|
||||||
|
|
||||||
def _force_enter_inline(self, update: Update, _: CallbackContext) -> None:
|
def _force_enter_inline(self, update: Update, _: CallbackContext) -> None:
|
||||||
if update.callback_query:
|
if update.callback_query:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
|
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||||
|
|
||||||
@ -28,113 +29,7 @@ def prec_satoshi(a, b) -> float:
|
|||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
gen_response = {
|
||||||
mocker.patch.multiple(
|
|
||||||
'freqtrade.exchange.Exchange',
|
|
||||||
fetch_ticker=ticker,
|
|
||||||
get_fee=fee,
|
|
||||||
_is_dry_limit_order_filled=MagicMock(side_effect=[False, True]),
|
|
||||||
)
|
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
|
||||||
patch_get_signal(freqtradebot)
|
|
||||||
rpc = RPC(freqtradebot)
|
|
||||||
|
|
||||||
freqtradebot.state = State.RUNNING
|
|
||||||
with pytest.raises(RPCException, match=r'.*no active trade*'):
|
|
||||||
rpc._rpc_trade_status()
|
|
||||||
|
|
||||||
freqtradebot.enter_positions()
|
|
||||||
|
|
||||||
# Open order...
|
|
||||||
results = rpc._rpc_trade_status()
|
|
||||||
assert results[0] == {
|
|
||||||
'trade_id': 1,
|
|
||||||
'pair': 'ETH/BTC',
|
|
||||||
'base_currency': 'ETH',
|
|
||||||
'quote_currency': 'BTC',
|
|
||||||
'open_date': ANY,
|
|
||||||
'open_timestamp': ANY,
|
|
||||||
'is_open': ANY,
|
|
||||||
'fee_open': ANY,
|
|
||||||
'fee_open_cost': ANY,
|
|
||||||
'fee_open_currency': ANY,
|
|
||||||
'fee_close': fee.return_value,
|
|
||||||
'fee_close_cost': ANY,
|
|
||||||
'fee_close_currency': ANY,
|
|
||||||
'open_rate_requested': ANY,
|
|
||||||
'open_trade_value': 0.0010025,
|
|
||||||
'close_rate_requested': ANY,
|
|
||||||
'sell_reason': ANY,
|
|
||||||
'exit_reason': ANY,
|
|
||||||
'exit_order_status': ANY,
|
|
||||||
'min_rate': ANY,
|
|
||||||
'max_rate': ANY,
|
|
||||||
'strategy': ANY,
|
|
||||||
'buy_tag': ANY,
|
|
||||||
'enter_tag': ANY,
|
|
||||||
'timeframe': 5,
|
|
||||||
'open_order_id': ANY,
|
|
||||||
'close_date': None,
|
|
||||||
'close_timestamp': None,
|
|
||||||
'open_rate': 1.098e-05,
|
|
||||||
'close_rate': None,
|
|
||||||
'current_rate': 1.099e-05,
|
|
||||||
'amount': 91.07468124,
|
|
||||||
'amount_requested': 91.07468124,
|
|
||||||
'stake_amount': 0.001,
|
|
||||||
'trade_duration': None,
|
|
||||||
'trade_duration_s': None,
|
|
||||||
'close_profit': None,
|
|
||||||
'close_profit_pct': None,
|
|
||||||
'close_profit_abs': None,
|
|
||||||
'current_profit': 0.0,
|
|
||||||
'current_profit_pct': 0.0,
|
|
||||||
'current_profit_abs': 0.0,
|
|
||||||
'profit_ratio': 0.0,
|
|
||||||
'profit_pct': 0.0,
|
|
||||||
'profit_abs': 0.0,
|
|
||||||
'profit_fiat': ANY,
|
|
||||||
'stop_loss_abs': 0.0,
|
|
||||||
'stop_loss_pct': None,
|
|
||||||
'stop_loss_ratio': None,
|
|
||||||
'stoploss_order_id': None,
|
|
||||||
'stoploss_last_update': ANY,
|
|
||||||
'stoploss_last_update_timestamp': ANY,
|
|
||||||
'initial_stop_loss_abs': 0.0,
|
|
||||||
'initial_stop_loss_pct': None,
|
|
||||||
'initial_stop_loss_ratio': None,
|
|
||||||
'stoploss_current_dist': -1.099e-05,
|
|
||||||
'stoploss_current_dist_ratio': -1.0,
|
|
||||||
'stoploss_current_dist_pct': pytest.approx(-100.0),
|
|
||||||
'stoploss_entry_dist': -0.0010025,
|
|
||||||
'stoploss_entry_dist_ratio': -1.0,
|
|
||||||
'open_order': '(limit buy rem=91.07468123)',
|
|
||||||
'realized_profit': 0.0,
|
|
||||||
'exchange': 'binance',
|
|
||||||
'leverage': 1.0,
|
|
||||||
'interest_rate': 0.0,
|
|
||||||
'liquidation_price': None,
|
|
||||||
'is_short': False,
|
|
||||||
'funding_fees': 0.0,
|
|
||||||
'trading_mode': TradingMode.SPOT,
|
|
||||||
'orders': [{
|
|
||||||
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
|
|
||||||
'cost': 0.0009999999999054, 'filled': 0.0, 'ft_order_side': 'buy',
|
|
||||||
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
|
|
||||||
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
|
|
||||||
'is_open': True, 'pair': 'ETH/BTC', 'order_id': ANY,
|
|
||||||
'remaining': 91.07468123, 'status': ANY, 'ft_is_entry': True,
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Fill open order ...
|
|
||||||
freqtradebot.manage_open_orders()
|
|
||||||
trades = Trade.get_open_trades()
|
|
||||||
freqtradebot.exit_positions(trades)
|
|
||||||
|
|
||||||
results = rpc._rpc_trade_status()
|
|
||||||
assert results[0] == {
|
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'base_currency': 'ETH',
|
'base_currency': 'ETH',
|
||||||
@ -213,91 +108,103 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'remaining': ANY, 'status': ANY, 'ft_is_entry': True,
|
'remaining': ANY, 'status': ANY, 'ft_is_entry': True,
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
fetch_ticker=ticker,
|
||||||
|
get_fee=fee,
|
||||||
|
_is_dry_limit_order_filled=MagicMock(side_effect=[False, True]),
|
||||||
|
)
|
||||||
|
|
||||||
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
patch_get_signal(freqtradebot)
|
||||||
|
rpc = RPC(freqtradebot)
|
||||||
|
|
||||||
|
freqtradebot.state = State.RUNNING
|
||||||
|
with pytest.raises(RPCException, match=r'.*no active trade*'):
|
||||||
|
rpc._rpc_trade_status()
|
||||||
|
|
||||||
|
freqtradebot.enter_positions()
|
||||||
|
|
||||||
|
# Open order...
|
||||||
|
results = rpc._rpc_trade_status()
|
||||||
|
response_unfilled = deepcopy(gen_response)
|
||||||
|
# Different from "filled" response:
|
||||||
|
response_unfilled.update({
|
||||||
|
'amount': 91.07468124,
|
||||||
|
'profit_ratio': 0.0,
|
||||||
|
'profit_pct': 0.0,
|
||||||
|
'profit_abs': 0.0,
|
||||||
|
'current_profit': 0.0,
|
||||||
|
'current_profit_pct': 0.0,
|
||||||
|
'current_profit_abs': 0.0,
|
||||||
|
'stop_loss_abs': 0.0,
|
||||||
|
'stop_loss_pct': None,
|
||||||
|
'stop_loss_ratio': None,
|
||||||
|
'stoploss_current_dist': -1.099e-05,
|
||||||
|
'stoploss_current_dist_ratio': -1.0,
|
||||||
|
'stoploss_current_dist_pct': pytest.approx(-100.0),
|
||||||
|
'stoploss_entry_dist': -0.0010025,
|
||||||
|
'stoploss_entry_dist_ratio': -1.0,
|
||||||
|
'initial_stop_loss_abs': 0.0,
|
||||||
|
'initial_stop_loss_pct': None,
|
||||||
|
'initial_stop_loss_ratio': None,
|
||||||
|
'open_order': '(limit buy rem=91.07468123)',
|
||||||
|
})
|
||||||
|
response_unfilled['orders'][0].update({
|
||||||
|
'is_open': True,
|
||||||
|
'filled': 0.0,
|
||||||
|
'remaining': 91.07468123
|
||||||
|
})
|
||||||
|
assert results[0] == response_unfilled
|
||||||
|
|
||||||
|
# Open order without remaining
|
||||||
|
trade = Trade.get_open_trades()[0]
|
||||||
|
# kucoin case (no remaining set).
|
||||||
|
trade.orders[0].remaining = None
|
||||||
|
Trade.commit()
|
||||||
|
|
||||||
|
results = rpc._rpc_trade_status()
|
||||||
|
# Reuse above object, only remaining changed.
|
||||||
|
response_unfilled['orders'][0].update({
|
||||||
|
'remaining': None
|
||||||
|
})
|
||||||
|
assert results[0] == response_unfilled
|
||||||
|
|
||||||
|
trade = Trade.get_open_trades()[0]
|
||||||
|
trade.orders[0].remaining = trade.amount
|
||||||
|
Trade.commit()
|
||||||
|
|
||||||
|
# Fill open order ...
|
||||||
|
freqtradebot.manage_open_orders()
|
||||||
|
trades = Trade.get_open_trades()
|
||||||
|
freqtradebot.exit_positions(trades)
|
||||||
|
|
||||||
|
results = rpc._rpc_trade_status()
|
||||||
|
|
||||||
|
response = deepcopy(gen_response)
|
||||||
|
assert results[0] == response
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
results = rpc._rpc_trade_status()
|
results = rpc._rpc_trade_status()
|
||||||
assert isnan(results[0]['current_profit'])
|
assert isnan(results[0]['current_profit'])
|
||||||
assert isnan(results[0]['current_rate'])
|
assert isnan(results[0]['current_rate'])
|
||||||
assert results[0] == {
|
response_norate = deepcopy(gen_response)
|
||||||
'trade_id': 1,
|
# Update elements that are NaN when no rate is available.
|
||||||
'pair': 'ETH/BTC',
|
response_norate.update({
|
||||||
'base_currency': 'ETH',
|
|
||||||
'quote_currency': 'BTC',
|
|
||||||
'open_date': ANY,
|
|
||||||
'open_timestamp': ANY,
|
|
||||||
'is_open': ANY,
|
|
||||||
'fee_open': ANY,
|
|
||||||
'fee_open_cost': ANY,
|
|
||||||
'fee_open_currency': ANY,
|
|
||||||
'fee_close': fee.return_value,
|
|
||||||
'fee_close_cost': ANY,
|
|
||||||
'fee_close_currency': ANY,
|
|
||||||
'open_rate_requested': ANY,
|
|
||||||
'open_trade_value': ANY,
|
|
||||||
'close_rate_requested': ANY,
|
|
||||||
'sell_reason': ANY,
|
|
||||||
'exit_reason': ANY,
|
|
||||||
'exit_order_status': ANY,
|
|
||||||
'min_rate': ANY,
|
|
||||||
'max_rate': ANY,
|
|
||||||
'strategy': ANY,
|
|
||||||
'buy_tag': ANY,
|
|
||||||
'enter_tag': ANY,
|
|
||||||
'timeframe': ANY,
|
|
||||||
'open_order_id': ANY,
|
|
||||||
'close_date': None,
|
|
||||||
'close_timestamp': None,
|
|
||||||
'open_rate': 1.098e-05,
|
|
||||||
'close_rate': None,
|
|
||||||
'current_rate': ANY,
|
|
||||||
'amount': 91.07468123,
|
|
||||||
'amount_requested': 91.07468124,
|
|
||||||
'trade_duration': ANY,
|
|
||||||
'trade_duration_s': ANY,
|
|
||||||
'stake_amount': 0.001,
|
|
||||||
'close_profit': None,
|
|
||||||
'close_profit_pct': None,
|
|
||||||
'close_profit_abs': None,
|
|
||||||
'current_profit': ANY,
|
|
||||||
'current_profit_pct': ANY,
|
|
||||||
'current_profit_abs': ANY,
|
|
||||||
'profit_ratio': ANY,
|
|
||||||
'profit_pct': ANY,
|
|
||||||
'profit_abs': ANY,
|
|
||||||
'profit_fiat': ANY,
|
|
||||||
'stop_loss_abs': 9.89e-06,
|
|
||||||
'stop_loss_pct': -10.0,
|
|
||||||
'stop_loss_ratio': -0.1,
|
|
||||||
'stoploss_order_id': None,
|
|
||||||
'stoploss_last_update': ANY,
|
|
||||||
'stoploss_last_update_timestamp': ANY,
|
|
||||||
'initial_stop_loss_abs': 9.89e-06,
|
|
||||||
'initial_stop_loss_pct': -10.0,
|
|
||||||
'initial_stop_loss_ratio': -0.1,
|
|
||||||
'stoploss_current_dist': ANY,
|
'stoploss_current_dist': ANY,
|
||||||
'stoploss_current_dist_ratio': ANY,
|
'stoploss_current_dist_ratio': ANY,
|
||||||
'stoploss_current_dist_pct': ANY,
|
'stoploss_current_dist_pct': ANY,
|
||||||
'stoploss_entry_dist': -0.00010402,
|
'profit_ratio': ANY,
|
||||||
'stoploss_entry_dist_ratio': -0.10376381,
|
'profit_pct': ANY,
|
||||||
'open_order': None,
|
'profit_abs': ANY,
|
||||||
'exchange': 'binance',
|
'current_profit_abs': ANY,
|
||||||
'realized_profit': 0.0,
|
'current_profit': ANY,
|
||||||
'leverage': 1.0,
|
'current_profit_pct': ANY,
|
||||||
'interest_rate': 0.0,
|
'current_rate': ANY,
|
||||||
'liquidation_price': None,
|
})
|
||||||
'is_short': False,
|
assert results[0] == response_norate
|
||||||
'funding_fees': 0.0,
|
|
||||||
'trading_mode': TradingMode.SPOT,
|
|
||||||
'orders': [{
|
|
||||||
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
|
|
||||||
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
|
|
||||||
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
|
|
||||||
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
|
|
||||||
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
|
|
||||||
'remaining': ANY, 'status': ANY, 'ft_is_entry': True,
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -8,16 +10,28 @@ from freqtrade.exceptions import OperationalException
|
|||||||
|
|
||||||
def test_parse_timerange_incorrect():
|
def test_parse_timerange_incorrect():
|
||||||
|
|
||||||
assert TimeRange('date', None, 1274486400, 0) == TimeRange.parse_timerange('20100522-')
|
timerange = TimeRange.parse_timerange('20100522-')
|
||||||
assert TimeRange(None, 'date', 0, 1274486400) == TimeRange.parse_timerange('-20100522')
|
assert TimeRange('date', None, 1274486400, 0) == timerange
|
||||||
|
assert timerange.timerange_str == '20100522-'
|
||||||
|
timerange = TimeRange.parse_timerange('-20100522')
|
||||||
|
assert TimeRange(None, 'date', 0, 1274486400) == timerange
|
||||||
|
assert timerange.timerange_str == '-20100522'
|
||||||
timerange = TimeRange.parse_timerange('20100522-20150730')
|
timerange = TimeRange.parse_timerange('20100522-20150730')
|
||||||
assert timerange == TimeRange('date', 'date', 1274486400, 1438214400)
|
assert timerange == TimeRange('date', 'date', 1274486400, 1438214400)
|
||||||
|
assert timerange.timerange_str == '20100522-20150730'
|
||||||
|
assert timerange.start_fmt == '2010-05-22 00:00:00'
|
||||||
|
assert timerange.stop_fmt == '2015-07-30 00:00:00'
|
||||||
|
|
||||||
# Added test for unix timestamp - BTC genesis date
|
# Added test for unix timestamp - BTC genesis date
|
||||||
assert TimeRange('date', None, 1231006505, 0) == TimeRange.parse_timerange('1231006505-')
|
assert TimeRange('date', None, 1231006505, 0) == TimeRange.parse_timerange('1231006505-')
|
||||||
assert TimeRange(None, 'date', 0, 1233360000) == TimeRange.parse_timerange('-1233360000')
|
assert TimeRange(None, 'date', 0, 1233360000) == TimeRange.parse_timerange('-1233360000')
|
||||||
timerange = TimeRange.parse_timerange('1231006505-1233360000')
|
timerange = TimeRange.parse_timerange('1231006505-1233360000')
|
||||||
assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
|
assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
|
||||||
|
assert isinstance(timerange.startdt, datetime)
|
||||||
|
assert isinstance(timerange.stopdt, datetime)
|
||||||
|
assert timerange.startdt == datetime.fromtimestamp(1231006505, tz=timezone.utc)
|
||||||
|
assert timerange.stopdt == datetime.fromtimestamp(1233360000, tz=timezone.utc)
|
||||||
|
assert timerange.timerange_str == '20090103-20090131'
|
||||||
|
|
||||||
timerange = TimeRange.parse_timerange('1231006505000-1233360000000')
|
timerange = TimeRange.parse_timerange('1231006505000-1233360000000')
|
||||||
assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
|
assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
|
||||||
@ -45,6 +59,7 @@ def test_subtract_start():
|
|||||||
x = TimeRange(None, 'date', 0, 1438214400)
|
x = TimeRange(None, 'date', 0, 1438214400)
|
||||||
x.subtract_start(300)
|
x.subtract_start(300)
|
||||||
assert not x.startts
|
assert not x.startts
|
||||||
|
assert not x.startdt
|
||||||
|
|
||||||
x = TimeRange('date', None, 1274486400, 0)
|
x = TimeRange('date', None, 1274486400, 0)
|
||||||
x.subtract_start(300)
|
x.subtract_start(300)
|
||||||
|
Loading…
Reference in New Issue
Block a user