Merge branch 'freqtrade:develop' into strategy_utils

This commit is contained in:
hippocritical 2023-03-18 20:15:12 +01:00 committed by GitHub
commit 763f4f4a3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 363 additions and 306 deletions

View File

@ -1,7 +1,7 @@
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from sqlalchemy import func from sqlalchemy import func, select
from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
@ -20,7 +20,7 @@ def start_convert_db(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
init_db(config['db_url']) init_db(config['db_url'])
session_target = Trade._session session_target = Trade.session
init_db(config['db_url_from']) init_db(config['db_url_from'])
logger.info("Starting db migration.") logger.info("Starting db migration.")
@ -36,16 +36,16 @@ def start_convert_db(args: Dict[str, Any]) -> None:
session_target.commit() session_target.commit()
for pairlock in PairLock.query: for pairlock in PairLock.get_all_locks():
pairlock_count += 1 pairlock_count += 1
make_transient(pairlock) make_transient(pairlock)
session_target.add(pairlock) session_target.add(pairlock)
session_target.commit() session_target.commit()
# Update sequences # Update sequences
max_trade_id = session_target.query(func.max(Trade.id)).scalar() max_trade_id = session_target.scalar(select(func.max(Trade.id)))
max_order_id = session_target.query(func.max(Order.id)).scalar() max_order_id = session_target.scalar(select(func.max(Order.id)))
max_pairlock_id = session_target.query(func.max(PairLock.id)).scalar() max_pairlock_id = session_target.scalar(select(func.max(PairLock.id)))
set_sequence_ids(session_target.get_bind(), set_sequence_ids(session_target.get_bind(),
trade_id=max_trade_id, trade_id=max_trade_id,

View File

@ -373,7 +373,7 @@ def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataF
filters = [] filters = []
if strategy: if strategy:
filters.append(Trade.strategy == strategy) filters.append(Trade.strategy == strategy)
trades = trade_list_to_dataframe(Trade.get_trades(filters).all()) trades = trade_list_to_dataframe(list(Trade.get_trades(filters).all()))
return trades return trades

View File

@ -251,7 +251,7 @@ class FreqaiDataKitchen:
(drop_index == 0) & (drop_index_labels == 0) (drop_index == 0) & (drop_index_labels == 0)
] ]
logger.info( logger.info(
f"dropped {len(unfiltered_df) - len(filtered_df)} training points" f"{self.pair}: dropped {len(unfiltered_df) - len(filtered_df)} training points"
f" due to NaNs in populated dataset {len(unfiltered_df)}." f" due to NaNs in populated dataset {len(unfiltered_df)}."
) )
if (1 - len(filtered_df) / len(unfiltered_df)) > 0.1 and self.live: if (1 - len(filtered_df) / len(unfiltered_df)) > 0.1 and self.live:
@ -675,7 +675,7 @@ class FreqaiDataKitchen:
] ]
logger.info( logger.info(
f"SVM tossed {len(y_pred) - kept_points.sum()}" f"{self.pair}: SVM tossed {len(y_pred) - kept_points.sum()}"
f" test points from {len(y_pred)} total points." f" test points from {len(y_pred)} total points."
) )
@ -949,7 +949,7 @@ class FreqaiDataKitchen:
if (len(do_predict) - do_predict.sum()) > 0: if (len(do_predict) - do_predict.sum()) > 0:
logger.info( logger.info(
f"DI tossed {len(do_predict) - do_predict.sum()} predictions for " f"{self.pair}: DI tossed {len(do_predict) - do_predict.sum()} predictions for "
"being too far from training data." "being too far from training data."
) )

View File

@ -104,6 +104,7 @@ class IFreqaiModel(ABC):
self.data_provider: Optional[DataProvider] = None self.data_provider: Optional[DataProvider] = None
self.max_system_threads = max(int(psutil.cpu_count() * 2 - 2), 1) self.max_system_threads = max(int(psutil.cpu_count() * 2 - 2), 1)
self.can_short = True # overridden in start() with strategy.can_short self.can_short = True # overridden in start() with strategy.can_short
self.model: Any = None
record_params(config, self.full_path) record_params(config, self.full_path)
@ -338,13 +339,14 @@ class IFreqaiModel(ABC):
except Exception as msg: except Exception as msg:
logger.warning( logger.warning(
f"Training {pair} raised exception {msg.__class__.__name__}. " f"Training {pair} raised exception {msg.__class__.__name__}. "
f"Message: {msg}, skipping.") f"Message: {msg}, skipping.", exc_info=True)
self.model = None
self.dd.pair_dict[pair]["trained_timestamp"] = int( self.dd.pair_dict[pair]["trained_timestamp"] = int(
tr_train.stopts) tr_train.stopts)
if self.plot_features: if self.plot_features and self.model is not None:
plot_feature_importance(self.model, pair, dk, self.plot_features) plot_feature_importance(self.model, pair, dk, self.plot_features)
if self.save_backtest_models: if self.save_backtest_models and self.model is not None:
logger.info('Saving backtest model to disk.') logger.info('Saving backtest model to disk.')
self.dd.save_data(self.model, pair, dk) self.dd.save_data(self.model, pair, dk)
else: else:

View File

@ -819,7 +819,7 @@ class FreqtradeBot(LoggingMixin):
trade.orders.append(order_obj) trade.orders.append(order_obj)
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
# Updating wallets # Updating wallets

View File

@ -54,12 +54,9 @@ def init_db(db_url: str) -> None:
# https://docs.sqlalchemy.org/en/13/orm/contextual.html#thread-local-scope # https://docs.sqlalchemy.org/en/13/orm/contextual.html#thread-local-scope
# Scoped sessions proxy requests to the appropriate thread-local session. # Scoped sessions proxy requests to the appropriate thread-local session.
# We should use the scoped_session object - not a seperately initialized version # We should use the scoped_session object - not a seperately initialized version
Trade._session = scoped_session(sessionmaker(bind=engine, autoflush=False)) Trade.session = scoped_session(sessionmaker(bind=engine, autoflush=False))
Order._session = Trade._session Order.session = Trade.session
PairLock._session = Trade._session PairLock.session = Trade.session
Trade.query = Trade._session.query_property()
Order.query = Trade._session.query_property()
PairLock.query = Trade._session.query_property()
previous_tables = inspect(engine).get_table_names() previous_tables = inspect(engine).get_table_names()
ModelBase.metadata.create_all(engine) ModelBase.metadata.create_all(engine)

View File

@ -1,8 +1,8 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any, ClassVar, Dict, Optional from typing import Any, ClassVar, Dict, Optional
from sqlalchemy import String, or_ from sqlalchemy import ScalarResult, String, or_, select
from sqlalchemy.orm import Mapped, Query, QueryPropertyDescriptor, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.persistence.base import ModelBase, SessionType from freqtrade.persistence.base import ModelBase, SessionType
@ -13,8 +13,7 @@ class PairLock(ModelBase):
Pair Locks database model. Pair Locks database model.
""" """
__tablename__ = 'pairlocks' __tablename__ = 'pairlocks'
query: ClassVar[QueryPropertyDescriptor] session: ClassVar[SessionType]
_session: ClassVar[SessionType]
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
@ -37,7 +36,8 @@ class PairLock(ModelBase):
f'lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})') f'lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})')
@staticmethod @staticmethod
def query_pair_locks(pair: Optional[str], now: datetime, side: str = '*') -> Query: def query_pair_locks(
pair: Optional[str], now: datetime, side: str = '*') -> ScalarResult['PairLock']:
""" """
Get all currently active locks for this pair Get all currently active locks for this pair
:param pair: Pair to check for. Returns all current locks if pair is empty :param pair: Pair to check for. Returns all current locks if pair is empty
@ -53,9 +53,11 @@ class PairLock(ModelBase):
else: else:
filters.append(PairLock.side == '*') filters.append(PairLock.side == '*')
return PairLock.query.filter( return PairLock.session.scalars(select(PairLock).filter(*filters))
*filters
) @staticmethod
def get_all_locks() -> ScalarResult['PairLock']:
return PairLock.session.scalars(select(PairLock))
def to_json(self) -> Dict[str, Any]: def to_json(self) -> Dict[str, Any]:
return { return {

View File

@ -1,6 +1,8 @@
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import List, Optional from typing import List, Optional, Sequence
from sqlalchemy import select
from freqtrade.exchange import timeframe_to_next_date from freqtrade.exchange import timeframe_to_next_date
from freqtrade.persistence.models import PairLock from freqtrade.persistence.models import PairLock
@ -51,15 +53,15 @@ class PairLocks():
active=True active=True
) )
if PairLocks.use_db: if PairLocks.use_db:
PairLock.query.session.add(lock) PairLock.session.add(lock)
PairLock.query.session.commit() PairLock.session.commit()
else: else:
PairLocks.locks.append(lock) PairLocks.locks.append(lock)
return lock return lock
@staticmethod @staticmethod
def get_pair_locks( def get_pair_locks(pair: Optional[str], now: Optional[datetime] = None,
pair: Optional[str], now: Optional[datetime] = None, side: str = '*') -> List[PairLock]: side: str = '*') -> Sequence[PairLock]:
""" """
Get all currently active locks for this pair Get all currently active locks for this pair
:param pair: Pair to check for. Returns all current locks if pair is empty :param pair: Pair to check for. Returns all current locks if pair is empty
@ -106,7 +108,7 @@ class PairLocks():
for lock in locks: for lock in locks:
lock.active = False lock.active = False
if PairLocks.use_db: if PairLocks.use_db:
PairLock.query.session.commit() PairLock.session.commit()
@staticmethod @staticmethod
def unlock_reason(reason: str, now: Optional[datetime] = None) -> None: def unlock_reason(reason: str, now: Optional[datetime] = None) -> None:
@ -126,11 +128,11 @@ class PairLocks():
PairLock.active.is_(True), PairLock.active.is_(True),
PairLock.reason == reason PairLock.reason == reason
] ]
locks = PairLock.query.filter(*filters) locks = PairLock.session.scalars(select(PairLock).filter(*filters)).all()
for lock in locks: for lock in locks:
logger.info(f"Releasing lock for {lock.pair} with reason '{reason}'.") logger.info(f"Releasing lock for {lock.pair} with reason '{reason}'.")
lock.active = False lock.active = False
PairLock.query.session.commit() PairLock.session.commit()
else: else:
# used in backtesting mode; don't show log messages for speed # used in backtesting mode; don't show log messages for speed
locksb = PairLocks.get_pair_locks(None) locksb = PairLocks.get_pair_locks(None)
@ -165,11 +167,11 @@ class PairLocks():
) )
@staticmethod @staticmethod
def get_all_locks() -> List[PairLock]: def get_all_locks() -> Sequence[PairLock]:
""" """
Return all locks, also locks with expired end date Return all locks, also locks with expired end date
""" """
if PairLocks.use_db: if PairLocks.use_db:
return PairLock.query.all() return PairLock.get_all_locks().all()
else: else:
return PairLocks.locks return PairLocks.locks

View File

@ -5,11 +5,11 @@ import logging
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from math import isclose from math import isclose
from typing import Any, ClassVar, Dict, List, Optional, cast from typing import Any, ClassVar, Dict, List, Optional, Sequence, cast
from sqlalchemy import Enum, Float, ForeignKey, Integer, String, UniqueConstraint, desc, func from sqlalchemy import (Enum, Float, ForeignKey, Integer, ScalarResult, Select, String,
from sqlalchemy.orm import (Mapped, Query, QueryPropertyDescriptor, lazyload, mapped_column, UniqueConstraint, desc, func, select)
relationship) from sqlalchemy.orm import Mapped, lazyload, mapped_column, relationship
from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES, from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPEN_EXCHANGE_STATES,
BuySell, LongShort) BuySell, LongShort)
@ -36,8 +36,7 @@ class Order(ModelBase):
Mirrors CCXT Order structure Mirrors CCXT Order structure
""" """
__tablename__ = 'orders' __tablename__ = 'orders'
query: ClassVar[QueryPropertyDescriptor] session: ClassVar[SessionType]
_session: ClassVar[SessionType]
# Uniqueness should be ensured over pair, order_id # Uniqueness should be ensured over pair, order_id
# its likely that order_id is unique per Pair on some exchanges. # its likely that order_id is unique per Pair on some exchanges.
@ -263,12 +262,12 @@ class Order(ModelBase):
return o return o
@staticmethod @staticmethod
def get_open_orders() -> List['Order']: def get_open_orders() -> Sequence['Order']:
""" """
Retrieve open orders from the database Retrieve open orders from the database
:return: List of open orders :return: List of open orders
""" """
return Order.query.filter(Order.ft_is_open.is_(True)).all() return Order.session.scalars(select(Order).filter(Order.ft_is_open.is_(True))).all()
@staticmethod @staticmethod
def order_by_id(order_id: str) -> Optional['Order']: def order_by_id(order_id: str) -> Optional['Order']:
@ -276,7 +275,7 @@ class Order(ModelBase):
Retrieve order based on order_id Retrieve order based on order_id
:return: Order or None :return: Order or None
""" """
return Order.query.filter(Order.order_id == order_id).first() return Order.session.scalars(select(Order).filter(Order.order_id == order_id)).first()
class LocalTrade(): class LocalTrade():
@ -1153,7 +1152,9 @@ class LocalTrade():
get open trade count get open trade count
""" """
if Trade.use_db: if Trade.use_db:
return Trade.query.filter(Trade.is_open.is_(True)).count() return Trade.session.execute(
select(func.count(Trade.id)).filter(Trade.is_open.is_(True))
).scalar_one()
else: else:
return LocalTrade.bt_open_open_trade_count return LocalTrade.bt_open_open_trade_count
@ -1186,8 +1187,7 @@ class Trade(ModelBase, LocalTrade):
Note: Fields must be aligned with LocalTrade class Note: Fields must be aligned with LocalTrade class
""" """
__tablename__ = 'trades' __tablename__ = 'trades'
query: ClassVar[QueryPropertyDescriptor] session: ClassVar[SessionType]
_session: ClassVar[SessionType]
use_db: bool = True use_db: bool = True
@ -1287,18 +1287,18 @@ class Trade(ModelBase, LocalTrade):
def delete(self) -> None: def delete(self) -> None:
for order in self.orders: for order in self.orders:
Order.query.session.delete(order) Order.session.delete(order)
Trade.query.session.delete(self) Trade.session.delete(self)
Trade.commit() Trade.commit()
@staticmethod @staticmethod
def commit(): def commit():
Trade.query.session.commit() Trade.session.commit()
@staticmethod @staticmethod
def rollback(): def rollback():
Trade.query.session.rollback() Trade.session.rollback()
@staticmethod @staticmethod
def get_trades_proxy(*, pair: Optional[str] = None, is_open: Optional[bool] = None, def get_trades_proxy(*, pair: Optional[str] = None, is_open: Optional[bool] = None,
@ -1332,7 +1332,7 @@ class Trade(ModelBase, LocalTrade):
) )
@staticmethod @staticmethod
def get_trades(trade_filter=None, include_orders: bool = True) -> Query['Trade']: def get_trades_query(trade_filter=None, include_orders: bool = True) -> Select:
""" """
Helper function to query Trades using filters. Helper function to query Trades using filters.
NOTE: Not supported in Backtesting. NOTE: Not supported in Backtesting.
@ -1347,22 +1347,35 @@ class Trade(ModelBase, LocalTrade):
if trade_filter is not None: if trade_filter is not None:
if not isinstance(trade_filter, list): if not isinstance(trade_filter, list):
trade_filter = [trade_filter] trade_filter = [trade_filter]
this_query = Trade.query.filter(*trade_filter) this_query = select(Trade).filter(*trade_filter)
else: else:
this_query = Trade.query this_query = select(Trade)
if not include_orders: if not include_orders:
# Don't load order relations # Don't load order relations
# Consider using noload or raiseload instead of lazyload # Consider using noload or raiseload instead of lazyload
this_query = this_query.options(lazyload(Trade.orders)) this_query = this_query.options(lazyload(Trade.orders))
return this_query return this_query
@staticmethod
def get_trades(trade_filter=None, include_orders: bool = True) -> ScalarResult['Trade']:
"""
Helper function to query Trades using filters.
NOTE: Not supported in Backtesting.
:param trade_filter: Optional filter to apply to trades
Can be either a Filter object, or a List of filters
e.g. `(trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True),])`
e.g. `(trade_filter=Trade.id == trade_id)`
:return: unsorted query object
"""
return Trade.session.scalars(Trade.get_trades_query(trade_filter, include_orders))
@staticmethod @staticmethod
def get_open_order_trades() -> List['Trade']: def get_open_order_trades() -> List['Trade']:
""" """
Returns all open trades Returns all open trades
NOTE: Not supported in Backtesting. NOTE: Not supported in Backtesting.
""" """
return Trade.get_trades(Trade.open_order_id.isnot(None)).all() return cast(List[Trade], Trade.get_trades(Trade.open_order_id.isnot(None)).all())
@staticmethod @staticmethod
def get_open_trades_without_assigned_fees(): def get_open_trades_without_assigned_fees():
@ -1392,11 +1405,12 @@ class Trade(ModelBase, LocalTrade):
Retrieves total realized profit Retrieves total realized profit
""" """
if Trade.use_db: if Trade.use_db:
total_profit = Trade.query.with_entities( total_profit: float = Trade.session.execute(
func.sum(Trade.close_profit_abs)).filter(Trade.is_open.is_(False)).scalar() select(func.sum(Trade.close_profit_abs)).filter(Trade.is_open.is_(False))
).scalar_one()
else: else:
total_profit = sum( total_profit = sum(t.close_profit_abs # type: ignore
t.close_profit_abs for t in LocalTrade.get_trades_proxy(is_open=False)) for t in LocalTrade.get_trades_proxy(is_open=False))
return total_profit or 0 return total_profit or 0
@staticmethod @staticmethod
@ -1406,8 +1420,9 @@ class Trade(ModelBase, LocalTrade):
in stake currency in stake currency
""" """
if Trade.use_db: if Trade.use_db:
total_open_stake_amount = Trade.query.with_entities( total_open_stake_amount = Trade.session.scalar(
func.sum(Trade.stake_amount)).filter(Trade.is_open.is_(True)).scalar() select(func.sum(Trade.stake_amount)).filter(Trade.is_open.is_(True))
)
else: else:
total_open_stake_amount = sum( total_open_stake_amount = sum(
t.stake_amount for t in LocalTrade.get_trades_proxy(is_open=True)) t.stake_amount for t in LocalTrade.get_trades_proxy(is_open=True))
@ -1423,15 +1438,18 @@ class Trade(ModelBase, LocalTrade):
if minutes: if minutes:
start_date = datetime.now(timezone.utc) - timedelta(minutes=minutes) start_date = datetime.now(timezone.utc) - timedelta(minutes=minutes)
filters.append(Trade.close_date >= start_date) filters.append(Trade.close_date >= start_date)
pair_rates = Trade.query.with_entities(
pair_rates = Trade.session.execute(
select(
Trade.pair, Trade.pair,
func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count') func.count(Trade.pair).label('count')
).filter(*filters)\ ).filter(*filters)
.group_by(Trade.pair) \ .group_by(Trade.pair)
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs'))
.all() ).all()
return [ return [
{ {
'pair': pair, 'pair': pair,
@ -1456,15 +1474,16 @@ class Trade(ModelBase, LocalTrade):
if (pair is not None): if (pair is not None):
filters.append(Trade.pair == pair) filters.append(Trade.pair == pair)
enter_tag_perf = Trade.query.with_entities( enter_tag_perf = Trade.session.execute(
select(
Trade.enter_tag, Trade.enter_tag,
func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count') func.count(Trade.pair).label('count')
).filter(*filters)\ ).filter(*filters)
.group_by(Trade.enter_tag) \ .group_by(Trade.enter_tag)
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs'))
.all() ).all()
return [ return [
{ {
@ -1488,16 +1507,16 @@ class Trade(ModelBase, LocalTrade):
filters: List = [Trade.is_open.is_(False)] filters: List = [Trade.is_open.is_(False)]
if (pair is not None): if (pair is not None):
filters.append(Trade.pair == pair) filters.append(Trade.pair == pair)
sell_tag_perf = Trade.session.execute(
sell_tag_perf = Trade.query.with_entities( select(
Trade.exit_reason, Trade.exit_reason,
func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count') func.count(Trade.pair).label('count')
).filter(*filters)\ ).filter(*filters)
.group_by(Trade.exit_reason) \ .group_by(Trade.exit_reason)
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs'))
.all() ).all()
return [ return [
{ {
@ -1521,18 +1540,18 @@ class Trade(ModelBase, LocalTrade):
filters: List = [Trade.is_open.is_(False)] filters: List = [Trade.is_open.is_(False)]
if (pair is not None): if (pair is not None):
filters.append(Trade.pair == pair) filters.append(Trade.pair == pair)
mix_tag_perf = Trade.session.execute(
mix_tag_perf = Trade.query.with_entities( select(
Trade.id, Trade.id,
Trade.enter_tag, Trade.enter_tag,
Trade.exit_reason, Trade.exit_reason,
func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count') func.count(Trade.pair).label('count')
).filter(*filters)\ ).filter(*filters)
.group_by(Trade.id) \ .group_by(Trade.id)
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs'))
.all() ).all()
return_list: List[Dict] = [] return_list: List[Dict] = []
for id, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf: for id, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf:
@ -1568,11 +1587,15 @@ class Trade(ModelBase, LocalTrade):
NOTE: Not supported in Backtesting. NOTE: Not supported in Backtesting.
:returns: Tuple containing (pair, profit_sum) :returns: Tuple containing (pair, profit_sum)
""" """
best_pair = Trade.query.with_entities( best_pair = Trade.session.execute(
Trade.pair, func.sum(Trade.close_profit).label('profit_sum') select(
).filter(Trade.is_open.is_(False) & (Trade.close_date >= start_date)) \ Trade.pair,
.group_by(Trade.pair) \ func.sum(Trade.close_profit).label('profit_sum')
.order_by(desc('profit_sum')).first() ).filter(Trade.is_open.is_(False) & (Trade.close_date >= start_date))
.group_by(Trade.pair)
.order_by(desc('profit_sum'))
).first()
return best_pair return best_pair
@staticmethod @staticmethod
@ -1582,12 +1605,13 @@ class Trade(ModelBase, LocalTrade):
NOTE: Not supported in Backtesting. NOTE: Not supported in Backtesting.
:returns: Tuple containing (pair, profit_sum) :returns: Tuple containing (pair, profit_sum)
""" """
trading_volume = Order.query.with_entities( trading_volume = Trade.session.execute(
select(
func.sum(Order.cost).label('volume') func.sum(Order.cost).label('volume')
).filter( ).filter(
Order.order_filled_date >= start_date, Order.order_filled_date >= start_date,
Order.status == 'closed' Order.status == 'closed'
).scalar() )).scalar_one()
return trading_volume return trading_volume
@staticmethod @staticmethod

View File

@ -5,7 +5,7 @@ import logging
from abc import abstractmethod from abc import abstractmethod
from datetime import date, datetime, timedelta, timezone from datetime import date, datetime, timedelta, timezone
from math import isnan from math import isnan
from typing import Any, Dict, Generator, List, Optional, Tuple, Union from typing import Any, Dict, Generator, List, Optional, Sequence, Tuple, Union
import arrow import arrow
import psutil import psutil
@ -13,6 +13,7 @@ from dateutil.relativedelta import relativedelta
from dateutil.tz import tzlocal from dateutil.tz import tzlocal
from numpy import NAN, inf, int64, mean from numpy import NAN, inf, int64, mean
from pandas import DataFrame, NaT from pandas import DataFrame, NaT
from sqlalchemy import func, select
from freqtrade import __version__ from freqtrade import __version__
from freqtrade.configuration.timerange import TimeRange from freqtrade.configuration.timerange import TimeRange
@ -122,7 +123,8 @@ class RPC:
if config['max_open_trades'] != float('inf') else -1), if config['max_open_trades'] != float('inf') else -1),
'minimal_roi': config['minimal_roi'].copy() if 'minimal_roi' in config else {}, 'minimal_roi': config['minimal_roi'].copy() if 'minimal_roi' in config else {},
'stoploss': config.get('stoploss'), 'stoploss': config.get('stoploss'),
'stoploss_on_exchange': config.get('stoploss_on_exchange', False), 'stoploss_on_exchange': config.get('order_types',
{}).get('stoploss_on_exchange', False),
'trailing_stop': config.get('trailing_stop'), 'trailing_stop': config.get('trailing_stop'),
'trailing_stop_positive': config.get('trailing_stop_positive'), 'trailing_stop_positive': config.get('trailing_stop_positive'),
'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'),
@ -158,7 +160,7 @@ class RPC:
""" """
# Fetch open trades # Fetch open trades
if trade_ids: if trade_ids:
trades: List[Trade] = Trade.get_trades(trade_filter=Trade.id.in_(trade_ids)).all() trades: Sequence[Trade] = Trade.get_trades(trade_filter=Trade.id.in_(trade_ids)).all()
else: else:
trades = Trade.get_open_trades() trades = Trade.get_open_trades()
@ -339,11 +341,13 @@ class RPC:
for day in range(0, timescale): for day in range(0, timescale):
profitday = start_date - time_offset(day) profitday = start_date - time_offset(day)
# Only query for necessary columns for performance reasons. # Only query for necessary columns for performance reasons.
trades = Trade.query.session.query(Trade.close_profit_abs).filter( trades = Trade.session.execute(
Trade.is_open.is_(False), select(Trade.close_profit_abs)
.filter(Trade.is_open.is_(False),
Trade.close_date >= profitday, Trade.close_date >= profitday,
Trade.close_date < (profitday + time_offset(1)) Trade.close_date < (profitday + time_offset(1)))
).order_by(Trade.close_date).all() .order_by(Trade.close_date)
).all()
curdayprofit = sum( curdayprofit = sum(
trade.close_profit_abs for trade in trades if trade.close_profit_abs is not None) trade.close_profit_abs for trade in trades if trade.close_profit_abs is not None)
@ -381,14 +385,19 @@ class RPC:
""" Returns the X last trades """ """ Returns the X last trades """
order_by: Any = Trade.id if order_by_id else Trade.close_date.desc() order_by: Any = Trade.id if order_by_id else Trade.close_date.desc()
if limit: if limit:
trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by( trades = Trade.session.scalars(
order_by).limit(limit).offset(offset) Trade.get_trades_query([Trade.is_open.is_(False)])
.order_by(order_by)
.limit(limit)
.offset(offset))
else: else:
trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by( trades = Trade.session.scalars(
Trade.close_date.desc()) Trade.get_trades_query([Trade.is_open.is_(False)])
.order_by(Trade.close_date.desc()))
output = [trade.to_json() for trade in trades] output = [trade.to_json() for trade in trades]
total_trades = Trade.get_trades([Trade.is_open.is_(False)]).count() total_trades = Trade.session.scalar(
select(func.count(Trade.id)).filter(Trade.is_open.is_(False)))
return { return {
"trades": output, "trades": output,
@ -436,8 +445,8 @@ class RPC:
""" Returns cumulative profit statistics """ """ Returns cumulative profit statistics """
trade_filter = ((Trade.is_open.is_(False) & (Trade.close_date >= start_date)) | trade_filter = ((Trade.is_open.is_(False) & (Trade.close_date >= start_date)) |
Trade.is_open.is_(True)) Trade.is_open.is_(True))
trades: List[Trade] = Trade.get_trades( trades: Sequence[Trade] = Trade.session.scalars(Trade.get_trades_query(
trade_filter, include_orders=False).order_by(Trade.id).all() trade_filter, include_orders=False).order_by(Trade.id)).all()
profit_all_coin = [] profit_all_coin = []
profit_all_ratio = [] profit_all_ratio = []
@ -946,12 +955,12 @@ class RPC:
def _rpc_delete_lock(self, lockid: Optional[int] = None, def _rpc_delete_lock(self, lockid: Optional[int] = None,
pair: Optional[str] = None) -> Dict[str, Any]: pair: Optional[str] = None) -> Dict[str, Any]:
""" Delete specific lock(s) """ """ Delete specific lock(s) """
locks = [] locks: Sequence[PairLock] = []
if pair: if pair:
locks = PairLocks.get_pair_locks(pair) locks = PairLocks.get_pair_locks(pair)
if lockid: if lockid:
locks = PairLock.query.filter(PairLock.id == lockid).all() locks = PairLock.session.scalars(select(PairLock).filter(PairLock.id == lockid)).all()
for lock in locks: for lock in locks:
lock.active = False lock.active = False

View File

@ -1,6 +1,7 @@
import logging import logging
from packaging import version from packaging import version
from sqlalchemy import select
from freqtrade.constants import Config from freqtrade.constants import Config
from freqtrade.enums.tradingmode import TradingMode from freqtrade.enums.tradingmode import TradingMode
@ -44,7 +45,7 @@ def _migrate_binance_futures_db(config: Config):
# Should symbol be migrated too? # Should symbol be migrated too?
# order.symbol = new_pair # order.symbol = new_pair
Trade.commit() Trade.commit()
pls = PairLock.query.filter(PairLock.pair.notlike('%:%')) pls = PairLock.session.scalars(select(PairLock).filter(PairLock.pair.notlike('%:%'))).all()
for pl in pls: for pl in pls:
pl.pair = f"{pl.pair}:{config['stake_currency']}" pl.pair = f"{pl.pair}:{config['stake_currency']}"
# print(pls) # print(pls)

View File

@ -299,7 +299,7 @@ def create_mock_trades(fee, is_short: Optional[bool] = False, use_db: bool = Tru
""" """
def add_trade(trade): def add_trade(trade):
if use_db: if use_db:
Trade.query.session.add(trade) Trade.session.add(trade)
else: else:
LocalTrade.add_bt_trade(trade) LocalTrade.add_bt_trade(trade)
is_short1 = is_short if is_short is not None else True is_short1 = is_short if is_short is not None else True
@ -332,11 +332,11 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
Create some fake trades ... Create some fake trades ...
""" """
if use_db: if use_db:
Trade.query.session.rollback() Trade.session.rollback()
def add_trade(trade): def add_trade(trade):
if use_db: if use_db:
Trade.query.session.add(trade) Trade.session.add(trade)
else: else:
LocalTrade.add_bt_trade(trade) LocalTrade.add_bt_trade(trade)
@ -366,7 +366,7 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
add_trade(trade) add_trade(trade)
if use_db: if use_db:
Trade.query.session.flush() Trade.session.flush()
def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool = True): def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool = True):
@ -375,7 +375,7 @@ def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool
""" """
def add_trade(trade): def add_trade(trade):
if use_db: if use_db:
Trade.query.session.add(trade) Trade.session.add(trade)
else: else:
LocalTrade.add_bt_trade(trade) LocalTrade.add_bt_trade(trade)

View File

@ -4,7 +4,7 @@ from pathlib import Path
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from sqlalchemy import create_engine, text from sqlalchemy import create_engine, select, text
from freqtrade.constants import DEFAULT_DB_PROD_URL from freqtrade.constants import DEFAULT_DB_PROD_URL
from freqtrade.enums import TradingMode from freqtrade.enums import TradingMode
@ -21,8 +21,8 @@ spot, margin, futures = TradingMode.SPOT, TradingMode.MARGIN, TradingMode.FUTURE
def test_init_create_session(default_conf): def test_init_create_session(default_conf):
# Check if init create a session # Check if init create a session
init_db(default_conf['db_url']) init_db(default_conf['db_url'])
assert hasattr(Trade, '_session') assert hasattr(Trade, 'session')
assert 'scoped_session' in type(Trade._session).__name__ assert 'scoped_session' in type(Trade.session).__name__
def test_init_custom_db_url(default_conf, tmpdir): def test_init_custom_db_url(default_conf, tmpdir):
@ -34,7 +34,7 @@ def test_init_custom_db_url(default_conf, tmpdir):
init_db(default_conf['db_url']) init_db(default_conf['db_url'])
assert Path(filename).is_file() assert Path(filename).is_file()
r = Trade._session.execute(text("PRAGMA journal_mode")) r = Trade.session.execute(text("PRAGMA journal_mode"))
assert r.first() == ('wal',) assert r.first() == ('wal',)
@ -235,8 +235,9 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
# Run init to test migration # Run init to test migration
init_db(default_conf['db_url']) init_db(default_conf['db_url'])
assert len(Trade.query.filter(Trade.id == 1).all()) == 1 trades = Trade.session.scalars(select(Trade).filter(Trade.id == 1)).all()
trade = Trade.query.filter(Trade.id == 1).first() assert len(trades) == 1
trade = trades[0]
assert trade.fee_open == fee.return_value assert trade.fee_open == fee.return_value
assert trade.fee_close == fee.return_value assert trade.fee_close == fee.return_value
assert trade.open_rate_requested is None assert trade.open_rate_requested is None
@ -404,9 +405,9 @@ def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
init_db(default_conf['db_url']) init_db(default_conf['db_url'])
assert len(PairLock.query.all()) == 2 assert len(PairLock.get_all_locks().all()) == 2
assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1 assert len(PairLock.session.scalars(select(PairLock).filter(PairLock.pair == '*')).all()) == 1
pairlocks = PairLock.query.filter(PairLock.pair == 'ETH/BTC').all() pairlocks = PairLock.session.scalars(select(PairLock).filter(PairLock.pair == 'ETH/BTC')).all()
assert len(pairlocks) == 1 assert len(pairlocks) == 1
pairlocks[0].pair == 'ETH/BTC' pairlocks[0].pair == 'ETH/BTC'
pairlocks[0].side == '*' pairlocks[0].side == '*'

View File

@ -4,6 +4,7 @@ from types import FunctionType
import arrow import arrow
import pytest import pytest
from sqlalchemy import select
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.enums import TradingMode from freqtrade.enums import TradingMode
@ -1494,7 +1495,7 @@ def test_stoploss_reinitialization(default_conf, fee):
assert trade.stop_loss_pct == -0.05 assert trade.stop_loss_pct == -0.05
assert trade.initial_stop_loss == 0.95 assert trade.initial_stop_loss == 0.95
assert trade.initial_stop_loss_pct == -0.05 assert trade.initial_stop_loss_pct == -0.05
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
# Lower stoploss # Lower stoploss
@ -1556,7 +1557,7 @@ def test_stoploss_reinitialization_leverage(default_conf, fee):
assert trade.stop_loss_pct == -0.1 assert trade.stop_loss_pct == -0.1
assert trade.initial_stop_loss == 0.98 assert trade.initial_stop_loss == 0.98
assert trade.initial_stop_loss_pct == -0.1 assert trade.initial_stop_loss_pct == -0.1
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
# Lower stoploss # Lower stoploss
@ -1618,7 +1619,7 @@ def test_stoploss_reinitialization_short(default_conf, fee):
assert trade.stop_loss_pct == -0.1 assert trade.stop_loss_pct == -0.1
assert trade.initial_stop_loss == 1.02 assert trade.initial_stop_loss == 1.02
assert trade.initial_stop_loss_pct == -0.1 assert trade.initial_stop_loss_pct == -0.1
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
# Lower stoploss # Lower stoploss
Trade.stoploss_reinitialization(-0.15) Trade.stoploss_reinitialization(-0.15)
@ -1793,17 +1794,17 @@ def test_get_trades_proxy(fee, use_db, is_short):
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])
def test_get_trades__query(fee, is_short): def test_get_trades__query(fee, is_short):
query = Trade.get_trades([]) query = Trade.get_trades_query([])
# without orders there should be no join issued. # without orders there should be no join issued.
query1 = Trade.get_trades([], include_orders=False) query1 = Trade.get_trades_query([], include_orders=False)
# Empty "with-options -> default - selectin" # Empty "with-options -> default - selectin"
assert query._with_options == () assert query._with_options == ()
assert query1._with_options != () assert query1._with_options != ()
create_mock_trades(fee, is_short) create_mock_trades(fee, is_short)
query = Trade.get_trades([]) query = Trade.get_trades_query([])
query1 = Trade.get_trades([], include_orders=False) query1 = Trade.get_trades_query([], include_orders=False)
assert query._with_options == () assert query._with_options == ()
assert query1._with_options != () assert query1._with_options != ()
@ -2016,6 +2017,7 @@ def test_Trade_object_idem():
'get_open_trades_without_assigned_fees', 'get_open_trades_without_assigned_fees',
'get_open_order_trades', 'get_open_order_trades',
'get_trades', 'get_trades',
'get_trades_query',
'get_exit_reason_performance', 'get_exit_reason_performance',
'get_enter_tag_performance', 'get_enter_tag_performance',
'get_mix_tag_performance', 'get_mix_tag_performance',
@ -2443,8 +2445,8 @@ def test_order_to_ccxt(limit_buy_order_open):
order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy') order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy')
order.ft_trade_id = 1 order.ft_trade_id = 1
order.query.session.add(order) order.session.add(order)
Order.query.session.commit() Order.session.commit()
order_resp = Order.order_by_id(limit_buy_order_open['id']) order_resp = Order.order_by_id(limit_buy_order_open['id'])
assert order_resp assert order_resp
@ -2546,7 +2548,7 @@ def test_recalc_trade_from_orders_dca(data) -> None:
leverage=1.0, leverage=1.0,
trading_mode=TradingMode.SPOT trading_mode=TradingMode.SPOT
) )
Trade.query.session.add(trade) Trade.session.add(trade)
for idx, (order, result) in enumerate(data['orders']): for idx, (order, result) in enumerate(data['orders']):
amount = order[1] amount = order[1]
@ -2575,11 +2577,11 @@ def test_recalc_trade_from_orders_dca(data) -> None:
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()
Trade.commit() Trade.commit()
orders1 = Order.query.all() orders1 = Order.session.scalars(select(Order)).all()
assert orders1 assert orders1
assert len(orders1) == idx + 1 assert len(orders1) == idx + 1
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert len(trade.orders) == idx + 1 assert len(trade.orders) == idx + 1
if idx < len(data) - 1: if idx < len(data) - 1:
@ -2596,6 +2598,6 @@ def test_recalc_trade_from_orders_dca(data) -> None:
assert pytest.approx(trade.close_profit_abs) == data['end_profit'] assert pytest.approx(trade.close_profit_abs) == data['end_profit']
assert pytest.approx(trade.close_profit) == data['end_profit_ratio'] assert pytest.approx(trade.close_profit) == data['end_profit_ratio']
assert not trade.is_open assert not trade.is_open
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None

View File

@ -711,8 +711,8 @@ def test_PrecisionFilter_error(mocker, whitelist_conf) -> None:
def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None: def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None:
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}] whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}]
if hasattr(Trade, 'query'): if hasattr(Trade, 'session'):
del Trade.query del Trade.session
mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True))
exchange = get_patched_exchange(mocker, whitelist_conf) exchange = get_patched_exchange(mocker, whitelist_conf)
pm = PairListManager(exchange, whitelist_conf, MagicMock()) pm = PairListManager(exchange, whitelist_conf, MagicMock())

View File

@ -14,7 +14,7 @@ def test_PairLocks(use_db):
PairLocks.use_db = use_db PairLocks.use_db = use_db
# No lock should be present # No lock should be present
if use_db: if use_db:
assert len(PairLock.query.all()) == 0 assert len(PairLock.get_all_locks().all()) == 0
assert PairLocks.use_db == use_db assert PairLocks.use_db == use_db
@ -88,13 +88,13 @@ def test_PairLocks(use_db):
if use_db: if use_db:
locks = PairLocks.get_all_locks() locks = PairLocks.get_all_locks()
locks_db = PairLock.query.all() locks_db = PairLock.get_all_locks().all()
assert len(locks) == len(locks_db) assert len(locks) == len(locks_db)
assert len(locks_db) > 0 assert len(locks_db) > 0
else: else:
# Nothing was pushed to the database # Nothing was pushed to the database
assert len(PairLocks.get_all_locks()) > 0 assert len(PairLocks.get_all_locks()) > 0
assert len(PairLock.query.all()) == 0 assert len(PairLock.get_all_locks().all()) == 0
# Reset use-db variable # Reset use-db variable
PairLocks.reset_locks() PairLocks.reset_locks()
PairLocks.use_db = True PairLocks.use_db = True
@ -107,7 +107,7 @@ def test_PairLocks_getlongestlock(use_db):
# No lock should be present # No lock should be present
PairLocks.use_db = use_db PairLocks.use_db = use_db
if use_db: if use_db:
assert len(PairLock.query.all()) == 0 assert len(PairLock.get_all_locks().all()) == 0
assert PairLocks.use_db == use_db assert PairLocks.use_db == use_db
@ -139,7 +139,7 @@ def test_PairLocks_reason(use_db):
PairLocks.use_db = use_db PairLocks.use_db = use_db
# No lock should be present # No lock should be present
if use_db: if use_db:
assert len(PairLock.query.all()) == 0 assert len(PairLock.get_all_locks().all()) == 0
assert PairLocks.use_db == use_db assert PairLocks.use_db == use_db

View File

@ -74,7 +74,7 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
trade.close(close_price) trade.close(close_price)
trade.exit_reason = exit_reason trade.exit_reason = exit_reason
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
return trade return trade

View File

@ -4,6 +4,7 @@ from unittest.mock import ANY, MagicMock, PropertyMock
import pytest import pytest
from numpy import isnan from numpy import isnan
from sqlalchemy import select
from freqtrade.edge import PairInfo from freqtrade.edge import PairInfo
from freqtrade.enums import SignalDirection, State, TradingMode from freqtrade.enums import SignalDirection, State, TradingMode
@ -354,7 +355,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
with pytest.raises(RPCException, match='invalid argument'): with pytest.raises(RPCException, match='invalid argument'):
rpc._rpc_delete('200') rpc._rpc_delete('200')
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
trades[1].stoploss_order_id = '1234' trades[1].stoploss_order_id = '1234'
trades[2].stoploss_order_id = '1234' trades[2].stoploss_order_id = '1234'
assert len(trades) > 2 assert len(trades) > 2
@ -717,7 +718,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
mocker.patch(f'{EXMS}._dry_is_price_crossed', MagicMock(return_value=False)) mocker.patch(f'{EXMS}._dry_is_price_crossed', MagicMock(return_value=False))
freqtradebot.enter_positions() freqtradebot.enter_positions()
# make an limit-buy open trade # make an limit-buy open trade
trade = Trade.query.filter(Trade.id == '3').first() trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first()
filled_amount = trade.amount / 2 filled_amount = trade.amount / 2
# Fetch order - it's open first, and closed after cancel_order is called. # Fetch order - it's open first, and closed after cancel_order is called.
mocker.patch( mocker.patch(
@ -753,7 +754,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
freqtradebot.config['max_open_trades'] = 3 freqtradebot.config['max_open_trades'] = 3
freqtradebot.enter_positions() freqtradebot.enter_positions()
trade = Trade.query.filter(Trade.id == '2').first() trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first()
amount = trade.amount amount = trade.amount
# make an limit-buy open trade, if there is no 'filled', don't sell it # make an limit-buy open trade, if there is no 'filled', don't sell it
mocker.patch( mocker.patch(
@ -771,7 +772,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
assert cancel_order_mock.call_count == 2 assert cancel_order_mock.call_count == 2
assert trade.amount == amount assert trade.amount == amount
trade = Trade.query.filter(Trade.id == '3').first() trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first()
# make an limit-sell open trade # make an limit-sell open trade
mocker.patch( mocker.patch(

View File

@ -14,6 +14,7 @@ from fastapi import FastAPI, WebSocketDisconnect
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from requests.auth import _basic_auth_str from requests.auth import _basic_auth_str
from sqlalchemy import select
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.enums import CandleType, RunMode, State, TradingMode from freqtrade.enums import CandleType, RunMode, State, TradingMode
@ -624,7 +625,7 @@ def test_api_trades(botclient, mocker, fee, markets, is_short):
assert rc.json()['offset'] == 0 assert rc.json()['offset'] == 0
create_mock_trades(fee, is_short=is_short) create_mock_trades(fee, is_short=is_short)
Trade.query.session.flush() Trade.session.flush()
rc = client_get(client, f"{BASE_URI}/trades") rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc) assert_response(rc)
@ -652,7 +653,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short):
assert_response(rc, 404) assert_response(rc, 404)
assert rc.json()['detail'] == 'Trade not found.' assert rc.json()['detail'] == 'Trade not found.'
Trade.query.session.rollback() Trade.rollback()
create_mock_trades(fee, is_short=is_short) create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/trade/3") rc = client_get(client, f"{BASE_URI}/trade/3")
@ -677,7 +678,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
create_mock_trades(fee, is_short=is_short) create_mock_trades(fee, is_short=is_short)
ftbot.strategy.order_types['stoploss_on_exchange'] = True ftbot.strategy.order_types['stoploss_on_exchange'] = True
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
trades[1].stoploss_order_id = '1234' trades[1].stoploss_order_id = '1234'
Trade.commit() Trade.commit()
assert len(trades) > 2 assert len(trades) > 2
@ -685,7 +686,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
rc = client_delete(client, f"{BASE_URI}/trades/1") rc = client_delete(client, f"{BASE_URI}/trades/1")
assert_response(rc) assert_response(rc)
assert rc.json()['result_msg'] == 'Deleted trade 1. Closed 1 open orders.' assert rc.json()['result_msg'] == 'Deleted trade 1. Closed 1 open orders.'
assert len(trades) - 1 == len(Trade.query.all()) assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all())
assert cancel_mock.call_count == 1 assert cancel_mock.call_count == 1
cancel_mock.reset_mock() cancel_mock.reset_mock()
@ -694,11 +695,11 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
assert_response(rc, 502) assert_response(rc, 502)
assert cancel_mock.call_count == 0 assert cancel_mock.call_count == 0
assert len(trades) - 1 == len(Trade.query.all()) assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all())
rc = client_delete(client, f"{BASE_URI}/trades/2") rc = client_delete(client, f"{BASE_URI}/trades/2")
assert_response(rc) assert_response(rc)
assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 2 open orders.' assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 2 open orders.'
assert len(trades) - 2 == len(Trade.query.all()) assert len(trades) - 2 == len(Trade.session.scalars(select(Trade)).all())
assert stoploss_mock.call_count == 1 assert stoploss_mock.call_count == 1
rc = client_delete(client, f"{BASE_URI}/trades/502") rc = client_delete(client, f"{BASE_URI}/trades/502")
@ -943,7 +944,7 @@ def test_api_performance(botclient, fee):
) )
trade.close_profit = trade.calc_profit_ratio(trade.close_rate) trade.close_profit = trade.calc_profit_ratio(trade.close_rate)
trade.close_profit_abs = trade.calc_profit(trade.close_rate) trade.close_profit_abs = trade.calc_profit(trade.close_rate)
Trade.query.session.add(trade) Trade.session.add(trade)
trade = Trade( trade = Trade(
pair='XRP/ETH', pair='XRP/ETH',
@ -960,7 +961,7 @@ def test_api_performance(botclient, fee):
trade.close_profit = trade.calc_profit_ratio(trade.close_rate) trade.close_profit = trade.calc_profit_ratio(trade.close_rate)
trade.close_profit_abs = trade.calc_profit(trade.close_rate) trade.close_profit_abs = trade.calc_profit(trade.close_rate)
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
rc = client_get(client, f"{BASE_URI}/performance") rc = client_get(client, f"{BASE_URI}/performance")
@ -1290,7 +1291,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
data={"tradeid": "1"}) data={"tradeid": "1"})
assert_response(rc, 502) assert_response(rc, 502)
assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"} assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"}
Trade.query.session.rollback() Trade.rollback()
create_mock_trades(fee) create_mock_trades(fee)
trade = Trade.get_trades([Trade.id == 5]).first() trade = Trade.get_trades([Trade.id == 5]).first()
@ -1299,7 +1300,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
data={"tradeid": "5", "ordertype": "market", "amount": 23}) data={"tradeid": "5", "ordertype": "market", "amount": 23})
assert_response(rc) assert_response(rc)
assert rc.json() == {'result': 'Created sell order for trade 5.'} assert rc.json() == {'result': 'Created sell order for trade 5.'}
Trade.query.session.rollback() Trade.rollback()
trade = Trade.get_trades([Trade.id == 5]).first() trade = Trade.get_trades([Trade.id == 5]).first()
assert pytest.approx(trade.amount) == 100 assert pytest.approx(trade.amount) == 100
@ -1309,7 +1310,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
data={"tradeid": "5"}) data={"tradeid": "5"})
assert_response(rc) assert_response(rc)
assert rc.json() == {'result': 'Created sell order for trade 5.'} assert rc.json() == {'result': 'Created sell order for trade 5.'}
Trade.query.session.rollback() Trade.rollback()
trade = Trade.get_trades([Trade.id == 5]).first() trade = Trade.get_trades([Trade.id == 5]).first()
assert trade.is_open is False assert trade.is_open is False

View File

@ -14,6 +14,7 @@ import arrow
import pytest import pytest
import time_machine import time_machine
from pandas import DataFrame from pandas import DataFrame
from sqlalchemy import select
from telegram import Chat, Message, ReplyKeyboardMarkup, Update from telegram import Chat, Message, ReplyKeyboardMarkup, Update
from telegram.error import BadRequest, NetworkError, TelegramError from telegram.error import BadRequest, NetworkError, TelegramError
@ -692,7 +693,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
context = MagicMock() context = MagicMock()
# Test with invalid 2nd argument (should silently pass) # Test with invalid 2nd argument (should silently pass)
@ -945,7 +946,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
# Create some test data # Create some test data
freqtradebot.enter_positions() freqtradebot.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
# Increase the price and sell it # Increase the price and sell it
@ -1020,7 +1021,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee,
fetch_ticker=ticker_sell_down fetch_ticker=ticker_sell_down
) )
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
# /forceexit 1 # /forceexit 1

View File

@ -10,6 +10,7 @@ from unittest.mock import ANY, MagicMock, PropertyMock, patch
import arrow import arrow
import pytest import pytest
from pandas import DataFrame from pandas import DataFrame
from sqlalchemy import select
from freqtrade.constants import CANCEL_REASON, UNLIMITED_STAKE_AMOUNT from freqtrade.constants import CANCEL_REASON, UNLIMITED_STAKE_AMOUNT
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RPCMessageType, RunMode, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RPCMessageType, RunMode,
@ -247,7 +248,7 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
caplog.clear() caplog.clear()
############################################# #############################################
ticker_val.update({ ticker_val.update({
@ -278,7 +279,7 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade is not None assert trade is not None
assert trade.stake_amount == 60.0 assert trade.stake_amount == 60.0
@ -286,7 +287,7 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -
assert trade.open_date is not None assert trade.open_date is not None
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.order_by(Trade.id.desc()).first() trade = Trade.session.scalars(select(Trade).order_by(Trade.id.desc())).first()
assert trade is not None assert trade is not None
assert trade.stake_amount == 60.0 assert trade.stake_amount == 60.0
@ -317,7 +318,7 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_order,
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.create_trade('ETH/USDT') freqtrade.create_trade('ETH/USDT')
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade is not None assert trade is not None
assert pytest.approx(trade.stake_amount) == 60.0 assert pytest.approx(trade.stake_amount) == 60.0
@ -568,12 +569,12 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, lim
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.get_open_trades()
assert not trades assert not trades
freqtrade.process() freqtrade.process()
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.get_open_trades()
assert len(trades) == 1 assert len(trades) == 1
trade = trades[0] trade = trades[0]
assert trade is not None assert trade is not None
@ -640,11 +641,11 @@ def test_process_trade_handling(default_conf_usdt, ticker_usdt, limit_buy_order_
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.get_open_trades()
assert not trades assert not trades
freqtrade.process() freqtrade.process()
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.get_open_trades()
assert len(trades) == 1 assert len(trades) == 1
# Nothing happened ... # Nothing happened ...
@ -671,7 +672,7 @@ def test_process_trade_no_whitelist_pair(default_conf_usdt, ticker_usdt, limit_b
assert pair not in default_conf_usdt['exchange']['pair_whitelist'] assert pair not in default_conf_usdt['exchange']['pair_whitelist']
# create open trade not in whitelist # create open trade not in whitelist
Trade.query.session.add(Trade( Trade.session.add(Trade(
pair=pair, pair=pair,
stake_amount=0.001, stake_amount=0.001,
fee_open=fee.return_value, fee_open=fee.return_value,
@ -681,7 +682,7 @@ def test_process_trade_no_whitelist_pair(default_conf_usdt, ticker_usdt, limit_b
open_rate=0.01, open_rate=0.01,
exchange='binance', exchange='binance',
)) ))
Trade.query.session.add(Trade( Trade.session.add(Trade(
pair='ETH/USDT', pair='ETH/USDT',
stake_amount=0.001, stake_amount=0.001,
fee_open=fee.return_value, fee_open=fee.return_value,
@ -838,7 +839,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
# Should create an open trade with an open order id # Should create an open trade with an open order id
# As the order is not fulfilled yet # As the order is not fulfilled yet
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert trade.is_open is True assert trade.is_open is True
@ -865,7 +866,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order)) mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order))
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[2] trade = Trade.session.scalars(select(Trade)).all()[2]
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None
@ -883,7 +884,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
order['id'] = '555' order['id'] = '555'
mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order)) mocker.patch(f'{EXMS}.create_order', MagicMock(return_value=order))
assert freqtrade.execute_entry(pair, stake_amount) assert freqtrade.execute_entry(pair, stake_amount)
trade = Trade.query.all()[3] trade = Trade.session.scalars(select(Trade)).all()[3]
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None
@ -896,7 +897,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0 freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[4] trade = Trade.session.scalars(select(Trade)).all()[4]
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert pytest.approx(trade.stake_amount) == 150 assert pytest.approx(trade.stake_amount) == 150
@ -905,7 +906,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
order['id'] = '557' order['id'] = '557'
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0 freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[5] trade = Trade.session.scalars(select(Trade)).all()[5]
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert pytest.approx(trade.stake_amount) == 2.0 assert pytest.approx(trade.stake_amount) == 2.0
@ -934,7 +935,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
order['id'] = '5566' order['id'] = '5566'
freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508 freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[6] trade = Trade.session.scalars(select(Trade)).all()[6]
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert trade.open_rate_requested == 0.508 assert trade.open_rate_requested == 0.508
@ -951,7 +952,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
) )
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[7] trade = Trade.session.scalars(select(Trade)).all()[7]
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert trade.open_rate_requested == 10 assert trade.open_rate_requested == 10
@ -961,7 +962,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
order['id'] = '5568' order['id'] = '5568'
freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price" freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.all()[8] trade = Trade.session.scalars(select(Trade)).all()[8]
# Trade(id=9, pair=ETH/USDT, amount=0.20000000, is_short=False, # Trade(id=9, pair=ETH/USDT, amount=0.20000000, is_short=False,
# leverage=1.0, open_rate=10.00000000, open_since=...) # leverage=1.0, open_rate=10.00000000, open_since=...)
# Trade(id=9, pair=ETH/USDT, amount=0.60000000, is_short=True, # Trade(id=9, pair=ETH/USDT, amount=0.60000000, is_short=True,
@ -982,7 +983,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
freqtrade.exchange.get_max_pair_stake_amount = MagicMock(return_value=500) freqtrade.exchange.get_max_pair_stake_amount = MagicMock(return_value=500)
assert freqtrade.execute_entry(pair, 2000, is_short=is_short) assert freqtrade.execute_entry(pair, 2000, is_short=is_short)
trade = Trade.query.all()[9] trade = Trade.session.scalars(select(Trade)).all()[9]
trade.is_short = is_short trade.is_short = is_short
assert pytest.approx(trade.stake_amount) == 500 assert pytest.approx(trade.stake_amount) == 500
@ -991,7 +992,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
freqtrade.strategy.leverage.reset_mock() freqtrade.strategy.leverage.reset_mock()
assert freqtrade.execute_entry(pair, 200, leverage_=3) assert freqtrade.execute_entry(pair, 200, leverage_=3)
assert freqtrade.strategy.leverage.call_count == 0 assert freqtrade.strategy.leverage.call_count == 0
trade = Trade.query.all()[10] trade = Trade.session.scalars(select(Trade)).all()[10]
assert trade.leverage == 1 if trading_mode == 'spot' else 3 assert trade.leverage == 1 if trading_mode == 'spot' else 3
@ -1053,7 +1054,7 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order,
freqtrade.strategy.leverage = MagicMock(return_value=5.0) freqtrade.strategy.leverage = MagicMock(return_value=5.0)
assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short) assert freqtrade.execute_entry(pair, stake_amount, is_short=is_short)
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade.leverage == 5.0 assert trade.leverage == 5.0
# assert trade.stake_amount == 2 # assert trade.stake_amount == 2
@ -1158,7 +1159,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# as a trade actually happened # as a trade actually happened
caplog.clear() caplog.clear()
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1271,7 +1272,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1316,7 +1317,7 @@ def test_create_stoploss_order_invalid_order(
freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.strategy.order_types['stoploss_on_exchange'] = True
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
caplog.clear() caplog.clear()
freqtrade.create_stoploss_order(trade, 200) freqtrade.create_stoploss_order(trade, 200)
@ -1367,7 +1368,7 @@ def test_create_stoploss_order_insufficient_funds(
freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.strategy.order_types['stoploss_on_exchange'] = True
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
caplog.clear() caplog.clear()
freqtrade.create_stoploss_order(trade, 200) freqtrade.create_stoploss_order(trade, 200)
@ -1435,7 +1436,7 @@ def test_handle_stoploss_on_exchange_trailing(
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1554,7 +1555,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1668,7 +1669,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1796,7 +1797,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
trade.stoploss_order_id = 100 trade.stoploss_order_id = 100
@ -2162,7 +2163,7 @@ def test_handle_trade(
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
@ -2217,7 +2218,7 @@ def test_handle_overlapping_signals(
freqtrade.enter_positions() freqtrade.enter_positions()
# Buy and Sell triggering, so doing nothing ... # Buy and Sell triggering, so doing nothing ...
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 0 assert nb_trades == 0
@ -2225,7 +2226,7 @@ def test_handle_overlapping_signals(
# Buy is triggering, so buying ... # Buy is triggering, so buying ...
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions() freqtrade.enter_positions()
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
for trade in trades: for trade in trades:
trade.is_short = is_short trade.is_short = is_short
nb_trades = len(trades) nb_trades = len(trades)
@ -2235,7 +2236,7 @@ def test_handle_overlapping_signals(
# Buy and Sell are not triggering, so doing nothing ... # Buy and Sell are not triggering, so doing nothing ...
patch_get_signal(freqtrade, enter_long=False) patch_get_signal(freqtrade, enter_long=False)
assert freqtrade.handle_trade(trades[0]) is False assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
for trade in trades: for trade in trades:
trade.is_short = is_short trade.is_short = is_short
nb_trades = len(trades) nb_trades = len(trades)
@ -2248,7 +2249,7 @@ def test_handle_overlapping_signals(
else: else:
patch_get_signal(freqtrade, enter_long=True, exit_long=True) patch_get_signal(freqtrade, enter_long=True, exit_long=True)
assert freqtrade.handle_trade(trades[0]) is False assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
for trade in trades: for trade in trades:
trade.is_short = is_short trade.is_short = is_short
nb_trades = len(trades) nb_trades = len(trades)
@ -2260,7 +2261,7 @@ def test_handle_overlapping_signals(
patch_get_signal(freqtrade, enter_long=False, exit_short=True) patch_get_signal(freqtrade, enter_long=False, exit_short=True)
else: else:
patch_get_signal(freqtrade, enter_long=False, exit_long=True) patch_get_signal(freqtrade, enter_long=False, exit_long=True)
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
for trade in trades: for trade in trades:
trade.is_short = is_short trade.is_short = is_short
assert freqtrade.handle_trade(trades[0]) is True assert freqtrade.handle_trade(trades[0]) is True
@ -2291,7 +2292,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_order_open, fee,
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
trade.is_open = True trade.is_open = True
@ -2333,7 +2334,7 @@ def test_handle_trade_use_exit_signal(
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
trade.is_open = True trade.is_open = True
@ -2370,7 +2371,7 @@ def test_close_trade(
# Create trade and sell it # Create trade and sell it
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
@ -2427,7 +2428,7 @@ def test_manage_open_orders_entry_usercustom(
open_trade.is_short = is_short open_trade.is_short = is_short
open_trade.orders[0].side = 'sell' if is_short else 'buy' open_trade.orders[0].side = 'sell' if is_short else 'buy'
open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy' open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
# Ensure default is to return empty (so not mocked yet) # Ensure default is to return empty (so not mocked yet)
@ -2438,7 +2439,8 @@ def test_manage_open_orders_entry_usercustom(
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False) freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 1 assert nb_trades == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 1 assert freqtrade.strategy.check_entry_timeout.call_count == 1
@ -2446,7 +2448,8 @@ def test_manage_open_orders_entry_usercustom(
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 1 assert nb_trades == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 1 assert freqtrade.strategy.check_entry_timeout.call_count == 1
@ -2456,7 +2459,8 @@ def test_manage_open_orders_entry_usercustom(
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
assert cancel_order_wr_mock.call_count == 1 assert cancel_order_wr_mock.call_count == 1
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 0 assert nb_trades == 0
assert freqtrade.strategy.check_entry_timeout.call_count == 1 assert freqtrade.strategy.check_entry_timeout.call_count == 1
@ -2486,7 +2490,7 @@ def test_manage_open_orders_entry(
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.is_short = is_short open_trade.is_short = is_short
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False) freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
@ -2495,7 +2499,8 @@ def test_manage_open_orders_entry(
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 0 assert nb_trades == 0
# Custom user buy-timeout is never called # Custom user buy-timeout is never called
@ -2524,7 +2529,7 @@ def test_adjust_entry_cancel(
) )
open_trade.is_short = is_short open_trade.is_short = is_short
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
# Timeout to not interfere # Timeout to not interfere
@ -2533,9 +2538,10 @@ def test_adjust_entry_cancel(
# check that order is cancelled # check that order is cancelled
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=None) freqtrade.strategy.adjust_entry_price = MagicMock(return_value=None)
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
assert len(trades) == 0 assert len(trades) == 0
assert len(Order.query.all()) == 0 assert len(Order.session.scalars(select(Order)).all()) == 0
assert log_has_re( assert log_has_re(
f"{'Sell' if is_short else 'Buy'} order user requested order cancel*", caplog) f"{'Sell' if is_short else 'Buy'} order user requested order cancel*", caplog)
assert log_has_re( assert log_has_re(
@ -2565,7 +2571,7 @@ def test_adjust_entry_maintain_replace(
) )
open_trade.is_short = is_short open_trade.is_short = is_short
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
# Timeout to not interfere # Timeout to not interfere
@ -2574,7 +2580,8 @@ def test_adjust_entry_maintain_replace(
# Check that order is maintained # Check that order is maintained
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=old_order['price']) freqtrade.strategy.adjust_entry_price = MagicMock(return_value=old_order['price'])
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
assert len(trades) == 1 assert len(trades) == 1
assert len(Order.get_open_orders()) == 1 assert len(Order.get_open_orders()) == 1
# Entry adjustment is called # Entry adjustment is called
@ -2584,9 +2591,10 @@ def test_adjust_entry_maintain_replace(
freqtrade.get_valid_enter_price_and_stake = MagicMock(return_value={100, 10, 1}) freqtrade.get_valid_enter_price_and_stake = MagicMock(return_value={100, 10, 1})
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234) freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
assert len(trades) == 1 assert len(trades) == 1
nb_all_orders = len(Order.query.all()) nb_all_orders = len(Order.session.scalars(select(Order)).all())
assert nb_all_orders == 2 assert nb_all_orders == 2
# New order seems to be in closed status? # New order seems to be in closed status?
# nb_open_orders = len(Order.get_open_orders()) # nb_open_orders = len(Order.get_open_orders())
@ -2618,14 +2626,15 @@ def test_check_handle_cancelled_buy(
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.orders = [] open_trade.orders = []
open_trade.is_short = is_short open_trade.is_short = is_short
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
# check it does cancel buy orders over the time limit # check it does cancel buy orders over the time limit
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
assert len(trades) == 0 assert len(trades) == 0
assert log_has_re( assert log_has_re(
f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog) f"{'Sell' if is_short else 'Buy'} order cancelled on exchange for Trade.*", caplog)
@ -2649,14 +2658,15 @@ def test_manage_open_orders_buy_exception(
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.is_short = is_short open_trade.is_short = is_short
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
# check it does cancel buy orders over the time limit # check it does cancel buy orders over the time limit
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 1 assert rpc_mock.call_count == 1
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 1 assert nb_trades == 1
@ -2691,7 +2701,7 @@ def test_manage_open_orders_exit_usercustom(
open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
open_trade_usdt.close_profit_abs = 0.001 open_trade_usdt.close_profit_abs = 0.001
Trade.query.session.add(open_trade_usdt) Trade.session.add(open_trade_usdt)
Trade.commit() Trade.commit()
# Ensure default is false # Ensure default is false
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
@ -2771,7 +2781,7 @@ def test_manage_open_orders_exit(
open_trade_usdt.close_profit_abs = 0.001 open_trade_usdt.close_profit_abs = 0.001
open_trade_usdt.is_short = is_short open_trade_usdt.is_short = is_short
Trade.query.session.add(open_trade_usdt) Trade.session.add(open_trade_usdt)
Trade.commit() Trade.commit()
freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False) freqtrade.strategy.check_exit_timeout = MagicMock(return_value=False)
@ -2811,7 +2821,7 @@ def test_check_handle_cancelled_exit(
open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime open_trade_usdt.close_date = arrow.utcnow().shift(minutes=-601).datetime
open_trade_usdt.is_short = is_short open_trade_usdt.is_short = is_short
Trade.query.session.add(open_trade_usdt) Trade.session.add(open_trade_usdt)
Trade.commit() Trade.commit()
# check it does cancel sell orders over the time limit # check it does cancel sell orders over the time limit
@ -2848,7 +2858,7 @@ def test_manage_open_orders_partial(
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
prior_stake = open_trade.stake_amount prior_stake = open_trade.stake_amount
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
# check it does cancel buy orders over the time limit # check it does cancel buy orders over the time limit
@ -2856,7 +2866,8 @@ def test_manage_open_orders_partial(
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 3 assert rpc_mock.call_count == 3
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
assert len(trades) == 1 assert len(trades) == 1
assert trades[0].amount == 23.0 assert trades[0].amount == 23.0
assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount / leverage assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount / leverage
@ -2893,7 +2904,7 @@ def test_manage_open_orders_partial_fee(
open_trade.fee_open = fee() open_trade.fee_open = fee()
open_trade.fee_close = fee() open_trade.fee_close = fee()
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
# cancelling a half-filled order should update the amount to the bought amount # cancelling a half-filled order should update the amount to the bought amount
# and apply fees if necessary. # and apply fees if necessary.
@ -2903,7 +2914,8 @@ def test_manage_open_orders_partial_fee(
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 3 assert rpc_mock.call_count == 3
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
assert len(trades) == 1 assert len(trades) == 1
# Verify that trade has been updated # Verify that trade has been updated
assert trades[0].amount == (limit_buy_order_old_partial['amount'] - assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
@ -2943,7 +2955,7 @@ def test_manage_open_orders_partial_except(
open_trade.fee_open = fee() open_trade.fee_open = fee()
open_trade.fee_close = fee() open_trade.fee_close = fee()
Trade.query.session.add(open_trade) Trade.session.add(open_trade)
Trade.commit() Trade.commit()
# cancelling a half-filled order should update the amount to the bought amount # cancelling a half-filled order should update the amount to the bought amount
# and apply fees if necessary. # and apply fees if necessary.
@ -2953,7 +2965,8 @@ def test_manage_open_orders_partial_except(
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 3 assert rpc_mock.call_count == 3
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() trades = Trade.session.scalars(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
assert len(trades) == 1 assert len(trades) == 1
# Verify that trade has been updated # Verify that trade has been updated
@ -2982,7 +2995,7 @@ def test_manage_open_orders_exception(default_conf_usdt, ticker_usdt, open_trade
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
Trade.query.session.add(open_trade_usdt) Trade.session.add(open_trade_usdt)
Trade.commit() Trade.commit()
caplog.clear() caplog.clear()
@ -3011,7 +3024,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
freqtrade._notify_enter_cancel = MagicMock() freqtrade._notify_enter_cancel = MagicMock()
trade = mock_trade_usdt_4(fee, is_short) trade = mock_trade_usdt_4(fee, is_short)
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
l_order['filled'] = 0.0 l_order['filled'] = 0.0
@ -3061,7 +3074,7 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
trade = mock_trade_usdt_4(fee, is_short) trade = mock_trade_usdt_4(fee, is_short)
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason) assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
@ -3095,7 +3108,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock() freqtrade._notify_enter_cancel = MagicMock()
trade = mock_trade_usdt_4(fee, is_short) trade = mock_trade_usdt_4(fee, is_short)
Trade.query.session.add(trade) Trade.session.add(trade)
Trade.commit() Trade.commit()
l_order['filled'] = 0.0 l_order['filled'] = 0.0
l_order['status'] = 'open' l_order['status'] = 'open'
@ -3261,7 +3274,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
freqtrade.enter_positions() freqtrade.enter_positions()
rpc_mock.reset_mock() rpc_mock.reset_mock()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short assert trade.is_short == is_short
assert trade assert trade
assert freqtrade.strategy.confirm_trade_exit.call_count == 0 assert freqtrade.strategy.confirm_trade_exit.call_count == 0
@ -3342,7 +3355,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
# Create some test data # Create some test data
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
@ -3415,7 +3428,7 @@ def test_execute_trade_exit_custom_exit_price(
freqtrade.enter_positions() freqtrade.enter_positions()
rpc_mock.reset_mock() rpc_mock.reset_mock()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert freqtrade.strategy.confirm_trade_exit.call_count == 0 assert freqtrade.strategy.confirm_trade_exit.call_count == 0
@ -3492,7 +3505,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
# Create some test data # Create some test data
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short assert trade.is_short == is_short
assert trade assert trade
@ -3566,7 +3579,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
PairLock.session = MagicMock() PairLock.session = MagicMock()
freqtrade.config['dry_run'] = False freqtrade.config['dry_run'] = False
@ -3611,7 +3624,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
# Create some test data # Create some test data
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
trades = [trade] trades = [trade]
@ -3631,7 +3644,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS) exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)
) )
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
assert cancel_order.call_count == 1 assert cancel_order.call_count == 1
@ -3669,7 +3682,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
# Create some test data # Create some test data
freqtrade.enter_positions() freqtrade.enter_positions()
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trades = [trade] trades = [trade]
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None
@ -3755,7 +3768,7 @@ def test_execute_trade_exit_market_order(
# Create some test data # Create some test data
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
@ -3830,7 +3843,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
# Create some test data # Create some test data
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
@ -3898,7 +3911,7 @@ def test_exit_profit_only(
exit_type=ExitType.NONE)) exit_type=ExitType.NONE))
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short assert trade.is_short == is_short
oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside) oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside)
trade.update_order(limit_order[eside]) trade.update_order(limit_order[eside])
@ -3941,7 +3954,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_order, limit_order_ope
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
amnt = trade.amount amnt = trade.amount
oobj = Order.parse_from_ccxt_object(limit_order['buy'], limit_order['buy']['symbol'], 'buy') oobj = Order.parse_from_ccxt_object(limit_order['buy'], limit_order['buy']['symbol'], 'buy')
@ -4009,7 +4022,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
# Create some test data # Create some test data
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
assert trade assert trade
@ -4064,7 +4077,7 @@ def test_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
oobj = Order.parse_from_ccxt_object( oobj = Order.parse_from_ccxt_object(
limit_order[eside], limit_order[eside]['symbol'], eside) limit_order[eside], limit_order[eside]['symbol'], eside)
@ -4114,7 +4127,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open,
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short assert trade.is_short == is_short
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -4189,7 +4202,7 @@ def test_trailing_stop_loss_positive(
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade.is_short == is_short assert trade.is_short == is_short
oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside) oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside)
trade.update_order(limit_order[eside]) trade.update_order(limit_order[eside])
@ -4286,7 +4299,7 @@ def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limi
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
oobj = Order.parse_from_ccxt_object( oobj = Order.parse_from_ccxt_object(
@ -4752,7 +4765,7 @@ def test_order_book_depth_of_market(
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
if is_high_delta: if is_high_delta:
assert trade is None assert trade is None
else: else:
@ -4763,7 +4776,7 @@ def test_order_book_depth_of_market(
assert trade.open_date is not None assert trade.open_date is not None
assert trade.exchange == 'binance' assert trade.exchange == 'binance'
assert len(Trade.query.all()) == 1 assert len(Trade.session.scalars(select(Trade)).all()) == 1
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object( oobj = Order.parse_from_ccxt_object(
@ -4860,7 +4873,7 @@ def test_order_book_exit_pricing(
freqtrade.enter_positions() freqtrade.enter_positions()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
time.sleep(0.01) # Race condition fix time.sleep(0.01) # Race condition fix
@ -4932,7 +4945,7 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_
n = bot.enter_positions() n = bot.enter_positions()
assert n == 2 assert n == 2
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
assert len(trades) == 2 assert len(trades) == 2
bot.config['max_open_trades'] = 3 bot.config['max_open_trades'] = 3
@ -4965,7 +4978,7 @@ def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, lim
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
create_mock_trades(fee, is_short=is_short) create_mock_trades(fee, is_short=is_short)
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
assert len(trades) == MOCK_TRADE_COUNT assert len(trades) == MOCK_TRADE_COUNT
freqtrade.cancel_all_open_orders() freqtrade.cancel_all_open_orders()
assert buy_mock.call_count == buy_calls assert buy_mock.call_count == buy_calls
@ -4981,7 +4994,7 @@ def test_check_for_open_trades(mocker, default_conf_usdt, fee, is_short):
assert freqtrade.rpc.send_msg.call_count == 0 assert freqtrade.rpc.send_msg.call_count == 0
create_mock_trades(fee, is_short) create_mock_trades(fee, is_short)
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short trade.is_short = is_short
trade.is_open = True trade.is_open = True
@ -5149,7 +5162,7 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog, is_sh
exchange='binance', exchange='binance',
is_short=is_short is_short=is_short
) )
Trade.query.session.add(trade) Trade.session.add(trade)
freqtrade.handle_insufficient_funds(trade) freqtrade.handle_insufficient_funds(trade)
# assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) # assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
@ -5546,10 +5559,10 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
assert freqtrade.execute_entry(pair, stake_amount) assert freqtrade.execute_entry(pair, stake_amount)
# Should create an closed trade with an no open order id # Should create an closed trade with an no open order id
# Order is filled and trade is open # Order is filled and trade is open
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 1 assert len(orders) == 1
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.is_open is True assert trade.is_open is True
assert trade.open_order_id is None assert trade.open_order_id is None
@ -5559,7 +5572,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
# Assume it does nothing since order is closed and trade is open # Assume it does nothing since order is closed and trade is open
freqtrade.update_trades_without_assigned_fees() freqtrade.update_trades_without_assigned_fees()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.is_open is True assert trade.is_open is True
assert trade.open_order_id is None assert trade.open_order_id is None
@ -5569,7 +5582,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.is_open is True assert trade.is_open is True
assert trade.open_order_id is None assert trade.open_order_id is None
@ -5595,10 +5608,10 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=open_dca_order_1)) mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', MagicMock(return_value=open_dca_order_1))
assert freqtrade.execute_entry(pair, stake_amount, trade=trade) assert freqtrade.execute_entry(pair, stake_amount, trade=trade)
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 2 assert len(orders) == 2
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id == '651' assert trade.open_order_id == '651'
assert trade.open_rate == 11 assert trade.open_rate == 11
@ -5628,14 +5641,14 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', fetch_order_mm) mocker.patch(f'{EXMS}.fetch_order_or_stoploss_order', fetch_order_mm)
freqtrade.update_trades_without_assigned_fees() freqtrade.update_trades_without_assigned_fees()
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 2 assert len(orders) == 2
# Assert that the trade is found as open and without fees # Assert that the trade is found as open and without fees
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
assert len(trades) == 1 assert len(trades) == 1
# Assert trade is as expected # Assert trade is as expected
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id == '651' assert trade.open_order_id == '651'
assert trade.open_rate == 11 assert trade.open_rate == 11
@ -5672,14 +5685,14 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
# Assert trade is as expected (averaged dca) # Assert trade is as expected (averaged dca)
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None
assert pytest.approx(trade.open_rate) == 9.90909090909 assert pytest.approx(trade.open_rate) == 9.90909090909
assert trade.amount == 22 assert trade.amount == 22
assert pytest.approx(trade.stake_amount) == 218 assert pytest.approx(trade.stake_amount) == 218
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 2 assert len(orders) == 2
@ -5714,14 +5727,14 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
assert freqtrade.execute_entry(pair, stake_amount, trade=trade) assert freqtrade.execute_entry(pair, stake_amount, trade=trade)
# Assert trade is as expected (averaged dca) # Assert trade is as expected (averaged dca)
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None
assert pytest.approx(trade.open_rate) == 8.729729729729 assert pytest.approx(trade.open_rate) == 8.729729729729
assert trade.amount == 37 assert trade.amount == 37
assert trade.stake_amount == 323 assert trade.stake_amount == 323
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 3 assert len(orders) == 3
@ -5752,7 +5765,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
sub_trade_amt=15) sub_trade_amt=15)
# Assert trade is as expected (averaged dca) # Assert trade is as expected (averaged dca)
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.is_open assert trade.is_open
@ -5760,7 +5773,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
assert trade.stake_amount == 192.05405405405406 assert trade.stake_amount == 192.05405405405406
assert pytest.approx(trade.open_rate) == 8.729729729729 assert pytest.approx(trade.open_rate) == 8.729729729729
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 4 assert len(orders) == 4
@ -5825,10 +5838,10 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
assert freqtrade.execute_entry(pair, amount) assert freqtrade.execute_entry(pair, amount)
# Should create an closed trade with an no open order id # Should create an closed trade with an no open order id
# Order is filled and trade is open # Order is filled and trade is open
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 1 assert len(orders) == 1
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.is_open is True assert trade.is_open is True
assert trade.open_order_id is None assert trade.open_order_id is None
@ -5838,7 +5851,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
# Assume it does nothing since order is closed and trade is open # Assume it does nothing since order is closed and trade is open
freqtrade.update_trades_without_assigned_fees() freqtrade.update_trades_without_assigned_fees()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.is_open is True assert trade.is_open is True
assert trade.open_order_id is None assert trade.open_order_id is None
@ -5848,7 +5861,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
freqtrade.manage_open_orders() freqtrade.manage_open_orders()
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.is_open is True assert trade.is_open is True
assert trade.open_order_id is None assert trade.open_order_id is None
@ -5884,7 +5897,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
assert len(trades) == 1 assert len(trades) == 1
# Assert trade is as expected (averaged dca) # Assert trade is as expected (averaged dca)
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.amount == 50 assert trade.amount == 50
@ -5893,7 +5906,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
assert pytest.approx(trade.realized_profit) == -152.375 assert pytest.approx(trade.realized_profit) == -152.375
assert pytest.approx(trade.close_profit_abs) == -152.375 assert pytest.approx(trade.close_profit_abs) == -152.375
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 2 assert len(orders) == 2
# Make sure the closed order is found as the second order. # Make sure the closed order is found as the second order.
@ -5926,7 +5939,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
sub_trade_amt=amount) sub_trade_amt=amount)
# Assert trade is as expected (averaged dca) # Assert trade is as expected (averaged dca)
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.amount == 50 assert trade.amount == 50
@ -5935,7 +5948,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None:
# Trade fully realized # Trade fully realized
assert pytest.approx(trade.realized_profit) == 94.25 assert pytest.approx(trade.realized_profit) == 94.25
assert pytest.approx(trade.close_profit_abs) == 94.25 assert pytest.approx(trade.close_profit_abs) == 94.25
orders = Order.query.all() orders = Order.session.scalars(select(Order)).all()
assert orders assert orders
assert len(orders) == 3 assert len(orders) == 3
@ -6020,11 +6033,11 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None:
exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT), exit_check=ExitCheckTuple(exit_type=ExitType.PARTIAL_EXIT),
sub_trade_amt=amount) sub_trade_amt=amount)
orders1 = Order.query.all() orders1 = Order.session.scalars(select(Order)).all()
assert orders1 assert orders1
assert len(orders1) == idx + 1 assert len(orders1) == idx + 1
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
if idx < len(data) - 1: if idx < len(data) - 1:
assert trade.is_open is True assert trade.is_open is True
@ -6039,7 +6052,7 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, data) -> None:
order_obj = trade.select_order(order[0], False) order_obj = trade.select_order(order[0], False)
assert order_obj.order_id == f'60{idx}' assert order_obj.order_id == f'60{idx}'
trade = Trade.query.first() trade = Trade.session.scalars(select(Trade)).first()
assert trade assert trade
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.is_open is False assert trade.is_open is False

View File

@ -1,6 +1,7 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from sqlalchemy import select
from freqtrade.enums import ExitCheckTuple, ExitType, TradingMode from freqtrade.enums import ExitCheckTuple, ExitType, TradingMode
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
@ -91,7 +92,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
assert freqtrade.strategy.confirm_trade_exit.call_count == 0 assert freqtrade.strategy.confirm_trade_exit.call_count == 0
wallets_mock.reset_mock() wallets_mock.reset_mock()
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
# Make sure stoploss-order is open and trade is bought (since we mock update_trade_state) # Make sure stoploss-order is open and trade is bought (since we mock update_trade_state)
for trade in trades: for trade in trades:
stoploss_order_closed['id'] = '3' stoploss_order_closed['id'] = '3'
@ -179,13 +180,13 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
n = freqtrade.enter_positions() n = freqtrade.enter_positions()
assert n == 4 assert n == 4
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
assert len(trades) == 4 assert len(trades) == 4
assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1 assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1
rpc._rpc_force_entry('TKN/BTC', None) rpc._rpc_force_entry('TKN/BTC', None)
trades = Trade.query.all() trades = Trade.session.scalars(select(Trade)).all()
assert len(trades) == 5 assert len(trades) == 5
for trade in trades: for trade in trades: