updated mkdocs and leverage docs

Added tests for set_liquidation_price and set_stop_loss
updated params in interestmode enum
This commit is contained in:
Sam Germain 2021-07-12 19:39:35 -06:00
parent 256160740e
commit af8875574c
7 changed files with 169 additions and 80 deletions

View File

@ -1,3 +1,17 @@
# Leverage
For shorts, the currency which pays the interest fee for the `borrowed` currency is purchased at the same time of the closing trade (This means that the amount purchased in short closing trades is greater than the amount sold in short opening trades). For shorts, the currency which pays the interest fee for the `borrowed` currency is purchased at the same time of the closing trade (This means that the amount purchased in short closing trades is greater than the amount sold in short opening trades).
For longs, the currency which pays the interest fee for the `borrowed` will already be owned by the user and does not need to be purchased. The interest is subtracted from the close_value of the trade. For longs, the currency which pays the interest fee for the `borrowed` will already be owned by the user and does not need to be purchased. The interest is subtracted from the close_value of the trade.
## Binance margin trading interest formula
I (interest) = P (borrowed money) * R (daily_interest/24) * ceiling(T) (in hours)
[source](https://www.binance.com/en/support/faq/360030157812)
## Kraken margin trading interest formula
Opening fee = P (borrowed money) * R (quat_hourly_interest)
Rollover fee = P (borrowed money) * R (quat_hourly_interest) * ceiling(T/4) (in hours)
I (interest) = Opening fee + Rollover fee
[source](https://support.kraken.com/hc/en-us/articles/206161568-What-are-the-fees-for-margin-trading-)

View File

@ -17,14 +17,12 @@ class InterestMode(Enum):
HOURSPER4 = "HOURSPER4" # Hours per 4 hour segment HOURSPER4 = "HOURSPER4" # Hours per 4 hour segment
NONE = "NONE" NONE = "NONE"
def __call__(self, *args, **kwargs): def __call__(self, borrowed: Decimal, rate: Decimal, hours: Decimal):
borrowed, rate, hours = kwargs["borrowed"], kwargs["rate"], kwargs["hours"]
if self.name == "HOURSPERDAY": if self.name == "HOURSPERDAY":
return borrowed * rate * ceil(hours)/twenty_four return borrowed * rate * ceil(hours)/twenty_four
elif self.name == "HOURSPER4": elif self.name == "HOURSPER4":
# Probably rounded based on https://kraken-fees-calculator.github.io/ # Rounded based on https://kraken-fees-calculator.github.io/
return borrowed * rate * (1+ceil(hours/four)) return borrowed * rate * (1+ceil(hours/four))
else: else:
raise OperationalException("Leverage not available on this exchange with freqtrade") raise OperationalException("Leverage not available on this exchange with freqtrade")

View File

@ -149,17 +149,16 @@ def migrate_orders_table(decl_base, inspector, engine, table_back_name: str, col
# let SQLAlchemy create the schema as required # let SQLAlchemy create the schema as required
decl_base.metadata.create_all(engine) decl_base.metadata.create_all(engine)
leverage = get_column_def(cols, 'leverage', '1.0') leverage = get_column_def(cols, 'leverage', '1.0')
is_short = get_column_def(cols, 'is_short', 'False') # is_short = get_column_def(cols, 'is_short', 'False')
# TODO-mg: Should liquidation price go in here?
with engine.begin() as connection: with engine.begin() as connection:
connection.execute(text(f""" connection.execute(text(f"""
insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, average, remaining, cost, status, symbol, order_type, side, price, amount, filled, average, remaining, cost,
order_date, order_filled_date, order_update_date, leverage, is_short) order_date, order_filled_date, order_update_date, leverage)
select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, null average, remaining, cost, status, symbol, order_type, side, price, amount, filled, null average, remaining, cost,
order_date, order_filled_date, order_update_date, order_date, order_filled_date, order_update_date, {leverage} leverage
{leverage} leverage, {is_short} is_short
from {table_back_name} from {table_back_name}
""")) """))

View File

@ -133,7 +133,6 @@ class Order(_DECL_BASE):
order_update_date = Column(DateTime, nullable=True) order_update_date = Column(DateTime, nullable=True)
leverage = Column(Float, nullable=True, default=1.0) leverage = Column(Float, nullable=True, default=1.0)
is_short = Column(Boolean, nullable=True, default=False)
def __repr__(self): def __repr__(self):
@ -159,7 +158,7 @@ class Order(_DECL_BASE):
self.remaining = order.get('remaining', self.remaining) self.remaining = order.get('remaining', self.remaining)
self.cost = order.get('cost', self.cost) self.cost = order.get('cost', self.cost)
self.leverage = order.get('leverage', self.leverage) self.leverage = order.get('leverage', self.leverage)
# TODO-mg: is_short?
if 'timestamp' in order and order['timestamp'] is not None: if 'timestamp' in order and order['timestamp'] is not None:
self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc) self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc)
@ -301,44 +300,42 @@ class LocalTrade():
def __init__(self, **kwargs): def __init__(self, **kwargs):
for key in kwargs: for key in kwargs:
setattr(self, key, kwargs[key]) setattr(self, key, kwargs[key])
if self.liquidation_price:
self.set_liquidation_price(self.liquidation_price) self.set_liquidation_price(self.liquidation_price)
self.recalc_open_trade_value() self.recalc_open_trade_value()
def set_stop_loss_helper(self, stop_loss: Optional[float], liquidation_price: Optional[float]):
"""Helper function for set_liquidation_price and set_stop_loss"""
# Stoploss would be better as a computed variable,
# but that messes up the database so it might not be possible
if liquidation_price is not None:
if stop_loss is not None:
if self.is_short:
self.stop_loss = min(stop_loss, liquidation_price)
else:
self.stop_loss = max(stop_loss, liquidation_price)
else:
self.stop_loss = liquidation_price
self.initial_stop_loss = liquidation_price
self.liquidation_price = liquidation_price
else:
# programmming error check: 1 of liqudication_price or stop_loss must be set
assert stop_loss is not None
if not self.stop_loss:
self.initial_stop_loss = stop_loss
self.stop_loss = stop_loss
def set_stop_loss(self, stop_loss: float): def set_stop_loss(self, stop_loss: float):
""" """
Method you should use to set self.stop_loss. Method you should use to set self.stop_loss.
Assures stop_loss is not passed the liquidation price Assures stop_loss is not passed the liquidation price
""" """
self.set_stop_loss_helper(stop_loss=stop_loss, liquidation_price=self.liquidation_price) if self.liquidation_price is not None:
if self.is_short:
sl = min(stop_loss, self.liquidation_price)
else:
sl = max(stop_loss, self.liquidation_price)
else:
sl = stop_loss
if not self.stop_loss:
self.initial_stop_loss = sl
self.stop_loss = sl
def set_liquidation_price(self, liquidation_price: float): def set_liquidation_price(self, liquidation_price: float):
""" """
Method you should use to set self.liquidation price. Method you should use to set self.liquidation price.
Assures stop_loss is not passed the liquidation price Assures stop_loss is not passed the liquidation price
""" """
self.set_stop_loss_helper(stop_loss=self.stop_loss, liquidation_price=liquidation_price) if self.stop_loss is not None:
if self.is_short:
self.stop_loss = min(self.stop_loss, liquidation_price)
else:
self.stop_loss = max(self.stop_loss, liquidation_price)
else:
self.initial_stop_loss = liquidation_price
self.stop_loss = liquidation_price
self.liquidation_price = liquidation_price
def __repr__(self): def __repr__(self):
open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed' open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed'

View File

@ -20,6 +20,7 @@ nav:
- Web Hook: webhook-config.md - Web Hook: webhook-config.md
- Data Downloading: data-download.md - Data Downloading: data-download.md
- Backtesting: backtesting.md - Backtesting: backtesting.md
- Leverage: leverage.md
- Hyperopt: hyperopt.md - Hyperopt: hyperopt.md
- Utility Sub-commands: utils.md - Utility Sub-commands: utils.md
- Plotting: plotting.md - Plotting: plotting.md
@ -40,24 +41,24 @@ nav:
- Contributors Guide: developer.md - Contributors Guide: developer.md
theme: theme:
name: material name: material
logo: 'images/logo.png' logo: "images/logo.png"
favicon: 'images/logo.png' favicon: "images/logo.png"
custom_dir: 'docs/overrides' custom_dir: "docs/overrides"
palette: palette:
- scheme: default - scheme: default
primary: 'blue grey' primary: "blue grey"
accent: 'tear' accent: "tear"
toggle: toggle:
icon: material/toggle-switch-off-outline icon: material/toggle-switch-off-outline
name: Switch to dark mode name: Switch to dark mode
- scheme: slate - scheme: slate
primary: 'blue grey' primary: "blue grey"
accent: 'tear' accent: "tear"
toggle: toggle:
icon: material/toggle-switch-off-outline icon: material/toggle-switch-off-outline
name: Switch to dark mode name: Switch to dark mode
extra_css: extra_css:
- 'stylesheets/ft.extra.css' - "stylesheets/ft.extra.css"
extra_javascript: extra_javascript:
- javascripts/config.js - javascripts/config.js
- https://polyfill.io/v3/polyfill.min.js?features=es6 - https://polyfill.io/v3/polyfill.min.js?features=es6

View File

@ -418,6 +418,7 @@ def leverage_order_sell():
'amount': 123.0, 'amount': 123.0,
'filled': 123.0, 'filled': 123.0,
'remaining': 0.0, 'remaining': 0.0,
'leverage': 5.0
} }

View File

@ -105,6 +105,85 @@ def test_is_opening_closing_trade(fee):
assert trade.is_closing_trade('sell') is False assert trade.is_closing_trade('sell') is False
@pytest.mark.usefixtures("init_persistence")
def test_set_stop_loss_liquidation_price(fee):
trade = Trade(
id=2,
pair='ETH/BTC',
stake_amount=0.001,
open_rate=0.01,
amount=5,
is_open=True,
open_date=arrow.utcnow().datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='binance',
is_short=False,
leverage=2.0
)
trade.set_liquidation_price(0.09)
assert trade.liquidation_price == 0.09
assert trade.stop_loss == 0.09
assert trade.initial_stop_loss == 0.09
trade.set_stop_loss(0.1)
assert trade.liquidation_price == 0.09
assert trade.stop_loss == 0.1
assert trade.initial_stop_loss == 0.09
trade.set_liquidation_price(0.08)
assert trade.liquidation_price == 0.08
assert trade.stop_loss == 0.1
assert trade.initial_stop_loss == 0.09
trade.set_liquidation_price(0.11)
assert trade.liquidation_price == 0.11
assert trade.stop_loss == 0.11
assert trade.initial_stop_loss == 0.09
trade.set_stop_loss(0.1)
assert trade.liquidation_price == 0.11
assert trade.stop_loss == 0.11
assert trade.initial_stop_loss == 0.09
trade.stop_loss = None
trade.liquidation_price = None
trade.initial_stop_loss = None
trade.set_stop_loss(0.07)
assert trade.liquidation_price is None
assert trade.stop_loss == 0.07
assert trade.initial_stop_loss == 0.07
trade.is_short = True
trade.stop_loss = None
trade.initial_stop_loss = None
trade.set_liquidation_price(0.09)
assert trade.liquidation_price == 0.09
assert trade.stop_loss == 0.09
assert trade.initial_stop_loss == 0.09
trade.set_stop_loss(0.08)
assert trade.liquidation_price == 0.09
assert trade.stop_loss == 0.08
assert trade.initial_stop_loss == 0.09
trade.set_liquidation_price(0.1)
assert trade.liquidation_price == 0.1
assert trade.stop_loss == 0.08
assert trade.initial_stop_loss == 0.09
trade.set_liquidation_price(0.07)
assert trade.liquidation_price == 0.07
assert trade.stop_loss == 0.07
assert trade.initial_stop_loss == 0.09
trade.set_stop_loss(0.1)
assert trade.liquidation_price == 0.07
assert trade.stop_loss == 0.07
assert trade.initial_stop_loss == 0.09
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog): def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog):
""" """
@ -729,7 +808,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert orders[1].order_id == 'stop_order_id222' assert orders[1].order_id == 'stop_order_id222'
assert orders[1].ft_order_side == 'stoploss' assert orders[1].ft_order_side == 'stoploss'
assert orders[0].is_short is False # assert orders[0].is_short is False
def test_migrate_mid_state(mocker, default_conf, fee, caplog): def test_migrate_mid_state(mocker, default_conf, fee, caplog):