telegram message fix
This commit is contained in:
parent
06d14b81dd
commit
ec8e0b1fcb
@ -1238,16 +1238,26 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False,
|
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False,
|
||||||
sub_trade: bool = False) -> None:
|
sub_trade: bool = False, order: Dict = None) -> None:
|
||||||
"""
|
"""
|
||||||
Sends rpc notification when a sell occurred.
|
Sends rpc notification when a sell occurred.
|
||||||
"""
|
"""
|
||||||
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
|
||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
|
||||||
# Use cached rates here - it was updated seconds ago.
|
# Use cached rates here - it was updated seconds ago.
|
||||||
current_rate = self.exchange.get_rate(
|
current_rate = self.exchange.get_rate(
|
||||||
trade.pair, refresh=False, side="sell") if not fill else None
|
trade.pair, refresh=False, side="sell") if not fill else None
|
||||||
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
if order:
|
||||||
|
profit_rate = safe_value_fallback(order, 'average', 'price')
|
||||||
|
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||||
|
profit = trade.process_sell_sub_trade(order, is_closed=False)
|
||||||
|
open_rate = trade.get_open_rate(profit, profit_rate, amount)
|
||||||
|
open_cost=open_rate * amount * (1+ trade.fee_open)
|
||||||
|
profit_ratio = ( open_cost + profit)/open_cost - 1
|
||||||
|
else:
|
||||||
|
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||||
|
profit = trade.calc_profit(rate=profit_rate)
|
||||||
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
|
amount = trade.amount
|
||||||
|
open_rate = trade.open_rate
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
@ -1259,11 +1269,11 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
'gain': gain,
|
'gain': gain,
|
||||||
'limit': profit_rate,
|
'limit': profit_rate,
|
||||||
'order_type': order_type,
|
'order_type': order_type,
|
||||||
'amount': trade.amount,
|
'amount': amount,
|
||||||
'open_rate': trade.open_rate,
|
'open_rate': open_rate,
|
||||||
'close_rate': trade.close_rate,
|
'close_rate': trade.close_rate,
|
||||||
'current_rate': current_rate,
|
'current_rate': current_rate,
|
||||||
'profit_amount': profit_trade,
|
'profit_amount': profit,
|
||||||
'profit_ratio': profit_ratio,
|
'profit_ratio': profit_ratio,
|
||||||
'buy_tag': trade.buy_tag,
|
'buy_tag': trade.buy_tag,
|
||||||
'sell_reason': trade.sell_reason,
|
'sell_reason': trade.sell_reason,
|
||||||
@ -1275,10 +1285,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
'sub_trade': sub_trade,
|
'sub_trade': sub_trade,
|
||||||
}
|
}
|
||||||
|
|
||||||
if 'fiat_display_currency' in self.config:
|
|
||||||
msg.update({
|
|
||||||
'fiat_currency': self.config['fiat_display_currency'],
|
|
||||||
})
|
|
||||||
|
|
||||||
# Send the message
|
# Send the message
|
||||||
self.rpc.send_msg(msg)
|
self.rpc.send_msg(msg)
|
||||||
@ -1382,9 +1389,10 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
|
||||||
if not trade.is_open:
|
if not trade.is_open:
|
||||||
|
self.handle_protections(trade.pair)
|
||||||
|
if order.get('side', None) == 'sell':
|
||||||
if send_msg and not stoploss_order and not trade.open_order_id:
|
if send_msg and not stoploss_order and not trade.open_order_id:
|
||||||
self._notify_exit(trade, '', True, sub_trade=sub_trade)
|
self._notify_exit(trade, '', True, sub_trade=sub_trade)
|
||||||
self.handle_protections(trade.pair)
|
|
||||||
elif send_msg and not trade.open_order_id:
|
elif send_msg and not trade.open_order_id:
|
||||||
# Buy fill
|
# Buy fill
|
||||||
self._notify_enter(trade, order, fill=True, sub_trade=sub_trade)
|
self._notify_enter(trade, order, fill=True, sub_trade=sub_trade)
|
||||||
|
@ -495,10 +495,16 @@ class LocalTrade():
|
|||||||
raise ValueError(f'Unknown order type: {order_type}')
|
raise ValueError(f'Unknown order type: {order_type}')
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
def process_sell_sub_trade(self, order: Dict) -> None:
|
def process_sell_sub_trade(self, order: Dict, is_closed: bool = True) -> float:
|
||||||
orders = (self.select_filled_orders('buy'))
|
orders = (self.select_filled_orders('buy'))
|
||||||
sell_rate = float(safe_value_fallback(order, 'average', 'price'))
|
# is_closed = order['ft_is_open']
|
||||||
sell_amount = float(safe_value_fallback(order, 'filled', 'amount'))
|
sell_amount = float(safe_value_fallback(order, 'filled', 'amount'))
|
||||||
|
if is_closed:
|
||||||
|
if sell_amount == self.amount:
|
||||||
|
self.close(safe_value_fallback(order, 'average', 'price'))
|
||||||
|
Trade.commit()
|
||||||
|
return
|
||||||
|
sell_rate = float(safe_value_fallback(order, 'average', 'price'))
|
||||||
profit = 0.0
|
profit = 0.0
|
||||||
idx = -1
|
idx = -1
|
||||||
while sell_amount:
|
while sell_amount:
|
||||||
@ -507,25 +513,25 @@ class LocalTrade():
|
|||||||
buy_rate = b_order.average or b_order.price
|
buy_rate = b_order.average or b_order.price
|
||||||
if sell_amount < buy_amount:
|
if sell_amount < buy_amount:
|
||||||
amount = sell_amount
|
amount = sell_amount
|
||||||
b_order.filled -= amount
|
if is_closed:
|
||||||
|
b_order.filled -= amount
|
||||||
else:
|
else:
|
||||||
if sell_amount == self.amount:
|
if is_closed:
|
||||||
self.close(safe_value_fallback(order, 'average', 'price'))
|
b_order.is_fully_realized = True
|
||||||
Trade.commit()
|
self.update_order(b_order)
|
||||||
return
|
|
||||||
b_order.is_fully_realized = True
|
|
||||||
self.update_order(b_order)
|
|
||||||
idx -= 1
|
idx -= 1
|
||||||
amount = buy_amount
|
amount = buy_amount
|
||||||
sell_amount -= amount
|
sell_amount -= amount
|
||||||
profit += self.calc_profit2(buy_rate, sell_rate, amount)
|
profit += self.calc_profit2(buy_rate, sell_rate, amount)
|
||||||
b_order2 = orders[idx]
|
b_order2 = orders[idx]
|
||||||
amount2 = b_order2.filled or b_order2.amount
|
amount2 = b_order2.filled or b_order2.amount
|
||||||
b_order2.average = (b_order2.average * amount2 - profit) / amount2
|
if is_closed :
|
||||||
self.update_order(b_order2)
|
b_order2.average = (b_order2.average * amount2 - profit) / amount2
|
||||||
Order.query.session.commit()
|
self.update_order(b_order2)
|
||||||
self.recalc_trade_from_orders()
|
Order.query.session.commit()
|
||||||
Trade.commit()
|
self.recalc_trade_from_orders()
|
||||||
|
Trade.commit()
|
||||||
|
return profit
|
||||||
|
|
||||||
def calc_profit2(self, open_rate: float, close_rate: float,
|
def calc_profit2(self, open_rate: float, close_rate: float,
|
||||||
amount: float) -> float:
|
amount: float) -> float:
|
||||||
@ -533,6 +539,12 @@ class LocalTrade():
|
|||||||
(Decimal(1 - self.fee_close) * Decimal(close_rate) -
|
(Decimal(1 - self.fee_close) * Decimal(close_rate) -
|
||||||
Decimal(1 + self.fee_open) * Decimal(open_rate)))
|
Decimal(1 + self.fee_open) * Decimal(open_rate)))
|
||||||
|
|
||||||
|
def get_open_rate(self, profit: float, close_rate: float,
|
||||||
|
amount: float) -> float:
|
||||||
|
return float(Decimal(amount) *
|
||||||
|
(Decimal(1 - self.fee_close) * Decimal(close_rate)) -
|
||||||
|
profit)/(Decimal(amount) * Decimal(1 + self.fee_open))
|
||||||
|
|
||||||
def close(self, rate: float, *, show_msg: bool = True) -> None:
|
def close(self, rate: float, *, show_msg: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Sets close_rate to the given rate, calculates total profit
|
Sets close_rate to the given rate, calculates total profit
|
||||||
|
@ -218,7 +218,7 @@ class RPC:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def _rpc_status_table(self, stake_currency: str,
|
def _rpc_status_table(self, stake_currency: str,
|
||||||
fiat_display_currency: str) -> Tuple[List, List, float]:
|
fiat_display_currency: str, show_order: bool = False) -> Tuple[List, List, float]:
|
||||||
trades = Trade.get_open_trades()
|
trades = Trade.get_open_trades()
|
||||||
if not trades:
|
if not trades:
|
||||||
raise RPCException('no active trade')
|
raise RPCException('no active trade')
|
||||||
@ -232,8 +232,17 @@ class RPC:
|
|||||||
trade.pair, refresh=False, side="sell")
|
trade.pair, refresh=False, side="sell")
|
||||||
except (PricingError, ExchangeError):
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
trade_profit = trade.calc_profit(current_rate)
|
if show_order:
|
||||||
profit_str = f'{trade.calc_profit_ratio(current_rate):.2%}'
|
b_order = trade.select_order('buy', is_open=False)
|
||||||
|
amount = b_order.filled or b_order.amount
|
||||||
|
open_rate = b_order.average or b_order.price
|
||||||
|
open_cost=open_rate * amount * (1+ trade.fee_open)
|
||||||
|
trade_profit = trade.calc_profit2(open_rate, current_rate, amount)
|
||||||
|
profit_pct = ( open_cost + profit)/open_cost - 1
|
||||||
|
profit_str = f'{profit_pct:.2%}'
|
||||||
|
else:
|
||||||
|
trade_profit = trade.calc_profit(current_rate)
|
||||||
|
profit_str = f'{trade.calc_profit_ratio(current_rate):.2%}'
|
||||||
if self._fiat_converter:
|
if self._fiat_converter:
|
||||||
fiat_profit = self._fiat_converter.convert_amount(
|
fiat_profit = self._fiat_converter.convert_amount(
|
||||||
trade_profit,
|
trade_profit,
|
||||||
@ -244,11 +253,14 @@ class RPC:
|
|||||||
profit_str += f" ({fiat_profit:.2f})"
|
profit_str += f" ({fiat_profit:.2f})"
|
||||||
fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \
|
fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \
|
||||||
else fiat_profit_sum + fiat_profit
|
else fiat_profit_sum + fiat_profit
|
||||||
|
last_sell_order = trade.select_order('sell')
|
||||||
|
last_sell_order_id = last_sell_order.order_id if last_sell_order else None
|
||||||
detail_trade = [
|
detail_trade = [
|
||||||
trade.id,
|
trade.id,
|
||||||
trade.pair + ('*' if (trade.open_order_id is not None
|
trade.pair + ('*' if (trade.open_order_id ==
|
||||||
and trade.close_rate_requested is None) else '')
|
trade.select_order('buy').order_id) else '')
|
||||||
+ ('**' if (trade.close_rate_requested is not None) else ''),
|
+ ('**' if (trade.open_order_id ==
|
||||||
|
last_sell_order_id) else ''),
|
||||||
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
|
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
|
||||||
profit_str
|
profit_str
|
||||||
]
|
]
|
||||||
|
@ -291,7 +291,6 @@ class Telegram(RPCHandler):
|
|||||||
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
|
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
|
||||||
f"*Buy Tag:* `{msg['buy_tag']}`\n"
|
f"*Buy Tag:* `{msg['buy_tag']}`\n"
|
||||||
f"*Sell Reason:* `{msg['sell_reason']}`\n"
|
f"*Sell Reason:* `{msg['sell_reason']}`\n"
|
||||||
f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n"
|
|
||||||
f"*Amount:* `{msg['amount']:.8f}`\n"
|
f"*Amount:* `{msg['amount']:.8f}`\n"
|
||||||
f"*Open Rate:* `{msg['open_rate']:.8f}`\n")
|
f"*Open Rate:* `{msg['open_rate']:.8f}`\n")
|
||||||
|
|
||||||
@ -314,6 +313,8 @@ class Telegram(RPCHandler):
|
|||||||
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
||||||
|
|
||||||
message += ")`"
|
message += ")`"
|
||||||
|
else:
|
||||||
|
message += f"\n*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`"
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
|
def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
|
||||||
@ -453,7 +454,9 @@ class Telegram(RPCHandler):
|
|||||||
if context.args and 'table' in context.args:
|
if context.args and 'table' in context.args:
|
||||||
self._status_table(update, context)
|
self._status_table(update, context)
|
||||||
return
|
return
|
||||||
|
if context.args and 'order' in context.args:
|
||||||
|
self._status_table(update, context, show_order=True)
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# Check if there's at least one numerical ID provided.
|
# Check if there's at least one numerical ID provided.
|
||||||
@ -525,7 +528,7 @@ class Telegram(RPCHandler):
|
|||||||
self._send_msg(str(e))
|
self._send_msg(str(e))
|
||||||
|
|
||||||
@authorized_only
|
@authorized_only
|
||||||
def _status_table(self, update: Update, context: CallbackContext) -> None:
|
def _status_table(self, update: Update, context: CallbackContext, show_order: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Handler for /status table.
|
Handler for /status table.
|
||||||
Returns the current TradeThread status in table format
|
Returns the current TradeThread status in table format
|
||||||
@ -536,7 +539,7 @@ class Telegram(RPCHandler):
|
|||||||
try:
|
try:
|
||||||
fiat_currency = self._config.get('fiat_display_currency', '')
|
fiat_currency = self._config.get('fiat_display_currency', '')
|
||||||
statlist, head, fiat_profit_sum = self._rpc._rpc_status_table(
|
statlist, head, fiat_profit_sum = self._rpc._rpc_status_table(
|
||||||
self._config['stake_currency'], fiat_currency)
|
self._config['stake_currency'], fiat_currency, show_order)
|
||||||
|
|
||||||
show_total = not isnan(fiat_profit_sum) and len(statlist) > 1
|
show_total = not isnan(fiat_profit_sum) and len(statlist) > 1
|
||||||
max_trades_per_msg = 50
|
max_trades_per_msg = 50
|
||||||
|
@ -1879,11 +1879,11 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
|||||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||||
'*Buy Tag:* `buy_signal1`\n'
|
'*Buy Tag:* `buy_signal1`\n'
|
||||||
'*Sell Reason:* `stop_loss`\n'
|
'*Sell Reason:* `stop_loss`\n'
|
||||||
'*Duration:* `1:00:00 (60.0 min)`\n'
|
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Current Rate:* `0.00003201`\n'
|
||||||
'*Close Rate:* `0.00003201`'
|
'*Close Rate:* `0.00003201`\n'
|
||||||
|
'*Duration:* `1:00:00 (60.0 min)`'
|
||||||
)
|
)
|
||||||
|
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
@ -1911,11 +1911,11 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
|||||||
'*Unrealized Profit:* `-57.41%`\n'
|
'*Unrealized Profit:* `-57.41%`\n'
|
||||||
'*Buy Tag:* `buy_signal1`\n'
|
'*Buy Tag:* `buy_signal1`\n'
|
||||||
'*Sell Reason:* `stop_loss`\n'
|
'*Sell Reason:* `stop_loss`\n'
|
||||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Current Rate:* `0.00003201`\n'
|
||||||
'*Close Rate:* `0.00003201`'
|
'*Close Rate:* `0.00003201`\n'
|
||||||
|
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
||||||
)
|
)
|
||||||
# Reset singleton function to avoid random breaks
|
# Reset singleton function to avoid random breaks
|
||||||
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
||||||
@ -1982,10 +1982,10 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
|
|||||||
'*Profit:* `-57.41%`\n'
|
'*Profit:* `-57.41%`\n'
|
||||||
'*Buy Tag:* `buy_signal1`\n'
|
'*Buy Tag:* `buy_signal1`\n'
|
||||||
'*Sell Reason:* `stop_loss`\n'
|
'*Sell Reason:* `stop_loss`\n'
|
||||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Close Rate:* `0.00003201`'
|
'*Close Rate:* `0.00003201`\n'
|
||||||
|
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -2081,11 +2081,11 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
|||||||
'*Unrealized Profit:* `-57.41%`\n'
|
'*Unrealized Profit:* `-57.41%`\n'
|
||||||
'*Buy Tag:* `buy_signal1`\n'
|
'*Buy Tag:* `buy_signal1`\n'
|
||||||
'*Sell Reason:* `stop_loss`\n'
|
'*Sell Reason:* `stop_loss`\n'
|
||||||
'*Duration:* `2:35:03 (155.1 min)`\n'
|
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Current Rate:* `0.00003201`\n'
|
||||||
'*Close Rate:* `0.00003201`'
|
'*Close Rate:* `0.00003201`\n'
|
||||||
|
'*Duration:* `2:35:03 (155.1 min)`'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4559,6 +4559,8 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
|||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.open_order_id is None
|
assert trade.open_order_id is None
|
||||||
|
assert trade.is_open
|
||||||
|
print(trade.is_open)
|
||||||
assert trade.amount == 22
|
assert trade.amount == 22
|
||||||
assert trade.stake_amount == 203.5625
|
assert trade.stake_amount == 203.5625
|
||||||
assert pytest.approx(trade.open_rate) == 9.252840909090908
|
assert pytest.approx(trade.open_rate) == 9.252840909090908
|
||||||
@ -4570,7 +4572,285 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
|||||||
# Make sure the closed order is found as the second order.
|
# Make sure the closed order is found as the second order.
|
||||||
order = trade.select_order('sell', False)
|
order = trade.select_order('sell', False)
|
||||||
assert order.order_id == '653'
|
assert order.order_id == '653'
|
||||||
|
|
||||||
|
def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_exchange(mocker)
|
||||||
|
patch_wallet(mocker, free=10000)
|
||||||
|
default_conf_usdt.update({
|
||||||
|
"position_adjustment_enable": True,
|
||||||
|
"dry_run": False,
|
||||||
|
"stake_amount": 10.0,
|
||||||
|
"dry_run_wallet": 1000.0,
|
||||||
|
})
|
||||||
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
|
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
||||||
|
bid = 11
|
||||||
|
stake_amount = 10
|
||||||
|
buy_rate_mock = MagicMock(return_value=bid)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
get_rate=buy_rate_mock,
|
||||||
|
fetch_ticker=MagicMock(return_value={
|
||||||
|
'bid': 10,
|
||||||
|
'ask': 12,
|
||||||
|
'last': 11
|
||||||
|
}),
|
||||||
|
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||||
|
get_fee=fee,
|
||||||
|
)
|
||||||
|
pair = 'ETH/USDT'
|
||||||
|
# Initial buy
|
||||||
|
closed_successful_buy_order = {
|
||||||
|
'pair': pair,
|
||||||
|
'ft_pair': pair,
|
||||||
|
'ft_order_side': 'buy',
|
||||||
|
'side': 'buy',
|
||||||
|
'type': 'limit',
|
||||||
|
'status': 'closed',
|
||||||
|
'price': bid,
|
||||||
|
'average': bid,
|
||||||
|
'cost': bid * stake_amount,
|
||||||
|
'amount': stake_amount,
|
||||||
|
'filled': stake_amount,
|
||||||
|
'ft_is_open': False,
|
||||||
|
'id': '650',
|
||||||
|
'order_id': '650'
|
||||||
|
}
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
|
MagicMock(return_value=closed_successful_buy_order))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||||
|
MagicMock(return_value=closed_successful_buy_order))
|
||||||
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
# Should create an closed trade with an no open order id
|
||||||
|
# Order is filled and trade is open
|
||||||
|
orders = Order.query.all()
|
||||||
|
assert orders
|
||||||
|
assert len(orders) == 1
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert trade.is_open is True
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
assert trade.open_rate == 11
|
||||||
|
assert trade.stake_amount == 110
|
||||||
|
|
||||||
|
# Assume it does nothing since order is closed and trade is open
|
||||||
|
freqtrade.update_closed_trades_without_assigned_fees()
|
||||||
|
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert trade.is_open is True
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
assert trade.open_rate == 11
|
||||||
|
assert trade.stake_amount == 110
|
||||||
|
assert not trade.fee_updated('buy')
|
||||||
|
|
||||||
|
freqtrade.check_handle_timedout()
|
||||||
|
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert trade.is_open is True
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
assert trade.open_rate == 11
|
||||||
|
assert trade.stake_amount == 110
|
||||||
|
assert not trade.fee_updated('buy')
|
||||||
|
|
||||||
|
# First position adjustment buy.
|
||||||
|
open_dca_order_1 = {
|
||||||
|
'ft_pair': pair,
|
||||||
|
'ft_order_side': 'buy',
|
||||||
|
'side': 'buy',
|
||||||
|
'type': 'limit',
|
||||||
|
'status': None,
|
||||||
|
'price': 9,
|
||||||
|
'amount': 12,
|
||||||
|
'cost': 108,
|
||||||
|
'ft_is_open': True,
|
||||||
|
'id': '651',
|
||||||
|
'order_id': '651'
|
||||||
|
}
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
|
MagicMock(return_value=open_dca_order_1))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||||
|
MagicMock(return_value=open_dca_order_1))
|
||||||
|
assert freqtrade.execute_entry(pair, stake_amount, trade=trade)
|
||||||
|
|
||||||
|
orders = Order.query.all()
|
||||||
|
assert orders
|
||||||
|
assert len(orders) == 2
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert trade.open_order_id == '651'
|
||||||
|
assert trade.open_rate == 11
|
||||||
|
assert trade.amount == 10
|
||||||
|
assert trade.stake_amount == 110
|
||||||
|
assert not trade.fee_updated('buy')
|
||||||
|
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
|
||||||
|
assert len(trades) == 1
|
||||||
|
assert trade.is_open
|
||||||
|
assert not trade.fee_updated('buy')
|
||||||
|
order = trade.select_order('buy', False)
|
||||||
|
assert order
|
||||||
|
assert order.order_id == '650'
|
||||||
|
|
||||||
|
def make_sure_its_651(*args, **kwargs):
|
||||||
|
|
||||||
|
if args[0] == '650':
|
||||||
|
return closed_successful_buy_order
|
||||||
|
if args[0] == '651':
|
||||||
|
return open_dca_order_1
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Assume it does nothing since order is still open
|
||||||
|
fetch_order_mm = MagicMock(side_effect=make_sure_its_651)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.create_order', fetch_order_mm)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', fetch_order_mm)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', fetch_order_mm)
|
||||||
|
freqtrade.update_closed_trades_without_assigned_fees()
|
||||||
|
|
||||||
|
orders = Order.query.all()
|
||||||
|
assert orders
|
||||||
|
assert len(orders) == 2
|
||||||
|
# Assert that the trade is found as open and without fees
|
||||||
|
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
|
||||||
|
assert len(trades) == 1
|
||||||
|
# Assert trade is as expected
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert trade.open_order_id == '651'
|
||||||
|
assert trade.open_rate == 11
|
||||||
|
assert trade.amount == 10
|
||||||
|
assert trade.stake_amount == 110
|
||||||
|
assert not trade.fee_updated('buy')
|
||||||
|
|
||||||
|
# Make sure the closed order is found as the first order.
|
||||||
|
order = trade.select_order('buy', False)
|
||||||
|
assert order.order_id == '650'
|
||||||
|
|
||||||
|
# Now close the order so it should update.
|
||||||
|
closed_dca_order_1 = {
|
||||||
|
'ft_pair': pair,
|
||||||
|
'ft_order_side': 'buy',
|
||||||
|
'side': 'buy',
|
||||||
|
'type': 'limit',
|
||||||
|
'status': 'closed',
|
||||||
|
'price': 9,
|
||||||
|
'average': 9,
|
||||||
|
'amount': 12,
|
||||||
|
'filled': 12,
|
||||||
|
'cost': 108,
|
||||||
|
'ft_is_open': False,
|
||||||
|
'id': '651',
|
||||||
|
'order_id': '651'
|
||||||
|
}
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
|
MagicMock(return_value=closed_dca_order_1))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||||
|
MagicMock(return_value=closed_dca_order_1))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||||
|
MagicMock(return_value=closed_dca_order_1))
|
||||||
|
freqtrade.check_handle_timedout()
|
||||||
|
|
||||||
|
# Assert trade is as expected (averaged dca)
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
assert pytest.approx(trade.open_rate) == 9.90909090909
|
||||||
|
assert trade.amount == 22
|
||||||
|
assert trade.stake_amount == 218
|
||||||
|
|
||||||
|
orders = Order.query.all()
|
||||||
|
assert orders
|
||||||
|
assert len(orders) == 2
|
||||||
|
|
||||||
|
# Make sure the closed order is found as the second order.
|
||||||
|
order = trade.select_order('buy', False)
|
||||||
|
assert order.order_id == '651'
|
||||||
|
|
||||||
|
# Assert that the trade is not found as open and without fees
|
||||||
|
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
|
||||||
|
assert len(trades) == 1
|
||||||
|
|
||||||
|
# Add a second DCA
|
||||||
|
closed_dca_order_2 = {
|
||||||
|
'ft_pair': pair,
|
||||||
|
'status': 'closed',
|
||||||
|
'ft_order_side': 'buy',
|
||||||
|
'side': 'buy',
|
||||||
|
'type': 'limit',
|
||||||
|
'price': 7,
|
||||||
|
'average': 7,
|
||||||
|
'amount': 15,
|
||||||
|
'filled': 15,
|
||||||
|
'cost': 105,
|
||||||
|
'ft_is_open': False,
|
||||||
|
'id': '652',
|
||||||
|
'order_id': '652'
|
||||||
|
}
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
|
MagicMock(return_value=closed_dca_order_2))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||||
|
MagicMock(return_value=closed_dca_order_2))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||||
|
MagicMock(return_value=closed_dca_order_2))
|
||||||
|
assert freqtrade.execute_entry(pair, stake_amount, trade=trade)
|
||||||
|
|
||||||
|
# Assert trade is as expected (averaged dca)
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
assert pytest.approx(trade.open_rate) == 8.729729729729
|
||||||
|
assert trade.amount == 37
|
||||||
|
assert trade.stake_amount == 323
|
||||||
|
|
||||||
|
orders = Order.query.all()
|
||||||
|
assert orders
|
||||||
|
assert len(orders) == 3
|
||||||
|
|
||||||
|
# Make sure the closed order is found as the second order.
|
||||||
|
order = trade.select_order('buy', False)
|
||||||
|
assert order.order_id == '652'
|
||||||
|
closed_sell_dca_order_1 = {
|
||||||
|
'ft_pair': pair,
|
||||||
|
'status': 'closed',
|
||||||
|
'ft_order_side': 'sell',
|
||||||
|
'side': 'sell',
|
||||||
|
'type': 'limit',
|
||||||
|
'price': 8,
|
||||||
|
'average': 8,
|
||||||
|
'amount': 15,
|
||||||
|
'filled': 15,
|
||||||
|
'cost': 120,
|
||||||
|
'ft_is_open': False,
|
||||||
|
'id': '653',
|
||||||
|
'order_id': '653'
|
||||||
|
}
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
|
MagicMock(return_value=closed_sell_dca_order_1))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||||
|
MagicMock(return_value=closed_sell_dca_order_1))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
|
||||||
|
MagicMock(return_value=closed_sell_dca_order_1))
|
||||||
|
assert freqtrade.execute_trade_exit(trade=trade, limit=8,
|
||||||
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS),
|
||||||
|
sub_trade_amt=15)
|
||||||
|
|
||||||
|
# Assert trade is as expected (averaged dca)
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert trade.open_order_id is None
|
||||||
|
assert trade.amount == 22
|
||||||
|
assert trade.stake_amount == 203.5625
|
||||||
|
assert pytest.approx(trade.open_rate) == 9.252840909090908
|
||||||
|
|
||||||
|
orders = Order.query.all()
|
||||||
|
assert orders
|
||||||
|
assert len(orders) == 4
|
||||||
|
|
||||||
|
# Make sure the closed order is found as the second order.
|
||||||
|
order = trade.select_order('sell', False)
|
||||||
|
assert order.order_id == '653'
|
||||||
|
|
||||||
def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None:
|
def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||||
default_conf_usdt.update({
|
default_conf_usdt.update({
|
||||||
|
Loading…
Reference in New Issue
Block a user