New funding fee methods

This commit is contained in:
Sam Germain 2021-08-25 23:01:07 -06:00
parent b854350e8d
commit d6d5bae2a1
6 changed files with 30 additions and 54 deletions

View File

@ -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)

View File

@ -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}
"""))

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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