Merge pull request #2 from freqtrade/develop
update develop from base repo
This commit is contained in:
@@ -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'] = {}
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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}. '
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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):
|
||||
|
@@ -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}')
|
||||
|
@@ -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": {},
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user