Merge pull request #9348 from freqtrade/new_release

New release 2023.10
This commit is contained in:
Matthias 2023-10-31 06:42:58 +01:00 committed by GitHub
commit f142abfb76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 4501 additions and 1389 deletions

View File

@ -108,7 +108,7 @@ jobs:
- name: Run Ruff
run: |
ruff check --format=github .
ruff check --output-format=github .
- name: Mypy
run: |
@ -217,7 +217,7 @@ jobs:
- name: Run Ruff
run: |
ruff check --format=github .
ruff check --output-format=github .
- name: Mypy
run: |
@ -287,7 +287,7 @@ jobs:
- name: Run Ruff
run: |
ruff check --format=github .
ruff check --output-format=github .
- name: Mypy
run: |

View File

@ -15,10 +15,10 @@ repos:
additional_dependencies:
- types-cachetools==5.3.0.6
- types-filelock==3.2.7
- types-requests==2.31.0.4
- types-requests==2.31.0.10
- types-tabulate==0.9.0.3
- types-python-dateutil==2.8.19.14
- SQLAlchemy==2.0.21
- SQLAlchemy==2.0.22
# stages: [push]
- repo: https://github.com/pycqa/isort
@ -30,7 +30,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.270'
rev: 'v0.1.1'
hooks:
- id: ruff

View File

@ -23,7 +23,7 @@ WORKDIR /freqtrade
# Install dependencies
FROM base as python-deps
RUN apt-get update \
&& apt-get -y install build-essential libssl-dev libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get -y install build-essential libssl-dev libffi-dev libopenblas-dev libgfortran5 pkg-config cmake gcc \
&& apt-get clean \
&& pip install --upgrade pip \
&& echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > /etc/pip.conf

View File

@ -31,9 +31,9 @@ optional arguments:
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `json`).
(default: `feather`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.

View File

@ -136,6 +136,43 @@ Freqtrade will not attempt to change these settings.
The Kraken API does only provide 720 historic candles, which is sufficient for Freqtrade dry-run and live trade modes, but is a problem for backtesting.
To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data.
To speed up downloading, you can download the [trades zip files](https://support.kraken.com/hc/en-us/articles/360047543791-Downloadable-historical-market-data-time-and-sales-) kraken provides.
These are usually updated once per quarter. Freqtrade expects these files to be placed in `user_data/data/kraken/trades_csv`.
A structure as follows can make sense if using incremental files, with the "full" history in one directory, and incremental files in different directories.
The assumption for this mode is that the data is downloaded and unzipped keeping filenames as they are.
Duplicate content will be ignored (based on timestamp) - though the assumption is that there is no gap in the data.
This means, if your "full" history ends in Q4 2022 - then both incremental updates Q1 2023 and Q2 2023 are available.
Not having this will lead to incomplete data, and therefore invalid results while using the data.
```
└── trades_csv
    ├── Kraken_full_history
   │   ├── BCHEUR.csv
   │   └── XBTEUR.csv
   ├── Kraken_Trading_History_Q1_2023
   │   ├── BCHEUR.csv
   │   └── XBTEUR.csv
   └── Kraken_Trading_History_Q2_2023
      ├── BCHEUR.csv
      └── XBTEUR.csv
```
You can convert these files into freqtrade files:
``` bash
freqtrade convert-trade-data --exchange kraken --format-from kraken_csv --format-to feather
# Convert trade data to different ohlcv timeframes
freqtrade trades-to-ohlcv -p BTC/EUR BCH/EUR --exchange kraken -t 1m 5m 15m 1h
```
The converted data also makes downloading data possible, and will start the download after the latest loaded trade.
``` bash
freqtrade download-data --exchange kraken --dl-trades -p BTC/EUR BCH/EUR
```
!!! Warning "Downloading data from kraken"
Downloading kraken data will require significantly more memory (RAM) than any other exchange, as the trades-data needs to be converted into candles on your machine.
It will also take a long time, as freqtrade will need to download every single trade that happened on the exchange for the pair / timerange combination, therefore please be patient.

View File

@ -40,11 +40,41 @@ usage: freqtrade recursive-analysis [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--startup-candle STARTUP_CANDLES [STARTUP_CANDLES ...]]
optional arguments:
-p PAIR, --pairs PAIR
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `feather`).
-p PAIR, --pairs PAIR
Limit command to this pair.
--startup-candle STARTUP_CANDLE [STARTUP_CANDLE ...]
--startup-candle STARTUP_CANDLE [STARTUP_CANDLE ...]
Provide a space-separated list of startup_candle_count to
be checked. Default : `199 399 499 999 1999`.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
--timerange TIMERANGE
Specify what timerange of data to use.
```
### Why are odd-numbered default startup candles used?

View File

@ -1,6 +1,6 @@
markdown==3.4.4
markdown==3.5
mkdocs==1.5.3
mkdocs-material==9.4.1
mkdocs-material==9.4.6
mdx_truly_sane_lists==1.3
pymdown-extensions==10.3
pymdown-extensions==10.3.1
jinja2==3.1.2

View File

@ -1008,6 +1008,10 @@ The following lists some common patterns which should be avoided to prevent frus
- don't use `dataframe['volume'].mean()`. This uses the full DataFrame for backtesting, including data from the future. Use `dataframe['volume'].rolling(<window>).mean()` instead
- don't use `.resample('1h')`. This uses the left border of the interval, so moves data from an hour to the start of the hour. Use `.resample('1h', label='right')` instead.
!!! Tip "Identifying problems"
You may also want to check the 2 helper commands [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md), which can each help you figure out problems with your strategy in different ways.
Please treat them as what they are - helpers to identify most common problems. A negative result of each does not guarantee that there's none of the above errors included.
### Colliding signals
When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are 1), freqtrade will do nothing and ignore the entry signal. This will avoid trades that enter, and exit immediately. Obviously, this can potentially lead to missed entries.

View File

@ -1,5 +1,5 @@
""" Freqtrade bot """
__version__ = '2023.9'
__version__ = '2023.10'
if 'dev' in __version__:
from pathlib import Path

View File

@ -65,8 +65,8 @@ ARGS_BUILD_CONFIG = ["config"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_CONVERT_DATA_TRADES = ["pairs", "format_from_trades", "format_to", "erase", "exchange"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "trading_mode", "candle_types"]
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"]
@ -268,7 +268,7 @@ class Arguments:
parents=[_common_parser],
)
convert_trade_data_cmd.set_defaults(func=partial(start_convert_data, ohlcv=False))
self._build_args(optionlist=ARGS_CONVERT_DATA, parser=convert_trade_data_cmd)
self._build_args(optionlist=ARGS_CONVERT_DATA_TRADES, parser=convert_trade_data_cmd)
# Add trades-to-ohlcv subcommand
convert_trade_data_cmd = subparsers.add_parser(

View File

@ -421,6 +421,12 @@ AVAILABLE_CLI_OPTIONS = {
'desired timeframe as specified as --timeframes/-t.',
action='store_true',
),
"format_from_trades": Arg(
'--format-from',
help='Source format for data conversion.',
choices=constants.AVAILABLE_DATAHANDLERS + ['kraken_csv'],
required=True,
),
"format_from": Arg(
'--format-from',
help='Source format for data conversion.',

View File

@ -85,7 +85,7 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
erase=args['erase'])
else:
convert_trades_format(config,
convert_from=args['format_from'], convert_to=args['format_to'],
convert_from=args['format_from_trades'], convert_to=args['format_to'],
erase=args['erase'])

View File

@ -140,7 +140,7 @@ def start_lookahead_analysis(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments()
:return: None
"""
from freqtrade.optimize.lookahead_analysis_helpers import LookaheadAnalysisSubFunctions
from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFunctions
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
LookaheadAnalysisSubFunctions.start(config)
@ -152,7 +152,7 @@ def start_recursive_analysis(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments()
:return: None
"""
from freqtrade.optimize.recursive_analysis_helpers import RecursiveAnalysisSubFunctions
from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
RecursiveAnalysisSubFunctions.start(config)

View File

@ -12,6 +12,7 @@ from freqtrade.configuration import TimeRange
from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TRADES_DTYPES,
Config, TradeList)
from freqtrade.enums import CandleType
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
@ -127,6 +128,16 @@ def convert_trades_format(config: Config, convert_from: str, convert_to: str, er
:param convert_to: Target format
:param erase: Erase source data (does not apply if source and target format are identical)
"""
if convert_from == 'kraken_csv':
if config['exchange']['name'] != 'kraken':
raise OperationalException(
'Converting from csv is only supported for kraken.'
'Please refer to the documentation for details about this special mode.'
)
from freqtrade.data.converter.trade_converter_kraken import import_kraken_trades_from_csv
import_kraken_trades_from_csv(config, convert_to)
return
from freqtrade.data.history.idatahandler import get_datahandler
src = get_datahandler(config['datadir'], convert_from)
trg = get_datahandler(config['datadir'], convert_to)

View File

@ -0,0 +1,70 @@
import logging
from pathlib import Path
import pandas as pd
from freqtrade.constants import DATETIME_PRINT_FORMAT, DEFAULT_TRADES_COLUMNS, Config
from freqtrade.data.converter.trade_converter import (trades_convert_types,
trades_df_remove_duplicates)
from freqtrade.data.history.idatahandler import get_datahandler
from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import ExchangeResolver
logger = logging.getLogger(__name__)
KRAKEN_CSV_TRADE_COLUMNS = ['timestamp', 'price', 'amount']
def import_kraken_trades_from_csv(config: Config, convert_to: str):
"""
Import kraken trades from csv
"""
if config['exchange']['name'] != 'kraken':
raise OperationalException('This function is only for the kraken exchange.')
datadir: Path = config['datadir']
data_handler = get_datahandler(datadir, data_format=convert_to)
tradesdir: Path = config['datadir'] / 'trades_csv'
exchange = ExchangeResolver.load_exchange(config, validate=False)
# iterate through directories in this directory
data_symbols = {p.stem for p in tradesdir.rglob('*.csv')}
# create pair/filename mapping
markets = {
(m['symbol'], m['altname']) for m in exchange.markets.values()
if m.get('altname') in data_symbols
}
logger.info(f"Found csv files for {', '.join(data_symbols)}.")
for pair, name in markets:
dfs = []
# Load and combine all csv files for this pair
for f in tradesdir.rglob(f"{name}.csv"):
df = pd.read_csv(f, names=KRAKEN_CSV_TRADE_COLUMNS)
dfs.append(df)
# Load existing trades data
if not dfs:
# edgecase, can only happen if the file was deleted between the above glob and here
logger.info(f"No data found for pair {pair}")
continue
trades = pd.concat(dfs, ignore_index=True)
trades.loc[:, 'timestamp'] = trades['timestamp'] * 1e3
trades.loc[:, 'cost'] = trades['price'] * trades['amount']
for col in DEFAULT_TRADES_COLUMNS:
if col not in trades.columns:
trades[col] = ''
trades = trades[DEFAULT_TRADES_COLUMNS]
trades = trades_convert_types(trades)
trades_df = trades_df_remove_duplicates(trades)
logger.info(f"{pair}: {len(trades_df)} trades, from "
f"{trades_df['date'].min():{DATETIME_PRINT_FORMAT}} to "
f"{trades_df['date'].max():{DATETIME_PRINT_FORMAT}}")
data_handler.trades_store(pair, trades_df)

View File

@ -123,10 +123,14 @@ class Binance(Exchange):
def funding_fee_cutoff(self, open_date: datetime):
"""
Funding fees are only charged at full hours (usually every 4-8h).
Therefore a trade opening at 10:00:01 will not be charged a funding fee until the next hour.
On binance, this cutoff is 15s.
https://github.com/freqtrade/freqtrade/pull/5779#discussion_r740175931
:param open_date: The open date for a trade
:return: The cutoff open time for when a funding fee is charged
:return: True if the date falls on a full hour, False otherwise
"""
return open_date.minute > 0 or (open_date.minute == 0 and open_date.second > 15)
return open_date.minute == 0 and open_date.second < 15
def dry_run_liquidation_price(
self,

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ import ccxt
from freqtrade.constants import BuySell
from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exceptions import DDosProtection, ExchangeError, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
from freqtrade.util.datetime_helpers import dt_now, dt_ts
@ -202,8 +202,11 @@ class Bybit(Exchange):
"""
# Bybit does not provide "applied" funding fees per position.
if self.trading_mode == TradingMode.FUTURES:
return self._fetch_and_calculate_funding_fees(
pair, amount, is_short, open_date)
try:
return self._fetch_and_calculate_funding_fees(
pair, amount, is_short, open_date)
except ExchangeError:
logger.warning(f"Could not update funding fees for {pair}.")
return 0.0
def fetch_orders(self, pair: str, since: datetime, params: Optional[Dict] = None) -> List[Dict]:

View File

@ -2279,6 +2279,7 @@ class Exchange:
from_id = t[-1][1]
else:
logger.debug("Stopping as no more trades were returned.")
break
except asyncio.CancelledError:
logger.debug("Async operation Interrupted, breaking trades DL loop.")
@ -2304,6 +2305,11 @@ class Exchange:
try:
t = await self._async_fetch_trades(pair, since=since)
if t:
# No more trades to download available at the exchange,
# So we repeatedly get the same trade over and over again.
if since == t[-1][0] and len(t) == 1:
logger.debug("Stopping because no more trades are available.")
break
since = t[-1][0]
trades.extend(t)
# Reached the end of the defined-download period
@ -2312,6 +2318,7 @@ class Exchange:
f"Stopping because until was reached. {t[-1][0]} > {until}")
break
else:
logger.debug("Stopping as no more trades were returned.")
break
except asyncio.CancelledError:
logger.debug("Async operation Interrupted, breaking trades DL loop.")
@ -2653,12 +2660,14 @@ class Exchange:
"""
return 0.0
def funding_fee_cutoff(self, open_date: datetime):
def funding_fee_cutoff(self, open_date: datetime) -> bool:
"""
Funding fees are only charged at full hours (usually every 4-8h).
Therefore a trade opening at 10:00:01 will not be charged a funding fee until the next hour.
:param open_date: The open date for a trade
:return: The cutoff open time for when a funding fee is charged
:return: True if the date falls on a full hour, False otherwise
"""
return open_date.minute > 0 or open_date.second > 0
return open_date.minute == 0 and open_date.second == 0
@retrier
def set_margin_mode(self, pair: str, margin_mode: MarginMode, accept_fail: bool = False,
@ -2706,15 +2715,16 @@ class Exchange:
"""
if self.funding_fee_cutoff(open_date):
open_date += timedelta(hours=1)
# Shift back to 1h candle to avoid missing funding fees
# Only really relevant for trades very close to the full hour
open_date = timeframe_to_prev_date('1h', open_date)
timeframe = self._ft_has['mark_ohlcv_timeframe']
timeframe_ff = self._ft_has.get('funding_fee_timeframe',
self._ft_has['mark_ohlcv_timeframe'])
if not close_date:
close_date = datetime.now(timezone.utc)
open_timestamp = int(timeframe_to_prev_date(timeframe, open_date).timestamp()) * 1000
# close_timestamp = int(close_date.timestamp()) * 1000
since_ms = int(timeframe_to_prev_date(timeframe, open_date).timestamp()) * 1000
mark_comb: PairWithTimeframe = (
pair, timeframe, CandleType.from_string(self._ft_has["mark_ohlcv_price"]))
@ -2722,7 +2732,7 @@ class Exchange:
funding_comb: PairWithTimeframe = (pair, timeframe_ff, CandleType.FUNDING_RATE)
candle_histories = self.refresh_latest_ohlcv(
[mark_comb, funding_comb],
since_ms=open_timestamp,
since_ms=since_ms,
cache=False,
drop_incomplete=False,
)
@ -2733,8 +2743,7 @@ class Exchange:
except KeyError:
raise ExchangeError("Could not find funding rates.") from None
funding_mark_rates = self.combine_funding_and_mark(
funding_rates=funding_rates, mark_rates=mark_rates)
funding_mark_rates = self.combine_funding_and_mark(funding_rates, mark_rates)
return self.calculate_funding_fees(
funding_mark_rates,
@ -2781,7 +2790,7 @@ class Exchange:
amount: float,
is_short: bool,
open_date: datetime,
close_date: Optional[datetime] = None,
close_date: datetime,
time_in_ratio: Optional[float] = None
) -> float:
"""
@ -2797,8 +2806,8 @@ class Exchange:
fees: float = 0
if not df.empty:
df = df[(df['date'] >= open_date) & (df['date'] <= close_date)]
fees = sum(df['open_fund'] * df['open_mark'] * amount)
df1 = df[(df['date'] >= open_date) & (df['date'] <= close_date)]
fees = sum(df1['open_fund'] * df1['open_mark'] * amount)
# Negate fees for longs as funding_fees expects it this way based on live endpoints.
return fees if is_short else -fees
@ -2813,17 +2822,19 @@ class Exchange:
:param amount: Trade amount
:param open_date: Open date of the trade
:return: funding fee since open_date
:raises: ExchangeError if something goes wrong.
"""
if self.trading_mode == TradingMode.FUTURES:
if self._config['dry_run']:
funding_fees = self._fetch_and_calculate_funding_fees(
pair, amount, is_short, open_date)
else:
funding_fees = self._get_funding_fees_from_exchange(pair, open_date)
return funding_fees
else:
return 0.0
try:
if self._config['dry_run']:
funding_fees = self._fetch_and_calculate_funding_fees(
pair, amount, is_short, open_date)
else:
funding_fees = self._get_funding_fees_from_exchange(pair, open_date)
return funding_fees
except ExchangeError:
logger.warning(f"Could not update funding fees for {pair}.")
return 0.0
def get_liquidation_price(
self,

View File

@ -195,7 +195,7 @@ class Kraken(Exchange):
amount: float,
is_short: bool,
open_date: datetime,
close_date: Optional[datetime] = None,
close_date: datetime,
time_in_ratio: Optional[float] = None
) -> float:
"""

View File

@ -159,7 +159,7 @@ class BaseEnvironment(gym.Env):
function is designed for tracking incremented objects,
events, actions inside the training environment.
For example, a user can call this to track the
frequency of occurence of an `is_valid` call in
frequency of occurrence of an `is_valid` call in
their `calculate_reward()`:
def calculate_reward(self, action: int) -> float:

View File

@ -296,8 +296,7 @@ class FreqaiDataDrawer:
f"for more than {len(dataframe.index)} candles.")
df_concat = pd.concat([hist_preds, new_pred], ignore_index=True, keys=hist_preds.keys())
# remove last row because we will append that later in append_model_predictions()
df_concat = df_concat.iloc[:-1]
# any missing values will get zeroed out so users can see the exact
# downtime in FreqUI
df_concat = df_concat.fillna(0)

View File

@ -1,8 +1,9 @@
import logging
from pathlib import Path
from typing import Any, Dict, Type
from typing import Any, Dict, List, Optional, Type
import torch as th
from stable_baselines3.common.callbacks import ProgressBarCallback
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
from freqtrade.freqai.RL.Base5ActionRLEnv import Actions, Base5ActionRLEnv, Positions
@ -73,19 +74,27 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
'trained agent.')
model = self.dd.model_dictionary[dk.pair]
model.set_env(self.train_env)
callbacks: List[Any] = [self.eval_callback, self.tensorboard_callback]
progressbar_callback: Optional[ProgressBarCallback] = None
if self.rl_config.get('progress_bar', False):
progressbar_callback = ProgressBarCallback()
callbacks.insert(0, progressbar_callback)
model.learn(
total_timesteps=int(total_timesteps),
callback=[self.eval_callback, self.tensorboard_callback],
progress_bar=self.rl_config.get('progress_bar', False)
)
try:
model.learn(
total_timesteps=int(total_timesteps),
callback=callbacks,
)
finally:
if progressbar_callback:
progressbar_callback.on_training_end()
if Path(dk.data_path / "best_model.zip").is_file():
logger.info('Callback found a best model.')
best_model = self.MODELCLASS.load(dk.data_path / "best_model")
return best_model
logger.info('Couldnt find best model, using final model instead.')
logger.info("Couldn't find best model, using final model instead.")
return model

View File

@ -49,7 +49,13 @@ class TensorboardCallback(BaseCallback):
local_info = self.locals["infos"][0]
if self.training_env is None:
return True
tensorboard_metrics = self.training_env.get_attr("tensorboard_metrics")[0]
if hasattr(self.training_env, 'envs'):
tensorboard_metrics = self.training_env.envs[0].unwrapped.tensorboard_metrics
else:
# For RL-multiproc - usage of [0] might need to be evaluated
tensorboard_metrics = self.training_env.get_attr("tensorboard_metrics")[0]
for metric in local_info:
if metric not in ["episode", "terminal_observation"]:

View File

@ -132,7 +132,7 @@ class FreqtradeBot(LoggingMixin):
# TODO: This would be more efficient if scheduled in utc time, and performed at each
# TODO: funding interval, specified by funding_fee_times on the exchange classes
for time_slot in range(0, 24):
for minutes in [0, 15, 30, 45]:
for minutes in [1, 31]:
t = str(time(time_slot, minutes, 2))
self._schedule.every().day.at(t).do(update)
self.last_process: Optional[datetime] = None
@ -199,6 +199,7 @@ class FreqtradeBot(LoggingMixin):
# Only update open orders on startup
# This will update the database after the initial migration
self.startup_update_open_orders()
self.update_funding_fees()
def process(self) -> None:
"""
@ -312,22 +313,19 @@ class FreqtradeBot(LoggingMixin):
open_trades = Trade.get_open_trade_count()
return max(0, self.config['max_open_trades'] - open_trades)
def update_funding_fees(self):
def update_funding_fees(self) -> None:
if self.trading_mode == TradingMode.FUTURES:
trades = Trade.get_open_trades()
try:
for trade in trades:
funding_fees = self.exchange.get_funding_fees(
trades: List[Trade] = Trade.get_open_trades()
for trade in trades:
trade.set_funding_fees(
self.exchange.get_funding_fees(
pair=trade.pair,
amount=trade.amount,
is_short=trade.is_short,
open_date=trade.date_last_filled_utc
)
trade.funding_fees = funding_fees
except ExchangeError:
logger.warning("Could not update funding fees for open trades.")
open_date=trade.date_last_filled_utc)
)
def startup_backpopulate_precision(self):
def startup_backpopulate_precision(self) -> None:
trades = Trade.get_trades([Trade.contract_size.is_(None)])
for trade in trades:
@ -374,17 +372,13 @@ class FreqtradeBot(LoggingMixin):
fo = order.to_ccxt_object()
fo['status'] = 'canceled'
self.handle_cancel_order(
fo, order.order_id, order.trade,
constants.CANCEL_REASON['TIMEOUT']
fo, order, order.trade, constants.CANCEL_REASON['TIMEOUT']
)
except ExchangeError as e:
logger.warning(f"Error updating Order {order.order_id} due to {e}")
if self.trading_mode == TradingMode.FUTURES:
self._schedule.run_pending()
def update_trades_without_assigned_fees(self) -> None:
"""
Update closed trades without close fees assigned.
@ -749,6 +743,7 @@ class FreqtradeBot(LoggingMixin):
:param pair: pair for which we want to create a LIMIT_BUY
:param stake_amount: amount of stake-currency for the pair
:return: True if a buy order is created, false if it fails.
:raise: DependencyException or it's subclasses like ExchangeError.
"""
time_in_force = self.strategy.order_time_in_force['entry']
@ -835,14 +830,15 @@ class FreqtradeBot(LoggingMixin):
base_currency = self.exchange.get_pair_base_currency(pair)
open_date = datetime.now(timezone.utc)
funding_fees = self.exchange.get_funding_fees(
pair=pair,
amount=amount + trade.amount if trade else amount,
is_short=is_short,
open_date=trade.date_last_filled_utc if trade else open_date
)
# This is a new trade
if trade is None:
funding_fees = 0.0
try:
funding_fees = self.exchange.get_funding_fees(
pair=pair, amount=amount, is_short=is_short, open_date=open_date)
except ExchangeError:
logger.warning("Could not find funding fee.")
trade = Trade(
pair=pair,
@ -878,6 +874,7 @@ class FreqtradeBot(LoggingMixin):
trade.is_open = True
trade.fee_open_currency = None
trade.open_rate_requested = enter_limit_requested
trade.set_funding_fees(funding_fees)
trade.orders.append(order_obj)
trade.recalc_trade_from_orders()
@ -1087,7 +1084,11 @@ class FreqtradeBot(LoggingMixin):
trades_closed = 0
for trade in trades:
if not trade.has_open_orders and not self.wallets.check_exit_amount(trade):
if (
not trade.has_open_orders
and not trade.stoploss_order_id
and not self.wallets.check_exit_amount(trade)
):
logger.warning(
f'Not enough {trade.safe_base_currency} in wallet to exit {trade}. '
'Trying to recover.')
@ -1331,6 +1332,7 @@ class FreqtradeBot(LoggingMixin):
:return: None
"""
for trade in Trade.get_open_trades():
open_order: Order
for open_order in trade.open_orders:
try:
order = self.exchange.fetch_order(open_order.order_id, trade.pair)
@ -1351,22 +1353,23 @@ class FreqtradeBot(LoggingMixin):
)
):
self.handle_cancel_order(
order, open_order.order_id, trade, constants.CANCEL_REASON['TIMEOUT']
order, open_order, trade, constants.CANCEL_REASON['TIMEOUT']
)
else:
self.replace_order(order, open_order, trade)
def handle_cancel_order(self, order: Dict, order_id: str, trade: Trade, reason: str) -> None:
def handle_cancel_order(self, order: Dict, order_obj: Order, trade: Trade, reason: str) -> None:
"""
Check if current analyzed order timed out and cancel if necessary.
:param order: Order dict grabbed with exchange.fetch_order()
:param order_obj: Order object from the database.
:param trade: Trade object.
:return: None
"""
if order['side'] == trade.entry_side:
self.handle_cancel_enter(trade, order, order_id, reason)
self.handle_cancel_enter(trade, order, order_obj, reason)
else:
canceled = self.handle_cancel_exit(trade, order, order_id, reason)
canceled = self.handle_cancel_exit(trade, order, order_obj, reason)
canceled_count = trade.get_canceled_exit_order_count()
max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
if (canceled and max_timeouts > 0 and canceled_count >= max_timeouts):
@ -1431,7 +1434,7 @@ class FreqtradeBot(LoggingMixin):
trade=trade, order=order_obj, pair=trade.pair,
current_time=datetime.now(timezone.utc), proposed_rate=proposed_rate,
current_order_rate=order_obj.safe_price, entry_tag=trade.enter_tag,
side=trade.entry_side)
side=trade.trade_direction)
replacing = True
cancel_reason = constants.CANCEL_REASON['REPLACE']
@ -1440,7 +1443,7 @@ class FreqtradeBot(LoggingMixin):
cancel_reason = constants.CANCEL_REASON['USER_CANCEL']
if order_obj.price != adjusted_entry_price:
# cancel existing order if new price is supplied or None
res = self.handle_cancel_enter(trade, order, order_obj.order_id, cancel_reason,
res = self.handle_cancel_enter(trade, order, order_obj, cancel_reason,
replacing=replacing)
if not res:
self.replace_order_failed(
@ -1448,15 +1451,21 @@ class FreqtradeBot(LoggingMixin):
return
if adjusted_entry_price:
# place new order only if new price is supplied
if not self.execute_entry(
pair=trade.pair,
stake_amount=(
order_obj.safe_remaining * order_obj.safe_price / trade.leverage),
price=adjusted_entry_price,
trade=trade,
is_short=trade.is_short,
mode='replace',
):
try:
if not self.execute_entry(
pair=trade.pair,
stake_amount=(
order_obj.safe_remaining * order_obj.safe_price / trade.leverage),
price=adjusted_entry_price,
trade=trade,
is_short=trade.is_short,
mode='replace',
):
self.replace_order_failed(
trade, f"Could not replace order for {trade}.")
except DependencyException as exception:
logger.warning(
f'Unable to replace order for {trade.pair}: {exception}')
self.replace_order_failed(trade, f"Could not replace order for {trade}.")
def cancel_all_open_orders(self) -> None:
@ -1475,29 +1484,28 @@ class FreqtradeBot(LoggingMixin):
if order['side'] == trade.entry_side:
self.handle_cancel_enter(
trade, order, open_order.order_id, constants.CANCEL_REASON['ALL_CANCELLED']
trade, order, open_order, constants.CANCEL_REASON['ALL_CANCELLED']
)
elif order['side'] == trade.exit_side:
self.handle_cancel_exit(
trade, order, open_order.order_id, constants.CANCEL_REASON['ALL_CANCELLED']
trade, order, open_order, constants.CANCEL_REASON['ALL_CANCELLED']
)
Trade.commit()
def handle_cancel_enter(
self, trade: Trade, order: Dict, order_id: str,
self, trade: Trade, order: Dict, order_obj: Order,
reason: str, replacing: Optional[bool] = False
) -> bool:
"""
entry cancel - cancel order
:param order_obj: Order object from the database.
:param replacing: Replacing order - prevent trade deletion.
:return: True if trade was fully cancelled
"""
was_trade_fully_canceled = False
order_id = order_obj.order_id
side = trade.entry_side.capitalize()
if not trade.has_open_orders:
logger.warning(f"No open order for {trade}.")
return False
if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
filled_val: float = order.get('filled', 0.0) or 0.0
@ -1510,8 +1518,8 @@ class FreqtradeBot(LoggingMixin):
f"Order {order_id} for {trade.pair} not cancelled, "
f"as the filled amount of {filled_val} would result in an unexitable trade.")
return False
corder = self.exchange.cancel_order_with_result(order_id, trade.pair,
trade.amount)
corder = self.exchange.cancel_order_with_result(order_id, trade.pair, trade.amount)
order_obj.ft_cancel_reason = reason
# if replacing, retry fetching the order 3 times if the status is not what we need
if replacing:
retry_count = 0
@ -1532,9 +1540,10 @@ class FreqtradeBot(LoggingMixin):
else:
# Order was cancelled already, so we can reuse the existing dict
corder = order
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
if order_obj.ft_cancel_reason is None:
order_obj.ft_cancel_reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
logger.info(f'{side} order {reason} for {trade}.')
logger.info(f'{side} order {order_obj.ft_cancel_reason} for {trade}.')
# Using filled to determine the filled amount
filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled')
@ -1547,7 +1556,7 @@ class FreqtradeBot(LoggingMixin):
if open_order_count < 1 and trade.nr_of_successful_entries == 0 and not replacing:
logger.info(f'{side} order fully cancelled. Removing {trade} from database.')
trade.delete()
reason += f", {constants.CANCEL_REASON['FULLY_CANCELLED']}"
order_obj.ft_cancel_reason += f", {constants.CANCEL_REASON['FULLY_CANCELLED']}"
else:
self.update_trade_state(trade, order_id, corder)
logger.info(f'{side} Order timeout for {trade}.')
@ -1557,21 +1566,21 @@ class FreqtradeBot(LoggingMixin):
self.update_trade_state(trade, order_id, corder)
logger.info(f'Partial {trade.entry_side} order timeout for {trade}.')
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
order_obj.ft_cancel_reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
self.wallets.update()
self._notify_enter_cancel(trade, order_type=self.strategy.order_types['entry'],
reason=reason)
reason=order_obj.ft_cancel_reason)
return was_trade_fully_canceled
def handle_cancel_exit(
self, trade: Trade, order: Dict, order_id: str,
reason: str
self, trade: Trade, order: Dict, order_obj: Order, reason: str
) -> bool:
"""
exit order cancel - cancel order and update trade
:return: True if exit order was cancelled, false otherwise
"""
order_id = order_obj.order_id
cancelled = False
# Cancelled orders may have the status of 'canceled' or 'closed'
if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
@ -1596,7 +1605,7 @@ class FreqtradeBot(LoggingMixin):
sub_trade=trade.amount != order['amount']
)
return False
order_obj.ft_cancel_reason = reason
try:
order = self.exchange.cancel_order_with_result(
order['id'], trade.pair, trade.amount)
@ -1615,19 +1624,22 @@ class FreqtradeBot(LoggingMixin):
trade.exit_reason = exit_reason_prev
cancelled = True
else:
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
if order_obj.ft_cancel_reason is None:
order_obj.ft_cancel_reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
trade.exit_reason = None
self.update_trade_state(trade, order['id'], order)
logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.')
logger.info(
f'{trade.exit_side.capitalize()} order {order_obj.ft_cancel_reason} for {trade}.')
trade.close_rate = None
trade.close_rate_requested = None
self._notify_exit_cancel(
trade,
order_type=self.strategy.order_types['exit'],
reason=reason, order_id=order['id'], sub_trade=trade.amount != order['amount']
reason=order_obj.ft_cancel_reason, order_id=order['id'],
sub_trade=trade.amount != order['amount']
)
return cancelled
@ -1679,15 +1691,13 @@ class FreqtradeBot(LoggingMixin):
:param exit_check: CheckTuple with signal and reason
:return: True if it succeeds False
"""
try:
trade.funding_fees = self.exchange.get_funding_fees(
trade.set_funding_fees(
self.exchange.get_funding_fees(
pair=trade.pair,
amount=trade.amount,
is_short=trade.is_short,
open_date=trade.date_last_filled_utc,
)
except ExchangeError:
logger.warning("Could not update funding fee.")
open_date=trade.date_last_filled_utc)
)
exit_type = 'exit'
exit_reason = exit_tag or exit_check.exit_reason
@ -1910,7 +1920,7 @@ class FreqtradeBot(LoggingMixin):
if self.exchange.check_order_canceled_empty(order):
# Trade has been cancelled on exchange
# Handling of this will happen in check_handle_timedout.
# Handling of this will happen in handle_cancel_order.
return True
order_obj_or_none = trade.select_order_by_order_id(order_id)

View File

@ -8,15 +8,13 @@ logger = logging.getLogger(__name__)
def set_loggers(verbosity: int = 0, api_verbosity: str = 'info') -> None:
"""
Set the logging level for third party libraries
:param verbosity: Verbosity level. amount of `-v` passed to the command line
:return: None
"""
logging.getLogger('requests').setLevel(
logging.INFO if verbosity <= 1 else logging.DEBUG
)
logging.getLogger("urllib3").setLevel(
logging.INFO if verbosity <= 1 else logging.DEBUG
)
for logger_name in ('requests', 'urllib3', 'httpcore'):
logging.getLogger(logger_name).setLevel(
logging.INFO if verbosity <= 1 else logging.DEBUG
)
logging.getLogger('ccxt.base.exchange').setLevel(
logging.INFO if verbosity <= 2 else logging.DEBUG
)

View File

View File

@ -7,7 +7,7 @@ import pandas as pd
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.lookahead_analysis import LookaheadAnalysis
from freqtrade.optimize.analysis.lookahead import LookaheadAnalysis
from freqtrade.resolvers import StrategyResolver

View File

@ -64,7 +64,7 @@ class RecursiveAnalysis(BaseAnalysis):
self.dict_recursive[indicator][part.startup_candle] = f"{diff:.3f}%"
else:
logger.info("No difference found. Stop the process.")
logger.info("No variance on indicator(s) found due to recursive formula.")
break
# For lookahead bias check
@ -100,7 +100,7 @@ class RecursiveAnalysis(BaseAnalysis):
# logger.info("part value {:.5f}".format(values_diff_other))
else:
logger.info("No lookahead bias on indicators found. Stop the process.")
logger.info("No lookahead bias on indicators found.")
def prepare_data(self, varholder: VarHolder, pairs_to_load: List[DataFrame]):
@ -120,6 +120,7 @@ class RecursiveAnalysis(BaseAnalysis):
prepare_data_config['exchange']['pair_whitelist'] = pairs_to_load
backtesting = Backtesting(prepare_data_config, self.exchange)
self.exchange = backtesting.exchange
backtesting._set_strategy(backtesting.strategylist[0])
varholder.data, varholder.timerange = backtesting.load_bt_data()

View File

@ -5,7 +5,7 @@ from typing import Any, Dict, List
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.recursive_analysis import RecursiveAnalysis
from freqtrade.optimize.analysis.recursive import RecursiveAnalysis
from freqtrade.resolvers import StrategyResolver
@ -31,10 +31,13 @@ class RecursiveAnalysisSubFunctions:
temp_data.append(values.get(int(candle), '-'))
data.append(temp_data)
from tabulate import tabulate
table = tabulate(data, headers=headers, tablefmt="orgtbl")
print(table)
return table, headers, data
if len(data) > 0:
from tabulate import tabulate
table = tabulate(data, headers=headers, tablefmt="orgtbl")
print(table)
return table, headers, data
return None, None, data
@staticmethod
def calculate_config_overrides(config: Config):
@ -81,8 +84,7 @@ class RecursiveAnalysisSubFunctions:
if not (strategy_list := config.get('strategy_list', [])):
if config.get('strategy') is None:
raise OperationalException(
"No Strategy specified. Please specify a strategy via --strategy or "
"--strategy-list"
"No Strategy specified. Please specify a strategy via --strategy"
)
strategy_list = [config['strategy']]
@ -100,7 +102,5 @@ class RecursiveAnalysisSubFunctions:
RecursiveAnalysisSubFunctions.text_table_recursive_analysis_instances(
RecursiveAnalysis_instances)
else:
logger.error("There were no strategies specified neither through "
"--strategy nor through "
"--strategy-list "
logger.error("There was no strategy specified through --strategy "
"or timeframe was not specified.")

View File

@ -525,10 +525,10 @@ class Backtesting:
# This should not be reached...
return row[OPEN_IDX]
def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple
) -> LocalTrade:
def _get_adjust_trade_entry_for_candle(
self, trade: LocalTrade, row: Tuple, current_time: datetime
) -> LocalTrade:
current_rate = row[OPEN_IDX]
current_date = row[DATE_IDX].to_pydatetime()
current_profit = trade.calc_profit_ratio(current_rate)
min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, current_rate, -0.1)
max_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_rate)
@ -536,7 +536,7 @@ class Backtesting:
stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position,
default_retval=None, supress_error=True)(
trade=trade, # type: ignore[arg-type]
current_time=current_date, current_rate=current_rate,
current_time=current_time, current_rate=current_rate,
current_profit=current_profit, min_stake=min_stake,
max_stake=min(max_stake, stake_available),
current_entry_rate=current_rate, current_exit_rate=current_rate,
@ -569,10 +569,10 @@ class Backtesting:
# Remaining stake is too low to be sold.
return trade
exit_ = ExitCheckTuple(ExitType.PARTIAL_EXIT)
pos_trade = self._get_exit_for_signal(trade, row, exit_, amount)
pos_trade = self._get_exit_for_signal(trade, row, exit_, current_time, amount)
if pos_trade is not None:
order = pos_trade.orders[-1]
if self._try_close_open_order(order, trade, current_date, row):
if self._try_close_open_order(order, trade, current_time, row):
trade.recalc_trade_from_orders()
self.wallets.update()
return pos_trade
@ -615,11 +615,11 @@ class Backtesting:
def _get_exit_for_signal(
self, trade: LocalTrade, row: Tuple, exit_: ExitCheckTuple,
current_time: datetime,
amount: Optional[float] = None) -> Optional[LocalTrade]:
exit_candle_time: datetime = row[DATE_IDX].to_pydatetime()
if exit_.exit_flag:
trade.close_date = exit_candle_time
trade.close_date = current_time
exit_reason = exit_.exit_reason
amount_ = amount if amount is not None else trade.amount
trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
@ -647,10 +647,10 @@ class Backtesting:
default_retval=close_rate)(
pair=trade.pair,
trade=trade, # type: ignore[arg-type]
current_time=exit_candle_time,
current_time=current_time,
proposed_rate=close_rate, current_profit=current_profit,
exit_tag=exit_reason)
if rate != close_rate:
if rate is not None and rate != close_rate:
close_rate = price_to_precision(rate, trade.price_precision,
self.precision_mode)
# We can't place orders lower than current low.
@ -673,7 +673,7 @@ class Backtesting:
time_in_force=time_in_force,
sell_reason=exit_reason, # deprecated
exit_reason=exit_reason,
current_time=exit_candle_time)):
current_time=current_time)):
return None
trade.exit_reason = exit_reason
@ -714,21 +714,24 @@ class Backtesting:
trade.orders.append(order)
return trade
def _check_trade_exit(self, trade: LocalTrade, row: Tuple) -> Optional[LocalTrade]:
exit_candle_time: datetime = row[DATE_IDX].to_pydatetime()
def _check_trade_exit(
self, trade: LocalTrade, row: Tuple, current_time: datetime
) -> Optional[LocalTrade]:
if self.trading_mode == TradingMode.FUTURES:
trade.funding_fees = self.exchange.calculate_funding_fees(
self.futures_data[trade.pair],
amount=trade.amount,
is_short=trade.is_short,
open_date=trade.date_last_filled_utc,
close_date=exit_candle_time,
trade.set_funding_fees(
self.exchange.calculate_funding_fees(
self.futures_data[trade.pair],
amount=trade.amount,
is_short=trade.is_short,
open_date=trade.date_last_filled_utc,
close_date=current_time
)
)
# Check if we need to adjust our current positions
if self.strategy.position_adjustment_enable:
trade = self._get_adjust_trade_entry_for_candle(trade, row)
trade = self._get_adjust_trade_entry_for_candle(trade, row, current_time)
enter = row[SHORT_IDX] if trade.is_short else row[LONG_IDX]
exit_sig = row[ESHORT_IDX] if trade.is_short else row[ELONG_IDX]
@ -738,7 +741,7 @@ class Backtesting:
low=row[LOW_IDX], high=row[HIGH_IDX]
)
for exit_ in exits:
t = self._get_exit_for_signal(trade, row, exit_)
t = self._get_exit_for_signal(trade, row, exit_, current_time)
if t:
return t
return None
@ -760,7 +763,7 @@ class Backtesting:
) # default value is the open rate
# We can't place orders higher than current high (otherwise it'd be a stop limit entry)
# which freqtrade does not support in live.
if new_rate != propose_rate:
if new_rate is not None and new_rate != propose_rate:
propose_rate = price_to_precision(new_rate, price_precision,
self.precision_mode)
if direction == "short":
@ -1145,7 +1148,7 @@ class Backtesting:
# 4. Create exit orders (if any)
if not trade.has_open_orders:
self._check_trade_exit(trade, row) # Place exit order if necessary
self._check_trade_exit(trade, row, current_time) # Place exit order if necessary
# 5. Process exit orders.
order = trade.select_order(trade.exit_side, is_open=True)

View File

@ -115,6 +115,7 @@ def migrate_trades_and_orders_table(
# Futures Properties
interest_rate = get_column_def(cols, 'interest_rate', '0.0')
funding_fees = get_column_def(cols, 'funding_fees', '0.0')
funding_fee_running = get_column_def(cols, 'funding_fee_running', 'null')
max_stake_amount = get_column_def(cols, 'max_stake_amount', 'stake_amount')
# If ticker-interval existed use that, else null.
@ -163,7 +164,7 @@ def migrate_trades_and_orders_table(
max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag,
timeframe, open_trade_value, close_profit_abs,
trading_mode, leverage, liquidation_price, is_short,
interest_rate, funding_fees, realized_profit,
interest_rate, funding_fees, funding_fee_running, realized_profit,
amount_precision, price_precision, precision_mode, contract_size,
max_stake_amount
)
@ -192,7 +193,8 @@ def migrate_trades_and_orders_table(
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
{trading_mode} trading_mode, {leverage} leverage, {liquidation_price} liquidation_price,
{is_short} is_short, {interest_rate} interest_rate,
{funding_fees} funding_fees, {realized_profit} realized_profit,
{funding_fees} funding_fees, {funding_fee_running} funding_fee_running,
{realized_profit} realized_profit,
{amount_precision} amount_precision, {price_precision} price_precision,
{precision_mode} precision_mode, {contract_size} contract_size,
{max_stake_amount} max_stake_amount
@ -220,6 +222,7 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List):
funding_fee = get_column_def(cols_order, 'funding_fee', '0.0')
ft_amount = get_column_def(cols_order, 'ft_amount', 'coalesce(amount, 0.0)')
ft_price = get_column_def(cols_order, 'ft_price', 'coalesce(price, 0.0)')
ft_cancel_reason = get_column_def(cols_order, 'ft_cancel_reason', 'null')
# sqlite does not support literals for booleans
with engine.begin() as connection:
@ -227,13 +230,13 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List):
insert into orders (id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, average, remaining, cost,
stop_price, order_date, order_filled_date, order_update_date, ft_fee_base, funding_fee,
ft_amount, ft_price
ft_amount, ft_price, ft_cancel_reason
)
select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, {average} average, remaining,
cost, {stop_price} stop_price, order_date, order_filled_date,
order_update_date, {ft_fee_base} ft_fee_base, {funding_fee} funding_fee,
{ft_amount} ft_amount, {ft_price} ft_price
{ft_amount} ft_amount, {ft_price} ft_price, {ft_cancel_reason} ft_cancel_reason
from {table_back_name}
"""))
@ -328,8 +331,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
# if ('orders' not in previous_tables
# or not has_column(cols_orders, 'funding_fee')):
migrating = False
# if not has_column(cols_orders, 'ft_price'):
if not has_column(cols_trades, 'is_stop_loss_trailing'):
# if not has_column(cols_orders, 'ft_cancel_reason'):
if not has_column(cols_trades, 'funding_fee_running'):
migrating = True
logger.info(f"Running database migration for trades - "
f"backup: {table_back_name}, {order_table_bak_name}")

View File

@ -68,6 +68,7 @@ class Order(ModelBase):
ft_is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True)
ft_amount: Mapped[float] = mapped_column(Float(), nullable=False)
ft_price: Mapped[float] = mapped_column(Float(), nullable=False)
ft_cancel_reason: Mapped[str] = mapped_column(String(CUSTOM_TAG_MAX_LENGTH), nullable=True)
order_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
status: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
@ -173,10 +174,6 @@ class Order(ModelBase):
self.ft_is_open = True
if self.status in NON_OPEN_EXCHANGE_STATES:
self.ft_is_open = False
if self.trade:
# Assign funding fee up to this point
# (represents the funding fee since the last order)
self.funding_fee = self.trade.funding_fees
if (order.get('filled', 0.0) or 0.0) > 0 and not self.order_filled_date:
self.order_filled_date = dt_from_ts(
safe_value_fallback(order, 'lastTradeTimestamp', default_value=dt_ts())
@ -237,6 +234,7 @@ class Order(ModelBase):
'price': self.price,
'remaining': self.remaining,
'ft_fee_base': self.ft_fee_base,
'funding_fee': self.funding_fee,
})
return resp
@ -248,7 +246,8 @@ class Order(ModelBase):
self.ft_is_open = False
# Assign funding fees to Order.
# Assumes backtesting will use date_last_filled_utc to calculate future funding fees.
self.funding_fee = trade.funding_fees
self.funding_fee = trade.funding_fee_running
trade.funding_fee_running = 0.0
if (self.ft_order_side == trade.entry_side and self.price):
trade.open_rate = self.price
@ -395,6 +394,9 @@ class LocalTrade:
# Futures properties
funding_fees: Optional[float] = None
# Used to keep running funding fees - between the last filled order and now
# Shall not be used for calculations!
funding_fee_running: Optional[float] = None
@property
def stoploss_or_liquidation(self) -> float:
@ -534,6 +536,7 @@ class LocalTrade:
for key in kwargs:
setattr(self, key, kwargs[key])
self.recalc_open_trade_value()
self.orders = []
if self.trading_mode == TradingMode.MARGIN and self.interest_rate is None:
raise OperationalException(
f"{self.trading_mode.value} trading requires param interest_rate on trades")
@ -660,6 +663,16 @@ class LocalTrade:
return
self.liquidation_price = liquidation_price
def set_funding_fees(self, funding_fee: float) -> None:
"""
Assign funding fees to Trade.
"""
if funding_fee is None:
return
self.funding_fee_running = funding_fee
prior_funding_fees = sum([o.funding_fee for o in self.orders if o.funding_fee])
self.funding_fees = prior_funding_fees + funding_fee
def __set_stop_loss(self, stop_loss: float, percent: float):
"""
Method used internally to set self.stop_loss.
@ -740,6 +753,10 @@ class LocalTrade:
return
logger.info(f'Updating trade (id={self.id}) ...')
if order.ft_order_side != 'stoploss':
order.funding_fee = self.funding_fee_running
# Reset running funding fees
self.funding_fee_running = 0.0
if order.ft_order_side == self.entry_side:
# Update open rate and actual amount
@ -1285,6 +1302,99 @@ class LocalTrade:
trade.adjust_stop_loss(trade.open_rate, desired_stoploss)
logger.info(f"New stoploss: {trade.stop_loss}.")
@classmethod
def from_json(cls, json_str: str) -> Self:
"""
Create a Trade instance from a json string.
Used for debugging purposes - please keep.
:param json_str: json string to parse
:return: Trade instance
"""
import rapidjson
data = rapidjson.loads(json_str)
trade = cls(
__FROM_JSON=True,
id=data["trade_id"],
pair=data["pair"],
base_currency=data["base_currency"],
stake_currency=data["quote_currency"],
is_open=data["is_open"],
exchange=data["exchange"],
amount=data["amount"],
amount_requested=data["amount_requested"],
stake_amount=data["stake_amount"],
strategy=data["strategy"],
enter_tag=data["enter_tag"],
timeframe=data["timeframe"],
fee_open=data["fee_open"],
fee_open_cost=data["fee_open_cost"],
fee_open_currency=data["fee_open_currency"],
fee_close=data["fee_close"],
fee_close_cost=data["fee_close_cost"],
fee_close_currency=data["fee_close_currency"],
open_date=datetime.fromtimestamp(data["open_timestamp"] // 1000, tz=timezone.utc),
open_rate=data["open_rate"],
open_rate_requested=data["open_rate_requested"],
open_trade_value=data["open_trade_value"],
close_date=(datetime.fromtimestamp(data["close_timestamp"] // 1000, tz=timezone.utc)
if data["close_timestamp"] else None),
realized_profit=data["realized_profit"],
close_rate=data["close_rate"],
close_rate_requested=data["close_rate_requested"],
close_profit=data["close_profit"],
close_profit_abs=data["close_profit_abs"],
exit_reason=data["exit_reason"],
exit_order_status=data["exit_order_status"],
stop_loss=data["stop_loss_abs"],
stop_loss_pct=data["stop_loss_ratio"],
stoploss_order_id=data["stoploss_order_id"],
stoploss_last_update=(
datetime.fromtimestamp(data["stoploss_last_update_timestamp"] // 1000,
tz=timezone.utc)
if data["stoploss_last_update_timestamp"] else None),
initial_stop_loss=data["initial_stop_loss_abs"],
initial_stop_loss_pct=data["initial_stop_loss_ratio"],
min_rate=data["min_rate"],
max_rate=data["max_rate"],
leverage=data["leverage"],
interest_rate=data["interest_rate"],
liquidation_price=data["liquidation_price"],
is_short=data["is_short"],
trading_mode=data["trading_mode"],
funding_fees=data["funding_fees"],
amount_precision=data.get('amount_precision', None),
price_precision=data.get('price_precision', None),
precision_mode=data.get('precision_mode', None),
contract_size=data.get('contract_size', None),
)
for order in data["orders"]:
order_obj = Order(
amount=order["amount"],
ft_amount=order["amount"],
ft_order_side=order["ft_order_side"],
ft_pair=order["pair"],
ft_is_open=order["is_open"],
order_id=order["order_id"],
status=order["status"],
average=order["average"],
cost=order["cost"],
filled=order["filled"],
order_date=datetime.strptime(order["order_date"], DATETIME_PRINT_FORMAT),
order_filled_date=(datetime.fromtimestamp(
order["order_filled_timestamp"] // 1000, tz=timezone.utc)
if order["order_filled_timestamp"] else None),
order_type=order["order_type"],
price=order["price"],
ft_price=order["price"],
remaining=order["remaining"],
funding_fee=order.get("funding_fee", None),
)
trade.orders.append(order_obj)
return trade
class Trade(ModelBase, LocalTrade):
"""
@ -1388,6 +1498,8 @@ class Trade(ModelBase, LocalTrade):
# Futures properties
funding_fees: Mapped[Optional[float]] = mapped_column(
Float(), nullable=True, default=None) # type: ignore
funding_fee_running: Mapped[Optional[float]] = mapped_column(
Float(), nullable=True, default=None) # type: ignore
def __init__(self, **kwargs):
from_json = kwargs.pop('__FROM_JSON', None)
@ -1728,95 +1840,3 @@ class Trade(ModelBase, LocalTrade):
Order.status == 'closed'
)).scalar_one()
return trading_volume
@classmethod
def from_json(cls, json_str: str) -> Self:
"""
Create a Trade instance from a json string.
Used for debugging purposes - please keep.
:param json_str: json string to parse
:return: Trade instance
"""
import rapidjson
data = rapidjson.loads(json_str)
trade = cls(
__FROM_JSON=True,
id=data["trade_id"],
pair=data["pair"],
base_currency=data["base_currency"],
stake_currency=data["quote_currency"],
is_open=data["is_open"],
exchange=data["exchange"],
amount=data["amount"],
amount_requested=data["amount_requested"],
stake_amount=data["stake_amount"],
strategy=data["strategy"],
enter_tag=data["enter_tag"],
timeframe=data["timeframe"],
fee_open=data["fee_open"],
fee_open_cost=data["fee_open_cost"],
fee_open_currency=data["fee_open_currency"],
fee_close=data["fee_close"],
fee_close_cost=data["fee_close_cost"],
fee_close_currency=data["fee_close_currency"],
open_date=datetime.fromtimestamp(data["open_timestamp"] // 1000, tz=timezone.utc),
open_rate=data["open_rate"],
open_rate_requested=data["open_rate_requested"],
open_trade_value=data["open_trade_value"],
close_date=(datetime.fromtimestamp(data["close_timestamp"] // 1000, tz=timezone.utc)
if data["close_timestamp"] else None),
realized_profit=data["realized_profit"],
close_rate=data["close_rate"],
close_rate_requested=data["close_rate_requested"],
close_profit=data["close_profit"],
close_profit_abs=data["close_profit_abs"],
exit_reason=data["exit_reason"],
exit_order_status=data["exit_order_status"],
stop_loss=data["stop_loss_abs"],
stop_loss_pct=data["stop_loss_ratio"],
stoploss_order_id=data["stoploss_order_id"],
stoploss_last_update=(
datetime.fromtimestamp(data["stoploss_last_update_timestamp"] // 1000,
tz=timezone.utc)
if data["stoploss_last_update_timestamp"] else None),
initial_stop_loss=data["initial_stop_loss_abs"],
initial_stop_loss_pct=data["initial_stop_loss_ratio"],
min_rate=data["min_rate"],
max_rate=data["max_rate"],
leverage=data["leverage"],
interest_rate=data["interest_rate"],
liquidation_price=data["liquidation_price"],
is_short=data["is_short"],
trading_mode=data["trading_mode"],
funding_fees=data["funding_fees"],
amount_precision=data.get('amount_precision', None),
price_precision=data.get('price_precision', None),
precision_mode=data.get('precision_mode', None),
contract_size=data.get('contract_size', None),
)
for order in data["orders"]:
order_obj = Order(
amount=order["amount"],
ft_amount=order["amount"],
ft_order_side=order["ft_order_side"],
ft_pair=order["pair"],
ft_is_open=order["is_open"],
order_id=order["order_id"],
status=order["status"],
average=order["average"],
cost=order["cost"],
filled=order["filled"],
order_date=datetime.strptime(order["order_date"], DATETIME_PRINT_FORMAT),
order_filled_date=(datetime.fromtimestamp(
order["order_filled_timestamp"] // 1000, tz=timezone.utc)
if order["order_filled_timestamp"] else None),
order_type=order["order_type"],
price=order["price"],
ft_price=order["price"],
remaining=order["remaining"],
)
trade.orders.append(order_obj)
return trade

View File

@ -27,6 +27,7 @@ coingecko_mapping = {
'usdt': 'tether',
'busd': 'binance-usd',
'tusd': 'true-usd',
'usdc': 'usd-coin',
}

View File

@ -795,14 +795,14 @@ class RPC:
if order['side'] == trade.entry_side:
fully_canceled = self._freqtrade.handle_cancel_enter(
trade, order, oo.order_id, CANCEL_REASON['FORCE_EXIT'])
trade, order, oo, CANCEL_REASON['FORCE_EXIT'])
trade_entry_cancelation_res['cancel_state'] = fully_canceled
trade_entry_cancelation_registry.append(trade_entry_cancelation_res)
if order['side'] == trade.exit_side:
# Cancel order - so it is placed anew with a fresh price.
self._freqtrade.handle_cancel_exit(
trade, order, oo.order_id, CANCEL_REASON['FORCE_EXIT'])
trade, order, oo, CANCEL_REASON['FORCE_EXIT'])
if all(tocr['cancel_state'] is False for tocr in trade_entry_cancelation_registry):
if trade.has_open_orders:
@ -955,7 +955,7 @@ class RPC:
logger.info(f"Cannot query order for {trade} due to {e}.", exc_info=True)
raise RPCException("Order not found.")
self._freqtrade.handle_cancel_order(
order, open_order.order_id, trade, CANCEL_REASON['USER_CANCEL'])
order, open_order, trade, CANCEL_REASON['USER_CANCEL'])
Trade.commit()
def _rpc_delete(self, trade_id: int) -> Dict[str, Union[str, int]]:

View File

@ -1244,10 +1244,6 @@ class IStrategy(ABC, HyperStrategyMixin):
and trade.liquidation_price <= (high or current_rate)
and trade.is_short)
if (liq_higher_long or liq_lower_short):
logger.debug(f"{trade.pair} - Liquidation price hit. exit_type=ExitType.LIQUIDATION")
return ExitCheckTuple(exit_type=ExitType.LIQUIDATION)
# evaluate if the stoploss was hit if stoploss is not on exchange
# in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
# regular stoploss handling.
@ -1268,6 +1264,10 @@ class IStrategy(ABC, HyperStrategyMixin):
return ExitCheckTuple(exit_type=exit_type)
if (liq_higher_long or liq_lower_short):
logger.debug(f"{trade.pair} - Liquidation price hit. exit_type=ExitType.LIQUIDATION")
return ExitCheckTuple(exit_type=ExitType.LIQUIDATION)
return ExitCheckTuple(exit_type=ExitType.NONE)
def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]:

View File

@ -226,7 +226,7 @@ def crossed(series1, series2, direction=None):
series1.shift(1) >= series2.shift(1)))
if direction is None:
return above or below
return above | below
return above if direction == "above" else below

View File

@ -82,6 +82,11 @@ extend-select = [
# "TCH", # flake8-type-checking
"PTH", # flake8-use-pathlib
]
extend-ignore = [
"E241", # Multiple spaces after comma
"E272", # Multiple spaces before keyword
"E221", # Multiple spaces before operator
]
[tool.ruff.mccabe]
max-complexity = 12

View File

@ -7,24 +7,24 @@
-r docs/requirements-docs.txt
coveralls==3.3.1
ruff==0.0.291
mypy==1.5.1
pre-commit==3.4.0
ruff==0.1.1
mypy==1.6.1
pre-commit==3.5.0
pytest==7.4.2
pytest-asyncio==0.21.1
pytest-cov==4.1.0
pytest-mock==3.11.1
pytest-mock==3.12.0
pytest-random-order==1.1.0
isort==5.12.0
# For datetime mocking
time-machine==2.13.0
# Convert jupyter notebooks to markdown documents
nbconvert==7.8.0
nbconvert==7.9.2
# mypy types
types-cachetools==5.3.0.6
types-filelock==3.2.7
types-requests==2.31.0.4
types-requests==2.31.0.10
types-tabulate==0.9.0.3
types-python-dateutil==2.8.19.14

View File

@ -8,5 +8,5 @@ joblib==1.3.2
catboost==1.2.2; 'arm' not in platform_machine
lightgbm==4.1.0
xgboost==2.0.0
tensorboard==2.14.0
tensorboard==2.15.0
datasieve==0.1.7

View File

@ -2,7 +2,7 @@
-r requirements.txt
# Required for hyperopt
scipy==1.11.2
scipy==1.11.3
scikit-learn==1.1.3
scikit-optimize==0.9.0
filelock==3.12.4

View File

@ -1,19 +1,19 @@
numpy==1.26.0; platform_machine != 'armv7l'
numpy==1.25.2; platform_machine == 'armv7l'
numpy==1.26.1; platform_machine != 'armv7l'
pandas==2.0.3
pandas-ta==0.3.14b
ccxt==4.0.105
cryptography==41.0.3
aiohttp==3.8.5
SQLAlchemy==2.0.21
python-telegram-bot==20.5
ccxt==4.1.22
cryptography==41.0.4
aiohttp==3.8.6
SQLAlchemy==2.0.22
python-telegram-bot==20.6
# can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.24.1
arrow==1.2.3
arrow==1.3.0
cachetools==5.3.1
requests==2.31.0
urllib3==2.0.5
urllib3==2.0.7
jsonschema==4.19.1
TA-Lib==0.4.28
technical==1.4.0
@ -23,27 +23,27 @@ jinja2==3.1.2
tables==3.8.0
blosc==1.11.1
joblib==1.3.2
rich==13.5.3
rich==13.6.0
pyarrow==13.0.0; platform_machine != 'armv7l'
# find first, C search in arrays
py_find_1st==1.1.5
# Load ticker files 30% faster
python-rapidjson==1.11
python-rapidjson==1.12
# Properly format api responses
orjson==3.9.7
orjson==3.9.9
# Notify systemd
sdnotify==0.3.2
# API Server
fastapi==0.103.1
pydantic==2.3.0
fastapi==0.104.0
pydantic==2.4.2
uvicorn==0.23.2
pyjwt==2.8.0
aiofiles==23.2.1
psutil==5.9.5
psutil==5.9.6
# Support for colorized terminal output
colorama==0.4.6
@ -54,11 +54,11 @@ prompt-toolkit==3.0.36
python-dateutil==2.8.2
#Futures
schedule==1.2.0
schedule==1.2.1
#WS Messages
websockets==11.0.3
websockets==12.0
janus==1.0.0
ast-comments==1.1.0
packaging==23.1
ast-comments==1.1.2
packaging==23.2

View File

@ -0,0 +1,52 @@
from datetime import datetime, timezone
from pathlib import Path
from shutil import copytree
from unittest.mock import PropertyMock
import pytest
from freqtrade.data.converter.trade_converter_kraken import import_kraken_trades_from_csv
from freqtrade.data.history.idatahandler import get_datahandler
from freqtrade.exceptions import OperationalException
from tests.conftest import EXMS, log_has, log_has_re, patch_exchange
def test_import_kraken_trades_from_csv(testdatadir, tmpdir, caplog, default_conf_usdt, mocker):
with pytest.raises(OperationalException, match="This function is only for the kraken exchange"):
import_kraken_trades_from_csv(default_conf_usdt, 'feather')
default_conf_usdt['exchange']['name'] = 'kraken'
patch_exchange(mocker, id='kraken')
mocker.patch(f'{EXMS}.markets', PropertyMock(return_value={
'BCH/EUR': {'symbol': 'BCH/EUR', 'id': 'BCHEUR', 'altname': 'BCHEUR'},
}))
tmpdir1 = Path(tmpdir)
dstfile = tmpdir1 / 'BCH_EUR-trades.feather'
assert not dstfile.is_file()
default_conf_usdt['datadir'] = tmpdir1
# There's 2 files in this tree, containing a total of 2 days.
# tests/testdata/kraken/
# └── trades_csv
# ├── BCHEUR.csv <-- 2023-01-01
# └── incremental_q2
# └── BCHEUR.csv <-- 2023-01-02
copytree(testdatadir / 'kraken/trades_csv', tmpdir1 / 'trades_csv')
import_kraken_trades_from_csv(default_conf_usdt, 'feather')
assert log_has("Found csv files for BCHEUR.", caplog)
assert log_has_re(r"BCH/EUR: 340 trades.* 2023-01-01.* 2023-01-02.*", caplog)
assert dstfile.is_file()
dh = get_datahandler(tmpdir1, 'feather')
trades = dh.trades_load('BCH_EUR')
assert len(trades) == 340
assert trades['date'].min().to_pydatetime() == datetime(2023, 1, 1, 0, 3, 56,
tzinfo=timezone.utc)
assert trades['date'].max().to_pydatetime() == datetime(2023, 1, 2, 23, 17, 3,
tzinfo=timezone.utc)
# ID is not filled
assert len(trades.loc[trades['id'] != '']) == 0

View File

@ -3737,6 +3737,18 @@ def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_funding_fees(default_conf_usdt, mocker, exchange_name, caplog):
now = datetime.now(timezone.utc)
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
exchange = get_patched_exchange(mocker, default_conf_usdt, id=exchange_name)
exchange._fetch_and_calculate_funding_fees = MagicMock(side_effect=ExchangeError)
assert exchange.get_funding_fees('BTC/USDT:USDT', 1, False, now) == 0.0
assert exchange._fetch_and_calculate_funding_fees.call_count == 1
assert log_has("Could not update funding fees for BTC/USDT:USDT.", caplog)
@pytest.mark.parametrize("exchange_name", ['binance'])
def test__get_funding_fees_from_exchange(default_conf, mocker, exchange_name):
api_mock = MagicMock()
@ -4075,7 +4087,10 @@ def test_combine_funding_and_mark(
('binance', 1, 2, "2021-09-01 00:00:16", "2021-09-01 08:00:00", 30.0, -0.0002493),
('binance', 0, 1, "2021-09-01 00:00:00", "2021-09-01 07:59:59", 30.0, -0.00066479999),
('binance', 0, 2, "2021-09-01 00:00:00", "2021-09-01 12:00:00", 30.0, -0.00091409999),
('binance', 0, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.0002493),
# :01 must be rounded down.
('binance', 0, 2, "2021-09-01 00:00:01", "2021-09-01 08:00:00", 30.0, -0.00091409999),
('binance', 0, 2, "2021-08-31 23:58:00", "2021-09-01 08:00:00", 30.0, -0.00091409999),
('binance', 0, 2, "2021-09-01 00:10:01", "2021-09-01 08:00:00", 30.0, -0.0002493),
# TODO: Uncoment once _calculate_funding_fees can pas time_in_ratio to exchange._get_funding_fee
# ('kraken', "2021-09-01 00:00:00", "2021-09-01 08:00:00", 30.0, -0.0014937),
# ('kraken', "2021-09-01 00:00:15", "2021-09-01 08:00:00", 30.0, -0.0008289),
@ -4191,7 +4206,7 @@ def test__fetch_and_calculate_funding_fees_datetime_called(
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['4h', '8h']))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
d1 = datetime.strptime("2021-09-01 00:00:00 +0000", '%Y-%m-%d %H:%M:%S %z')
d1 = datetime.strptime("2021-08-31 23:00:01 +0000", '%Y-%m-%d %H:%M:%S %z')
time_machine.move_to("2021-09-01 08:00:00 +00:00")
funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, True, d1)

View File

@ -179,10 +179,9 @@ def test_set_initial_return_values(mocker, freqai_conf):
hist_pred_df = freqai.dd.historic_predictions[pair]
model_return_df = freqai.dd.model_return_values[pair]
assert (hist_pred_df['date_pred'].iloc[-1] ==
pd.Timestamp(end_x_plus_5) - pd.Timedelta(days=1))
assert hist_pred_df['date_pred'].iloc[-1] == pd.Timestamp(end_x_plus_5)
assert 'date_pred' in hist_pred_df.columns
assert hist_pred_df.shape[0] == 7 # Total rows: 5 from historic and 2 new zeros
assert hist_pred_df.shape[0] == 8
# compare values in model_return_df with hist_pred_df
assert (model_return_df["value"].values ==
@ -234,9 +233,9 @@ def test_set_initial_return_values_warning(mocker, freqai_conf):
hist_pred_df = freqai.dd.historic_predictions[pair]
model_return_df = freqai.dd.model_return_values[pair]
assert hist_pred_df['date_pred'].iloc[-1] == pd.Timestamp(end_x_plus_5) - pd.Timedelta(days=1)
assert hist_pred_df['date_pred'].iloc[-1] == pd.Timestamp(end_x_plus_5)
assert 'date_pred' in hist_pred_df.columns
assert hist_pred_df.shape[0] == 9 # Total rows: 5 from historic and 4 new zeros
assert hist_pred_df.shape[0] == 10
# compare values in model_return_df with hist_pred_df
assert (model_return_df["value"].values == hist_pred_df.tail(

View File

@ -665,7 +665,7 @@ def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None:
]
# No data available.
res = backtesting._check_trade_exit(trade, row_sell)
res = backtesting._check_trade_exit(trade, row_sell, row_sell[0].to_pydatetime())
assert res is not None
assert res.exit_reason == ExitType.ROI.value
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
@ -678,7 +678,7 @@ def test_backtest__check_trade_exit(default_conf, fee, mocker) -> None:
[], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag'])
res = backtesting._check_trade_exit(trade, row)
res = backtesting._check_trade_exit(trade, row, row[0].to_pydatetime())
assert res is None
@ -1006,7 +1006,7 @@ def test_backtest_one_detail_futures_funding_fees(
assert t.nr_of_successful_entries >= 6
# Funding fees will vary depending on the number of adjustment orders
# That number is a lot higher with detail data.
assert -20 < t.funding_fees < -0.1
assert -1.81 < t.funding_fees < -0.1
def test_backtest_timedout_entry_orders(default_conf, fee, mocker, testdatadir) -> None:

View File

@ -133,6 +133,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
]
backtesting.strategy.leverage = MagicMock(return_value=leverage)
trade = backtesting._enter_trade(pair, row=row, direction='long')
current_time = row[0].to_pydatetime()
assert trade
assert pytest.approx(trade.stake_amount) == 100.0
assert pytest.approx(trade.amount) == 47.61904762 * leverage
@ -140,7 +141,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
backtesting.strategy.adjust_trade_position = MagicMock(return_value=None)
assert pytest.approx(trade.liquidation_price) == (0.10278333 if leverage == 1 else 1.2122249)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
assert trade
assert pytest.approx(trade.stake_amount) == 100.0
assert pytest.approx(trade.amount) == 47.61904762 * leverage
@ -148,7 +149,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
# Increase position by 100
backtesting.strategy.adjust_trade_position = MagicMock(return_value=100)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
assert trade
assert pytest.approx(trade.stake_amount) == 200.0
@ -159,7 +160,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
# Reduce by more than amount - no change to trade.
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-500)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
assert trade
assert pytest.approx(trade.stake_amount) == 200.0
@ -170,7 +171,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
# Reduce position by 50
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-100)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
assert trade
assert pytest.approx(trade.stake_amount) == 100.0
@ -182,7 +183,7 @@ def test_backtest_position_adjustment_detailed(default_conf, fee, mocker, levera
# Adjust below minimum
backtesting.strategy.adjust_trade_position = MagicMock(return_value=-99)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row)
trade = backtesting._get_adjust_trade_entry_for_candle(trade, row, current_time)
assert trade
assert pytest.approx(trade.stake_amount) == 100.0

View File

@ -8,8 +8,8 @@ import pytest
from freqtrade.commands.optimize_commands import start_lookahead_analysis
from freqtrade.data.history import get_timerange
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.lookahead_analysis import Analysis, LookaheadAnalysis
from freqtrade.optimize.lookahead_analysis_helpers import LookaheadAnalysisSubFunctions
from freqtrade.optimize.analysis.lookahead import Analysis, LookaheadAnalysis
from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFunctions
from tests.conftest import EXMS, get_args, log_has_re, patch_exchange
@ -32,7 +32,7 @@ def test_start_lookahead_analysis(mocker):
single_mock = MagicMock()
text_table_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.optimize.lookahead_analysis_helpers.LookaheadAnalysisSubFunctions',
'freqtrade.optimize.analysis.lookahead_helpers.LookaheadAnalysisSubFunctions',
initialize_single_lookahead_analysis=single_mock,
text_table_lookahead_analysis_instances=text_table_mock,
)
@ -117,7 +117,7 @@ def test_lookahead_helper_start(lookahead_conf, mocker) -> None:
single_mock = MagicMock()
text_table_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.optimize.lookahead_analysis_helpers.LookaheadAnalysisSubFunctions',
'freqtrade.optimize.analysis.lookahead_helpers.LookaheadAnalysisSubFunctions',
initialize_single_lookahead_analysis=single_mock,
text_table_lookahead_analysis_instances=text_table_mock,
)
@ -324,7 +324,7 @@ def test_initialize_single_lookahead_analysis(lookahead_conf, mocker, caplog):
lookahead_conf['timeframe'] = '5m'
lookahead_conf['timerange'] = '20180119-20180122'
start_mock = mocker.patch('freqtrade.optimize.lookahead_analysis.LookaheadAnalysis.start')
start_mock = mocker.patch('freqtrade.optimize.analysis.lookahead.LookaheadAnalysis.start')
strategy_obj = {
'name': "strategy_test_v3_with_lookahead_bias",
'location': Path(lookahead_conf['strategy_path'], f"{lookahead_conf['strategy']}.py")

View File

@ -8,8 +8,8 @@ import pytest
from freqtrade.commands.optimize_commands import start_recursive_analysis
from freqtrade.data.history import get_timerange
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.recursive_analysis import RecursiveAnalysis
from freqtrade.optimize.recursive_analysis_helpers import RecursiveAnalysisSubFunctions
from freqtrade.optimize.analysis.recursive import RecursiveAnalysis
from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions
from tests.conftest import get_args, log_has_re, patch_exchange
@ -29,7 +29,7 @@ def test_start_recursive_analysis(mocker):
single_mock = MagicMock()
text_table_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.optimize.recursive_analysis_helpers.RecursiveAnalysisSubFunctions',
'freqtrade.optimize.analysis.recursive_helpers.RecursiveAnalysisSubFunctions',
initialize_single_recursive_analysis=single_mock,
text_table_recursive_analysis_instances=text_table_mock,
)
@ -83,7 +83,7 @@ def test_recursive_helper_start(recursive_conf, mocker) -> None:
single_mock = MagicMock()
text_table_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.optimize.recursive_analysis_helpers.RecursiveAnalysisSubFunctions',
'freqtrade.optimize.analysis.recursive_helpers.RecursiveAnalysisSubFunctions',
initialize_single_recursive_analysis=single_mock,
text_table_recursive_analysis_instances=text_table_mock,
)
@ -133,7 +133,7 @@ def test_initialize_single_recursive_analysis(recursive_conf, mocker, caplog):
recursive_conf['timeframe'] = '5m'
recursive_conf['timerange'] = '20180119-20180122'
start_mock = mocker.patch('freqtrade.optimize.recursive_analysis.RecursiveAnalysis.start')
start_mock = mocker.patch('freqtrade.optimize.analysis.recursive.RecursiveAnalysis.start')
strategy_obj = {
'name': "strategy_test_v3_recursive_issue",
'location': Path(recursive_conf['strategy_path'], f"{recursive_conf['strategy']}.py")

View File

@ -583,7 +583,7 @@ def test_calc_open_close_trade_price(
oobj.update_from_ccxt_object(entry_order)
trade.update_trade(oobj)
trade.funding_fees = funding_fees
trade.funding_fee_running = funding_fees
oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', trade.exit_side)
oobj._trade_live = trade
@ -591,7 +591,9 @@ def test_calc_open_close_trade_price(
trade.update_trade(oobj)
assert trade.is_open is False
# Funding fees transfer from funding_fee_running to funding_Fees
assert trade.funding_fees == funding_fees
assert trade.orders[-1].funding_fee == funding_fees
assert pytest.approx(trade._calc_open_trade_value(trade.amount, trade.open_rate)) == open_value
assert pytest.approx(trade.calc_close_trade_value(trade.close_rate)) == close_value
@ -2094,11 +2096,10 @@ def test_Trade_object_idem():
'get_enter_tag_performance',
'get_mix_tag_performance',
'get_trading_volume',
'from_json',
'validate_string_len',
)
EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count',
'total_profit')
'total_profit', 'from_json',)
# Parent (LocalTrade) should have the same attributes
for item in trade:

View File

@ -1,8 +1,10 @@
import json
from datetime import datetime, timezone
import pytest
from freqtrade.persistence.trade_model import Trade
from freqtrade.persistence.trade_model import LocalTrade, Trade
from tests.conftest import create_mock_trades_usdt
@pytest.mark.usefixtures("init_persistence")
@ -170,7 +172,8 @@ def test_trade_fromjson():
"order_filled_date": "2022-10-18 09:45:22",
"order_type": "market",
"price": 0.2592,
"remaining": 0.0
"remaining": 0.0,
"funding_fee": -0.055
}
]
}"""
@ -192,3 +195,72 @@ def test_trade_fromjson():
last_o = trade.orders[-1]
assert last_o.order_filled_utc == datetime(2022, 10, 18, 9, 45, 22, tzinfo=timezone.utc)
assert isinstance(last_o.order_date, datetime)
assert last_o.funding_fee == -0.055
@pytest.mark.usefixtures("init_persistence")
def test_trade_serialize_load_back(fee):
create_mock_trades_usdt(fee, None)
t = Trade.get_trades([Trade.id == 1]).first()
assert t.id == 1
t.funding_fees = 0.025
t.orders[0].funding_fee = 0.0125
assert len(t.orders) == 2
Trade.commit()
tjson = t.to_json(False)
assert isinstance(tjson, dict)
trade_string = json.dumps(tjson)
trade = Trade.from_json(trade_string)
assert trade.id == t.id
assert trade.funding_fees == t.funding_fees
assert len(trade.orders) == len(t.orders)
assert trade.orders[0].funding_fee == t.orders[0].funding_fee
excluded = [
'trade_id', 'quote_currency', 'open_timestamp', 'close_timestamp',
'realized_profit_ratio', 'close_profit_pct',
'trade_duration_s', 'trade_duration',
'profit_ratio', 'profit_pct', 'profit_abs', 'stop_loss_abs',
'initial_stop_loss_abs',
'orders',
]
failed = []
# Ensure all attributes written can be read.
for obj, value in tjson.items():
if obj in excluded:
continue
tattr = getattr(trade, obj, None)
if isinstance(tattr, datetime):
tattr = tattr.strftime('%Y-%m-%d %H:%M:%S')
if tattr != value:
failed.append((obj, tattr, value))
assert tjson.get('trade_id') == trade.id
assert tjson.get('quote_currency') == trade.stake_currency
assert tjson.get('stop_loss_abs') == trade.stop_loss
assert tjson.get('initial_stop_loss_abs') == trade.initial_stop_loss
excluded_o = [
'order_filled_timestamp', 'ft_is_entry', 'pair', 'is_open', 'order_timestamp',
]
order_obj = trade.orders[0]
for obj, value in tjson['orders'][0].items():
if obj in excluded_o:
continue
tattr = getattr(order_obj, obj, None)
if isinstance(tattr, datetime):
tattr = tattr.strftime('%Y-%m-%d %H:%M:%S')
if tattr != value:
failed.append((obj, tattr, value))
assert tjson['orders'][0]['pair'] == order_obj.ft_pair
assert not failed
trade2 = LocalTrade.from_json(trade_string)
assert len(trade2.orders) == len(t.orders)
trade3 = LocalTrade.from_json(trade_string)
assert len(trade3.orders) == len(t.orders)

View File

@ -99,6 +99,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'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, 'ft_fee_base': None,
'funding_fee': ANY,
}],
}
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())

View File

@ -422,7 +422,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
# enable custom stoploss, expected after 1st call, expected after 2nd call
(0.2, 0.9, ExitType.NONE, None, False, False, 0.3, 0.9, ExitType.NONE, None),
(0.2, 0.9, ExitType.NONE, None, False, False, -0.2, 0.9, ExitType.STOP_LOSS, None),
(0.2, 0.9, ExitType.NONE, 0.8, False, False, -0.2, 0.9, ExitType.LIQUIDATION, None),
(0.2, 0.9, ExitType.NONE, 0.92, False, False, -0.09, 0.9, ExitType.LIQUIDATION, None),
(0.2, 1.14, ExitType.NONE, None, True, False, 0.05, 1.14, ExitType.TRAILING_STOP_LOSS,
None),
(0.01, 0.96, ExitType.NONE, None, True, False, 0.05, 1, ExitType.NONE, None),

View File

@ -559,7 +559,6 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
fetch_ticker=ticker_usdt,
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
get_fee=fee,
get_funding_fees=MagicMock(side_effect=ExchangeError()),
)
freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade)
@ -567,7 +566,6 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
# Create 2 existing trades
freqtrade.execute_entry('ETH/USDT', default_conf_usdt['stake_amount'])
freqtrade.execute_entry('NEO/BTC', default_conf_usdt['stake_amount'])
assert log_has("Could not find funding fee.", caplog)
assert len(Trade.get_open_trades()) == 2
# Change order_id for new orders
@ -2899,6 +2897,51 @@ def test_adjust_entry_replace_fail(
assert freqtrade.strategy.adjust_entry_price.call_count == 1
@pytest.mark.parametrize("is_short", [False, True])
def test_adjust_entry_replace_fail_create_order(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
limit_sell_order_old, fee, mocker, caplog, is_short
) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
old_order = limit_sell_order_old if is_short else limit_buy_order_old
old_order['id'] = open_trade.open_orders[0].order_id
limit_entry_cancel = deepcopy(old_order)
limit_entry_cancel['status'] = 'canceled'
cancel_order_mock = MagicMock(return_value=limit_entry_cancel)
fetch_order_mock = MagicMock(return_value=old_order)
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker_usdt,
fetch_order=fetch_order_mock,
cancel_order_with_result=cancel_order_mock,
get_fee=fee
)
mocker.patch('freqtrade.freqtradebot.sleep')
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_entry',
side_effect=DependencyException())
open_trade.is_short = is_short
Trade.session.add(open_trade)
Trade.commit()
# Timeout to not interfere
freqtrade.strategy.ft_check_timed_out = MagicMock(return_value=False)
# Attempt replace order - which fails
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=12234)
freqtrade.manage_open_orders()
trades = Trade.session.scalars(
select(Trade)
.where(Trade.is_open.is_(True))
).all()
assert len(trades) == 0
assert len(Order.session.scalars(select(Order)).all()) == 0
assert fetch_order_mock.call_count == 1
assert log_has_re(
r"Could not replace order for.*", caplog)
@pytest.mark.parametrize("is_short", [False, True])
def test_adjust_entry_maintain_replace(
default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
@ -3398,20 +3441,20 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
l_order['filled'] = 0.0
l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
assert freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
assert cancel_order_mock.call_count == 1
cancel_order_mock.reset_mock()
caplog.clear()
l_order['filled'] = 0.01
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
assert cancel_order_mock.call_count == 0
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unexitable.*", caplog)
caplog.clear()
cancel_order_mock.reset_mock()
l_order['filled'] = 2
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
assert cancel_order_mock.call_count == 1
# Order remained open for some reason (cancel failed)
@ -3419,12 +3462,12 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
cancel_order_mock = MagicMock(return_value=cancel_entry_order)
mocker.patch(f'{EXMS}.cancel_order_with_result', cancel_order_mock)
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
# min_pair_stake empty should not crash
mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=None)
assert not freqtrade.handle_cancel_enter(
trade, limit_order[entry_side(is_short)], trade.open_orders_ids[0], reason
trade, limit_order[entry_side(is_short)], trade.open_orders[0], reason
)
# Retry ...
@ -3435,7 +3478,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
co_mock = mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=cbo)
fo_mock = mocker.patch(f'{EXMS}.fetch_order', return_value=cbo)
assert not freqtrade.handle_cancel_enter(
trade, cbo, cbo['id'], reason, replacing=True
trade, cbo, trade.open_orders[0], reason, replacing=True
)
assert co_mock.call_count == 1
assert fo_mock.call_count == 3
@ -3460,7 +3503,7 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho
Trade.session.add(trade)
Trade.commit()
assert freqtrade.handle_cancel_enter(
trade, limit_buy_order_canceled_empty, trade.open_orders_ids[0], reason
trade, limit_buy_order_canceled_empty, trade.open_orders[0], reason
)
assert cancel_order_mock.call_count == 0
assert log_has_re(
@ -3498,7 +3541,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order
l_order['filled'] = 0.0
l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
assert freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
assert cancel_order_mock.call_count == 1
cancel_order_mock.reset_mock()
@ -3506,7 +3549,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order
order = deepcopy(l_order)
order['status'] = 'canceled'
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders_ids[0], reason)
assert not freqtrade.handle_cancel_enter(trade, l_order, trade.open_orders[0], reason)
assert cancel_order_mock.call_count == 1
@ -3587,8 +3630,9 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee, is_short,
'amount': 1,
'status': "open"}
reason = CANCEL_REASON['TIMEOUT']
order_obj = trade.open_orders[-1]
send_msg_mock.reset_mock()
assert freqtrade.handle_cancel_exit(trade, order, order['id'], reason)
assert freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
assert cancel_order_mock.call_count == 1
assert send_msg_mock.call_count == 1
assert trade.close_rate is None
@ -3600,14 +3644,14 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee, is_short,
# Partial exit - below exit threshold
order['amount'] = amount * leverage
order['filled'] = amount * 0.99 * leverage
assert not freqtrade.handle_cancel_exit(trade, order, order['id'], reason)
assert not freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
# Assert cancel_order was not called (callcount remains unchanged)
assert cancel_order_mock.call_count == 1
assert send_msg_mock.call_count == 1
assert (send_msg_mock.call_args_list[0][0][0]['reason']
== CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'])
assert not freqtrade.handle_cancel_exit(trade, order, order['id'], reason)
assert not freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
assert (send_msg_mock.call_args_list[0][0][0]['reason']
== CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'])
@ -3619,7 +3663,7 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee, is_short,
send_msg_mock.reset_mock()
order['filled'] = amount * 0.5 * leverage
assert freqtrade.handle_cancel_exit(trade, order, order['id'], reason)
assert freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
assert send_msg_mock.call_count == 1
assert (send_msg_mock.call_args_list[0][0][0]['reason']
== CANCEL_REASON['PARTIALLY_FILLED'])
@ -3635,13 +3679,14 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
# TODO: should not be magicmock
trade = MagicMock()
order_id = '125'
order_obj = MagicMock()
order_obj.order_id = '125'
reason = CANCEL_REASON['TIMEOUT']
order = {'remaining': 1,
'id': '125',
'amount': 1,
'status': "open"}
assert not freqtrade.handle_cancel_exit(trade, order, order_id, reason)
assert not freqtrade.handle_cancel_exit(trade, order, order_obj, reason)
# mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=order)
# assert not freqtrade.handle_cancel_exit(trade, order, reason)
@ -4161,7 +4206,6 @@ def test_execute_trade_exit_market_order(
fetch_ticker=ticker_usdt,
get_fee=fee,
_dry_is_price_crossed=MagicMock(return_value=True),
get_funding_fees=MagicMock(side_effect=ExchangeError()),
)
patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt)
@ -4187,7 +4231,6 @@ def test_execute_trade_exit_market_order(
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
exit_check=ExitCheckTuple(exit_type=ExitType.ROI)
)
assert log_has("Could not update funding fee.", caplog)
assert not trade.is_open
assert pytest.approx(trade.close_profit) == profit_ratio
@ -5874,16 +5917,17 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None:
@pytest.mark.parametrize('trading_mode,calls,t1,t2', [
('spot', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
('margin', 0, "2021-09-01 00:00:00", "2021-09-01 08:00:00"),
('futures', 31, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
('futures', 32, "2021-08-31 23:59:59", "2021-09-01 08:00:01"),
('futures', 32, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:05"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:06"),
('futures', 33, "2021-08-31 23:59:59", "2021-09-01 08:00:07"),
('futures', 33, "2021-08-31 23:59:58", "2021-09-01 08:00:07"),
('futures', 15, "2021-09-01 00:01:02", "2021-09-01 08:00:01"),
('futures', 16, "2021-09-01 00:00:02", "2021-09-01 08:00:01"),
('futures', 16, "2021-08-31 23:59:59", "2021-09-01 08:00:01"),
('futures', 16, "2021-09-01 00:00:02", "2021-09-01 08:00:02"),
('futures', 16, "2021-08-31 23:59:59", "2021-09-01 08:00:02"),
('futures', 16, "2021-08-31 23:59:59", "2021-09-01 08:00:03"),
('futures', 16, "2021-08-31 23:59:59", "2021-09-01 08:00:04"),
('futures', 17, "2021-08-31 23:59:59", "2021-09-01 08:01:05"),
('futures', 17, "2021-08-31 23:59:59", "2021-09-01 08:01:06"),
('futures', 17, "2021-08-31 23:59:59", "2021-09-01 08:01:07"),
('futures', 17, "2021-08-31 23:59:58", "2021-09-01 08:01:07"),
])
def test_update_funding_fees_schedule(mocker, default_conf, trading_mode, calls, time_machine,
t1, t2):
@ -5933,7 +5977,7 @@ def test_update_funding_fees(
time: 8, mark: 1.2, fundRate: 0.00032715, nominal_value: 147.6, fundFee: 0.04828734
"""
# SETUP
time_machine.move_to("2021-09-01 00:00:00 +00:00")
time_machine.move_to("2021-09-01 00:00:16 +00:00")
open_order = limit_order_open[entry_side(is_short)]
open_exit_order = limit_order_open[exit_side(is_short)]

View File

@ -0,0 +1,148 @@
1672531436,90.540000,1.10448420
1672531471,90.450000,1.00000000
1672531647,90.360000,0.28600000
1672533909,90.000000,0.05000000
1672533909,90.000000,0.55555555
1672533909,90.000000,0.05000000
1672539535,90.160000,5.15191003
1672539535,90.170000,6.19997081
1672541045,90.170000,0.17270970
1672541045,90.170000,0.81109774
1672547372,89.940000,0.51535378
1672549388,89.820000,0.10000000
1672552963,90.020000,0.49279466
1672556400,90.020000,0.02188895
1672556899,89.880000,0.95129174
1672559791,90.040000,0.11100000
1672562703,90.130000,5.00000000
1672563176,90.040000,0.11106175
1672568240,90.260000,0.49146128
1672568359,90.270000,2.00000000
1672568359,90.270000,15.00000000
1672568359,90.280000,15.00000000
1672568359,90.290000,8.00000000
1672568359,90.310000,1.10000000
1672568359,90.310000,5.00000000
1672568359,90.320000,7.24209194
1672568359,90.320000,0.33774286
1672568359,90.340000,7.70694586
1672568359,90.350000,1.00000000
1672568359,90.360000,1.00000000
1672568359,90.360000,3.44290939
1672568359,90.370000,1.00000000
1672568359,90.370000,1.49359404
1672568359,90.380000,1.00000000
1672568359,90.390000,1.00000000
1672568359,90.390000,0.65058415
1672568359,90.390000,3.38801189
1672568359,90.420000,3.42212570
1672568359,90.440000,5.15202596
1672568359,90.450000,3.38991070
1672568359,90.450000,0.19329203
1672568359,90.460000,21.52896864
1672568526,90.250000,0.28048094
1672569016,90.340000,5.95581833
1672569115,90.340000,6.00000000
1672570237,90.310000,0.14993569
1672571388,90.370000,0.21230004
1672571631,90.370000,0.74619835
1672574412,90.400000,0.12438419
1672576112,90.400000,0.53000000
1672577367,90.400000,0.47000000
1672577367,90.370000,0.10698000
1672579099,90.360000,0.49091739
1672580071,90.310000,0.29699158
1672580225,90.300000,9.00000000
1672581264,90.280000,0.17823568
1672581264,90.280000,0.49008938
1672581589,90.250000,4.00000000
1672582660,90.250000,0.17506004
1672582660,90.250000,3.75716335
1672585445,90.180000,0.17600000
1672585589,90.180000,0.05426674
1672586068,90.190000,0.20000000
1672587250,90.120000,1.33484000
1672587559,90.100000,0.21882576
1672587585,90.100000,0.28075194
1672588454,90.160000,0.19694290
1672588454,90.170000,0.92488128
1672589636,90.080000,0.29193475
1672590151,90.150000,14.08171960
1672590566,90.120000,1.01627000
1672590888,90.270000,4.25070441
1672590952,90.250000,0.14121710
1672590952,90.250000,1.98437057
1672591010,90.200000,0.34152350
1672591010,90.190000,9.00575666
1672591132,90.300000,0.89294468
1672591380,90.310000,0.10531140
1672591380,90.320000,0.72237064
1672591390,90.260000,0.17076175
1672591390,90.250000,0.93726662
1672591626,90.280000,0.13803658
1672591626,90.270000,1.39856040
1672592025,90.330000,0.18053494
1672592350,90.340000,3.82317350
1672592350,90.360000,5.66081594
1672592350,90.370000,0.23660665
1672592350,90.380000,0.89618051
1672592398,90.370000,1.04662011
1672592444,90.360000,0.12419225
1672593227,90.360000,1.96390112
1672599481,90.710000,0.05520000
1672599657,90.710000,39.94480000
1672600545,90.720000,1.09096000
1672600545,90.720000,39.99904000
1672601235,90.790000,0.11022927
1672601584,90.790000,0.11897764
1672601584,90.800000,0.16215666
1672602875,90.830000,1.00000000
1672604100,90.820000,0.53216284
1672604129,90.820000,0.05503577
1672604219,90.810000,0.07234981
1672607019,90.920000,2.00000000
1672607019,90.920000,40.00000000
1672607019,90.920000,8.00000000
1672607067,90.930000,2.00000000
1672607067,90.930000,8.00000000
1672607067,90.930000,21.35000000
1672607076,90.910000,5.50000000
1672607196,90.910000,2.00000000
1672607196,90.910000,0.30030000
1672607310,90.890000,0.96144343
1672607310,90.900000,0.50292482
1672607703,90.760000,1.00000000
1672607703,90.750000,3.52448259
1672607703,90.750000,0.15295233
1672607703,90.740000,0.82256508
1672608691,90.790000,0.19963741
1672608691,90.810000,0.31977406
1672609051,90.820000,0.38000000
1672609580,90.780000,0.24004396
1672609580,90.770000,5.25995604
1672609695,90.740000,0.98519900
1672609697,90.740000,0.14629976
1672609698,90.740000,0.10000000
1672610001,90.740000,0.21984068
1672610001,90.740000,0.08898963
1672610507,90.760000,0.35960205
1672610527,90.760000,0.74039795
1672610527,90.760000,0.11030671
1672610527,90.760000,1.82902226
1672611326,90.680000,0.08710000
1672612351,90.680000,1.18193004
1672612351,90.660000,0.11985422
1672612460,90.640000,0.22000000
1672612460,90.600000,0.24680297
1672612460,90.590000,0.65058415
1672612460,90.590000,1.10000000
1672612460,90.570000,2.78261288
1672612485,90.520000,2.00000000
1672612485,90.530000,1.10000000
1672612485,90.530000,5.00000000
1672613044,90.520000,1.08661000
1672613471,90.550000,0.22739075
1672613471,90.560000,0.92169701
1672613957,90.490000,0.50000000
1672614482,90.520000,0.19893239
1672615187,90.530000,1.00000000
1 1672531436 90.540000 1.10448420
2 1672531471 90.450000 1.00000000
3 1672531647 90.360000 0.28600000
4 1672533909 90.000000 0.05000000
5 1672533909 90.000000 0.55555555
6 1672533909 90.000000 0.05000000
7 1672539535 90.160000 5.15191003
8 1672539535 90.170000 6.19997081
9 1672541045 90.170000 0.17270970
10 1672541045 90.170000 0.81109774
11 1672547372 89.940000 0.51535378
12 1672549388 89.820000 0.10000000
13 1672552963 90.020000 0.49279466
14 1672556400 90.020000 0.02188895
15 1672556899 89.880000 0.95129174
16 1672559791 90.040000 0.11100000
17 1672562703 90.130000 5.00000000
18 1672563176 90.040000 0.11106175
19 1672568240 90.260000 0.49146128
20 1672568359 90.270000 2.00000000
21 1672568359 90.270000 15.00000000
22 1672568359 90.280000 15.00000000
23 1672568359 90.290000 8.00000000
24 1672568359 90.310000 1.10000000
25 1672568359 90.310000 5.00000000
26 1672568359 90.320000 7.24209194
27 1672568359 90.320000 0.33774286
28 1672568359 90.340000 7.70694586
29 1672568359 90.350000 1.00000000
30 1672568359 90.360000 1.00000000
31 1672568359 90.360000 3.44290939
32 1672568359 90.370000 1.00000000
33 1672568359 90.370000 1.49359404
34 1672568359 90.380000 1.00000000
35 1672568359 90.390000 1.00000000
36 1672568359 90.390000 0.65058415
37 1672568359 90.390000 3.38801189
38 1672568359 90.420000 3.42212570
39 1672568359 90.440000 5.15202596
40 1672568359 90.450000 3.38991070
41 1672568359 90.450000 0.19329203
42 1672568359 90.460000 21.52896864
43 1672568526 90.250000 0.28048094
44 1672569016 90.340000 5.95581833
45 1672569115 90.340000 6.00000000
46 1672570237 90.310000 0.14993569
47 1672571388 90.370000 0.21230004
48 1672571631 90.370000 0.74619835
49 1672574412 90.400000 0.12438419
50 1672576112 90.400000 0.53000000
51 1672577367 90.400000 0.47000000
52 1672577367 90.370000 0.10698000
53 1672579099 90.360000 0.49091739
54 1672580071 90.310000 0.29699158
55 1672580225 90.300000 9.00000000
56 1672581264 90.280000 0.17823568
57 1672581264 90.280000 0.49008938
58 1672581589 90.250000 4.00000000
59 1672582660 90.250000 0.17506004
60 1672582660 90.250000 3.75716335
61 1672585445 90.180000 0.17600000
62 1672585589 90.180000 0.05426674
63 1672586068 90.190000 0.20000000
64 1672587250 90.120000 1.33484000
65 1672587559 90.100000 0.21882576
66 1672587585 90.100000 0.28075194
67 1672588454 90.160000 0.19694290
68 1672588454 90.170000 0.92488128
69 1672589636 90.080000 0.29193475
70 1672590151 90.150000 14.08171960
71 1672590566 90.120000 1.01627000
72 1672590888 90.270000 4.25070441
73 1672590952 90.250000 0.14121710
74 1672590952 90.250000 1.98437057
75 1672591010 90.200000 0.34152350
76 1672591010 90.190000 9.00575666
77 1672591132 90.300000 0.89294468
78 1672591380 90.310000 0.10531140
79 1672591380 90.320000 0.72237064
80 1672591390 90.260000 0.17076175
81 1672591390 90.250000 0.93726662
82 1672591626 90.280000 0.13803658
83 1672591626 90.270000 1.39856040
84 1672592025 90.330000 0.18053494
85 1672592350 90.340000 3.82317350
86 1672592350 90.360000 5.66081594
87 1672592350 90.370000 0.23660665
88 1672592350 90.380000 0.89618051
89 1672592398 90.370000 1.04662011
90 1672592444 90.360000 0.12419225
91 1672593227 90.360000 1.96390112
92 1672599481 90.710000 0.05520000
93 1672599657 90.710000 39.94480000
94 1672600545 90.720000 1.09096000
95 1672600545 90.720000 39.99904000
96 1672601235 90.790000 0.11022927
97 1672601584 90.790000 0.11897764
98 1672601584 90.800000 0.16215666
99 1672602875 90.830000 1.00000000
100 1672604100 90.820000 0.53216284
101 1672604129 90.820000 0.05503577
102 1672604219 90.810000 0.07234981
103 1672607019 90.920000 2.00000000
104 1672607019 90.920000 40.00000000
105 1672607019 90.920000 8.00000000
106 1672607067 90.930000 2.00000000
107 1672607067 90.930000 8.00000000
108 1672607067 90.930000 21.35000000
109 1672607076 90.910000 5.50000000
110 1672607196 90.910000 2.00000000
111 1672607196 90.910000 0.30030000
112 1672607310 90.890000 0.96144343
113 1672607310 90.900000 0.50292482
114 1672607703 90.760000 1.00000000
115 1672607703 90.750000 3.52448259
116 1672607703 90.750000 0.15295233
117 1672607703 90.740000 0.82256508
118 1672608691 90.790000 0.19963741
119 1672608691 90.810000 0.31977406
120 1672609051 90.820000 0.38000000
121 1672609580 90.780000 0.24004396
122 1672609580 90.770000 5.25995604
123 1672609695 90.740000 0.98519900
124 1672609697 90.740000 0.14629976
125 1672609698 90.740000 0.10000000
126 1672610001 90.740000 0.21984068
127 1672610001 90.740000 0.08898963
128 1672610507 90.760000 0.35960205
129 1672610527 90.760000 0.74039795
130 1672610527 90.760000 0.11030671
131 1672610527 90.760000 1.82902226
132 1672611326 90.680000 0.08710000
133 1672612351 90.680000 1.18193004
134 1672612351 90.660000 0.11985422
135 1672612460 90.640000 0.22000000
136 1672612460 90.600000 0.24680297
137 1672612460 90.590000 0.65058415
138 1672612460 90.590000 1.10000000
139 1672612460 90.570000 2.78261288
140 1672612485 90.520000 2.00000000
141 1672612485 90.530000 1.10000000
142 1672612485 90.530000 5.00000000
143 1672613044 90.520000 1.08661000
144 1672613471 90.550000 0.22739075
145 1672613471 90.560000 0.92169701
146 1672613957 90.490000 0.50000000
147 1672614482 90.520000 0.19893239
148 1672615187 90.530000 1.00000000

View File

@ -0,0 +1,486 @@
1672620536,90.070000,1.10000000
1672621569,90.000000,0.05000000
1672621572,89.900000,0.16781327
1672621572,89.880000,3.21796979
1672621574,89.680000,0.15038625
1672621591,89.500000,0.40223000
1672621591,89.500000,0.00000463
1672621591,89.460000,0.42312537
1672621591,89.460000,0.00001340
1672621591,89.450000,0.27462660
1672621591,89.450000,1.55812912
1672621592,89.430000,3.49563000
1672621592,89.430000,0.00000245
1672621592,89.400000,3.24020755
1672621593,89.400000,0.00000579
1672621593,89.340000,3.39843000
1672621593,89.340000,0.00000367
1672621594,89.310000,3.46879000
1672621594,89.310000,0.00000151
1672621594,89.300000,0.23606849
1672621594,89.300000,0.00000151
1672621611,89.510000,0.05201346
1672623048,89.400000,0.05000000
1672623487,89.340000,3.29746218
1672624539,89.480000,0.13947779
1672630508,89.970000,0.48616536
1672633444,89.830000,0.36077173
1672633444,89.840000,0.18222827
1672635717,90.070000,0.15020808
1672638127,90.170000,0.11950000
1672638790,90.250000,2.08482000
1672639521,90.250000,0.00000100
1672639521,90.350000,0.16503183
1672639521,90.360000,0.10887277
1672641041,90.420000,0.33000000
1672643661,90.290000,0.16644000
1672643664,90.290000,0.00000378
1672643677,90.900000,0.27611440
1672643677,90.910000,3.55887567
1672643677,90.930000,1.94112433
1672643681,90.930000,1.45287534
1672643683,91.000000,0.26528000
1672643683,91.000000,0.27938000
1672643698,91.100000,1.02000000
1672643698,91.090000,1.10000000
1672643698,91.080000,9.37501459
1672643698,91.080000,10.00000000
1672643698,91.080000,30.20498538
1672643740,91.050000,2.00000000
1672643740,91.050000,1.25174226
1672643740,91.100000,0.21657000
1672643742,91.140000,3.41661000
1672643742,91.140000,0.00000339
1672643745,91.300000,0.22559000
1672643745,91.300000,0.20927000
1672643754,91.530000,1.46349415
1672643754,91.500000,0.05000000
1672643755,91.500000,0.19820000
1672643755,91.500000,0.23323000
1672643755,91.400000,0.25276000
1672643755,91.400000,0.24522000
1672643755,91.360000,0.17059000
1672643762,91.590000,3.38609591
1672643762,91.590000,1.70666409
1672643762,91.590000,5.00245000
1672643763,91.590000,0.00000222
1672643763,91.620000,3.28019778
1672643764,91.620000,0.00001175
1672643764,91.680000,3.35131000
1672643765,91.680000,0.00000053
1672643765,91.710000,1.99999947
1672643765,91.710000,1.45873628
1672643780,91.800000,0.24421000
1672643781,91.700000,0.24097000
1672643786,91.500000,0.24123000
1672643786,91.500000,0.23718000
1672643917,91.440000,0.09961400
1672643954,91.460000,1.46349415
1672644454,91.420000,1.10000000
1672644454,91.410000,0.90000000
1672644817,91.290000,0.97023360
1672644888,91.370000,5.00000000
1672645505,91.500000,0.55869071
1672645505,91.500000,0.20130929
1672646731,91.680000,0.10000000
1672647152,91.840000,1.10000000
1672647152,91.840000,0.72640000
1672647284,91.900000,0.10000000
1672647284,92.000000,0.10000000
1672647294,92.100000,0.07400739
1672647331,92.100000,0.02599261
1672647331,92.200000,0.10000000
1672647408,92.300000,0.10000000
1672647447,92.400000,0.10000000
1672647452,92.530000,1.10000000
1672647452,92.530000,7.07935280
1672647452,92.520000,1.82064720
1672647453,92.530000,7.08005995
1672647453,92.520000,2.91994005
1672647453,92.520000,1.10000000
1672647453,92.520000,7.08106089
1672647453,92.520000,0.11257595
1672647453,92.520000,1.70636316
1672647453,92.520000,1.61618244
1672647977,92.290000,1.10000000
1672647977,92.280000,6.10788595
1672648596,92.600000,0.28769588
1672649352,92.520000,1.00000000
1672649722,92.360000,0.43190135
1672649722,92.350000,0.56809865
1672650340,92.430000,1.10000000
1672650340,92.440000,0.17885343
1672650340,92.450000,0.63696354
1672651165,92.390000,40.00000000
1672651596,92.380000,1.00000000
1672651664,92.390000,2.00754507
1672651716,92.380000,1.00000000
1672651727,92.390000,1.40528155
1672651746,92.380000,0.10934211
1672651746,92.370000,0.14252789
1672651865,92.410000,0.10000000
1672651866,92.410000,0.10000000
1672651922,92.420000,1.02836794
1672651981,92.410000,0.33805200
1672651982,92.410000,2.96775605
1672652034,92.400000,40.00000000
1672652230,92.410000,3.00000003
1672652230,92.410000,40.00000000
1672652462,92.560000,4.72637791
1672652785,92.540000,0.26371093
1672652785,92.550000,0.06028907
1672653191,92.380000,0.11300328
1672653191,92.370000,0.08699672
1672654246,92.480000,1.10000000
1672654246,92.480000,0.18835710
1672654246,92.480000,0.71723426
1672654327,92.420000,1.10000000
1672654327,92.430000,0.90624217
1672654405,92.400000,0.71000000
1672654405,92.420000,1.10000000
1672654405,92.430000,0.19732780
1672654469,92.400000,0.44000000
1672654469,92.410000,0.18835711
1672654469,92.420000,1.10000000
1672654469,92.420000,0.27897069
1672654503,92.420000,1.56537046
1672654551,92.370000,2.00000000
1672654551,92.380000,0.00732780
1672656132,92.360000,0.51441726
1672656132,92.350000,0.07117974
1672656562,92.460000,1.09777201
1672656822,92.540000,0.25720863
1672656822,92.550000,0.17503688
1672657382,92.410000,0.88037274
1672657843,92.300000,0.52109000
1672659479,92.450000,0.86375500
1672659527,92.450000,0.95973892
1672659842,92.380000,0.10000000
1672659844,92.380000,0.28000000
1672659858,92.380000,0.40000000
1672659860,92.380000,0.59000000
1672659860,92.380000,0.21230377
1672659860,92.380000,0.19581295
1672659860,92.370000,0.05188328
1672659880,92.440000,0.41651079
1672659880,92.450000,1.58233921
1672660744,92.620000,0.20825539
1672660744,92.620000,2.00000000
1672660744,92.620000,0.81325838
1672660786,92.540000,0.40620484
1672660786,92.530000,0.50322423
1672661316,92.760000,3.00000000
1672661316,92.760000,0.51083000
1672661316,92.760000,0.00000750
1672661763,92.880000,0.40600000
1672662004,92.680000,0.63474730
1672662021,92.610000,0.99999999
1672662056,92.670000,0.51391586
1672662831,92.640000,0.20718714
1672662831,92.630000,0.19281286
1672663146,92.780000,0.20678545
1672664406,92.660000,0.20698630
1672664406,92.650000,0.09207754
1672665304,92.600000,0.27096400
1672665644,92.560000,0.10349315
1672665644,92.550000,0.50000000
1672665644,92.550000,1.10000000
1672665644,92.540000,2.29650685
1672665885,92.680000,0.25647281
1672666383,92.750000,1.10000000
1672666383,92.770000,0.33443428
1672666383,92.780000,1.61158033
1672666724,92.620000,0.26415235
1672666724,92.600000,0.00975325
1672667271,92.530000,0.21512394
1672667271,92.520000,3.44492536
1672667271,92.520000,1.10000000
1672667271,92.500000,13.00000000
1672667271,92.500000,4.17971070
1672667820,92.490000,0.90149481
1672668419,92.600000,0.05179685
1672668761,92.600000,0.20000000
1672668970,92.600000,1.00000000
1672668970,92.600000,0.36870115
1672669497,92.870000,0.27986569
1672669497,92.920000,0.23423604
1672670020,92.780000,0.24749482
1672670020,92.780000,0.22946945
1672670088,92.860000,0.39066985
1672670088,92.870000,0.60802221
1672670162,92.800000,0.19533493
1672670162,92.810000,1.57768714
1672671482,92.970000,0.05000000
1672671482,92.990000,0.25805040
1672671482,93.000000,0.05500000
1672671482,93.000000,1.17150537
1672671484,93.030000,1.90000000
1672671484,93.050000,0.10000000
1672671484,93.050000,0.09533493
1672671484,93.100000,5.50000000
1672671597,93.210000,0.28559636
1672671922,93.210000,0.06224165
1672672258,93.190000,0.05360162
1672672715,93.440000,0.35848855
1672673021,93.390000,0.24603359
1672673644,93.500000,1.25128947
1672673926,93.370000,0.10000000
1672673926,93.370000,0.10000000
1672674245,93.570000,3.00000000
1672674245,93.570000,0.27582332
1672674251,93.600000,3.00000000
1672674251,93.600000,0.27021894
1672674269,93.620000,0.05000000
1672674278,93.690000,0.16020297
1672674928,93.870000,16.00000000
1672674928,93.870000,0.42229400
1672674928,93.880000,3.00000000
1672674928,93.880000,0.83625840
1672675263,93.920000,0.15897954
1672675354,93.790000,0.05541000
1672675420,93.750000,3.42716504
1672675420,93.750000,1.10000000
1672675420,93.730000,0.30071309
1672675420,93.730000,0.26523930
1672675420,93.720000,3.43747306
1672675420,93.720000,2.00000000
1672675420,93.700000,2.00000000
1672675420,93.690000,3.50480003
1672675420,93.690000,17.74436742
1672675420,93.690000,2.53044755
1672675420,93.690000,5.59187611
1672675420,93.690000,1.10269129
1672675420,93.690000,5.00000000
1672675420,93.690000,10.00000000
1672675420,93.690000,1.99522711
1672677602,93.900000,0.33635572
1672677602,93.890000,0.02213283
1672677724,93.960000,0.20921217
1672677724,93.970000,4.01815113
1672677724,93.980000,1.10000000
1672677724,93.980000,4.99304515
1672678203,93.960000,0.53112491
1672678563,93.780000,1.06849022
1672678711,93.780000,0.25694338
1672678807,93.720000,0.21340200
1672678854,93.710000,0.20162914
1672678854,93.710000,1.00949171
1672678856,93.710000,1.48404486
1672678996,93.660000,0.21353800
1672679012,93.660000,0.21353800
1672679028,93.660000,0.21356100
1672679037,93.670000,0.11009220
1672679037,93.660000,1.78643900
1672679037,93.660000,3.52200393
1672679042,93.660000,0.21353800
1672679057,93.660000,0.21353800
1672679072,93.670000,0.21351600
1672679087,93.650000,0.11692327
1672679087,93.650000,0.09663773
1672679094,93.660000,4.95970000
1672679104,93.660000,0.21356100
1672679118,93.660000,0.21353800
1672679135,93.650000,0.11692328
1672679135,93.650000,0.09663772
1672679151,93.650000,0.21356100
1672679166,93.620000,0.21358400
1672679180,93.630000,0.21360700
1672679195,93.620000,0.21363000
1672679212,93.620000,0.21363000
1672679225,93.620000,0.21363000
1672679240,93.620000,0.21363000
1672679256,93.620000,0.21363000
1672679273,93.630000,0.21360700
1672679286,93.630000,0.21360700
1672679303,93.630000,0.21360700
1672679315,93.630000,1.39400000
1672679318,93.640000,0.21360700
1672679332,93.640000,0.21358400
1672679342,93.650000,40.00000000
1672679356,93.640000,0.21358400
1672679370,93.640000,0.21358400
1672679386,93.640000,0.21358400
1672679402,93.640000,0.21358400
1672679416,93.640000,0.21358400
1672679432,93.640000,0.21358400
1672679448,93.640000,0.21358400
1672679463,93.640000,0.21358400
1672679477,93.640000,0.21358400
1672679494,93.620000,0.21365200
1672679510,93.620000,0.21363000
1672679525,93.620000,0.21363000
1672679540,93.630000,0.21363000
1672679554,93.630000,0.21360700
1672679570,93.630000,0.21360700
1672679585,93.630000,0.21360700
1672679600,93.630000,0.21360700
1672679615,93.630000,0.21360700
1672679632,93.630000,0.21360700
1672679646,93.620000,0.21363000
1672679661,93.630000,0.21360700
1672679676,93.630000,0.21360700
1672679691,93.630000,0.21360700
1672679707,93.620000,0.21363000
1672679726,93.620000,0.21363000
1672679741,93.620000,0.21360700
1672679757,93.620000,0.21363000
1672679778,93.640000,0.21360700
1672679794,93.660000,0.21360700
1672679809,93.680000,0.21349300
1672679821,93.700000,0.21340200
1672679821,93.700000,0.21353800
1672679821,93.700000,0.21353800
1672679821,93.700000,0.21356100
1672679821,93.700000,0.21353800
1672679821,93.700000,0.21353800
1672679821,93.700000,0.21351600
1672679821,93.700000,0.21356100
1672679821,93.700000,0.21356100
1672679821,93.700000,0.21353800
1672679821,93.700000,0.21356100
1672679821,93.700000,0.21356100
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21358400
1672679821,93.700000,0.21365200
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21363000
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21360700
1672679821,93.700000,0.21349300
1672679829,93.690000,0.21349300
1672679943,93.730000,0.10000000
1672679943,93.740000,0.35573142
1672680124,93.760000,0.41611649
1672680124,93.770000,1.10000000
1672680124,93.770000,1.00289427
1672680599,93.710000,40.00000000
1672680599,93.710000,40.00000000
1672681203,93.760000,0.30635902
1672681203,93.770000,0.68274662
1672681262,93.710000,1.01899965
1672681304,93.700000,40.00000000
1672681323,93.750000,0.57938646
1672681386,93.750000,1.48381671
1672681458,93.750000,0.01067000
1672681669,93.630000,40.00000000
1672682646,93.650000,0.17545586
1672682646,93.660000,2.04572869
1672682700,93.660000,0.43630185
1672683062,93.520000,0.21349300
1672683333,93.400000,40.00000000
1672683540,93.370000,0.37323978
1672683540,93.360000,0.78018965
1672683664,93.400000,40.00000000
1672683664,93.400000,40.00000000
1672683912,93.360000,40.00000000
1672684035,93.380000,0.10000000
1672684681,93.400000,40.00000000
1672684681,93.400000,5.00000000
1672684895,93.450000,0.28232122
1672684895,93.450000,0.16798232
1672684895,93.460000,2.74269478
1672686428,93.590000,0.32054706
1672686593,93.610000,0.18606342
1672686593,93.600000,1.10000000
1672686593,93.600000,0.64000000
1672686593,93.600000,2.00000000
1672686593,93.600000,0.56906893
1672686763,93.580000,1.10000000
1672686763,93.570000,0.18606343
1672686763,93.560000,3.00000000
1672686763,93.550000,6.66393657
1672686853,93.600000,1.04000000
1672686853,93.580000,0.31000000
1672687010,93.660000,0.51441726
1672687010,93.670000,0.49536629
1672687144,93.660000,0.25720863
1672687144,93.670000,0.13025611
1672687924,93.380000,1.38703774
1672688043,93.450000,1.10000000
1672688043,93.470000,9.62310000
1672688163,93.450000,0.12860432
1672688163,93.460000,0.74330839
1672688463,93.450000,0.12860432
1672688463,93.460000,2.43888543
1672689094,93.500000,0.48000000
1672689094,93.500000,1.52000000
1672689122,93.500000,0.69746509
1672689605,93.380000,0.85913567
1672690623,93.360000,1.55373756
1672690623,93.380000,0.97334211
1672690731,93.280000,0.37258400
1672691284,93.510000,1.10000000
1672691284,93.520000,0.17017217
1672691284,93.530000,1.86482783
1672693176,93.340000,0.08092000
1672693262,93.400000,0.43291938
1672694405,93.280000,0.40741109
1672694828,93.200000,0.41321135
1672694828,93.200000,0.12328865
1672695290,93.240000,0.20660567
1672695290,93.230000,1.10000000
1672695290,93.220000,3.00000000
1672695290,93.210000,3.44792866
1672695290,93.200000,3.00000000
1672695290,93.200000,1.34083632
1672695290,93.200000,2.52411935
1672695567,93.230000,0.09530603
1672695997,93.310000,0.10280546
1672696328,93.270000,0.10705255
1672696328,93.260000,0.00294745
1672697520,93.390000,0.38943251
1672697520,93.400000,1.10000000
1672697520,93.400000,0.03084631
1672697584,93.400000,0.21413300
1672697656,93.400000,0.21413300
1672697671,93.400000,0.21314117
1672698111,93.500000,0.34000000
1672698111,93.500000,0.26617473
1672698111,93.510000,3.39382527
1672698284,93.430000,0.38132990
1672698284,93.420000,1.10000000
1672698284,93.400000,12.34766434
1672698922,93.220000,0.11677000
1672699624,93.220000,0.32375231
1672699624,93.230000,1.10000000
1672699624,93.230000,0.50000000
1672699624,93.240000,0.07624769
1672701005,93.000000,0.34305825
1672701389,93.180000,0.26786932
1672701423,93.180000,1.00000000
1672701423,93.190000,1.10000000
1672701423,93.190000,0.44235389
1 1672620536 90.070000 1.10000000
2 1672621569 90.000000 0.05000000
3 1672621572 89.900000 0.16781327
4 1672621572 89.880000 3.21796979
5 1672621574 89.680000 0.15038625
6 1672621591 89.500000 0.40223000
7 1672621591 89.500000 0.00000463
8 1672621591 89.460000 0.42312537
9 1672621591 89.460000 0.00001340
10 1672621591 89.450000 0.27462660
11 1672621591 89.450000 1.55812912
12 1672621592 89.430000 3.49563000
13 1672621592 89.430000 0.00000245
14 1672621592 89.400000 3.24020755
15 1672621593 89.400000 0.00000579
16 1672621593 89.340000 3.39843000
17 1672621593 89.340000 0.00000367
18 1672621594 89.310000 3.46879000
19 1672621594 89.310000 0.00000151
20 1672621594 89.300000 0.23606849
21 1672621594 89.300000 0.00000151
22 1672621611 89.510000 0.05201346
23 1672623048 89.400000 0.05000000
24 1672623487 89.340000 3.29746218
25 1672624539 89.480000 0.13947779
26 1672630508 89.970000 0.48616536
27 1672633444 89.830000 0.36077173
28 1672633444 89.840000 0.18222827
29 1672635717 90.070000 0.15020808
30 1672638127 90.170000 0.11950000
31 1672638790 90.250000 2.08482000
32 1672639521 90.250000 0.00000100
33 1672639521 90.350000 0.16503183
34 1672639521 90.360000 0.10887277
35 1672641041 90.420000 0.33000000
36 1672643661 90.290000 0.16644000
37 1672643664 90.290000 0.00000378
38 1672643677 90.900000 0.27611440
39 1672643677 90.910000 3.55887567
40 1672643677 90.930000 1.94112433
41 1672643681 90.930000 1.45287534
42 1672643683 91.000000 0.26528000
43 1672643683 91.000000 0.27938000
44 1672643698 91.100000 1.02000000
45 1672643698 91.090000 1.10000000
46 1672643698 91.080000 9.37501459
47 1672643698 91.080000 10.00000000
48 1672643698 91.080000 30.20498538
49 1672643740 91.050000 2.00000000
50 1672643740 91.050000 1.25174226
51 1672643740 91.100000 0.21657000
52 1672643742 91.140000 3.41661000
53 1672643742 91.140000 0.00000339
54 1672643745 91.300000 0.22559000
55 1672643745 91.300000 0.20927000
56 1672643754 91.530000 1.46349415
57 1672643754 91.500000 0.05000000
58 1672643755 91.500000 0.19820000
59 1672643755 91.500000 0.23323000
60 1672643755 91.400000 0.25276000
61 1672643755 91.400000 0.24522000
62 1672643755 91.360000 0.17059000
63 1672643762 91.590000 3.38609591
64 1672643762 91.590000 1.70666409
65 1672643762 91.590000 5.00245000
66 1672643763 91.590000 0.00000222
67 1672643763 91.620000 3.28019778
68 1672643764 91.620000 0.00001175
69 1672643764 91.680000 3.35131000
70 1672643765 91.680000 0.00000053
71 1672643765 91.710000 1.99999947
72 1672643765 91.710000 1.45873628
73 1672643780 91.800000 0.24421000
74 1672643781 91.700000 0.24097000
75 1672643786 91.500000 0.24123000
76 1672643786 91.500000 0.23718000
77 1672643917 91.440000 0.09961400
78 1672643954 91.460000 1.46349415
79 1672644454 91.420000 1.10000000
80 1672644454 91.410000 0.90000000
81 1672644817 91.290000 0.97023360
82 1672644888 91.370000 5.00000000
83 1672645505 91.500000 0.55869071
84 1672645505 91.500000 0.20130929
85 1672646731 91.680000 0.10000000
86 1672647152 91.840000 1.10000000
87 1672647152 91.840000 0.72640000
88 1672647284 91.900000 0.10000000
89 1672647284 92.000000 0.10000000
90 1672647294 92.100000 0.07400739
91 1672647331 92.100000 0.02599261
92 1672647331 92.200000 0.10000000
93 1672647408 92.300000 0.10000000
94 1672647447 92.400000 0.10000000
95 1672647452 92.530000 1.10000000
96 1672647452 92.530000 7.07935280
97 1672647452 92.520000 1.82064720
98 1672647453 92.530000 7.08005995
99 1672647453 92.520000 2.91994005
100 1672647453 92.520000 1.10000000
101 1672647453 92.520000 7.08106089
102 1672647453 92.520000 0.11257595
103 1672647453 92.520000 1.70636316
104 1672647453 92.520000 1.61618244
105 1672647977 92.290000 1.10000000
106 1672647977 92.280000 6.10788595
107 1672648596 92.600000 0.28769588
108 1672649352 92.520000 1.00000000
109 1672649722 92.360000 0.43190135
110 1672649722 92.350000 0.56809865
111 1672650340 92.430000 1.10000000
112 1672650340 92.440000 0.17885343
113 1672650340 92.450000 0.63696354
114 1672651165 92.390000 40.00000000
115 1672651596 92.380000 1.00000000
116 1672651664 92.390000 2.00754507
117 1672651716 92.380000 1.00000000
118 1672651727 92.390000 1.40528155
119 1672651746 92.380000 0.10934211
120 1672651746 92.370000 0.14252789
121 1672651865 92.410000 0.10000000
122 1672651866 92.410000 0.10000000
123 1672651922 92.420000 1.02836794
124 1672651981 92.410000 0.33805200
125 1672651982 92.410000 2.96775605
126 1672652034 92.400000 40.00000000
127 1672652230 92.410000 3.00000003
128 1672652230 92.410000 40.00000000
129 1672652462 92.560000 4.72637791
130 1672652785 92.540000 0.26371093
131 1672652785 92.550000 0.06028907
132 1672653191 92.380000 0.11300328
133 1672653191 92.370000 0.08699672
134 1672654246 92.480000 1.10000000
135 1672654246 92.480000 0.18835710
136 1672654246 92.480000 0.71723426
137 1672654327 92.420000 1.10000000
138 1672654327 92.430000 0.90624217
139 1672654405 92.400000 0.71000000
140 1672654405 92.420000 1.10000000
141 1672654405 92.430000 0.19732780
142 1672654469 92.400000 0.44000000
143 1672654469 92.410000 0.18835711
144 1672654469 92.420000 1.10000000
145 1672654469 92.420000 0.27897069
146 1672654503 92.420000 1.56537046
147 1672654551 92.370000 2.00000000
148 1672654551 92.380000 0.00732780
149 1672656132 92.360000 0.51441726
150 1672656132 92.350000 0.07117974
151 1672656562 92.460000 1.09777201
152 1672656822 92.540000 0.25720863
153 1672656822 92.550000 0.17503688
154 1672657382 92.410000 0.88037274
155 1672657843 92.300000 0.52109000
156 1672659479 92.450000 0.86375500
157 1672659527 92.450000 0.95973892
158 1672659842 92.380000 0.10000000
159 1672659844 92.380000 0.28000000
160 1672659858 92.380000 0.40000000
161 1672659860 92.380000 0.59000000
162 1672659860 92.380000 0.21230377
163 1672659860 92.380000 0.19581295
164 1672659860 92.370000 0.05188328
165 1672659880 92.440000 0.41651079
166 1672659880 92.450000 1.58233921
167 1672660744 92.620000 0.20825539
168 1672660744 92.620000 2.00000000
169 1672660744 92.620000 0.81325838
170 1672660786 92.540000 0.40620484
171 1672660786 92.530000 0.50322423
172 1672661316 92.760000 3.00000000
173 1672661316 92.760000 0.51083000
174 1672661316 92.760000 0.00000750
175 1672661763 92.880000 0.40600000
176 1672662004 92.680000 0.63474730
177 1672662021 92.610000 0.99999999
178 1672662056 92.670000 0.51391586
179 1672662831 92.640000 0.20718714
180 1672662831 92.630000 0.19281286
181 1672663146 92.780000 0.20678545
182 1672664406 92.660000 0.20698630
183 1672664406 92.650000 0.09207754
184 1672665304 92.600000 0.27096400
185 1672665644 92.560000 0.10349315
186 1672665644 92.550000 0.50000000
187 1672665644 92.550000 1.10000000
188 1672665644 92.540000 2.29650685
189 1672665885 92.680000 0.25647281
190 1672666383 92.750000 1.10000000
191 1672666383 92.770000 0.33443428
192 1672666383 92.780000 1.61158033
193 1672666724 92.620000 0.26415235
194 1672666724 92.600000 0.00975325
195 1672667271 92.530000 0.21512394
196 1672667271 92.520000 3.44492536
197 1672667271 92.520000 1.10000000
198 1672667271 92.500000 13.00000000
199 1672667271 92.500000 4.17971070
200 1672667820 92.490000 0.90149481
201 1672668419 92.600000 0.05179685
202 1672668761 92.600000 0.20000000
203 1672668970 92.600000 1.00000000
204 1672668970 92.600000 0.36870115
205 1672669497 92.870000 0.27986569
206 1672669497 92.920000 0.23423604
207 1672670020 92.780000 0.24749482
208 1672670020 92.780000 0.22946945
209 1672670088 92.860000 0.39066985
210 1672670088 92.870000 0.60802221
211 1672670162 92.800000 0.19533493
212 1672670162 92.810000 1.57768714
213 1672671482 92.970000 0.05000000
214 1672671482 92.990000 0.25805040
215 1672671482 93.000000 0.05500000
216 1672671482 93.000000 1.17150537
217 1672671484 93.030000 1.90000000
218 1672671484 93.050000 0.10000000
219 1672671484 93.050000 0.09533493
220 1672671484 93.100000 5.50000000
221 1672671597 93.210000 0.28559636
222 1672671922 93.210000 0.06224165
223 1672672258 93.190000 0.05360162
224 1672672715 93.440000 0.35848855
225 1672673021 93.390000 0.24603359
226 1672673644 93.500000 1.25128947
227 1672673926 93.370000 0.10000000
228 1672673926 93.370000 0.10000000
229 1672674245 93.570000 3.00000000
230 1672674245 93.570000 0.27582332
231 1672674251 93.600000 3.00000000
232 1672674251 93.600000 0.27021894
233 1672674269 93.620000 0.05000000
234 1672674278 93.690000 0.16020297
235 1672674928 93.870000 16.00000000
236 1672674928 93.870000 0.42229400
237 1672674928 93.880000 3.00000000
238 1672674928 93.880000 0.83625840
239 1672675263 93.920000 0.15897954
240 1672675354 93.790000 0.05541000
241 1672675420 93.750000 3.42716504
242 1672675420 93.750000 1.10000000
243 1672675420 93.730000 0.30071309
244 1672675420 93.730000 0.26523930
245 1672675420 93.720000 3.43747306
246 1672675420 93.720000 2.00000000
247 1672675420 93.700000 2.00000000
248 1672675420 93.690000 3.50480003
249 1672675420 93.690000 17.74436742
250 1672675420 93.690000 2.53044755
251 1672675420 93.690000 5.59187611
252 1672675420 93.690000 1.10269129
253 1672675420 93.690000 5.00000000
254 1672675420 93.690000 10.00000000
255 1672675420 93.690000 1.99522711
256 1672677602 93.900000 0.33635572
257 1672677602 93.890000 0.02213283
258 1672677724 93.960000 0.20921217
259 1672677724 93.970000 4.01815113
260 1672677724 93.980000 1.10000000
261 1672677724 93.980000 4.99304515
262 1672678203 93.960000 0.53112491
263 1672678563 93.780000 1.06849022
264 1672678711 93.780000 0.25694338
265 1672678807 93.720000 0.21340200
266 1672678854 93.710000 0.20162914
267 1672678854 93.710000 1.00949171
268 1672678856 93.710000 1.48404486
269 1672678996 93.660000 0.21353800
270 1672679012 93.660000 0.21353800
271 1672679028 93.660000 0.21356100
272 1672679037 93.670000 0.11009220
273 1672679037 93.660000 1.78643900
274 1672679037 93.660000 3.52200393
275 1672679042 93.660000 0.21353800
276 1672679057 93.660000 0.21353800
277 1672679072 93.670000 0.21351600
278 1672679087 93.650000 0.11692327
279 1672679087 93.650000 0.09663773
280 1672679094 93.660000 4.95970000
281 1672679104 93.660000 0.21356100
282 1672679118 93.660000 0.21353800
283 1672679135 93.650000 0.11692328
284 1672679135 93.650000 0.09663772
285 1672679151 93.650000 0.21356100
286 1672679166 93.620000 0.21358400
287 1672679180 93.630000 0.21360700
288 1672679195 93.620000 0.21363000
289 1672679212 93.620000 0.21363000
290 1672679225 93.620000 0.21363000
291 1672679240 93.620000 0.21363000
292 1672679256 93.620000 0.21363000
293 1672679273 93.630000 0.21360700
294 1672679286 93.630000 0.21360700
295 1672679303 93.630000 0.21360700
296 1672679315 93.630000 1.39400000
297 1672679318 93.640000 0.21360700
298 1672679332 93.640000 0.21358400
299 1672679342 93.650000 40.00000000
300 1672679356 93.640000 0.21358400
301 1672679370 93.640000 0.21358400
302 1672679386 93.640000 0.21358400
303 1672679402 93.640000 0.21358400
304 1672679416 93.640000 0.21358400
305 1672679432 93.640000 0.21358400
306 1672679448 93.640000 0.21358400
307 1672679463 93.640000 0.21358400
308 1672679477 93.640000 0.21358400
309 1672679494 93.620000 0.21365200
310 1672679510 93.620000 0.21363000
311 1672679525 93.620000 0.21363000
312 1672679540 93.630000 0.21363000
313 1672679554 93.630000 0.21360700
314 1672679570 93.630000 0.21360700
315 1672679585 93.630000 0.21360700
316 1672679600 93.630000 0.21360700
317 1672679615 93.630000 0.21360700
318 1672679632 93.630000 0.21360700
319 1672679646 93.620000 0.21363000
320 1672679661 93.630000 0.21360700
321 1672679676 93.630000 0.21360700
322 1672679691 93.630000 0.21360700
323 1672679707 93.620000 0.21363000
324 1672679726 93.620000 0.21363000
325 1672679741 93.620000 0.21360700
326 1672679757 93.620000 0.21363000
327 1672679778 93.640000 0.21360700
328 1672679794 93.660000 0.21360700
329 1672679809 93.680000 0.21349300
330 1672679821 93.700000 0.21340200
331 1672679821 93.700000 0.21353800
332 1672679821 93.700000 0.21353800
333 1672679821 93.700000 0.21356100
334 1672679821 93.700000 0.21353800
335 1672679821 93.700000 0.21353800
336 1672679821 93.700000 0.21351600
337 1672679821 93.700000 0.21356100
338 1672679821 93.700000 0.21356100
339 1672679821 93.700000 0.21353800
340 1672679821 93.700000 0.21356100
341 1672679821 93.700000 0.21356100
342 1672679821 93.700000 0.21358400
343 1672679821 93.700000 0.21360700
344 1672679821 93.700000 0.21363000
345 1672679821 93.700000 0.21363000
346 1672679821 93.700000 0.21363000
347 1672679821 93.700000 0.21363000
348 1672679821 93.700000 0.21363000
349 1672679821 93.700000 0.21360700
350 1672679821 93.700000 0.21360700
351 1672679821 93.700000 0.21360700
352 1672679821 93.700000 0.21360700
353 1672679821 93.700000 0.21358400
354 1672679821 93.700000 0.21358400
355 1672679821 93.700000 0.21358400
356 1672679821 93.700000 0.21358400
357 1672679821 93.700000 0.21358400
358 1672679821 93.700000 0.21358400
359 1672679821 93.700000 0.21358400
360 1672679821 93.700000 0.21358400
361 1672679821 93.700000 0.21358400
362 1672679821 93.700000 0.21358400
363 1672679821 93.700000 0.21365200
364 1672679821 93.700000 0.21363000
365 1672679821 93.700000 0.21363000
366 1672679821 93.700000 0.21363000
367 1672679821 93.700000 0.21360700
368 1672679821 93.700000 0.21360700
369 1672679821 93.700000 0.21360700
370 1672679821 93.700000 0.21360700
371 1672679821 93.700000 0.21360700
372 1672679821 93.700000 0.21360700
373 1672679821 93.700000 0.21363000
374 1672679821 93.700000 0.21360700
375 1672679821 93.700000 0.21360700
376 1672679821 93.700000 0.21360700
377 1672679821 93.700000 0.21363000
378 1672679821 93.700000 0.21363000
379 1672679821 93.700000 0.21360700
380 1672679821 93.700000 0.21363000
381 1672679821 93.700000 0.21360700
382 1672679821 93.700000 0.21360700
383 1672679821 93.700000 0.21349300
384 1672679829 93.690000 0.21349300
385 1672679943 93.730000 0.10000000
386 1672679943 93.740000 0.35573142
387 1672680124 93.760000 0.41611649
388 1672680124 93.770000 1.10000000
389 1672680124 93.770000 1.00289427
390 1672680599 93.710000 40.00000000
391 1672680599 93.710000 40.00000000
392 1672681203 93.760000 0.30635902
393 1672681203 93.770000 0.68274662
394 1672681262 93.710000 1.01899965
395 1672681304 93.700000 40.00000000
396 1672681323 93.750000 0.57938646
397 1672681386 93.750000 1.48381671
398 1672681458 93.750000 0.01067000
399 1672681669 93.630000 40.00000000
400 1672682646 93.650000 0.17545586
401 1672682646 93.660000 2.04572869
402 1672682700 93.660000 0.43630185
403 1672683062 93.520000 0.21349300
404 1672683333 93.400000 40.00000000
405 1672683540 93.370000 0.37323978
406 1672683540 93.360000 0.78018965
407 1672683664 93.400000 40.00000000
408 1672683664 93.400000 40.00000000
409 1672683912 93.360000 40.00000000
410 1672684035 93.380000 0.10000000
411 1672684681 93.400000 40.00000000
412 1672684681 93.400000 5.00000000
413 1672684895 93.450000 0.28232122
414 1672684895 93.450000 0.16798232
415 1672684895 93.460000 2.74269478
416 1672686428 93.590000 0.32054706
417 1672686593 93.610000 0.18606342
418 1672686593 93.600000 1.10000000
419 1672686593 93.600000 0.64000000
420 1672686593 93.600000 2.00000000
421 1672686593 93.600000 0.56906893
422 1672686763 93.580000 1.10000000
423 1672686763 93.570000 0.18606343
424 1672686763 93.560000 3.00000000
425 1672686763 93.550000 6.66393657
426 1672686853 93.600000 1.04000000
427 1672686853 93.580000 0.31000000
428 1672687010 93.660000 0.51441726
429 1672687010 93.670000 0.49536629
430 1672687144 93.660000 0.25720863
431 1672687144 93.670000 0.13025611
432 1672687924 93.380000 1.38703774
433 1672688043 93.450000 1.10000000
434 1672688043 93.470000 9.62310000
435 1672688163 93.450000 0.12860432
436 1672688163 93.460000 0.74330839
437 1672688463 93.450000 0.12860432
438 1672688463 93.460000 2.43888543
439 1672689094 93.500000 0.48000000
440 1672689094 93.500000 1.52000000
441 1672689122 93.500000 0.69746509
442 1672689605 93.380000 0.85913567
443 1672690623 93.360000 1.55373756
444 1672690623 93.380000 0.97334211
445 1672690731 93.280000 0.37258400
446 1672691284 93.510000 1.10000000
447 1672691284 93.520000 0.17017217
448 1672691284 93.530000 1.86482783
449 1672693176 93.340000 0.08092000
450 1672693262 93.400000 0.43291938
451 1672694405 93.280000 0.40741109
452 1672694828 93.200000 0.41321135
453 1672694828 93.200000 0.12328865
454 1672695290 93.240000 0.20660567
455 1672695290 93.230000 1.10000000
456 1672695290 93.220000 3.00000000
457 1672695290 93.210000 3.44792866
458 1672695290 93.200000 3.00000000
459 1672695290 93.200000 1.34083632
460 1672695290 93.200000 2.52411935
461 1672695567 93.230000 0.09530603
462 1672695997 93.310000 0.10280546
463 1672696328 93.270000 0.10705255
464 1672696328 93.260000 0.00294745
465 1672697520 93.390000 0.38943251
466 1672697520 93.400000 1.10000000
467 1672697520 93.400000 0.03084631
468 1672697584 93.400000 0.21413300
469 1672697656 93.400000 0.21413300
470 1672697671 93.400000 0.21314117
471 1672698111 93.500000 0.34000000
472 1672698111 93.500000 0.26617473
473 1672698111 93.510000 3.39382527
474 1672698284 93.430000 0.38132990
475 1672698284 93.420000 1.10000000
476 1672698284 93.400000 12.34766434
477 1672698922 93.220000 0.11677000
478 1672699624 93.220000 0.32375231
479 1672699624 93.230000 1.10000000
480 1672699624 93.230000 0.50000000
481 1672699624 93.240000 0.07624769
482 1672701005 93.000000 0.34305825
483 1672701389 93.180000 0.26786932
484 1672701423 93.180000 1.00000000
485 1672701423 93.190000 1.10000000
486 1672701423 93.190000 0.44235389