Merge branch 'freqtrade:develop' into bt-metrics2
This commit is contained in:
+29
-19
@@ -457,30 +457,40 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
orders = self.exchange.fetch_orders(trade.pair, trade.open_date_utc)
|
orders = self.exchange.fetch_orders(trade.pair, trade.open_date_utc)
|
||||||
|
prev_exit_reason = trade.exit_reason
|
||||||
|
prev_trade_state = trade.is_open
|
||||||
for order in orders:
|
for order in orders:
|
||||||
trade_order = [o for o in trade.orders if o.order_id == order['id']]
|
trade_order = [o for o in trade.orders if o.order_id == order['id']]
|
||||||
if trade_order:
|
|
||||||
continue
|
|
||||||
logger.info(f"Found previously unknown order {order['id']} for {trade.pair}.")
|
|
||||||
|
|
||||||
order_obj = Order.parse_from_ccxt_object(order, trade.pair, order['side'])
|
if trade_order:
|
||||||
order_obj.order_filled_date = datetime.fromtimestamp(
|
# We knew this order, but didn't have it updated properly
|
||||||
safe_value_fallback(order, 'lastTradeTimestamp', 'timestamp') // 1000,
|
order_obj = trade_order[0]
|
||||||
tz=timezone.utc)
|
else:
|
||||||
trade.orders.append(order_obj)
|
logger.info(f"Found previously unknown order {order['id']} for {trade.pair}.")
|
||||||
prev_exit_reason = trade.exit_reason
|
|
||||||
trade.exit_reason = ExitType.SOLD_ON_EXCHANGE.value
|
order_obj = Order.parse_from_ccxt_object(order, trade.pair, order['side'])
|
||||||
self.update_trade_state(trade, order['id'], order)
|
order_obj.order_filled_date = datetime.fromtimestamp(
|
||||||
|
safe_value_fallback(order, 'lastTradeTimestamp', 'timestamp') // 1000,
|
||||||
|
tz=timezone.utc)
|
||||||
|
trade.orders.append(order_obj)
|
||||||
|
Trade.commit()
|
||||||
|
trade.exit_reason = ExitType.SOLD_ON_EXCHANGE.value
|
||||||
|
|
||||||
|
self.update_trade_state(trade, order['id'], order, send_msg=False)
|
||||||
|
|
||||||
logger.info(f"handled order {order['id']}")
|
logger.info(f"handled order {order['id']}")
|
||||||
if not trade.is_open:
|
|
||||||
# Trade was just closed
|
# Refresh trade from database
|
||||||
trade.close_date = order_obj.order_filled_date
|
Trade.session.refresh(trade)
|
||||||
Trade.commit()
|
if not trade.is_open:
|
||||||
break
|
# Trade was just closed
|
||||||
else:
|
trade.close_date = trade.date_last_filled_utc
|
||||||
trade.exit_reason = prev_exit_reason
|
self.order_close_notify(trade, order_obj,
|
||||||
Trade.commit()
|
order_obj.ft_order_side == 'stoploss',
|
||||||
|
send_msg=prev_trade_state != trade.is_open)
|
||||||
|
else:
|
||||||
|
trade.exit_reason = prev_exit_reason
|
||||||
|
Trade.commit()
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
logger.warning("Error finding onexchange order.")
|
logger.warning("Error finding onexchange order.")
|
||||||
|
|||||||
+2
-2
@@ -156,7 +156,7 @@ def round_dict(d, n):
|
|||||||
return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()}
|
return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()}
|
||||||
|
|
||||||
|
|
||||||
def safe_value_fallback(obj: dict, key1: str, key2: str, default_value=None):
|
def safe_value_fallback(obj: dict, key1: str, key2: Optional[str] = None, default_value=None):
|
||||||
"""
|
"""
|
||||||
Search a value in obj, return this if it's not None.
|
Search a value in obj, return this if it's not None.
|
||||||
Then search key2 in obj - return that if it's not none - then use default_value.
|
Then search key2 in obj - return that if it's not none - then use default_value.
|
||||||
@@ -165,7 +165,7 @@ def safe_value_fallback(obj: dict, key1: str, key2: str, default_value=None):
|
|||||||
if key1 in obj and obj[key1] is not None:
|
if key1 in obj and obj[key1] is not None:
|
||||||
return obj[key1]
|
return obj[key1]
|
||||||
else:
|
else:
|
||||||
if key2 in obj and obj[key2] is not None:
|
if key2 and key2 in obj and obj[key2] is not None:
|
||||||
return obj[key2]
|
return obj[key2]
|
||||||
return default_value
|
return default_value
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ from freqtrade.exceptions import DependencyException, OperationalException
|
|||||||
from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, amount_to_contract_precision,
|
from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, amount_to_contract_precision,
|
||||||
price_to_precision)
|
price_to_precision)
|
||||||
from freqtrade.leverage import interest
|
from freqtrade.leverage import interest
|
||||||
|
from freqtrade.misc import safe_value_fallback
|
||||||
from freqtrade.persistence.base import ModelBase, SessionType
|
from freqtrade.persistence.base import ModelBase, SessionType
|
||||||
from freqtrade.util import FtPrecise, dt_now
|
from freqtrade.util import FtPrecise, dt_from_ts, dt_now, dt_ts
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -176,7 +177,9 @@ class Order(ModelBase):
|
|||||||
# (represents the funding fee since the last order)
|
# (represents the funding fee since the last order)
|
||||||
self.funding_fee = self.trade.funding_fees
|
self.funding_fee = self.trade.funding_fees
|
||||||
if (order.get('filled', 0.0) or 0.0) > 0 and not self.order_filled_date:
|
if (order.get('filled', 0.0) or 0.0) > 0 and not self.order_filled_date:
|
||||||
self.order_filled_date = datetime.now(timezone.utc)
|
self.order_filled_date = dt_from_ts(
|
||||||
|
safe_value_fallback(order, 'lastTradeTimestamp', default_value=dt_ts())
|
||||||
|
)
|
||||||
self.order_update_date = datetime.now(timezone.utc)
|
self.order_update_date = datetime.now(timezone.utc)
|
||||||
|
|
||||||
def to_ccxt_object(self, stopPriceName: str = 'stopPrice') -> Dict[str, Any]:
|
def to_ccxt_object(self, stopPriceName: str = 'stopPrice') -> Dict[str, Any]:
|
||||||
@@ -430,13 +433,20 @@ class LocalTrade:
|
|||||||
return self.amount
|
return self.amount
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_last_filled_utc(self) -> datetime:
|
def _date_last_filled_utc(self) -> Optional[datetime]:
|
||||||
""" Date of the last filled order"""
|
""" Date of the last filled order"""
|
||||||
orders = self.select_filled_orders()
|
orders = self.select_filled_orders()
|
||||||
if not orders:
|
if orders:
|
||||||
|
return max(o.order_filled_utc for o in orders if o.order_filled_utc)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date_last_filled_utc(self) -> datetime:
|
||||||
|
""" Date of the last filled order - or open_date if no orders are filled"""
|
||||||
|
dt_last_filled = self._date_last_filled_utc
|
||||||
|
if not dt_last_filled:
|
||||||
return self.open_date_utc
|
return self.open_date_utc
|
||||||
return max([self.open_date_utc,
|
return max([self.open_date_utc, dt_last_filled])
|
||||||
max(o.order_filled_utc for o in orders if o.order_filled_utc)])
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def open_date_utc(self):
|
def open_date_utc(self):
|
||||||
@@ -772,7 +782,7 @@ class LocalTrade:
|
|||||||
and marks trade as closed
|
and marks trade as closed
|
||||||
"""
|
"""
|
||||||
self.close_rate = rate
|
self.close_rate = rate
|
||||||
self.close_date = self.close_date or datetime.utcnow()
|
self.close_date = self.close_date or self._date_last_filled_utc or dt_now()
|
||||||
self.is_open = False
|
self.is_open = False
|
||||||
self.exit_order_status = 'closed'
|
self.exit_order_status = 'closed'
|
||||||
self.recalc_trade_from_orders(is_closing=True)
|
self.recalc_trade_from_orders(is_closing=True)
|
||||||
|
|||||||
@@ -600,7 +600,9 @@ def test_calc_open_close_trade_price(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_trade_close(fee):
|
def test_trade_close(fee, time_machine):
|
||||||
|
time_machine.move_to("2022-09-01 05:00:00 +00:00", tick=False)
|
||||||
|
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='ADA/USDT',
|
pair='ADA/USDT',
|
||||||
stake_amount=60.0,
|
stake_amount=60.0,
|
||||||
@@ -609,7 +611,7 @@ def test_trade_close(fee):
|
|||||||
is_open=True,
|
is_open=True,
|
||||||
fee_open=fee.return_value,
|
fee_open=fee.return_value,
|
||||||
fee_close=fee.return_value,
|
fee_close=fee.return_value,
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
open_date=dt_now() - timedelta(minutes=10),
|
||||||
interest_rate=0.0005,
|
interest_rate=0.0005,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
trading_mode=margin,
|
trading_mode=margin,
|
||||||
@@ -628,6 +630,7 @@ def test_trade_close(fee):
|
|||||||
status="closed",
|
status="closed",
|
||||||
order_type="limit",
|
order_type="limit",
|
||||||
side=trade.entry_side,
|
side=trade.entry_side,
|
||||||
|
order_filled_date=trade.open_date,
|
||||||
))
|
))
|
||||||
trade.orders.append(Order(
|
trade.orders.append(Order(
|
||||||
ft_order_side=trade.exit_side,
|
ft_order_side=trade.exit_side,
|
||||||
@@ -642,6 +645,7 @@ def test_trade_close(fee):
|
|||||||
status="closed",
|
status="closed",
|
||||||
order_type="limit",
|
order_type="limit",
|
||||||
side=trade.exit_side,
|
side=trade.exit_side,
|
||||||
|
order_filled_date=dt_now(),
|
||||||
))
|
))
|
||||||
assert trade.close_profit is None
|
assert trade.close_profit is None
|
||||||
assert trade.close_date is None
|
assert trade.close_date is None
|
||||||
@@ -650,14 +654,15 @@ def test_trade_close(fee):
|
|||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
assert pytest.approx(trade.close_profit) == 0.094513715
|
assert pytest.approx(trade.close_profit) == 0.094513715
|
||||||
assert trade.close_date is not None
|
assert trade.close_date is not None
|
||||||
|
assert trade.close_date_utc == dt_now()
|
||||||
|
|
||||||
new_date = datetime(2020, 2, 2, 15, 6, 1),
|
new_date = dt_now() + timedelta(minutes=5)
|
||||||
assert trade.close_date != new_date
|
assert trade.close_date_utc != new_date
|
||||||
# Close should NOT update close_date if the trade has been closed already
|
# Close should NOT update close_date if the trade has been closed already
|
||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
trade.close_date = new_date
|
trade.close_date = new_date
|
||||||
trade.close(2.2)
|
trade.close(2.2)
|
||||||
assert trade.close_date == new_date
|
assert trade.close_date_utc == new_date
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
|
|||||||
@@ -5687,7 +5687,8 @@ def test_handle_onexchange_order(mocker, default_conf_usdt, limit_order, is_shor
|
|||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
freqtrade.handle_onexchange_order(trade)
|
freqtrade.handle_onexchange_order(trade)
|
||||||
assert log_has_re(r"Found previously unknown order .*", caplog)
|
assert log_has_re(r"Found previously unknown order .*", caplog)
|
||||||
assert mock_uts.call_count == 1
|
# Update trade state is called twice, once for the known and once for the unknown order.
|
||||||
|
assert mock_uts.call_count == 2
|
||||||
assert mock_fo.call_count == 1
|
assert mock_fo.call_count == 1
|
||||||
|
|
||||||
trade = Trade.session.scalars(select(Trade)).first()
|
trade = Trade.session.scalars(select(Trade)).first()
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ def test_safe_value_fallback():
|
|||||||
|
|
||||||
assert safe_value_fallback(dict1, 'keyNo', 'keyNo') is None
|
assert safe_value_fallback(dict1, 'keyNo', 'keyNo') is None
|
||||||
assert safe_value_fallback(dict1, 'keyNo', 'keyNo', 55) == 55
|
assert safe_value_fallback(dict1, 'keyNo', 'keyNo', 55) == 55
|
||||||
|
assert safe_value_fallback(dict1, 'keyNo', default_value=55) == 55
|
||||||
|
assert safe_value_fallback(dict1, 'keyNo', None, default_value=55) == 55
|
||||||
|
|
||||||
|
|
||||||
def test_safe_value_fallback2():
|
def test_safe_value_fallback2():
|
||||||
|
|||||||
Reference in New Issue
Block a user