Merge branch 'develop' into feat/backtest_speedup_serialize
This commit is contained in:
@@ -257,8 +257,8 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). '
|
||||
'Different functions can generate completely different results, '
|
||||
'since the target for optimization is different. Built-in Hyperopt-loss-functions are: '
|
||||
'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, '
|
||||
'SortinoHyperOptLoss, SortinoHyperOptLossDaily.',
|
||||
'ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, '
|
||||
'SharpeHyperOptLossDaily, SortinoHyperOptLoss, SortinoHyperOptLossDaily.',
|
||||
metavar='NAME',
|
||||
),
|
||||
"hyperoptexportfilename": Arg(
|
||||
|
@@ -205,14 +205,14 @@ def start_show_trades(args: Dict[str, Any]) -> None:
|
||||
"""
|
||||
import json
|
||||
|
||||
from freqtrade.persistence import Trade, init
|
||||
from freqtrade.persistence import Trade, init_db
|
||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||
|
||||
if 'db_url' not in config:
|
||||
raise OperationalException("--db-url is required for this command.")
|
||||
|
||||
logger.info(f'Using DB: "{config["db_url"]}"')
|
||||
init(config['db_url'], clean_open_orders=False)
|
||||
init_db(config['db_url'], clean_open_orders=False)
|
||||
tfilter = []
|
||||
|
||||
if config.get('trade_ids'):
|
||||
|
@@ -9,10 +9,9 @@ from typing import Any, Dict, Optional, Tuple, Union
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from freqtrade import persistence
|
||||
from freqtrade.constants import LAST_BT_RESULT_FN
|
||||
from freqtrade.misc import json_load
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.persistence import Trade, init_db
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -218,7 +217,7 @@ def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataF
|
||||
Can also serve as protection to load the correct result.
|
||||
:return: Dataframe containing Trades
|
||||
"""
|
||||
persistence.init(db_url, clean_open_orders=False)
|
||||
init_db(db_url, clean_open_orders=False)
|
||||
|
||||
columns = ["pair", "open_date", "close_date", "profit", "profit_percent",
|
||||
"open_rate", "close_rate", "amount", "trade_duration", "sell_reason",
|
||||
|
@@ -310,8 +310,10 @@ class Edge:
|
||||
|
||||
# Calculating number of losing trades, average win and average loss
|
||||
df['nb_loss_trades'] = df['nb_trades'] - df['nb_win_trades']
|
||||
df['average_win'] = df['profit_sum'] / df['nb_win_trades']
|
||||
df['average_loss'] = df['loss_sum'] / df['nb_loss_trades']
|
||||
df['average_win'] = np.where(df['nb_win_trades'] == 0, 0.0,
|
||||
df['profit_sum'] / df['nb_win_trades'])
|
||||
df['average_loss'] = np.where(df['nb_loss_trades'] == 0, 0.0,
|
||||
df['loss_sum'] / df['nb_loss_trades'])
|
||||
|
||||
# Win rate = number of profitable trades / number of trades
|
||||
df['winrate'] = df['nb_win_trades'] / df['nb_trades']
|
||||
|
@@ -5,6 +5,7 @@ from freqtrade.exchange.exchange import Exchange
|
||||
# isort: on
|
||||
from freqtrade.exchange.bibox import Bibox
|
||||
from freqtrade.exchange.binance import Binance
|
||||
from freqtrade.exchange.bittrex import Bittrex
|
||||
from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
|
||||
get_exchange_bad_reason, is_exchange_bad,
|
||||
is_exchange_known_ccxt, is_exchange_officially_supported,
|
||||
|
@@ -20,20 +20,9 @@ class Binance(Exchange):
|
||||
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
||||
"trades_pagination": "id",
|
||||
"trades_pagination_arg": "fromId",
|
||||
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
|
||||
}
|
||||
|
||||
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
||||
"""
|
||||
get order book level 2 from exchange
|
||||
|
||||
20180619: binance support limits but only on specific range
|
||||
"""
|
||||
limit_range = [5, 10, 20, 50, 100, 500, 1000]
|
||||
# get next-higher step in the limit_range list
|
||||
limit = min(list(filter(lambda x: limit <= x, limit_range)))
|
||||
|
||||
return super().fetch_l2_order_book(pair, limit)
|
||||
|
||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
||||
"""
|
||||
Verify stop_loss against stoploss-order value (limit or price)
|
||||
|
23
freqtrade/exchange/bittrex.py
Normal file
23
freqtrade/exchange/bittrex.py
Normal file
@@ -0,0 +1,23 @@
|
||||
""" Bittrex exchange subclass """
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from freqtrade.exchange import Exchange
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Bittrex(Exchange):
|
||||
"""
|
||||
Bittrex exchange class. Contains adjustments needed for Freqtrade to work
|
||||
with this exchange.
|
||||
|
||||
Please note that this exchange is not included in the list of exchanges
|
||||
officially supported by the Freqtrade development team. So some features
|
||||
may still not work as expected.
|
||||
"""
|
||||
|
||||
_ft_has: Dict = {
|
||||
"l2_limit_range": [1, 25, 500],
|
||||
}
|
@@ -53,7 +53,7 @@ class Exchange:
|
||||
"ohlcv_partial_candle": True,
|
||||
"trades_pagination": "time", # Possible are "time" or "id"
|
||||
"trades_pagination_arg": "since",
|
||||
|
||||
"l2_limit_range": None,
|
||||
}
|
||||
_ft_has: Dict = {}
|
||||
|
||||
@@ -1069,6 +1069,16 @@ class Exchange:
|
||||
return self.fetch_stoploss_order(order_id, pair)
|
||||
return self.fetch_order(order_id, pair)
|
||||
|
||||
@staticmethod
|
||||
def get_next_limit_in_list(limit: int, limit_range: Optional[List[int]]):
|
||||
"""
|
||||
Get next greater value in the list.
|
||||
Used by fetch_l2_order_book if the api only supports a limited range
|
||||
"""
|
||||
if not limit_range:
|
||||
return limit
|
||||
return min([x for x in limit_range if limit <= x] + [max(limit_range)])
|
||||
|
||||
@retrier
|
||||
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
||||
"""
|
||||
@@ -1077,9 +1087,10 @@ class Exchange:
|
||||
Returns a dict in the format
|
||||
{'asks': [price, volume], 'bids': [price, volume]}
|
||||
"""
|
||||
limit1 = self.get_next_limit_in_list(limit, self._ft_has['l2_limit_range'])
|
||||
try:
|
||||
|
||||
return self._api.fetch_l2_order_book(pair, limit)
|
||||
return self._api.fetch_l2_order_book(pair, limit1)
|
||||
except ccxt.NotSupported as e:
|
||||
raise OperationalException(
|
||||
f'Exchange {self._api.name} does not support fetching order book.'
|
||||
|
@@ -12,7 +12,7 @@ from typing import Any, Dict, List, Optional
|
||||
import arrow
|
||||
from cachetools import TTLCache
|
||||
|
||||
from freqtrade import __version__, constants, persistence
|
||||
from freqtrade import __version__, constants
|
||||
from freqtrade.configuration import validate_config_consistency
|
||||
from freqtrade.data.converter import order_book_to_dataframe
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
@@ -22,7 +22,7 @@ from freqtrade.exceptions import (DependencyException, ExchangeError, Insufficie
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
|
||||
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
||||
from freqtrade.pairlist.pairlistmanager import PairListManager
|
||||
from freqtrade.persistence import Order, Trade
|
||||
from freqtrade.persistence import Order, Trade, cleanup_db, init_db
|
||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||
from freqtrade.rpc import RPCManager, RPCMessageType
|
||||
from freqtrade.state import State
|
||||
@@ -58,8 +58,8 @@ class FreqtradeBot:
|
||||
# Cache values for 1800 to avoid frequent polling of the exchange for prices
|
||||
# Caching only applies to RPC methods, so prices for open trades are still
|
||||
# refreshed once every iteration.
|
||||
self._sell_rate_cache = TTLCache(maxsize=100, ttl=1800)
|
||||
self._buy_rate_cache = TTLCache(maxsize=100, ttl=1800)
|
||||
self._sell_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||
self._buy_rate_cache: TTLCache = TTLCache(maxsize=100, ttl=1800)
|
||||
|
||||
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
|
||||
|
||||
@@ -68,7 +68,7 @@ class FreqtradeBot:
|
||||
|
||||
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
|
||||
|
||||
persistence.init(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
|
||||
init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
|
||||
|
||||
self.wallets = Wallets(self.config, self.exchange)
|
||||
|
||||
@@ -123,7 +123,7 @@ class FreqtradeBot:
|
||||
self.check_for_open_trades()
|
||||
|
||||
self.rpc.cleanup()
|
||||
persistence.cleanup()
|
||||
cleanup_db()
|
||||
|
||||
def startup(self) -> None:
|
||||
"""
|
||||
|
@@ -56,8 +56,8 @@ def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool =
|
||||
if log:
|
||||
logger.info(f'dumping json to "{filename}"')
|
||||
|
||||
with gzip.open(filename, 'w') as fp:
|
||||
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
|
||||
with gzip.open(filename, 'w') as fpz:
|
||||
rapidjson.dump(data, fpz, default=str, number_mode=rapidjson.NM_NATIVE)
|
||||
else:
|
||||
if log:
|
||||
logger.info(f'dumping json to "{filename}"')
|
||||
|
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
DefaultHyperOptLoss
|
||||
ShortTradeDurHyperOptLoss
|
||||
This module defines the default HyperoptLoss class which is being used for
|
||||
Hyperoptimization.
|
||||
"""
|
||||
@@ -26,7 +26,7 @@ EXPECTED_MAX_PROFIT = 3.0
|
||||
MAX_ACCEPTED_TRADE_DURATION = 300
|
||||
|
||||
|
||||
class DefaultHyperOptLoss(IHyperOptLoss):
|
||||
class ShortTradeDurHyperOptLoss(IHyperOptLoss):
|
||||
"""
|
||||
Defines the default loss function for hyperopt
|
||||
"""
|
||||
@@ -50,3 +50,7 @@ class DefaultHyperOptLoss(IHyperOptLoss):
|
||||
duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
|
||||
result = trade_loss + profit_loss + duration_loss
|
||||
return result
|
||||
|
||||
|
||||
# Create an alias for This to allow the legacy Method to work as well.
|
||||
DefaultHyperOptLoss = ShortTradeDurHyperOptLoss
|
||||
|
@@ -36,7 +36,7 @@ class IPairList(ABC):
|
||||
self._pairlist_pos = pairlist_pos
|
||||
self.refresh_period = self._pairlistconfig.get('refresh_period', 1800)
|
||||
self._last_refresh = 0
|
||||
self._log_cache = TTLCache(maxsize=1024, ttl=self.refresh_period)
|
||||
self._log_cache: TTLCache = TTLCache(maxsize=1024, ttl=self.refresh_period)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
@@ -1,3 +1,3 @@
|
||||
# flake8: noqa: F401
|
||||
|
||||
from freqtrade.persistence.models import Order, Trade, clean_dry_run_db, cleanup, init
|
||||
from freqtrade.persistence.models import Order, Trade, clean_dry_run_db, cleanup_db, init_db
|
||||
|
@@ -29,7 +29,7 @@ _DECL_BASE: Any = declarative_base()
|
||||
_SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls'
|
||||
|
||||
|
||||
def init(db_url: str, clean_open_orders: bool = False) -> None:
|
||||
def init_db(db_url: str, clean_open_orders: bool = False) -> None:
|
||||
"""
|
||||
Initializes this module with the given config,
|
||||
registers all known command handlers
|
||||
@@ -72,7 +72,7 @@ def init(db_url: str, clean_open_orders: bool = False) -> None:
|
||||
clean_dry_run_db()
|
||||
|
||||
|
||||
def cleanup() -> None:
|
||||
def cleanup_db() -> None:
|
||||
"""
|
||||
Flushes all pending operations to disk.
|
||||
:return: None
|
||||
@@ -399,7 +399,7 @@ class Trade(_DECL_BASE):
|
||||
self.close(order['average'])
|
||||
else:
|
||||
raise ValueError(f'Unknown order type: {order_type}')
|
||||
cleanup()
|
||||
cleanup_db()
|
||||
|
||||
def close(self, rate: float) -> None:
|
||||
"""
|
||||
|
@@ -563,7 +563,7 @@ class ApiServer(RPC):
|
||||
config.update({
|
||||
'strategy': strategy,
|
||||
})
|
||||
results = self._rpc_analysed_history_full(config, pair, timeframe, timerange)
|
||||
results = RPC._rpc_analysed_history_full(config, pair, timeframe, timerange)
|
||||
return jsonify(results)
|
||||
|
||||
@require_login
|
||||
|
@@ -656,8 +656,9 @@ class RPC:
|
||||
raise RPCException('Edge is not enabled.')
|
||||
return self._freqtrade.edge.accepted_pairs()
|
||||
|
||||
def _convert_dataframe_to_dict(self, strategy: str, pair: str, timeframe: str,
|
||||
dataframe: DataFrame, last_analyzed: datetime) -> Dict[str, Any]:
|
||||
@staticmethod
|
||||
def _convert_dataframe_to_dict(strategy: str, pair: str, timeframe: str, dataframe: DataFrame,
|
||||
last_analyzed: datetime) -> Dict[str, Any]:
|
||||
has_content = len(dataframe) != 0
|
||||
buy_signals = 0
|
||||
sell_signals = 0
|
||||
@@ -711,7 +712,8 @@ class RPC:
|
||||
return self._convert_dataframe_to_dict(self._freqtrade.config['strategy'],
|
||||
pair, timeframe, _data, last_analyzed)
|
||||
|
||||
def _rpc_analysed_history_full(self, config, pair: str, timeframe: str,
|
||||
@staticmethod
|
||||
def _rpc_analysed_history_full(config, pair: str, timeframe: str,
|
||||
timerange: str) -> Dict[str, Any]:
|
||||
timerange_parsed = TimeRange.parse_timerange(timerange)
|
||||
|
||||
@@ -726,8 +728,8 @@ class RPC:
|
||||
strategy = StrategyResolver.load_strategy(config)
|
||||
df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair})
|
||||
|
||||
return self._convert_dataframe_to_dict(strategy.get_strategy_name(), pair, timeframe,
|
||||
df_analyzed, arrow.Arrow.utcnow().datetime)
|
||||
return RPC._convert_dataframe_to_dict(strategy.get_strategy_name(), pair, timeframe,
|
||||
df_analyzed, arrow.Arrow.utcnow().datetime)
|
||||
|
||||
def _rpc_plot_config(self) -> Dict[str, Any]:
|
||||
|
||||
|
Reference in New Issue
Block a user