Merge pull request #2661 from freqtrade/wallet_dry
Introduce Dry-Run Wallet
This commit is contained in:
@@ -18,7 +18,7 @@ REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
|
||||
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
||||
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
||||
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'PriceFilter']
|
||||
DRY_RUN_WALLET = 999.9
|
||||
DRY_RUN_WALLET = 1000
|
||||
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
||||
|
||||
USERPATH_HYPEROPTS = 'hyperopts'
|
||||
@@ -75,7 +75,7 @@ CONF_SCHEMA = {
|
||||
},
|
||||
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
||||
'dry_run': {'type': 'boolean'},
|
||||
'dry_run_wallet': {'type': 'number'},
|
||||
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
|
||||
'process_only_new_candles': {'type': 'boolean'},
|
||||
'minimal_roi': {
|
||||
'type': 'object',
|
||||
@@ -275,6 +275,7 @@ CONF_SCHEMA = {
|
||||
'stake_currency',
|
||||
'stake_amount',
|
||||
'dry_run',
|
||||
'dry_run_wallet',
|
||||
'bid_strategy',
|
||||
'unfilledtimeout',
|
||||
'stoploss',
|
||||
|
@@ -18,7 +18,7 @@ from ccxt.base.decimal_to_precision import ROUND_DOWN, ROUND_UP
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade import (DependencyException, InvalidOrderException,
|
||||
OperationalException, TemporaryError, constants)
|
||||
OperationalException, TemporaryError)
|
||||
from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async
|
||||
from freqtrade.misc import deep_merge_dicts
|
||||
@@ -479,7 +479,7 @@ class Exchange:
|
||||
@retrier
|
||||
def get_balance(self, currency: str) -> float:
|
||||
if self._config['dry_run']:
|
||||
return constants.DRY_RUN_WALLET
|
||||
return self._config['dry_run_wallet']
|
||||
|
||||
# ccxt exception is already handled by get_balances
|
||||
balances = self.get_balances()
|
||||
|
@@ -62,7 +62,11 @@ class FreqtradeBot:
|
||||
|
||||
self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
|
||||
|
||||
persistence.init(self.config.get('db_url', None),
|
||||
clean_open_orders=self.config.get('dry_run', False))
|
||||
|
||||
self.wallets = Wallets(self.config, self.exchange)
|
||||
|
||||
self.dataprovider = DataProvider(self.config, self.exchange)
|
||||
|
||||
# Attach Dataprovider to Strategy baseclass
|
||||
@@ -78,9 +82,6 @@ class FreqtradeBot:
|
||||
|
||||
self.active_pair_whitelist = self._refresh_whitelist()
|
||||
|
||||
persistence.init(self.config.get('db_url', None),
|
||||
clean_open_orders=self.config.get('dry_run', False))
|
||||
|
||||
# Set initial bot state from config
|
||||
initial_state = self.config.get('initial_state')
|
||||
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
|
||||
@@ -231,8 +232,8 @@ class FreqtradeBot:
|
||||
# Check if stake_amount is fulfilled
|
||||
if available_amount < stake_amount:
|
||||
raise DependencyException(
|
||||
f"Available balance({available_amount} {self.config['stake_currency']}) is "
|
||||
f"lower than stake amount({stake_amount} {self.config['stake_currency']})"
|
||||
f"Available balance ({available_amount} {self.config['stake_currency']}) is "
|
||||
f"lower than stake amount ({stake_amount} {self.config['stake_currency']})"
|
||||
)
|
||||
|
||||
return stake_amount
|
||||
|
@@ -348,6 +348,7 @@ class RPC:
|
||||
'total': total,
|
||||
'symbol': symbol,
|
||||
'value': value,
|
||||
'note': 'Simulated balances' if self._freqtrade.config.get('dry_run', False) else ''
|
||||
}
|
||||
|
||||
def _rpc_start(self) -> Dict[str, str]:
|
||||
|
@@ -331,7 +331,15 @@ class Telegram(RPC):
|
||||
try:
|
||||
result = self._rpc_balance(self._config['stake_currency'],
|
||||
self._config.get('fiat_display_currency', ''))
|
||||
|
||||
output = ''
|
||||
if self._config['dry_run']:
|
||||
output += (
|
||||
f"*Warning:*Simulated balances in Dry Mode.\n"
|
||||
"This mode is still experimental!\n"
|
||||
"Starting capital: "
|
||||
f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n"
|
||||
)
|
||||
for currency in result['currencies']:
|
||||
if currency['est_stake'] > 0.0001:
|
||||
curr_output = "*{currency}:*\n" \
|
||||
|
@@ -4,7 +4,7 @@
|
||||
import logging
|
||||
from typing import Dict, NamedTuple, Any
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade import constants
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -23,14 +23,12 @@ class Wallets:
|
||||
self._config = config
|
||||
self._exchange = exchange
|
||||
self._wallets: Dict[str, Wallet] = {}
|
||||
self.start_cap = config['dry_run_wallet']
|
||||
|
||||
self.update()
|
||||
|
||||
def get_free(self, currency) -> float:
|
||||
|
||||
if self._config['dry_run']:
|
||||
return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET)
|
||||
|
||||
balance = self._wallets.get(currency)
|
||||
if balance and balance.free:
|
||||
return balance.free
|
||||
@@ -39,9 +37,6 @@ class Wallets:
|
||||
|
||||
def get_used(self, currency) -> float:
|
||||
|
||||
if self._config['dry_run']:
|
||||
return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET)
|
||||
|
||||
balance = self._wallets.get(currency)
|
||||
if balance and balance.used:
|
||||
return balance.used
|
||||
@@ -50,16 +45,42 @@ class Wallets:
|
||||
|
||||
def get_total(self, currency) -> float:
|
||||
|
||||
if self._config['dry_run']:
|
||||
return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET)
|
||||
|
||||
balance = self._wallets.get(currency)
|
||||
if balance and balance.total:
|
||||
return balance.total
|
||||
else:
|
||||
return 0
|
||||
|
||||
def update(self) -> None:
|
||||
def _update_dry(self) -> None:
|
||||
"""
|
||||
Update from database in dry-run mode
|
||||
- Apply apply profits of closed trades on top of stake amount
|
||||
- Subtract currently tied up stake_amount in open trades
|
||||
- update balances for currencies currently in trades
|
||||
"""
|
||||
closed_trades = Trade.get_trades(Trade.is_open.is_(False)).all()
|
||||
open_trades = Trade.get_trades(Trade.is_open.is_(True)).all()
|
||||
tot_profit = sum([trade.calc_profit() for trade in closed_trades])
|
||||
tot_in_trades = sum([trade.stake_amount for trade in open_trades])
|
||||
|
||||
current_stake = self.start_cap + tot_profit - tot_in_trades
|
||||
self._wallets[self._config['stake_currency']] = Wallet(
|
||||
self._config['stake_currency'],
|
||||
current_stake,
|
||||
0,
|
||||
current_stake
|
||||
)
|
||||
|
||||
for trade in open_trades:
|
||||
curr = trade.pair.split('/')[0]
|
||||
self._wallets[curr] = Wallet(
|
||||
curr,
|
||||
trade.amount,
|
||||
0,
|
||||
trade.amount
|
||||
)
|
||||
|
||||
def _update_live(self) -> None:
|
||||
|
||||
balances = self._exchange.get_balances()
|
||||
|
||||
@@ -71,6 +92,11 @@ class Wallets:
|
||||
balances[currency].get('total', None)
|
||||
)
|
||||
|
||||
def update(self) -> None:
|
||||
if self._config['dry_run']:
|
||||
self._update_dry()
|
||||
else:
|
||||
self._update_live()
|
||||
logger.info('Wallets synced.')
|
||||
|
||||
def get_all_balances(self) -> Dict[str, Any]:
|
||||
|
Reference in New Issue
Block a user