Merge pull request #2 from freqtrade/develop

update develop from base repo
This commit is contained in:
wr0ngc0degen
2021-04-25 05:50:03 +02:00
committed by GitHub
19 changed files with 312 additions and 112 deletions

View File

@@ -75,8 +75,6 @@ class Configuration:
# Normalize config
if 'internals' not in config:
config['internals'] = {}
# TODO: This can be deleted along with removal of deprecated
# experimental settings
if 'ask_strategy' not in config:
config['ask_strategy'] = {}

View File

@@ -89,7 +89,7 @@ class HDF5DataHandler(IDataHandler):
if timerange.starttype == 'date':
where.append(f"date >= Timestamp({timerange.startts * 1e9})")
if timerange.stoptype == 'date':
where.append(f"date < Timestamp({timerange.stopts * 1e9})")
where.append(f"date <= Timestamp({timerange.stopts * 1e9})")
pairdata = pd.read_hdf(filename, key=key, mode="r", where=where)

View File

@@ -363,7 +363,6 @@ class Exchange:
invalid_pairs = []
for pair in extended_pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format
if self.markets and pair not in self.markets:
raise OperationalException(
f'Pair {pair} is not available on {self.name}. '

View File

@@ -473,8 +473,7 @@ class FreqtradeBot(LoggingMixin):
(buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)
if buy and not sell:
stake_amount = self.wallets.get_trade_stake_amount(pair, self.get_free_open_trades(),
self.edge)
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
if not stake_amount:
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
return False

View File

@@ -273,11 +273,9 @@ class Backtesting:
return None
def _enter_trade(self, pair: str, row: List, max_open_trades: int,
open_trade_count: int) -> Optional[LocalTrade]:
def _enter_trade(self, pair: str, row: List) -> Optional[LocalTrade]:
try:
stake_amount = self.wallets.get_trade_stake_amount(
pair, max_open_trades - open_trade_count, None)
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
except DependencyException:
return None
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, row[OPEN_IDX], -0.05)
@@ -354,7 +352,7 @@ class Backtesting:
data: Dict = self._get_ohlcv_as_lists(processed)
# Indexes per pair, so some pairs are allowed to have a missing start.
indexes: Dict = {}
indexes: Dict = defaultdict(int)
tmp = start_date + timedelta(minutes=self.timeframe_min)
open_trades: Dict[str, List[LocalTrade]] = defaultdict(list)
@@ -365,9 +363,6 @@ class Backtesting:
open_trade_count_start = open_trade_count
for i, pair in enumerate(data):
if pair not in indexes:
indexes[pair] = 0
try:
row = data[pair][indexes[pair]]
except IndexError:
@@ -388,7 +383,7 @@ class Backtesting:
and tmp != end_date
and row[BUY_IDX] == 1 and row[SELL_IDX] != 1
and not PairLocks.is_pair_locked(pair, row[DATE_IDX])):
trade = self._enter_trade(pair, row, max_open_trades, open_trade_count_start)
trade = self._enter_trade(pair, row)
if trade:
# TODO: hacky workaround to avoid opening > max_open_trades
# This emulates previous behaviour - not sure if this is correct

View File

@@ -607,8 +607,7 @@ class RPC:
raise RPCException(f'position for {pair} already open - id: {trade.id}')
# gen stake amount
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(
pair, self._freqtrade.get_free_open_trades())
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
# execute buy
if self._freqtrade.execute_buy(pair, stakeamount, price, forcebuy=True):

View File

@@ -5,7 +5,7 @@ This module defines a base class for auto-hyperoptable strategies.
import logging
from abc import ABC, abstractmethod
from contextlib import suppress
from typing import Any, Iterator, Optional, Sequence, Tuple, Union
from typing import Any, Dict, Iterator, Optional, Sequence, Tuple, Union
with suppress(ImportError):
@@ -13,6 +13,7 @@ with suppress(ImportError):
from freqtrade.optimize.space import SKDecimal
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -25,6 +26,7 @@ class BaseParameter(ABC):
category: Optional[str]
default: Any
value: Any
hyperopt: bool = False
def __init__(self, *, default: Any, space: Optional[str] = None,
optimize: bool = True, load: bool = True, **kwargs):
@@ -121,6 +123,20 @@ class IntParameter(NumericParameter):
"""
return Integer(low=self.low, high=self.high, name=name, **self._space_params)
@property
def range(self):
"""
Get each value in this space as list.
Returns a List from low to high (inclusive) in Hyperopt mode.
Returns a List with 1 item (`value`) in "non-hyperopt" mode, to avoid
calculating 100ds of indicators.
"""
if self.hyperopt:
# Scikit-optimize ranges are "inclusive", while python's "range" is exclusive
return range(self.low, self.high + 1)
else:
return range(self.value, self.value + 1)
class RealParameter(NumericParameter):
default: float
@@ -227,12 +243,11 @@ class HyperStrategyMixin(object):
strategy logic.
"""
def __init__(self, *args, **kwargs):
def __init__(self, config: Dict[str, Any], *args, **kwargs):
"""
Initialize hyperoptable strategy mixin.
"""
self._load_params(getattr(self, 'buy_params', None))
self._load_params(getattr(self, 'sell_params', None))
self._load_hyper_params(config.get('runmode') == RunMode.HYPEROPT)
def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]:
"""
@@ -254,18 +269,29 @@ class HyperStrategyMixin(object):
(attr_name.startswith(category + '_') and attr.category is None)):
yield attr_name, attr
def _load_params(self, params: dict) -> None:
def _load_hyper_params(self, hyperopt: bool = False) -> None:
"""
Load Hyperoptable parameters
"""
self._load_params(getattr(self, 'buy_params', None), 'buy', hyperopt)
self._load_params(getattr(self, 'sell_params', None), 'sell', hyperopt)
def _load_params(self, params: dict, space: str, hyperopt: bool = False) -> None:
"""
Set optimizeable parameter values.
:param params: Dictionary with new parameter values.
"""
if not params:
return
logger.info(f"No params for {space} found, using default values.")
for attr_name, attr in self.enumerate_parameters():
if attr_name in params:
attr.hyperopt = hyperopt
if params and attr_name in params:
if attr.load:
attr.value = params[attr_name]
logger.info(f'Strategy Parameter: {attr_name} = {attr.value}')
else:
logger.warning(f'Parameter "{attr_name}" exists, but is disabled. '
f'Default value "{attr.value}" used.')
else:
logger.info(f'Strategy Parameter(default): {attr_name} = {attr.value}')

View File

@@ -282,6 +282,28 @@
"graph.show(renderer=\"browser\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Plot average profit per trade as distribution graph"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import plotly.figure_factory as ff\n",
"\n",
"hist_data = [trades.profit_ratio]\n",
"group_labels = ['profit_ratio'] # name of the dataset\n",
"\n",
"fig = ff.create_distplot(hist_data, group_labels,bin_size=0.01)\n",
"fig.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},

View File

@@ -130,14 +130,13 @@ class Wallets:
def get_all_balances(self) -> Dict[str, Any]:
return self._wallets
def _get_available_stake_amount(self) -> float:
def _get_available_stake_amount(self, val_tied_up: float) -> float:
"""
Return the total currently available balance in stake currency,
respecting tradable_balance_ratio.
Calculated as
(<open_trade stakes> + free amount ) * tradable_balance_ratio - <open_trade stakes>
(<open_trade stakes> + free amount) * tradable_balance_ratio - <open_trade stakes>
"""
val_tied_up = Trade.total_open_trades_stakes()
# Ensure <tradable_balance_ratio>% is used from the overall balance
# Otherwise we'd risk lowering stakes with each open trade.
@@ -146,26 +145,26 @@ class Wallets:
self._config['tradable_balance_ratio']) - val_tied_up
return available_amount
def _calculate_unlimited_stake_amount(self, free_open_trades: int) -> float:
def _calculate_unlimited_stake_amount(self, available_amount: float,
val_tied_up: float) -> float:
"""
Calculate stake amount for "unlimited" stake amount
:return: 0 if max number of trades reached, else stake_amount to use.
"""
if not free_open_trades:
if self._config['max_open_trades'] == 0:
return 0
available_amount = self._get_available_stake_amount()
possible_stake = (available_amount + val_tied_up) / self._config['max_open_trades']
# Theoretical amount can be above available amount - therefore limit to available amount!
return min(possible_stake, available_amount)
return available_amount / free_open_trades
def _check_available_stake_amount(self, stake_amount: float) -> float:
def _check_available_stake_amount(self, stake_amount: float, available_amount: float) -> float:
"""
Check if stake amount can be fulfilled with the available balance
for the stake currency
:return: float: Stake amount
:raise: DependencyException if balance is lower than stake-amount
"""
available_amount = self._get_available_stake_amount()
if self._config['amend_last_stake_amount']:
# Remaining amount needs to be at least stake_amount * last_stake_amount_min_ratio
@@ -183,7 +182,7 @@ class Wallets:
return stake_amount
def get_trade_stake_amount(self, pair: str, free_open_trades: int, edge=None) -> float:
def get_trade_stake_amount(self, pair: str, edge=None) -> float:
"""
Calculate stake amount for the trade
:return: float: Stake amount
@@ -192,17 +191,20 @@ class Wallets:
stake_amount: float
# Ensure wallets are uptodate.
self.update()
val_tied_up = Trade.total_open_trades_stakes()
available_amount = self._get_available_stake_amount(val_tied_up)
if edge:
stake_amount = edge.stake_amount(
pair,
self.get_free(self._config['stake_currency']),
self.get_total(self._config['stake_currency']),
Trade.total_open_trades_stakes()
val_tied_up
)
else:
stake_amount = self._config['stake_amount']
if stake_amount == UNLIMITED_STAKE_AMOUNT:
stake_amount = self._calculate_unlimited_stake_amount(free_open_trades)
stake_amount = self._calculate_unlimited_stake_amount(
available_amount, val_tied_up)
return self._check_available_stake_amount(stake_amount)
return self._check_available_stake_amount(stake_amount, available_amount)