diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index bdb8c3464..c087c87ff 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -11,7 +11,7 @@ import pandas as pd from pandas import DataFrame, to_datetime from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType logger = logging.getLogger(__name__) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 3e6eccdc8..12b02f744 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -13,8 +13,7 @@ from pandas import DataFrame from freqtrade.configuration import TimeRange from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe from freqtrade.data.history import load_pair_history -from freqtrade.enums import RunMode -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType, RunMode from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.exchange import Exchange, timeframe_to_seconds @@ -223,7 +222,7 @@ class DataProvider: raise OperationalException(NO_EXCHANGE_EXCEPTION) if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): return self._exchange.klines( - (pair, timeframe or self._config['timeframe'], candle_type), + (pair, timeframe or self._config['timeframe'], CandleType.from_string(candle_type)), copy=copy ) else: diff --git a/freqtrade/data/history/hdf5datahandler.py b/freqtrade/data/history/hdf5datahandler.py index b9585e22a..fe840527f 100644 --- a/freqtrade/data/history/hdf5datahandler.py +++ b/freqtrade/data/history/hdf5datahandler.py @@ -9,7 +9,7 @@ import pandas as pd from freqtrade.configuration import TimeRange from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, ListPairsWithTimeframes, TradeList) -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType from .idatahandler import IDataHandler diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 3fdb36e58..e9c31259f 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -12,7 +12,7 @@ from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS from freqtrade.data.converter import (clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_remove_duplicates, trades_to_ohlcv) from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.exchange import Exchange from freqtrade.misc import format_ms_time diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index 72758a325..239c9ab71 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -17,7 +17,7 @@ from freqtrade import misc from freqtrade.configuration import TimeRange from freqtrade.constants import ListPairsWithTimeframes, TradeList from freqtrade.data.converter import clean_ohlcv_dataframe, trades_remove_duplicates, trim_dataframe -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType from freqtrade.exchange import timeframe_to_seconds diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py index b4775f271..1ed5ae023 100644 --- a/freqtrade/data/history/jsondatahandler.py +++ b/freqtrade/data/history/jsondatahandler.py @@ -10,7 +10,7 @@ from freqtrade import misc from freqtrade.configuration import TimeRange from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, ListPairsWithTimeframes, TradeList from freqtrade.data.converter import trades_dict_to_list -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType from .idatahandler import IDataHandler diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index ad18efcf5..db6cda4d7 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -8,7 +8,7 @@ from typing import Dict, List, Optional, Tuple import arrow import ccxt -from freqtrade.enums import Collateral, TradingMode +from freqtrade.enums import CandleType, Collateral, TradingMode from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, OperationalException, TemporaryError) from freqtrade.exchange import Exchange @@ -197,14 +197,13 @@ class Binance(Exchange): raise OperationalException(e) from e async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool = False, - raise_: bool = False, - candle_type: str = '' + since_ms: int, candle_type: CandleType, + is_new_pair: bool = False, raise_: bool = False, ) -> Tuple[str, str, str, List]: """ Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date Does not work for other exchanges, which don't return the earliest data when called with "0" - :param candle_type: '', mark, index, premiumIndex, or funding_rate + :param candle_type: Any of the enum CandleType (must match trading mode!) """ if is_new_pair: x = await self._async_get_candle_history(pair, timeframe, 0, candle_type) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 329cecc3d..e75735c04 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -20,9 +20,9 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU from pandas import DataFrame from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, - ListPairsWithTimeframes) + ListPairsWithTimeframes, PairWithTimeframe) from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list -from freqtrade.enums import Collateral, TradingMode +from freqtrade.enums import CandleType, Collateral, TradingMode from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, InvalidOrderException, OperationalException, PricingError, RetryableOrderError, TemporaryError) @@ -92,7 +92,7 @@ class Exchange: self._config.update(config) # Holds last candle refreshed time of each pair - self._pairs_last_refresh_time: Dict[Tuple[str, str, str], int] = {} + self._pairs_last_refresh_time: Dict[PairWithTimeframe, int] = {} # Timestamp of last markets refresh self._last_markets_refresh: int = 0 @@ -105,7 +105,7 @@ class Exchange: self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800) # Holds candles - self._klines: Dict[Tuple[str, str, str], DataFrame] = {} + self._klines: Dict[PairWithTimeframe, DataFrame] = {} # Holds all open sell orders for dry_run self._dry_run_open_orders: Dict[str, Any] = {} @@ -359,7 +359,7 @@ class Exchange: or (self.trading_mode == TradingMode.FUTURES and self.market_is_future(market)) ) - def klines(self, pair_interval: Tuple[str, str, str], copy: bool = True) -> DataFrame: + def klines(self, pair_interval: PairWithTimeframe, copy: bool = True) -> DataFrame: if pair_interval in self._klines: return self._klines[pair_interval].copy() if copy else self._klines[pair_interval] else: @@ -1314,8 +1314,8 @@ class Exchange: # Historic data def get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool = False, - candle_type: str = '') -> List: + since_ms: int, candle_type: CandleType, + is_new_pair: bool = False) -> List: """ Get candle history using asyncio and returns the list of candles. Handles all async work for this. @@ -1327,7 +1327,7 @@ class Exchange: :return: List with candle (OHLCV) data """ data: List - pair, timeframe, candle_type, data = asyncio.get_event_loop().run_until_complete( + pair, _, _, data = asyncio.get_event_loop().run_until_complete( self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, since_ms=since_ms, is_new_pair=is_new_pair, candle_type=candle_type)) @@ -1335,13 +1335,13 @@ class Exchange: return data def get_historic_ohlcv_as_df(self, pair: str, timeframe: str, - since_ms: int, candle_type: str = '') -> DataFrame: + since_ms: int, candle_type: CandleType) -> DataFrame: """ Minimal wrapper around get_historic_ohlcv - converting the result into a dataframe :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from - :param candle_type: '', mark, index, premiumIndex, or funding_rate + :param candle_type: Any of the enum CandleType (must match trading mode!) :return: OHLCV DataFrame """ ticks = self.get_historic_ohlcv(pair, timeframe, since_ms=since_ms, candle_type=candle_type) @@ -1349,14 +1349,13 @@ class Exchange: drop_incomplete=self._ohlcv_partial_candle) async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, - since_ms: int, is_new_pair: bool = False, - raise_: bool = False, - candle_type: str = '' + since_ms: int, candle_type: CandleType, + is_new_pair: bool = False, raise_: bool = False, ) -> Tuple[str, str, str, List]: """ Download historic ohlcv :param is_new_pair: used by binance subclass to allow "fast" new pair downloading - :param candle_type: '', mark, index, premiumIndex, or funding_rate + :param candle_type: Any of the enum CandleType (must match trading mode!) """ one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) @@ -1391,8 +1390,8 @@ class Exchange: def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *, since_ms: Optional[int] = None, cache: bool = True, - candle_type: str = '' - ) -> Dict[Tuple[str, str, str], DataFrame]: + candle_type: CandleType = CandleType.SPOT_ + ) -> Dict[PairWithTimeframe, DataFrame]: """ Refresh in-memory OHLCV asynchronously and set `_klines` with the result Loops asynchronously over pair_list and downloads all pairs async (semi-parallel). @@ -1410,7 +1409,7 @@ class Exchange: # Gather coroutines to run for pair, timeframe, candle_type in set(pair_list): if ((pair, timeframe, candle_type) not in self._klines or not cache - or self._now_is_time_to_refresh(pair, timeframe)): + or self._now_is_time_to_refresh(pair, timeframe, candle_type)): if not since_ms and self.required_candle_call_count > 1: # Multiple calls for one pair - to get more history one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) @@ -1463,12 +1462,7 @@ class Exchange: return results_df - def _now_is_time_to_refresh( - self, - pair: str, - timeframe: str, - candle_type: str = '' - ) -> bool: + def _now_is_time_to_refresh(self, pair: str, timeframe: str, candle_type: CandleType) -> bool: # Timeframe in seconds interval_in_sec = timeframe_to_seconds(timeframe) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 95c92ee0b..43401be46 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -17,9 +17,7 @@ from freqtrade.data import history from freqtrade.data.btanalysis import trade_list_to_dataframe from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import BacktestState, SellType -from freqtrade.enums.candletype import CandleType -from freqtrade.enums.tradingmode import TradingMode +from freqtrade.enums import BacktestState, CandleType, SellType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.mixins import LoggingMixin diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py index f2e9aa4d3..de7e26336 100644 --- a/freqtrade/plugins/pairlist/AgeFilter.py +++ b/freqtrade/plugins/pairlist/AgeFilter.py @@ -9,6 +9,7 @@ import arrow from pandas import DataFrame from freqtrade.configuration import PeriodicCache +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.misc import plural from freqtrade.plugins.pairlist.IPairList import IPairList @@ -72,7 +73,7 @@ class AgeFilter(IPairList): :return: new allowlist """ needed_pairs = [ - (p, '1d', '') for p in pairlist + (p, '1d', CandleType.SPOT_) for p in pairlist if p not in self._symbolsChecked and p not in self._symbolsCheckFailed] if not needed_pairs: # Remove pairs that have been removed before @@ -88,7 +89,8 @@ class AgeFilter(IPairList): candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, cache=False) if self._enabled: for p in deepcopy(pairlist): - daily_candles = candles[(p, '1d', '')] if (p, '1d', '') in candles else None + daily_candles = candles[(p, '1d', CandleType.SPOT_)] if ( + p, '1d', CandleType.SPOT_) in candles else None if not self._validate_pair_loc(p, daily_candles): pairlist.remove(p) self.log_once(f"Validated {len(pairlist)} pairs.", logger.info) diff --git a/freqtrade/plugins/pairlist/ShuffleFilter.py b/freqtrade/plugins/pairlist/ShuffleFilter.py index 55cf9938f..663bba49b 100644 --- a/freqtrade/plugins/pairlist/ShuffleFilter.py +++ b/freqtrade/plugins/pairlist/ShuffleFilter.py @@ -5,7 +5,7 @@ import logging import random from typing import Any, Dict, List -from freqtrade.enums.runmode import RunMode +from freqtrade.enums import RunMode from freqtrade.plugins.pairlist.IPairList import IPairList diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index c06cd0897..299ea8c18 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -11,6 +11,7 @@ import numpy as np from cachetools.ttl import TTLCache from pandas import DataFrame +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.misc import plural from freqtrade.plugins.pairlist.IPairList import IPairList @@ -67,7 +68,7 @@ class VolatilityFilter(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new allowlist """ - needed_pairs = [(p, '1d', '') for p in pairlist if p not in self._pair_cache] + needed_pairs = [(p, '1d', CandleType.SPOT_) for p in pairlist if p not in self._pair_cache] since_ms = (arrow.utcnow() .floor('day') @@ -81,7 +82,8 @@ class VolatilityFilter(IPairList): if self._enabled: for p in deepcopy(pairlist): - daily_candles = candles[(p, '1d', '')] if (p, '1d', '') in candles else None + daily_candles = candles[(p, '1d', CandleType.SPOT_)] if ( + p, '1d', CandleType.SPOT_) in candles else None if not self._validate_pair_loc(p, daily_candles): pairlist.remove(p) return pairlist diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index 0493b67c9..7c9c9933a 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -10,6 +10,7 @@ from typing import Any, Dict, List import arrow from cachetools.ttl import TTLCache +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import format_ms_time @@ -160,7 +161,7 @@ class VolumePairList(IPairList): f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} " f"till {format_ms_time(to_ms)}", logger.info) needed_pairs = [ - (p, self._lookback_timeframe, '') for p in + (p, self._lookback_timeframe, CandleType.SPOT_) for p in [s['symbol'] for s in filtered_tickers] if p not in self._pair_cache ] @@ -173,8 +174,8 @@ class VolumePairList(IPairList): ) for i, p in enumerate(filtered_tickers): pair_candles = candles[ - (p['symbol'], self._lookback_timeframe, '') - ] if (p['symbol'], self._lookback_timeframe, '') in candles else None + (p['symbol'], self._lookback_timeframe, CandleType.SPOT_) + ] if (p['symbol'], self._lookback_timeframe, CandleType.SPOT_) in candles else None # in case of candle data calculate typical price and quoteVolume for candle if pair_candles is not None and not pair_candles.empty: pair_candles['typical_price'] = (pair_candles['high'] + pair_candles['low'] diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 048736ae0..31d92d41b 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -9,6 +9,7 @@ import arrow from cachetools.ttl import TTLCache from pandas import DataFrame +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.misc import plural from freqtrade.plugins.pairlist.IPairList import IPairList @@ -65,7 +66,7 @@ class RangeStabilityFilter(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new allowlist """ - needed_pairs = [(p, '1d', '') for p in pairlist if p not in self._pair_cache] + needed_pairs = [(p, '1d', CandleType.SPOT_) for p in pairlist if p not in self._pair_cache] since_ms = (arrow.utcnow() .floor('day') @@ -79,7 +80,8 @@ class RangeStabilityFilter(IPairList): if self._enabled: for p in deepcopy(pairlist): - daily_candles = candles[(p, '1d', '')] if (p, '1d', '') in candles else None + daily_candles = candles[(p, '1d', CandleType.SPOT_)] if ( + p, '1d', CandleType.SPOT_) in candles else None if not self._validate_pair_loc(p, daily_candles): pairlist.remove(p) return pairlist diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py index 5ea4c2386..5ae9a7e35 100644 --- a/freqtrade/plugins/pairlistmanager.py +++ b/freqtrade/plugins/pairlistmanager.py @@ -8,6 +8,7 @@ from typing import Dict, List from cachetools import TTLCache, cached from freqtrade.constants import ListPairsWithTimeframes +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist @@ -138,4 +139,4 @@ class PairListManager(): """ Create list of pair tuples with (pair, timeframe) """ - return [(pair, timeframe or self._config['timeframe'], '') for pair in pairs] + return [(pair, timeframe or self._config['timeframe'], CandleType.SPOT) for pair in pairs] diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index f69ddef43..8bf3d41f1 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -9,6 +9,7 @@ from fastapi.exceptions import HTTPException from freqtrade import __version__ from freqtrade.constants import USERPATH_STRATEGIES from freqtrade.data.history import get_datahandler +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.rpc import RPC from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, @@ -250,7 +251,7 @@ def get_strategy(strategy: str, config=Depends(get_config)): @router.get('/available_pairs', response_model=AvailablePairs, tags=['candle data']) def list_available_pairs(timeframe: Optional[str] = None, stake_currency: Optional[str] = None, - candletype: Optional[str] = None, config=Depends(get_config)): + candletype: Optional[CandleType] = None, config=Depends(get_config)): dh = get_datahandler(config['datadir'], config.get('dataformat_ohlcv', None)) diff --git a/freqtrade/strategy/informative_decorator.py b/freqtrade/strategy/informative_decorator.py index 1507f09ab..3d939e017 100644 --- a/freqtrade/strategy/informative_decorator.py +++ b/freqtrade/strategy/informative_decorator.py @@ -2,6 +2,7 @@ from typing import Any, Callable, NamedTuple, Optional, Union from pandas import DataFrame +from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.strategy.strategy_helper import merge_informative_pair @@ -14,7 +15,7 @@ class InformativeData(NamedTuple): timeframe: str fmt: Union[str, Callable[[Any], str], None] ffill: bool - candle_type: str = '' + candle_type: CandleType = CandleType.SPOT_ def informative(timeframe: str, asset: str = '', diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 8abb10bc7..2a3b4e754 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -13,8 +13,7 @@ from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import SellType, SignalDirection, SignalTagType, SignalType -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType, SellType, SignalDirection, SignalTagType, SignalType from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange.exchange import timeframe_to_next_date @@ -424,7 +423,9 @@ class IStrategy(ABC, HyperStrategyMixin): """ informative_pairs = self.informative_pairs() # Compatibility code for 2 tuple informative pairs - informative_pairs = [(p[0], p[1], p[2] if len(p) > 2 else '') for p in informative_pairs] + informative_pairs = [ + (p[0], p[1], CandleType.from_string(p[2]) if len(p) > 2 else CandleType.SPOT_) + for p in informative_pairs] for inf_data, _ in self._ft_informative: if inf_data.asset: pair_tf = (