• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

freqtrade / freqtrade / 6181253459

08 Sep 2023 06:04AM UTC coverage: 94.614% (+0.06%) from 94.556%
6181253459

push

github-actions

web-flow
Merge pull request #9159 from stash86/fix-adjust

remove old codes when we only can do partial entries

2 of 2 new or added lines in 1 file covered. (100.0%)

19114 of 20202 relevant lines covered (94.61%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.07
/freqtrade/data/history/history_utils.py
1
import logging
1✔
2
import operator
1✔
3
from datetime import datetime, timedelta
1✔
4
from pathlib import Path
1✔
5
from typing import Dict, List, Optional, Tuple
1✔
6

7
from pandas import DataFrame, concat
1✔
8

9
from freqtrade.configuration import TimeRange
1✔
10
from freqtrade.constants import (DATETIME_PRINT_FORMAT, DEFAULT_DATAFRAME_COLUMNS,
1✔
11
                                 DL_DATA_TIMEFRAMES, Config)
12
from freqtrade.data.converter import (clean_ohlcv_dataframe, ohlcv_to_dataframe,
1✔
13
                                      trades_df_remove_duplicates, trades_list_to_df,
14
                                      trades_to_ohlcv)
15
from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler
1✔
16
from freqtrade.enums import CandleType
1✔
17
from freqtrade.exceptions import OperationalException
1✔
18
from freqtrade.exchange import Exchange
1✔
19
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
1✔
20
from freqtrade.util import dt_ts, format_ms_time
1✔
21
from freqtrade.util.binance_mig import migrate_binance_futures_data
1✔
22
from freqtrade.util.datetime_helpers import dt_now
1✔
23

24

25
logger = logging.getLogger(__name__)
1✔
26

27

28
def load_pair_history(pair: str,
1✔
29
                      timeframe: str,
30
                      datadir: Path, *,
31
                      timerange: Optional[TimeRange] = None,
32
                      fill_up_missing: bool = True,
33
                      drop_incomplete: bool = False,
34
                      startup_candles: int = 0,
35
                      data_format: Optional[str] = None,
36
                      data_handler: Optional[IDataHandler] = None,
37
                      candle_type: CandleType = CandleType.SPOT
38
                      ) -> DataFrame:
39
    """
40
    Load cached ohlcv history for the given pair.
41

42
    :param pair: Pair to load data for
43
    :param timeframe: Timeframe (e.g. "5m")
44
    :param datadir: Path to the data storage location.
45
    :param data_format: Format of the data. Ignored if data_handler is set.
46
    :param timerange: Limit data to be loaded to this timerange
47
    :param fill_up_missing: Fill missing values with "No action"-candles
48
    :param drop_incomplete: Drop last candle assuming it may be incomplete.
49
    :param startup_candles: Additional candles to load at the start of the period
50
    :param data_handler: Initialized data-handler to use.
51
                         Will be initialized from data_format if not set
52
    :param candle_type: Any of the enum CandleType (must match trading mode!)
53
    :return: DataFrame with ohlcv data, or empty DataFrame
54
    """
55
    data_handler = get_datahandler(datadir, data_format, data_handler)
1✔
56

57
    return data_handler.ohlcv_load(pair=pair,
1✔
58
                                   timeframe=timeframe,
59
                                   timerange=timerange,
60
                                   fill_missing=fill_up_missing,
61
                                   drop_incomplete=drop_incomplete,
62
                                   startup_candles=startup_candles,
63
                                   candle_type=candle_type,
64
                                   )
65

66

67
def load_data(datadir: Path,
1✔
68
              timeframe: str,
69
              pairs: List[str], *,
70
              timerange: Optional[TimeRange] = None,
71
              fill_up_missing: bool = True,
72
              startup_candles: int = 0,
73
              fail_without_data: bool = False,
74
              data_format: str = 'feather',
75
              candle_type: CandleType = CandleType.SPOT,
76
              user_futures_funding_rate: Optional[int] = None,
77
              ) -> Dict[str, DataFrame]:
78
    """
79
    Load ohlcv history data for a list of pairs.
80

81
    :param datadir: Path to the data storage location.
82
    :param timeframe: Timeframe (e.g. "5m")
83
    :param pairs: List of pairs to load
84
    :param timerange: Limit data to be loaded to this timerange
85
    :param fill_up_missing: Fill missing values with "No action"-candles
86
    :param startup_candles: Additional candles to load at the start of the period
87
    :param fail_without_data: Raise OperationalException if no data is found.
88
    :param data_format: Data format which should be used. Defaults to json
89
    :param candle_type: Any of the enum CandleType (must match trading mode!)
90
    :return: dict(<pair>:<Dataframe>)
91
    """
92
    result: Dict[str, DataFrame] = {}
1✔
93
    if startup_candles > 0 and timerange:
1✔
94
        logger.info(f'Using indicator startup period: {startup_candles} ...')
1✔
95

96
    data_handler = get_datahandler(datadir, data_format)
1✔
97

98
    for pair in pairs:
1✔
99
        hist = load_pair_history(pair=pair, timeframe=timeframe,
1✔
100
                                 datadir=datadir, timerange=timerange,
101
                                 fill_up_missing=fill_up_missing,
102
                                 startup_candles=startup_candles,
103
                                 data_handler=data_handler,
104
                                 candle_type=candle_type,
105
                                 )
106
        if not hist.empty:
1✔
107
            result[pair] = hist
1✔
108
        else:
109
            if candle_type is CandleType.FUNDING_RATE and user_futures_funding_rate is not None:
1✔
110
                logger.warn(f"{pair} using user specified [{user_futures_funding_rate}]")
×
111
            elif candle_type not in (CandleType.SPOT, CandleType.FUTURES):
1✔
112
                result[pair] = DataFrame(columns=["date", "open", "close", "high", "low", "volume"])
1✔
113

114
    if fail_without_data and not result:
1✔
115
        raise OperationalException("No data found. Terminating.")
1✔
116
    return result
1✔
117

118

119
def refresh_data(*, datadir: Path,
1✔
120
                 timeframe: str,
121
                 pairs: List[str],
122
                 exchange: Exchange,
123
                 data_format: Optional[str] = None,
124
                 timerange: Optional[TimeRange] = None,
125
                 candle_type: CandleType,
126
                 ) -> None:
127
    """
128
    Refresh ohlcv history data for a list of pairs.
129

130
    :param datadir: Path to the data storage location.
131
    :param timeframe: Timeframe (e.g. "5m")
132
    :param pairs: List of pairs to load
133
    :param exchange: Exchange object
134
    :param data_format: dataformat to use
135
    :param timerange: Limit data to be loaded to this timerange
136
    :param candle_type: Any of the enum CandleType (must match trading mode!)
137
    """
138
    data_handler = get_datahandler(datadir, data_format)
1✔
139
    for idx, pair in enumerate(pairs):
1✔
140
        process = f'{idx}/{len(pairs)}'
1✔
141
        _download_pair_history(pair=pair, process=process,
1✔
142
                               timeframe=timeframe, datadir=datadir,
143
                               timerange=timerange, exchange=exchange, data_handler=data_handler,
144
                               candle_type=candle_type)
145

146

147
def _load_cached_data_for_updating(
1✔
148
    pair: str,
149
    timeframe: str,
150
    timerange: Optional[TimeRange],
151
    data_handler: IDataHandler,
152
    candle_type: CandleType,
153
    prepend: bool = False,
154
) -> Tuple[DataFrame, Optional[int], Optional[int]]:
155
    """
156
    Load cached data to download more data.
157
    If timerange is passed in, checks whether data from an before the stored data will be
158
    downloaded.
159
    If that's the case then what's available should be completely overwritten.
160
    Otherwise downloads always start at the end of the available data to avoid data gaps.
161
    Note: Only used by download_pair_history().
162
    """
163
    start = None
1✔
164
    end = None
1✔
165
    if timerange:
1✔
166
        if timerange.starttype == 'date':
1✔
167
            start = timerange.startdt
1✔
168
        if timerange.stoptype == 'date':
1✔
169
            end = timerange.stopdt
1✔
170

171
    # Intentionally don't pass timerange in - since we need to load the full dataset.
172
    data = data_handler.ohlcv_load(pair, timeframe=timeframe,
1✔
173
                                   timerange=None, fill_missing=False,
174
                                   drop_incomplete=True, warn_no_data=False,
175
                                   candle_type=candle_type)
176
    if not data.empty:
1✔
177
        if not prepend and start and start < data.iloc[0]['date']:
1✔
178
            # Earlier data than existing data requested, redownload all
179
            data = DataFrame(columns=DEFAULT_DATAFRAME_COLUMNS)
1✔
180
        else:
181
            if prepend:
1✔
182
                end = data.iloc[0]['date']
1✔
183
            else:
184
                start = data.iloc[-1]['date']
1✔
185
    start_ms = int(start.timestamp() * 1000) if start else None
1✔
186
    end_ms = int(end.timestamp() * 1000) if end else None
1✔
187
    return data, start_ms, end_ms
1✔
188

189

190
def _download_pair_history(pair: str, *,
1✔
191
                           datadir: Path,
192
                           exchange: Exchange,
193
                           timeframe: str = '5m',
194
                           process: str = '',
195
                           new_pairs_days: int = 30,
196
                           data_handler: Optional[IDataHandler] = None,
197
                           timerange: Optional[TimeRange] = None,
198
                           candle_type: CandleType,
199
                           erase: bool = False,
200
                           prepend: bool = False,
201
                           ) -> bool:
202
    """
203
    Download latest candles from the exchange for the pair and timeframe passed in parameters
204
    The data is downloaded starting from the last correct data that
205
    exists in a cache. If timerange starts earlier than the data in the cache,
206
    the full data will be redownloaded
207

208
    :param pair: pair to download
209
    :param timeframe: Timeframe (e.g "5m")
210
    :param timerange: range of time to download
211
    :param candle_type: Any of the enum CandleType (must match trading mode!)
212
    :param erase: Erase existing data
213
    :return: bool with success state
214
    """
215
    data_handler = get_datahandler(datadir, data_handler=data_handler)
1✔
216

217
    try:
1✔
218
        if erase:
1✔
219
            if data_handler.ohlcv_purge(pair, timeframe, candle_type=candle_type):
×
220
                logger.info(f'Deleting existing data for pair {pair}, {timeframe}, {candle_type}.')
×
221

222
        data, since_ms, until_ms = _load_cached_data_for_updating(
1✔
223
            pair, timeframe, timerange,
224
            data_handler=data_handler,
225
            candle_type=candle_type,
226
            prepend=prepend)
227

228
        logger.info(f'({process}) - Download history data for "{pair}", {timeframe}, '
1✔
229
                    f'{candle_type} and store in {datadir}. '
230
                    f'From {format_ms_time(since_ms) if since_ms else "start"} to '
231
                    f'{format_ms_time(until_ms) if until_ms else "now"}'
232
                    )
233

234
        logger.debug("Current Start: %s",
1✔
235
                     f"{data.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}"
236
                     if not data.empty else 'None')
237
        logger.debug("Current End: %s",
1✔
238
                     f"{data.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}"
239
                     if not data.empty else 'None')
240

241
        # Default since_ms to 30 days if nothing is given
242
        new_data = exchange.get_historic_ohlcv(pair=pair,
1✔
243
                                               timeframe=timeframe,
244
                                               since_ms=since_ms if since_ms else
245
                                               int((datetime.now() - timedelta(days=new_pairs_days)
246
                                                    ).timestamp()) * 1000,
247
                                               is_new_pair=data.empty,
248
                                               candle_type=candle_type,
249
                                               until_ms=until_ms if until_ms else None
250
                                               )
251
        # TODO: Maybe move parsing to exchange class (?)
252
        new_dataframe = ohlcv_to_dataframe(new_data, timeframe, pair,
1✔
253
                                           fill_missing=False, drop_incomplete=True)
254
        if data.empty:
1✔
255
            data = new_dataframe
1✔
256
        else:
257
            # Run cleaning again to ensure there were no duplicate candles
258
            # Especially between existing and new data.
259
            data = clean_ohlcv_dataframe(concat([data, new_dataframe], axis=0), timeframe, pair,
1✔
260
                                         fill_missing=False, drop_incomplete=False)
261

262
        logger.debug("New Start: %s",
1✔
263
                     f"{data.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}"
264
                     if not data.empty else 'None')
265
        logger.debug("New End: %s",
1✔
266
                     f"{data.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}"
267
                     if not data.empty else 'None')
268

269
        data_handler.ohlcv_store(pair, timeframe, data=data, candle_type=candle_type)
1✔
270
        return True
1✔
271

272
    except Exception:
1✔
273
        logger.exception(
1✔
274
            f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}.'
275
        )
276
        return False
1✔
277

278

279
def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes: List[str],
1✔
280
                                datadir: Path, trading_mode: str,
281
                                timerange: Optional[TimeRange] = None,
282
                                new_pairs_days: int = 30, erase: bool = False,
283
                                data_format: Optional[str] = None,
284
                                prepend: bool = False,
285
                                ) -> List[str]:
286
    """
287
    Refresh stored ohlcv data for backtesting and hyperopt operations.
288
    Used by freqtrade download-data subcommand.
289
    :return: List of pairs that are not available.
290
    """
291
    pairs_not_available = []
1✔
292
    data_handler = get_datahandler(datadir, data_format)
1✔
293
    candle_type = CandleType.get_default(trading_mode)
1✔
294
    process = ''
1✔
295
    for idx, pair in enumerate(pairs, start=1):
1✔
296
        if pair not in exchange.markets:
1✔
297
            pairs_not_available.append(pair)
1✔
298
            logger.info(f"Skipping pair {pair}...")
1✔
299
            continue
1✔
300
        for timeframe in timeframes:
1✔
301

302
            logger.debug(f'Downloading pair {pair}, {candle_type}, interval {timeframe}.')
1✔
303
            process = f'{idx}/{len(pairs)}'
1✔
304
            _download_pair_history(pair=pair, process=process,
1✔
305
                                   datadir=datadir, exchange=exchange,
306
                                   timerange=timerange, data_handler=data_handler,
307
                                   timeframe=str(timeframe), new_pairs_days=new_pairs_days,
308
                                   candle_type=candle_type,
309
                                   erase=erase, prepend=prepend)
310
        if trading_mode == 'futures':
1✔
311
            # Predefined candletype (and timeframe) depending on exchange
312
            # Downloads what is necessary to backtest based on futures data.
313
            tf_mark = exchange.get_option('mark_ohlcv_timeframe')
1✔
314
            fr_candle_type = CandleType.from_string(exchange.get_option('mark_ohlcv_price'))
1✔
315
            # All exchanges need FundingRate for futures trading.
316
            # The timeframe is aligned to the mark-price timeframe.
317
            for funding_candle_type in (CandleType.FUNDING_RATE, fr_candle_type):
1✔
318
                _download_pair_history(pair=pair, process=process,
1✔
319
                                       datadir=datadir, exchange=exchange,
320
                                       timerange=timerange, data_handler=data_handler,
321
                                       timeframe=str(tf_mark), new_pairs_days=new_pairs_days,
322
                                       candle_type=funding_candle_type,
323
                                       erase=erase, prepend=prepend)
324

325
    return pairs_not_available
1✔
326

327

328
def _download_trades_history(exchange: Exchange,
1✔
329
                             pair: str, *,
330
                             new_pairs_days: int = 30,
331
                             timerange: Optional[TimeRange] = None,
332
                             data_handler: IDataHandler
333
                             ) -> bool:
334
    """
335
    Download trade history from the exchange.
336
    Appends to previously downloaded trades data.
337
    """
338
    try:
1✔
339

340
        until = None
1✔
341
        since = 0
1✔
342
        if timerange:
1✔
343
            if timerange.starttype == 'date':
1✔
344
                since = timerange.startts * 1000
1✔
345
            if timerange.stoptype == 'date':
1✔
346
                until = timerange.stopts * 1000
×
347

348
        trades = data_handler.trades_load(pair)
1✔
349

350
        # TradesList columns are defined in constants.DEFAULT_TRADES_COLUMNS
351
        # DEFAULT_TRADES_COLUMNS: 0 -> timestamp
352
        # DEFAULT_TRADES_COLUMNS: 1 -> id
353

354
        if not trades.empty and since > 0 and since < trades.iloc[0]['timestamp']:
1✔
355
            # since is before the first trade
356
            logger.info(f"Start ({trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}) earlier than "
1✔
357
                        f"available data. Redownloading trades for {pair}...")
358
            trades = trades_list_to_df([])
1✔
359

360
        from_id = trades.iloc[-1]['id'] if not trades.empty else None
1✔
361
        if not trades.empty and since < trades.iloc[-1]['timestamp']:
1✔
362
            # Reset since to the last available point
363
            # - 5 seconds (to ensure we're getting all trades)
364
            since = trades.iloc[-1]['timestamp'] - (5 * 1000)
1✔
365
            logger.info(f"Using last trade date -5s - Downloading trades for {pair} "
1✔
366
                        f"since: {format_ms_time(since)}.")
367

368
        if not since:
1✔
369
            since = dt_ts(dt_now() - timedelta(days=new_pairs_days))
1✔
370

371
        logger.debug("Current Start: %s", 'None' if trades.empty else
1✔
372
                     f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}")
373
        logger.debug("Current End: %s", 'None' if trades.empty else
1✔
374
                     f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}")
375
        logger.info(f"Current Amount of trades: {len(trades)}")
1✔
376

377
        # Default since_ms to 30 days if nothing is given
378
        new_trades = exchange.get_historic_trades(pair=pair,
1✔
379
                                                  since=since,
380
                                                  until=until,
381
                                                  from_id=from_id,
382
                                                  )
383
        new_trades_df = trades_list_to_df(new_trades[1])
1✔
384
        trades = concat([trades, new_trades_df], axis=0)
1✔
385
        # Remove duplicates to make sure we're not storing data we don't need
386
        trades = trades_df_remove_duplicates(trades)
1✔
387
        data_handler.trades_store(pair, data=trades)
1✔
388

389
        logger.debug("New Start: %s", 'None' if trades.empty else
1✔
390
                     f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}")
391
        logger.debug("New End: %s", 'None' if trades.empty else
1✔
392
                     f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}")
393
        logger.info(f"New Amount of trades: {len(trades)}")
1✔
394
        return True
1✔
395

396
    except Exception:
1✔
397
        logger.exception(
1✔
398
            f'Failed to download historic trades for pair: "{pair}". '
399
        )
400
        return False
1✔
401

402

403
def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir: Path,
1✔
404
                                 timerange: TimeRange, new_pairs_days: int = 30,
405
                                 erase: bool = False, data_format: str = 'feather') -> List[str]:
406
    """
407
    Refresh stored trades data for backtesting and hyperopt operations.
408
    Used by freqtrade download-data subcommand.
409
    :return: List of pairs that are not available.
410
    """
411
    pairs_not_available = []
1✔
412
    data_handler = get_datahandler(datadir, data_format=data_format)
1✔
413
    for pair in pairs:
1✔
414
        if pair not in exchange.markets:
1✔
415
            pairs_not_available.append(pair)
1✔
416
            logger.info(f"Skipping pair {pair}...")
1✔
417
            continue
1✔
418

419
        if erase:
1✔
420
            if data_handler.trades_purge(pair):
1✔
421
                logger.info(f'Deleting existing data for pair {pair}.')
1✔
422

423
        logger.info(f'Downloading trades for pair {pair}.')
1✔
424
        _download_trades_history(exchange=exchange,
1✔
425
                                 pair=pair,
426
                                 new_pairs_days=new_pairs_days,
427
                                 timerange=timerange,
428
                                 data_handler=data_handler)
429
    return pairs_not_available
1✔
430

431

432
def convert_trades_to_ohlcv(
1✔
433
    pairs: List[str],
434
    timeframes: List[str],
435
    datadir: Path,
436
    timerange: TimeRange,
437
    erase: bool = False,
438
    data_format_ohlcv: str = 'feather',
439
    data_format_trades: str = 'feather',
440
    candle_type: CandleType = CandleType.SPOT
441
) -> None:
442
    """
443
    Convert stored trades data to ohlcv data
444
    """
445
    data_handler_trades = get_datahandler(datadir, data_format=data_format_trades)
1✔
446
    data_handler_ohlcv = get_datahandler(datadir, data_format=data_format_ohlcv)
1✔
447

448
    for pair in pairs:
1✔
449
        trades = data_handler_trades.trades_load(pair)
1✔
450
        for timeframe in timeframes:
1✔
451
            if erase:
1✔
452
                if data_handler_ohlcv.ohlcv_purge(pair, timeframe, candle_type=candle_type):
1✔
453
                    logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.')
1✔
454
            try:
1✔
455
                ohlcv = trades_to_ohlcv(trades, timeframe)
1✔
456
                # Store ohlcv
457
                data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv, candle_type=candle_type)
1✔
458
            except ValueError:
1✔
459
                logger.exception(f'Could not convert {pair} to OHLCV.')
1✔
460

461

462
def get_timerange(data: Dict[str, DataFrame]) -> Tuple[datetime, datetime]:
1✔
463
    """
464
    Get the maximum common timerange for the given backtest data.
465

466
    :param data: dictionary with preprocessed backtesting data
467
    :return: tuple containing min_date, max_date
468
    """
469
    timeranges = [
1✔
470
        (frame['date'].min().to_pydatetime(), frame['date'].max().to_pydatetime())
471
        for frame in data.values()
472
    ]
473
    return (min(timeranges, key=operator.itemgetter(0))[0],
1✔
474
            max(timeranges, key=operator.itemgetter(1))[1])
475

476

477
def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime,
1✔
478
                           max_date: datetime, timeframe_min: int) -> bool:
479
    """
480
    Validates preprocessed backtesting data for missing values and shows warnings about it that.
481

482
    :param data: preprocessed backtesting data (as DataFrame)
483
    :param pair: pair used for log output.
484
    :param min_date: start-date of the data
485
    :param max_date: end-date of the data
486
    :param timeframe_min: Timeframe in minutes
487
    """
488
    # total difference in minutes / timeframe-minutes
489
    expected_frames = int((max_date - min_date).total_seconds() // 60 // timeframe_min)
1✔
490
    found_missing = False
1✔
491
    dflen = len(data)
1✔
492
    if dflen < expected_frames:
1✔
493
        found_missing = True
1✔
494
        logger.warning("%s has missing frames: expected %s, got %s, that's %s missing values",
1✔
495
                       pair, expected_frames, dflen, expected_frames - dflen)
496
    return found_missing
1✔
497

498

499
def download_data_main(config: Config) -> None:
1✔
500

501
    timerange = TimeRange()
1✔
502
    if 'days' in config:
1✔
503
        time_since = (datetime.now() - timedelta(days=config['days'])).strftime("%Y%m%d")
1✔
504
        timerange = TimeRange.parse_timerange(f'{time_since}-')
1✔
505

506
    if 'timerange' in config:
1✔
507
        timerange = timerange.parse_timerange(config['timerange'])
1✔
508

509
    # Remove stake-currency to skip checks which are not relevant for datadownload
510
    config['stake_currency'] = ''
1✔
511

512
    pairs_not_available: List[str] = []
1✔
513

514
    # Init exchange
515
    from freqtrade.resolvers.exchange_resolver import ExchangeResolver
1✔
516
    exchange = ExchangeResolver.load_exchange(config, validate=False)
1✔
517
    available_pairs = [
1✔
518
        p for p in exchange.get_markets(
519
            tradable_only=True, active_only=not config.get('include_inactive')
520
            ).keys()
521
    ]
522

523
    expanded_pairs = dynamic_expand_pairlist(config, available_pairs)
1✔
524
    if 'timeframes' not in config:
1✔
525
        config['timeframes'] = DL_DATA_TIMEFRAMES
1✔
526

527
    # Manual validations of relevant settings
528
    if not config['exchange'].get('skip_pair_validation', False):
1✔
529
        exchange.validate_pairs(expanded_pairs)
1✔
530
    logger.info(f"About to download pairs: {expanded_pairs}, "
1✔
531
                f"intervals: {config['timeframes']} to {config['datadir']}")
532

533
    for timeframe in config['timeframes']:
1✔
534
        exchange.validate_timeframes(timeframe)
1✔
535

536
    # Start downloading
537
    try:
1✔
538
        if config.get('download_trades'):
1✔
539
            if config.get('trading_mode') == 'futures':
1✔
540
                raise OperationalException("Trade download not supported for futures.")
1✔
541
            pairs_not_available = refresh_backtest_trades_data(
1✔
542
                exchange, pairs=expanded_pairs, datadir=config['datadir'],
543
                timerange=timerange, new_pairs_days=config['new_pairs_days'],
544
                erase=bool(config.get('erase')), data_format=config['dataformat_trades'])
545

546
            # Convert downloaded trade data to different timeframes
547
            convert_trades_to_ohlcv(
1✔
548
                pairs=expanded_pairs, timeframes=config['timeframes'],
549
                datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
550
                data_format_ohlcv=config['dataformat_ohlcv'],
551
                data_format_trades=config['dataformat_trades'],
552
            )
553
        else:
554
            if not exchange.get_option('ohlcv_has_history', True):
1✔
555
                raise OperationalException(
1✔
556
                    f"Historic klines not available for {exchange.name}. "
557
                    "Please use `--dl-trades` instead for this exchange "
558
                    "(will unfortunately take a long time)."
559
                    )
560
            migrate_binance_futures_data(config)
1✔
561
            pairs_not_available = refresh_backtest_ohlcv_data(
1✔
562
                exchange, pairs=expanded_pairs, timeframes=config['timeframes'],
563
                datadir=config['datadir'], timerange=timerange,
564
                new_pairs_days=config['new_pairs_days'],
565
                erase=bool(config.get('erase')), data_format=config['dataformat_ohlcv'],
566
                trading_mode=config.get('trading_mode', 'spot'),
567
                prepend=config.get('prepend_data', False)
568
            )
569
    finally:
570
        if pairs_not_available:
1✔
571
            logger.info(f"Pairs [{','.join(pairs_not_available)}] not available "
1✔
572
                        f"on exchange {exchange.name}.")
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc