Modified files for partial trades operation

Signed-off-by: Es Fem <esfem.es@gmail.com>
This commit is contained in:
esfem 2020-10-29 20:39:35 -04:00 committed by Es Fem
parent 98a2811605
commit 1783202576
4 changed files with 352 additions and 34 deletions

View File

@ -26,7 +26,7 @@ from freqtrade.persistence import Order, Trade
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.state import State
from freqtrade.strategy.interface import IStrategy, SellType
from freqtrade.strategy.interface import IStrategy, SellType, PartialTradeTuple
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.wallets import Wallets
@ -345,11 +345,11 @@ class FreqtradeBot:
if not whitelist:
logger.info("Active pair whitelist is empty.")
else:
# Remove pairs for currently opened trades from the whitelist
'''# Remove pairs for currently opened trades from the whitelist
for trade in Trade.get_open_trades():
if trade.pair in whitelist:
whitelist.remove(trade.pair)
logger.debug('Ignoring %s in pair whitelist', trade.pair)
logger.debug('Ignoring %s in pair whitelist', trade.pair)'''
if not whitelist:
logger.info("No currency pair in active pair whitelist, "
@ -550,9 +550,9 @@ class FreqtradeBot:
return False
# running get_signal on historical data fetched
(buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)
(buy, sell, partial_buy, partial_sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)
if buy and not sell:
if buy or partial_buy.flag and not sell and not partial_sell.flag:
stake_amount = self.get_trade_stake_amount(pair)
if not stake_amount:
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
@ -565,13 +565,21 @@ class FreqtradeBot:
if ((bid_check_dom.get('enabled', False)) and
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
if self._check_depth_of_market_buy(pair, bid_check_dom):
logger.info(f'Executing Buy for {pair}.')
return self.execute_buy(pair, stake_amount)
if partial_buy.flag:
logger.info(f'Executing Partial Buy for {pair}.')
return self.execute_partial_buy(pair, partial_buy.amount)
else:
logger.info(f'Executing Buy for {pair}.')
return self.execute_buy(pair, stake_amount)
else:
return False
logger.info(f'Executing Buy for {pair}')
return self.execute_buy(pair, stake_amount)
if partial_buy.flag:
logger.info(f'Executing Partial Buy for {pair}')
return self.execute_partial_buy(pair, partial_buy.amount)
else:
logger.info(f'Executing Buy for {pair}')
return self.execute_buy(pair, stake_amount)
else:
return False
@ -701,6 +709,122 @@ class FreqtradeBot:
return True
def execute_partial_buy(self, pair: str, stake_amount: float, price: Optional[float] = None) -> bool:
"""
Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY
:return: True if a buy order is created, false if it fails.
"""
time_in_force = self.strategy.order_time_in_force['buy']
if price:
buy_limit_requested = price
else:
# Calculate price
buy_limit_requested = self.get_buy_rate(pair, True)
# in case you try to buy more than available
#stake_amount = self._safe_buy_amount(pair, stake_amount)
min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested)
if min_stake_amount is not None and min_stake_amount > stake_amount:
logger.warning(
f"Can't open a new trade for {pair}: stake amount "
f"is too small ({stake_amount} < {min_stake_amount})"
)
return False
amount = stake_amount / buy_limit_requested
order_type = self.strategy.order_types['buy']
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
time_in_force=time_in_force):
logger.info(f"User requested abortion of buying {pair}")
return False
amount = self.exchange.amount_to_precision(pair, amount)
order = self.exchange.buy(pair=pair, ordertype=order_type,
amount=amount, rate=buy_limit_requested,
time_in_force=time_in_force)
order_obj = Order.parse_from_ccxt_object(order, pair, 'buy')
order_id = order['id']
order_status = order.get('status', None)
# we assume the order is executed at the price requested
buy_limit_filled_price = buy_limit_requested
amount_requested = amount
if order_status == 'expired' or order_status == 'rejected':
order_tif = self.strategy.order_time_in_force['buy']
# return false if the order is not filled
if float(order['filled']) == 0:
logger.warning('Buy %s order with time in force %s for %s is %s by %s.'
' zero amount is fulfilled.',
order_tif, order_type, pair, order_status, self.exchange.name)
return False
else:
# the order is partially fulfilled
# in case of IOC orders we can check immediately
# if the order is fulfilled fully or partially
logger.warning('Buy %s order with time in force %s for %s is %s by %s.'
' %s amount fulfilled out of %s (%s remaining which is canceled).',
order_tif, order_type, pair, order_status, self.exchange.name,
order['filled'], order['amount'], order['remaining']
)
stake_amount = order['cost']
amount = safe_value_fallback(order, 'filled', 'amount')
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
# in case of FOK the order may be filled immediately and fully
elif order_status == 'closed':
stake_amount = order['cost']
amount = safe_value_fallback(order, 'filled', 'amount')
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
order['fee'] = fee
for trade in Trade.get_open_trades():
if trade.pair == pair:
break
if len(Trade.get_open_trades()) == 0:
trade = Trade(
pair=pair,
stake_amount=stake_amount,
amount=amount,
amount_requested=amount_requested,
fee_open=fee,
fee_close=fee,
open_rate=buy_limit_filled_price,
open_rate_requested=buy_limit_requested,
open_date=datetime.utcnow(),
exchange=_bot.exchange.id,
open_order_id=order_id,
strategy=_bot.strategy.get_strategy_name(),
timeframe=timeframe_to_minutes(_bot.config['timeframe'])
)
trade.orders.append(order_obj)
# Update fees if order is closed
if order_status == 'closed':
self.update_trade_state(trade, order_id, order)
Trade.session.add(trade)
else:
trade.open_order_id = order_id
trade.orders.append(order_obj)
self.update_trade_state(trade, order_id, order)
Trade.session.flush()
# Updating wallets
self.wallets.update()
self._notify_buy(trade, order_type)
return True
def _notify_buy(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a buy occured.
@ -831,7 +955,8 @@ class FreqtradeBot:
logger.debug('Handling %s ...', trade)
(buy, sell) = (False, False)
(buy, sell, partial_buy, partial_sell) = (False, False, PartialTradeTuple(flag=False, amount=0),
PartialTradeTuple(flag=False, amount=0))
config_ask_strategy = self.config.get('ask_strategy', {})
@ -840,7 +965,8 @@ class FreqtradeBot:
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
self.strategy.timeframe)
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df)
(buy, sell, partial_buy, partial_sell) = self.strategy.get_signal(trade.pair,
self.strategy.timeframe, analyzed_df)
if config_ask_strategy.get('use_order_book', False):
order_book_min = config_ask_strategy.get('order_book_min', 1)
@ -865,13 +991,13 @@ class FreqtradeBot:
# resulting in outdated RPC messages
self._sell_rate_cache[trade.pair] = sell_rate
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
if self._check_and_execute_sell(trade, sell_rate, buy, sell, partial_buy, partial_sell):
return True
else:
logger.debug('checking sell')
sell_rate = self.get_sell_rate(trade.pair, True)
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
if self._check_and_execute_sell(trade, sell_rate, buy, sell, partial_buy, partial_sell):
return True
logger.debug('Found no sell signal for %s.', trade)
@ -1001,18 +1127,23 @@ class FreqtradeBot:
f"for pair {trade.pair}.")
def _check_and_execute_sell(self, trade: Trade, sell_rate: float,
buy: bool, sell: bool) -> bool:
buy: bool, sell: bool, partial_buy: PartialTradeTuple,
partial_sell: PartialTradeTuple) -> bool:
"""
Check and execute sell
"""
should_sell = self.strategy.should_sell(
trade, sell_rate, datetime.utcnow(), buy, sell,
trade, sell_rate, datetime.utcnow(), buy, sell, partial_buy, partial_sell,
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
)
if should_sell.sell_flag:
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
self.execute_sell(trade, sell_rate, should_sell.sell_type)
if should_sell.sell_type == SellType.PARTIAL_SELL_SIGNAL:
logger.info(f'Executing Partial Sell for {trade.pair}. Reason: {should_sell.sell_type}')
self.execute_partial_sell(trade, partial_sell.amount, sell_rate, should_sell.sell_type)
else:
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
self.execute_sell(trade, sell_rate, should_sell.sell_type)
return True
return False
@ -1269,6 +1400,78 @@ class FreqtradeBot:
return True
def execute_partial_sell(self, trade: Trade, amount: float, limit: float, sell_reason: SellType) -> bool:
"""
Executes a limit sell for the given trade and limit
:param trade: Trade instance
:param limit: limit rate for the sell order
:param sellreason: Reason the sell was triggered
:return: True if it succeeds (supported) False (not supported)
:OSM metodo modificado para aceptar ventas parciales
"""
#TODO: Add a custom partial sell notification call
sell_type = 'sell'
# First cancelling stoploss on exchange ...
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
try:
self.exchange.cancel_stoploss_order(trade.stoploss_order_id, trade.pair)
except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
order_type = self.strategy.order_types[sell_type]
if sell_reason == SellType.EMERGENCY_SELL:
# Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market")
if amount > trade.amount:
amount = trade.amount
amount = self._safe_sell_amount(trade.pair, amount)
time_in_force = self.strategy.order_time_in_force['sell']
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
time_in_force=time_in_force,
sell_reason=sell_reason.value):
logger.info(f"User requested abortion of selling {trade.pair}")
return False
try:
# Execute sell and update trade record
order = self.exchange.sell(pair=trade.pair,
ordertype=order_type,
amount=amount, rate=limit,
time_in_force=time_in_force
)
except InsufficientFundsError as e:
logger.warning(f"Unable to place order {e}.")
# Try to figure out what went wrong
self.handle_insufficient_funds(trade)
return False
order_obj = Order.parse_from_ccxt_object(order, trade.pair, 'sell')
trade.orders.append(order_obj)
trade.open_order_id = order['id']
trade.close_rate_requested = limit
trade.sell_reason = sell_reason.value
# In case of market sell orders the order can be closed immediately
if order.get('status', 'unknown') == 'closed':
self.update_trade_state(trade, trade.open_order_id, order)
# force trade to contine opened, if amount >= trade amount close trade
if amount < trade.amount:
trade.is_open = True
Trade.session.flush()
#Lock pair for one candle to prevent immediate rebuys
self.strategy.lock_pair(trade.pair, timeframe_to_next_date(self.config['timeframe']))
self._notify_sell(trade, order_type)
return True
def _notify_sell(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a sell occured.
@ -1389,7 +1592,7 @@ class FreqtradeBot:
abs_tol=constants.MATH_CLOSE_PREC):
order['amount'] = new_amount
order.pop('filled', None)
trade.recalc_open_trade_price()
except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception)
@ -1400,7 +1603,7 @@ class FreqtradeBot:
trade.update(order)
# Updating wallets when order is closed
if not trade.is_open:
if order['status'] == 'closed':
self.wallets.update()
return False

View File

@ -125,6 +125,8 @@ class Order(_DECL_BASE):
filled = Column(Float, nullable=True)
remaining = Column(Float, nullable=True)
cost = Column(Float, nullable=True)
fee = Column(Float, nullable=True) # OSM
fee_cost = Column(Float, nullable=True) # OSM
order_date = Column(DateTime, nullable=True, default=datetime.utcnow)
order_filled_date = Column(DateTime, nullable=True)
order_update_date = Column(DateTime, nullable=True)
@ -151,6 +153,7 @@ class Order(_DECL_BASE):
self.filled = order.get('filled', self.filled)
self.remaining = order.get('remaining', self.remaining)
self.cost = order.get('cost', self.cost)
self.fee = order.get('fee', self.fee)
if 'timestamp' in order and order['timestamp'] is not None:
self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc)
@ -377,6 +380,10 @@ class Trade(_DECL_BASE):
return
logger.info('Updating trade (id=%s) ...', self.id)
# to be able to partially buy or sell
if order['amount'] < self.amount:
self.partial_update(order)
return
if order_type in ('market', 'limit') and order['side'] == 'buy':
# Update open rate and actual amount
@ -400,6 +407,44 @@ class Trade(_DECL_BASE):
raise ValueError(f'Unknown order type: {order_type}')
cleanup()
def partial_update(self, order: Dict) -> None:
"""
Updates this entity with amount and actual open/close rates,
modified to support multiple orders keeping the trade opened
:param order: order retrieved by exchange.fetch_order()
:return: None
"""
order_type = order['type']
if order_type in ('market', 'limit') and order['side'] == 'buy':
# Update open rate and actual amount
self.open_rate = self.average_open_rate(order['filled'],
safe_value_fallback(order, 'average', 'price'),
self.amount, self.open_rate)
self.amount = Decimal(self.amount or 0) + Decimal(order['filled'])
self.decrease_wallet(self, Decimal(order['filled']), self.open_rate)
if self.is_open and order['filled'] != 0:
logger.info(f'{order_type.upper()}_Partial BUY has been fulfilled for {self}.')
self.open_order_id = None
elif order_type in ('market', 'limit') and order['side'] == 'sell':
self.amount = (Decimal(self.amount or 0) - Decimal(order['filled']))
if self.is_open and order['filled'] != 0:
logger.info(f'{order_type.upper()}_Partial SELL has been fulfilled for {self}.')
self.partial_close(self, safe_value_fallback(order, 'average', 'price'))
self.increase_wallet(self, Decimal(order['filled']), order['price'])
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop'):
self.stoploss_order_id = None
self.close_rate_requested = self.stop_loss
if self.is_open:
logger.info(f'{order_type.upper()} is hit for {self}.')
self.close(order['average'])
else:
raise ValueError(f'Unknown order type: {order_type}')
cleanup()
def close(self, rate: float) -> None:
"""
Sets close_rate to the given rate, calculates total profit
@ -417,6 +462,17 @@ class Trade(_DECL_BASE):
self
)
def partial_close(self, rate: float) -> None:
""" modified close() to keep trade opened
"""
self.is_open = True
self.sell_order_status = 'closed'
self.open_order_id = None
logger.info(
'Updated position %s,',
self
)
def update_fee(self, fee_cost: float, fee_currency: Optional[str], fee_rate: Optional[float],
side: str) -> None:
"""
@ -494,6 +550,8 @@ class Trade(_DECL_BASE):
fee: Optional[float] = None) -> float:
"""
Calculate the absolute profit in stake currency between Close and Open trade
Modified to stop using open_tride_price and use open_rate instead,
which be actualized if partial buys.
:param fee: fee to use on the close rate (optional).
If rate is not set self.fee will be used
:param rate: close rate to compare with (optional).
@ -504,13 +562,15 @@ class Trade(_DECL_BASE):
rate=(rate or self.close_rate),
fee=(fee or self.fee_close)
)
profit = close_trade_price - self.open_trade_price
profit = close_trade_price - self.open_rate * self.amount
return float(f"{profit:.8f}")
def calc_profit_ratio(self, rate: Optional[float] = None,
fee: Optional[float] = None) -> float:
"""
Calculates the profit as ratio (including fee).
Modified to stop using open_tride_price and use open_rate instead,
which be actualized if partial buys.
:param rate: rate to compare with (optional).
If rate is not set self.close_rate will be used
:param fee: fee to use on the close rate (optional).
@ -520,7 +580,7 @@ class Trade(_DECL_BASE):
rate=(rate or self.close_rate),
fee=(fee or self.fee_close)
)
profit_ratio = (close_trade_price / self.open_trade_price) - 1
profit_ratio = (close_trade_price / (self.open_rate * self.amount)) - 1
return float(f"{profit_ratio:.8f}")
def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]:
@ -538,6 +598,32 @@ class Trade(_DECL_BASE):
else:
return None
def average_open_rate(self, order_amount, order_price, trade_amount, trade_open_rate):
"""
Calculates average entry price when increase an open position with a partial buy
:param order_amount: Amount in base coin to buy
:param order_price: rate at the time of order buy
:trade_amount: Actual amount of the open position. (befor adding new order amount)
:trade_open_rate: Actual open price of position.
:return: New open rate modified with the order data.
"""
#((order['amount'] * order['price']) + (self.amount * trade.open_rate)) / (order['amount'] + trade.amount)
return ((order_amount * order_price) + (trade_amount * trade_open_rate)) / (order_amount + trade_amount)
def increase_wallet(self, amount: float, rate: float) -> None:
sell_trade = Decimal(amount) * Decimal(rate)
fees = sell_trade * Decimal(self.fee_close)
close_trade_price = float(sell_trade - fees)
self.stake_amount = Decimal(self.stake_amount or 0) + Decimal(close_trade_price)
def decrease_wallet(self, amount: float, rate: float) -> None:
buy_trade = Decimal(amount * rate)
fees = buy_trade * Decimal(self.fee_open)
open_trade_price = float(buy_trade + fees)
self.stake_amount = Decimal(self.stake_amount or 0) - Decimal(open_trade_price)
@staticmethod
def get_trades(trade_filter=None) -> Query:
"""

View File

@ -30,6 +30,8 @@ class SignalType(Enum):
"""
BUY = "buy"
SELL = "sell"
PARTIAL_BUY = "partial_buy"
PARTIAL_SELL = "partial_sell"
class SellType(Enum):
@ -41,6 +43,7 @@ class SellType(Enum):
STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange"
TRAILING_STOP_LOSS = "trailing_stop_loss"
SELL_SIGNAL = "sell_signal"
PARTIAL_SELL_SIGNAL = "partial_sell_signal"
FORCE_SELL = "force_sell"
EMERGENCY_SELL = "emergency_sell"
NONE = ""
@ -57,6 +60,13 @@ class SellCheckTuple(NamedTuple):
sell_flag: bool
sell_type: SellType
class PartialTradeTuple(NamedTuple):
"""
NamedTuple for partial trade + amount
"""
flag: bool
amount: float
class IStrategy(ABC):
"""
@ -100,6 +110,8 @@ class IStrategy(ABC):
'stoploss': 'limit',
'stoploss_on_exchange': False,
'stoploss_on_exchange_interval': 60,
'partial_buy': 'limit',
'partial_sell': 'limit',
}
# Optional time in force
@ -423,18 +435,19 @@ class IStrategy(ABC):
else:
raise StrategyError(f"Dataframe returned from strategy has mismatching {message}.")
def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool]:
def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) \
-> Tuple[bool, bool, PartialTradeTuple, PartialTradeTuple]:
"""
Calculates current signal based based on the buy / sell columns of the dataframe.
Calculates current signal based based on the buy / sell / partial_buy / partial_sell columns of the dataframe.
Used by Bot to get the signal to buy or sell
:param pair: pair in format ANT/BTC
:param timeframe: timeframe to use
:param dataframe: Analyzed dataframe to get signal from.
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal
:return: (Buy, Sell,partial_buy, partial_sell) A bool-tuple indicating buy/sell signal
"""
if not isinstance(dataframe, DataFrame) or dataframe.empty:
logger.warning(f'Empty candle (OHLCV) data for pair {pair}')
return False, False
return False, False, PartialTradeTuple(False,0), PartialTradeTuple(False,0)
latest_date = dataframe['date'].max()
latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
@ -449,19 +462,22 @@ class IStrategy(ABC):
'Outdated history for pair %s. Last tick is %s minutes old',
pair, int((arrow.utcnow() - latest_date).total_seconds() // 60)
)
return False, False
return False, False, PartialTradeTuple(False,0), PartialTradeTuple(False,0)
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
latest['date'], pair, str(buy), str(sell))
return buy, sell
(buy, sell, partial_buy, partial_sell) = \
latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1,\
latest[SignalType.PARTIAL_BUY] == 1, latest[SignalType.PARTIAL_SELL == 1]
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s partial_buy = %s partial_sell = %s',
latest['date'], pair, str(buy), str(sell), str(partial_buy), str(partial_sell))
return buy, sell, partial_buy, partial_sell
def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
sell: bool, low: float = None, high: float = None,
force_stoploss: float = 0) -> SellCheckTuple:
sell: bool, partial_buy: PartialTradeTuple, partial_sell: PartialTradeTuple,
low: float = None, high: float = None, force_stoploss: float = 0) -> SellCheckTuple:
"""
This function evaluates if one of the conditions required to trigger a sell
has been reached, which can either be a stop-loss, ROI or sell-signal.
Modified to support partial trades
:param low: Only used during backtesting to simulate stoploss
:param high: Only used during backtesting, to simulate ROI
:param force_stoploss: Externally provided stoploss
@ -511,6 +527,11 @@ class IStrategy(ABC):
f"sell_type=SellType.SELL_SIGNAL")
return SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL)
if partial_sell.flag and not partial_buy.flag and not buy and config_ask_strategy.get('use_sell_signal', True):
logger.debug(f"{trade.pair} - Partial Sell signal received. partial_sell_flag=True, "
f"sell_type=SellType.PARTIAL_SELL_SIGNAL")
return SellCheckTuple(sell_flag=True, sell_type=SellType.PARTIAL_SELL_SIGNAL)
# This one is noisy, commented out...
# logger.debug(f"{trade.pair} - No sell signal. sell_flag=False")
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)

View File

@ -55,6 +55,7 @@ class Wallets:
def _update_dry(self) -> None:
"""
Update from database in dry-run mode
- Modified to support partial trades considering trade.stake_amount as wallet value (Beta)
- 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
@ -64,9 +65,9 @@ class Wallets:
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
current_stake = self.start_cap + tot_profit
_wallets[self._config['stake_currency']] = Wallet(
self._config['stake_currency'],
current_stake,
@ -82,6 +83,13 @@ class Wallets:
0,
trade.amount
)
current_stake += trade.stake_amount
_wallets[self._config['stake_currency']] = Wallet(
self._config['stake_currency'],
current_stake,
0,
current_stake
)
self._wallets = _wallets
def _update_live(self) -> None: