From d6d5bae2a12bf3052cf80cb3ad1899f5444b6354 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 25 Aug 2021 23:01:07 -0600 Subject: [PATCH] New funding fee methods --- freqtrade/freqtradebot.py | 23 ++++++----------- freqtrade/persistence/migrations.py | 7 +++--- freqtrade/persistence/models.py | 39 ++++++++--------------------- tests/leverage/test_leverage.py | 2 +- tests/rpc/test_rpc.py | 6 ++++- tests/test_persistence.py | 7 ++---- 6 files changed, 30 insertions(+), 54 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7b0a521bf..69b669f63 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,7 +20,6 @@ from freqtrade.enums import RPCMessageType, SellType, State, TradingMode from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, InvalidOrderException, PricingError) from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds -from freqtrade.leverage.funding_fee import FundingFee from freqtrade.misc import safe_value_fallback, safe_value_fallback2 from freqtrade.mixins import LoggingMixin from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db @@ -103,10 +102,10 @@ class FreqtradeBot(LoggingMixin): self._sell_lock = Lock() LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) - self.trading_mode = self.config['trading_mode'] - if self.trading_mode == TradingMode.FUTURES: - self.funding_fee = FundingFee() - self.funding_fee.start() + if 'trading_mode' in self.config: + self.trading_mode = self.config['trading_mode'] + else: + self.trading_mode = TradingMode.SPOT def notify_status(self, msg: str) -> None: """ @@ -243,9 +242,10 @@ class FreqtradeBot(LoggingMixin): open_trades = len(Trade.get_open_trades()) return max(0, self.config['max_open_trades'] - open_trades) - def get_funding_fees(): + def add_funding_fees(self, trade: Trade): if self.trading_mode == TradingMode.FUTURES: - return + funding_fees = self.exchange.get_funding_fees(trade.pair, trade.open_date) + trade.funding_fees = funding_fees def update_open_orders(self): """ @@ -262,6 +262,7 @@ class FreqtradeBot(LoggingMixin): try: fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair, order.ft_order_side == 'stoploss') + self.update_trade_state(order.trade, order.order_id, fo) except ExchangeError as e: @@ -568,10 +569,6 @@ class FreqtradeBot(LoggingMixin): amount = safe_value_fallback(order, 'filled', 'amount') buy_limit_filled_price = safe_value_fallback(order, 'average', 'price') - funding_fee = (self.funding_fee.initial_funding_fee(amount) - if self.trading_mode == TradingMode.FUTURES - else None) - # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') trade = Trade( @@ -590,14 +587,10 @@ class FreqtradeBot(LoggingMixin): strategy=self.strategy.get_strategy_name(), buy_tag=buy_tag, timeframe=timeframe_to_minutes(self.config['timeframe']), - funding_fee=funding_fee, trading_mode=self.trading_mode ) trade.orders.append(order_obj) - if self.trading_mode == TradingMode.FUTURES: - self.funding_fee.add_new_trade(trade) - # Update fees if order is closed if order_status == 'closed': self.update_trade_state(trade, order_id, order) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index f4deef45b..ec6f10e3f 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -61,8 +61,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col interest_rate = get_column_def(cols, 'interest_rate', '0.0') # Futures properties - funding_fee = get_column_def(cols, 'funding_fee', '0.0') - last_funding_adjustment = get_column_def(cols, 'last_funding_adjustment', 'null') + funding_fees = get_column_def(cols, 'funding_fees', '0.0') # If ticker-interval existed use that, else null. if has_column(cols, 'ticker_interval'): @@ -102,7 +101,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, timeframe, open_trade_value, close_profit_abs, trading_mode, leverage, isolated_liq, is_short, - interest_rate, funding_fee, last_funding_adjustment + interest_rate, funding_fees ) select id, lower(exchange), pair, is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost, @@ -121,7 +120,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs, {trading_mode} trading_mode, {leverage} leverage, {isolated_liq} isolated_liq, {is_short} is_short, {interest_rate} interest_rate, - {funding_fee} funding_fee, {last_funding_adjustment} last_funding_adjustment + {funding_fees} funding_fees from {table_back_name} """)) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 72d2fafc9..eabc36509 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -2,7 +2,7 @@ This module contains the class to persist trades into SQLite """ import logging -from datetime import datetime, timedelta, timezone +from datetime import datetime, timezone from decimal import Decimal from typing import Any, Dict, List, Optional @@ -16,7 +16,7 @@ from sqlalchemy.sql.schema import UniqueConstraint from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES from freqtrade.enums import SellType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException -from freqtrade.leverage.interest import interest +from freqtrade.leverage import interest from freqtrade.misc import safe_value_fallback from freqtrade.persistence.migrations import check_migrate @@ -57,7 +57,7 @@ def init_db(db_url: str, clean_open_orders: bool = False) -> None: f"is no valid database URL! (See {_SQL_DOCS_URL})") # https://docs.sqlalchemy.org/en/13/orm/contextual.html#thread-local-scope - # Scoped sessions proxy reque sts 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 Trade._session = scoped_session(sessionmaker(bind=engine, autoflush=True)) Trade.query = Trade._session.query_property() @@ -93,12 +93,6 @@ def clean_dry_run_db() -> None: Trade.commit() -def hour_rounder(t): - # Rounds to nearest hour by adding a timedelta hour if minute >= 30 - return ( - t.replace(second=0, microsecond=0, minute=0, hour=t.hour) + timedelta(hours=t.minute//30)) - - class Order(_DECL_BASE): """ Order database model @@ -282,8 +276,7 @@ class LocalTrade(): interest_rate: float = 0.0 # Futures properties - funding_fee: Optional[float] = None - last_funding_adjustment: Optional[datetime] = None + funding_fees: Optional[float] = None @property def has_no_leverage(self) -> bool: @@ -451,9 +444,7 @@ class LocalTrade(): 'isolated_liq': self.isolated_liq, 'is_short': self.is_short, 'trading_mode': self.trading_mode, - 'funding_fee': self.funding_fee, - 'last_funding_adjustment': (self.last_funding_adjustment.strftime(DATETIME_PRINT_FORMAT) - if self.last_funding_adjustment else None), + 'funding_fees': self.funding_fees, 'open_order_id': self.open_order_id, } @@ -531,10 +522,6 @@ class LocalTrade(): f"Trailing stoploss saved us: " f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.") - def adjust_funding_fee(self, adjustment): - self.funding_fee = self.funding_fee + adjustment - self.last_funding_adjustment = datetime.utcnow() - def update(self, order: Dict) -> None: """ Updates this entity with amount and actual open/close rates. @@ -673,7 +660,6 @@ class LocalTrade(): rate = Decimal(interest_rate or self.interest_rate) borrowed = Decimal(self.borrowed) - # TODO-lev: Pass trading mode to interest maybe return interest(exchange_name=self.exchange, borrowed=borrowed, rate=rate, hours=hours) def _calc_base_close(self, amount: Decimal, rate: Optional[float] = None, @@ -721,11 +707,8 @@ class LocalTrade(): return float(self._calc_base_close(amount, rate, fee) - total_interest) elif (trading_mode == TradingMode.FUTURES): - funding_fee = self.funding_fee or 0.0 - if self.is_short: - return float(self._calc_base_close(amount, rate, fee)) + funding_fee - else: - return float(self._calc_base_close(amount, rate, fee)) - funding_fee + funding_fees = self.funding_fees or 0.0 + return float(self._calc_base_close(amount, rate, fee)) + funding_fees else: raise OperationalException( f"{self.trading_mode.value} trading is not yet available using freqtrade") @@ -938,16 +921,16 @@ class Trade(_DECL_BASE, LocalTrade): trading_mode = Column(Enum(TradingMode)) + # Leverage trading properties leverage = Column(Float, nullable=True, default=1.0) - isolated_liq = Column(Float, nullable=True) is_short = Column(Boolean, nullable=False, default=False) + isolated_liq = Column(Float, nullable=True) - # Margin properties + # Margin Trading Properties interest_rate = Column(Float, nullable=False, default=0.0) # Futures properties - funding_fee = Column(Float, nullable=True, default=None) - last_funding_adjustment = Column(DateTime, nullable=True) + funding_fees = Column(Float, nullable=True, default=None) def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/tests/leverage/test_leverage.py b/tests/leverage/test_leverage.py index 9a6e99806..7b7ca0f9b 100644 --- a/tests/leverage/test_leverage.py +++ b/tests/leverage/test_leverage.py @@ -3,7 +3,7 @@ from math import isclose import pytest -from freqtrade.leverage.interest import interest +from freqtrade.leverage import interest ten_mins = Decimal(1/6) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 56e64db69..d78f40a96 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -8,7 +8,7 @@ import pytest from numpy import isnan from freqtrade.edge import PairInfo -from freqtrade.enums import State +from freqtrade.enums import State, TradingMode from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError from freqtrade.persistence import Trade from freqtrade.persistence.pairlock_middleware import PairLocks @@ -112,6 +112,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'interest_rate': 0.0, 'isolated_liq': None, 'is_short': False, + 'funding_fees': None, + 'trading_mode': TradingMode.SPOT } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -183,6 +185,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'interest_rate': 0.0, 'isolated_liq': None, 'is_short': False, + 'funding_fees': None, + 'trading_mode': TradingMode.SPOT } diff --git a/tests/test_persistence.py b/tests/test_persistence.py index a33f2c1b0..062aa65fe 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1724,8 +1724,7 @@ def test_to_json(default_conf, fee): 'isolated_liq': None, 'is_short': None, 'trading_mode': None, - 'funding_fee': None, - 'last_funding_adjustment': None + 'funding_fees': None, } # Simulate dry_run entries @@ -1798,8 +1797,7 @@ def test_to_json(default_conf, fee): 'isolated_liq': None, 'is_short': None, 'trading_mode': None, - 'funding_fee': None, - 'last_funding_adjustment': None + 'funding_fees': None, } @@ -2219,7 +2217,6 @@ def test_Trade_object_idem(): 'get_open_trades_without_assigned_fees', 'get_open_order_trades', 'get_trades', - 'last_funding_adjustment' ) # Parent (LocalTrade) should have the same attributes