Created FundingFee class and added funding_fee to LocalTrade and freqtradebot

This commit is contained in:
Sam Germain
2021-08-03 12:55:22 -06:00
parent 5184cc7749
commit b7891485b3
9 changed files with 223 additions and 37 deletions

View File

@@ -49,11 +49,21 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
strategy = get_column_def(cols, 'strategy', 'null')
buy_tag = get_column_def(cols, 'buy_tag', 'null')
trading_mode = get_column_def(cols, 'trading_mode', 'null')
# Leverage Properties
leverage = get_column_def(cols, 'leverage', '1.0')
interest_rate = get_column_def(cols, 'interest_rate', '0.0')
isolated_liq = get_column_def(cols, 'isolated_liq', 'null')
# sqlite does not support literals for booleans
is_short = get_column_def(cols, 'is_short', '0')
# Margin Properties
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')
# If ticker-interval existed use that, else null.
if has_column(cols, 'ticker_interval'):
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
@@ -91,7 +101,8 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag,
timeframe, open_trade_value, close_profit_abs,
leverage, interest_rate, isolated_liq, is_short
trading_mode, leverage, isolated_liq, is_short,
interest_rate, funding_fee, last_funding_adjustment
)
select id, lower(exchange), pair,
is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost,
@@ -108,8 +119,9 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
{sell_order_status} sell_order_status,
{strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe,
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
{leverage} leverage, {interest_rate} interest_rate,
{isolated_liq} isolated_liq, {is_short} is_short
{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
from {table_back_name}
"""))

View File

@@ -2,11 +2,11 @@
This module contains the class to persist trades into SQLite
"""
import logging
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from typing import Any, Dict, List, Optional
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Integer, String,
from sqlalchemy import (Boolean, Column, DateTime, Enum, Float, ForeignKey, Integer, String,
create_engine, desc, func, inspect)
from sqlalchemy.exc import NoSuchModuleError
from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session, sessionmaker
@@ -14,9 +14,9 @@ from sqlalchemy.pool import StaticPool
from sqlalchemy.sql.schema import UniqueConstraint
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
from freqtrade.enums import SellType
from freqtrade.enums import SellType, TradingMode
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.leverage import interest
from freqtrade.leverage.interest 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 requests to the appropriate thread-local session.
# Scoped sessions proxy reque sts 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,6 +93,12 @@ 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
@@ -265,14 +271,20 @@ class LocalTrade():
buy_tag: Optional[str] = None
timeframe: Optional[int] = None
trading_mode: TradingMode = TradingMode.SPOT
# Leverage trading properties
is_short: bool = False
isolated_liq: Optional[float] = None
is_short: bool = False
leverage: float = 1.0
# Margin trading properties
interest_rate: float = 0.0
# Futures properties
funding_fee: Optional[float] = None
last_funding_adjustment: Optional[datetime] = None
@property
def has_no_leverage(self) -> bool:
"""Returns true if this is a non-leverage, non-short trade"""
@@ -438,7 +450,10 @@ class LocalTrade():
'interest_rate': self.interest_rate,
'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),
'open_order_id': self.open_order_id,
}
@@ -516,6 +531,10 @@ 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.
@@ -654,8 +673,20 @@ 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,
fee: Optional[float] = None) -> Decimal:
close_trade = Decimal(amount) * Decimal(rate or self.close_rate) # type: ignore
fees = close_trade * Decimal(fee or self.fee_close)
if self.is_short:
return close_trade + fees
else:
return close_trade - fees
def calc_close_trade_value(self, rate: Optional[float] = None,
fee: Optional[float] = None,
interest_rate: Optional[float] = None) -> float:
@@ -672,20 +703,32 @@ class LocalTrade():
if rate is None and not self.close_rate:
return 0.0
interest = self.calculate_interest(interest_rate)
if self.is_short:
amount = Decimal(self.amount) + Decimal(interest)
else:
# Currency already owned for longs, no need to purchase
amount = Decimal(self.amount)
amount = Decimal(self.amount)
trading_mode = self.trading_mode or TradingMode.SPOT
close_trade = Decimal(amount) * Decimal(rate or self.close_rate) # type: ignore
fees = close_trade * Decimal(fee or self.fee_close)
if trading_mode == TradingMode.SPOT:
return float(self._calc_base_close(amount, rate, fee))
if self.is_short:
return float(close_trade + fees)
elif (trading_mode == TradingMode.MARGIN):
total_interest = self.calculate_interest(interest_rate)
if self.is_short:
amount = amount + total_interest
return float(self._calc_base_close(amount, rate, fee))
else:
# Currency already owned for longs, no need to purchase
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
else:
return float(close_trade - fees - interest)
raise OperationalException(
f"{self.trading_mode.value} trading is not yet available using freqtrade")
def calc_profit(self, rate: Optional[float] = None,
fee: Optional[float] = None,
@@ -893,14 +936,19 @@ class Trade(_DECL_BASE, LocalTrade):
buy_tag = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True)
# Leverage trading properties
leverage = Column(Float, nullable=True, default=1.0)
is_short = Column(Boolean, nullable=False, default=False)
isolated_liq = Column(Float, nullable=True)
trading_mode = Column(Enum(TradingMode))
# Margin Trading Properties
leverage = Column(Float, nullable=True, default=1.0)
isolated_liq = Column(Float, nullable=True)
is_short = Column(Boolean, nullable=False, default=False)
# Margin 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)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.recalc_open_trade_value()