@@ -9,12 +9,13 @@ from freqtrade.exchange.bitpanda import Bitpanda
|
||||
from freqtrade.exchange.bittrex import Bittrex
|
||||
from freqtrade.exchange.bybit import Bybit
|
||||
from freqtrade.exchange.coinbasepro import Coinbasepro
|
||||
from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
|
||||
is_exchange_known_ccxt, is_exchange_officially_supported,
|
||||
market_is_active, timeframe_to_minutes, timeframe_to_msecs,
|
||||
timeframe_to_next_date, timeframe_to_prev_date,
|
||||
timeframe_to_seconds, validate_exchange,
|
||||
validate_exchanges)
|
||||
from freqtrade.exchange.exchange import (amount_to_precision, available_exchanges, ccxt_exchanges,
|
||||
date_minus_candles, is_exchange_known_ccxt,
|
||||
is_exchange_officially_supported, market_is_active,
|
||||
price_to_precision, timeframe_to_minutes,
|
||||
timeframe_to_msecs, timeframe_to_next_date,
|
||||
timeframe_to_prev_date, timeframe_to_seconds,
|
||||
validate_exchange, validate_exchanges)
|
||||
from freqtrade.exchange.ftx import Ftx
|
||||
from freqtrade.exchange.gateio import Gateio
|
||||
from freqtrade.exchange.hitbtc import Hitbtc
|
||||
|
@@ -681,45 +681,35 @@ class Exchange:
|
||||
"""
|
||||
return endpoint in self._api.has and self._api.has[endpoint]
|
||||
|
||||
def get_precision_amount(self, pair: str) -> Optional[float]:
|
||||
"""
|
||||
Returns the amount precision of the exchange.
|
||||
:param pair: Pair to get precision for
|
||||
:return: precision for amount or None. Must be used in combination with precisionMode
|
||||
"""
|
||||
return self.markets.get(pair, {}).get('precision', {}).get('amount', None)
|
||||
|
||||
def get_precision_price(self, pair: str) -> Optional[float]:
|
||||
"""
|
||||
Returns the price precision of the exchange.
|
||||
:param pair: Pair to get precision for
|
||||
:return: precision for price or None. Must be used in combination with precisionMode
|
||||
"""
|
||||
return self.markets.get(pair, {}).get('precision', {}).get('price', None)
|
||||
|
||||
def amount_to_precision(self, pair: str, amount: float) -> float:
|
||||
"""
|
||||
Returns the amount to buy or sell to a precision the Exchange accepts
|
||||
Re-implementation of ccxt internal methods - ensuring we can test the result is correct
|
||||
based on our definitions.
|
||||
"""
|
||||
if self.markets[pair]['precision']['amount'] is not None:
|
||||
amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE,
|
||||
precision=self.markets[pair]['precision']['amount'],
|
||||
counting_mode=self.precisionMode,
|
||||
))
|
||||
|
||||
return amount
|
||||
"""
|
||||
return amount_to_precision(amount, self.get_precision_amount(pair), self.precisionMode)
|
||||
|
||||
def price_to_precision(self, pair: str, price: float) -> float:
|
||||
"""
|
||||
Returns the price rounded up to the precision the Exchange accepts.
|
||||
Partial Re-implementation of ccxt internal method decimal_to_precision(),
|
||||
which does not support rounding up
|
||||
TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and
|
||||
align with amount_to_precision().
|
||||
Rounds up
|
||||
"""
|
||||
if self.markets[pair]['precision']['price']:
|
||||
# price = float(decimal_to_precision(price, rounding_mode=ROUND,
|
||||
# precision=self.markets[pair]['precision']['price'],
|
||||
# counting_mode=self.precisionMode,
|
||||
# ))
|
||||
if self.precisionMode == TICK_SIZE:
|
||||
precision = FtPrecise(self.markets[pair]['precision']['price'])
|
||||
price_str = FtPrecise(price)
|
||||
missing = price_str % precision
|
||||
if not missing == FtPrecise("0"):
|
||||
price = round(float(str(price_str - missing + precision)), 14)
|
||||
else:
|
||||
symbol_prec = self.markets[pair]['precision']['price']
|
||||
big_price = price * pow(10, symbol_prec)
|
||||
price = ceil(big_price) / pow(10, symbol_prec)
|
||||
return price
|
||||
return price_to_precision(price, self.get_precision_price(pair), self.precisionMode)
|
||||
|
||||
def price_get_one_pip(self, pair: str, price: float) -> float:
|
||||
"""
|
||||
@@ -2862,3 +2852,61 @@ def market_is_active(market: Dict) -> bool:
|
||||
# See https://github.com/ccxt/ccxt/issues/4874,
|
||||
# https://github.com/ccxt/ccxt/issues/4075#issuecomment-434760520
|
||||
return market.get('active', True) is not False
|
||||
|
||||
|
||||
def amount_to_precision(amount: float, amount_precision: Optional[float],
|
||||
precisionMode: Optional[int]) -> float:
|
||||
"""
|
||||
Returns the amount to buy or sell to a precision the Exchange accepts
|
||||
Re-implementation of ccxt internal methods - ensuring we can test the result is correct
|
||||
based on our definitions.
|
||||
:param amount: amount to truncate
|
||||
:param amount_precision: amount precision to use.
|
||||
should be retrieved from markets[pair]['precision']['amount']
|
||||
:param precisionMode: precision mode to use. Should be used from precisionMode
|
||||
one of ccxt's DECIMAL_PLACES, SIGNIFICANT_DIGITS, or TICK_SIZE
|
||||
:return: truncated amount
|
||||
"""
|
||||
if amount_precision is not None and precisionMode is not None:
|
||||
precision = int(amount_precision) if precisionMode != TICK_SIZE else amount_precision
|
||||
# precision must be an int for non-ticksize inputs.
|
||||
amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE,
|
||||
precision=precision,
|
||||
counting_mode=precisionMode,
|
||||
))
|
||||
|
||||
return amount
|
||||
|
||||
|
||||
def price_to_precision(price: float, price_precision: Optional[float],
|
||||
precisionMode: Optional[int]) -> float:
|
||||
"""
|
||||
Returns the price rounded up to the precision the Exchange accepts.
|
||||
Partial Re-implementation of ccxt internal method decimal_to_precision(),
|
||||
which does not support rounding up
|
||||
TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and
|
||||
align with amount_to_precision().
|
||||
!!! Rounds up
|
||||
:param price: price to convert
|
||||
:param price_precision: price precision to use. Used from markets[pair]['precision']['price']
|
||||
:param precisionMode: precision mode to use. Should be used from precisionMode
|
||||
one of ccxt's DECIMAL_PLACES, SIGNIFICANT_DIGITS, or TICK_SIZE
|
||||
:return: price rounded up to the precision the Exchange accepts
|
||||
|
||||
"""
|
||||
if price_precision is not None and precisionMode is not None:
|
||||
# price = float(decimal_to_precision(price, rounding_mode=ROUND,
|
||||
# precision=price_precision,
|
||||
# counting_mode=self.precisionMode,
|
||||
# ))
|
||||
if precisionMode == TICK_SIZE:
|
||||
precision = FtPrecise(price_precision)
|
||||
price_str = FtPrecise(price)
|
||||
missing = price_str % precision
|
||||
if not missing == FtPrecise("0"):
|
||||
price = round(float(str(price_str - missing + precision)), 14)
|
||||
else:
|
||||
symbol_prec = price_precision
|
||||
big_price = price * pow(10, symbol_prec)
|
||||
price = ceil(big_price) / pow(10, symbol_prec)
|
||||
return price
|
||||
|
@@ -7,9 +7,8 @@ from freqtrade.constants import BuySell
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from freqtrade.enums.candletype import CandleType
|
||||
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.exchange import Exchange, date_minus_candles
|
||||
from freqtrade.exchange.common import retrier
|
||||
from freqtrade.exchange.exchange import date_minus_candles
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@@ -5,7 +5,6 @@ import copy
|
||||
import logging
|
||||
import traceback
|
||||
from datetime import datetime, time, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from math import isclose
|
||||
from threading import Lock
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
@@ -33,6 +32,7 @@ from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||
from freqtrade.rpc import RPCManager
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
from freqtrade.util import FtPrecise
|
||||
from freqtrade.wallets import Wallets
|
||||
|
||||
|
||||
@@ -159,6 +159,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
performs startup tasks
|
||||
"""
|
||||
self.rpc.startup_messages(self.config, self.pairlists, self.protections)
|
||||
# Update older trades with precision and precision mode
|
||||
self.startup_backpopulate_precision()
|
||||
if not self.edge:
|
||||
# Adjust stoploss if it was changed
|
||||
Trade.stoploss_reinitialization(self.strategy.stoploss)
|
||||
@@ -286,6 +288,17 @@ class FreqtradeBot(LoggingMixin):
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
def startup_backpopulate_precision(self):
|
||||
|
||||
trades = Trade.get_trades([Trade.precision_mode.is_(None)])
|
||||
for trade in trades:
|
||||
if trade.exchange != self.exchange.id:
|
||||
continue
|
||||
trade.precision_mode = self.exchange.precisionMode
|
||||
trade.amount_precision = self.exchange.get_precision_amount(trade.pair)
|
||||
trade.price_precision = self.exchange.get_precision_price(trade.pair)
|
||||
Trade.commit()
|
||||
|
||||
def startup_update_open_orders(self):
|
||||
"""
|
||||
Updates open orders based on order list kept in the database.
|
||||
@@ -565,7 +578,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
if stake_amount is not None and stake_amount < 0.0:
|
||||
# We should decrease our position
|
||||
amount = abs(float(Decimal(stake_amount) / Decimal(current_exit_rate)))
|
||||
amount = abs(float(FtPrecise(stake_amount) / FtPrecise(current_exit_rate)))
|
||||
if amount > trade.amount:
|
||||
# This is currently ineffective as remaining would become < min tradable
|
||||
# Fixing this would require checking for 0.0 there -
|
||||
@@ -738,7 +751,10 @@ class FreqtradeBot(LoggingMixin):
|
||||
leverage=leverage,
|
||||
is_short=is_short,
|
||||
trading_mode=self.trading_mode,
|
||||
funding_fees=funding_fees
|
||||
funding_fees=funding_fees,
|
||||
amount_precision=self.exchange.get_precision_amount(pair),
|
||||
price_precision=self.exchange.get_precision_price(pair),
|
||||
precision_mode=self.exchange.precisionMode,
|
||||
)
|
||||
else:
|
||||
# This is additional buy, we reset fee_open_currency so timeout checking can work
|
||||
|
@@ -1,20 +1,20 @@
|
||||
from decimal import Decimal
|
||||
from math import ceil
|
||||
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.util import FtPrecise
|
||||
|
||||
|
||||
one = Decimal(1.0)
|
||||
four = Decimal(4.0)
|
||||
twenty_four = Decimal(24.0)
|
||||
one = FtPrecise(1.0)
|
||||
four = FtPrecise(4.0)
|
||||
twenty_four = FtPrecise(24.0)
|
||||
|
||||
|
||||
def interest(
|
||||
exchange_name: str,
|
||||
borrowed: Decimal,
|
||||
rate: Decimal,
|
||||
hours: Decimal
|
||||
) -> Decimal:
|
||||
borrowed: FtPrecise,
|
||||
rate: FtPrecise,
|
||||
hours: FtPrecise
|
||||
) -> FtPrecise:
|
||||
"""
|
||||
Equation to calculate interest on margin trades
|
||||
|
||||
@@ -31,13 +31,13 @@ def interest(
|
||||
"""
|
||||
exchange_name = exchange_name.lower()
|
||||
if exchange_name == "binance":
|
||||
return borrowed * rate * ceil(hours) / twenty_four
|
||||
return borrowed * rate * FtPrecise(ceil(hours)) / twenty_four
|
||||
elif exchange_name == "kraken":
|
||||
# Rounded based on https://kraken-fees-calculator.github.io/
|
||||
return borrowed * rate * (one + ceil(hours / four))
|
||||
return borrowed * rate * (one + FtPrecise(ceil(hours / four)))
|
||||
elif exchange_name == "ftx":
|
||||
# As Explained under #Interest rates section in
|
||||
# https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer
|
||||
return borrowed * rate * ceil(hours) / twenty_four
|
||||
return borrowed * rate * FtPrecise(ceil(hours)) / twenty_four
|
||||
else:
|
||||
raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade")
|
||||
|
@@ -131,6 +131,7 @@ class Backtesting:
|
||||
self.fee = config['fee']
|
||||
else:
|
||||
self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0])
|
||||
self.precision_mode = self.exchange.precisionMode
|
||||
|
||||
self.timerange = TimeRange.parse_timerange(
|
||||
None if self.config.get('timerange') is None else str(self.config.get('timerange')))
|
||||
@@ -849,6 +850,9 @@ class Backtesting:
|
||||
trading_mode=self.trading_mode,
|
||||
leverage=leverage,
|
||||
# interest_rate=interest_rate,
|
||||
amount_precision=self.exchange.get_precision_amount(pair),
|
||||
price_precision=self.exchange.get_precision_price(pair),
|
||||
precision_mode=self.precision_mode,
|
||||
orders=[],
|
||||
)
|
||||
|
||||
|
@@ -130,6 +130,10 @@ def migrate_trades_and_orders_table(
|
||||
get_column_def(cols, 'sell_order_status', 'null'))
|
||||
amount_requested = get_column_def(cols, 'amount_requested', 'amount')
|
||||
|
||||
amount_precision = get_column_def(cols, 'amount_precision', 'null')
|
||||
price_precision = get_column_def(cols, 'price_precision', 'null')
|
||||
precision_mode = get_column_def(cols, 'precision_mode', 'null')
|
||||
|
||||
# Schema migration necessary
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(f"alter table trades rename to {trade_back_name}"))
|
||||
@@ -156,7 +160,8 @@ def migrate_trades_and_orders_table(
|
||||
max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag,
|
||||
timeframe, open_trade_value, close_profit_abs,
|
||||
trading_mode, leverage, liquidation_price, is_short,
|
||||
interest_rate, funding_fees, realized_profit
|
||||
interest_rate, funding_fees, realized_profit,
|
||||
amount_precision, price_precision, precision_mode
|
||||
)
|
||||
select id, lower(exchange), pair, {base_currency} base_currency,
|
||||
{stake_currency} stake_currency,
|
||||
@@ -182,7 +187,9 @@ def migrate_trades_and_orders_table(
|
||||
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
||||
{trading_mode} trading_mode, {leverage} leverage, {liquidation_price} liquidation_price,
|
||||
{is_short} is_short, {interest_rate} interest_rate,
|
||||
{funding_fees} funding_fees, {realized_profit} realized_profit
|
||||
{funding_fees} funding_fees, {realized_profit} realized_profit,
|
||||
{amount_precision} amount_precision, {price_precision} price_precision,
|
||||
{precision_mode} precision_mode
|
||||
from {trade_back_name}
|
||||
"""))
|
||||
|
||||
@@ -300,7 +307,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
|
||||
# Migrates both trades and orders table!
|
||||
# if ('orders' not in previous_tables
|
||||
# or not has_column(cols_orders, 'stop_price')):
|
||||
if not has_column(cols_trades, 'realized_profit'):
|
||||
if not has_column(cols_trades, 'precision_mode'):
|
||||
logger.info(f"Running database migration for trades - "
|
||||
f"backup: {table_back_name}, {order_table_bak_name}")
|
||||
migrate_trades_and_orders_table(
|
||||
|
@@ -3,7 +3,6 @@ This module contains the class to persist trades into SQLite
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from math import isclose
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
@@ -15,8 +14,10 @@ from freqtrade.constants import (DATETIME_PRINT_FORMAT, MATH_CLOSE_PREC, NON_OPE
|
||||
BuySell, LongShort)
|
||||
from freqtrade.enums import ExitType, TradingMode
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.exchange import amount_to_precision, price_to_precision
|
||||
from freqtrade.leverage import interest
|
||||
from freqtrade.persistence.base import _DECL_BASE
|
||||
from freqtrade.util import FtPrecise
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -292,6 +293,9 @@ class LocalTrade():
|
||||
timeframe: Optional[int] = None
|
||||
|
||||
trading_mode: TradingMode = TradingMode.SPOT
|
||||
amount_precision: Optional[float] = None
|
||||
price_precision: Optional[float] = None
|
||||
precision_mode: Optional[int] = None
|
||||
|
||||
# Leverage trading properties
|
||||
liquidation_price: Optional[float] = None
|
||||
@@ -523,9 +527,10 @@ class LocalTrade():
|
||||
"""
|
||||
Method used internally to set self.stop_loss.
|
||||
"""
|
||||
stop_loss_norm = price_to_precision(stop_loss, self.price_precision, self.precision_mode)
|
||||
if not self.stop_loss:
|
||||
self.initial_stop_loss = stop_loss
|
||||
self.stop_loss = stop_loss
|
||||
self.initial_stop_loss = stop_loss_norm
|
||||
self.stop_loss = stop_loss_norm
|
||||
|
||||
self.stop_loss_pct = -1 * abs(percent)
|
||||
self.stoploss_last_update = datetime.utcnow()
|
||||
@@ -553,7 +558,8 @@ class LocalTrade():
|
||||
# no stop loss assigned yet
|
||||
if self.initial_stop_loss_pct is None or refresh:
|
||||
self.__set_stop_loss(new_loss, stoploss)
|
||||
self.initial_stop_loss = new_loss
|
||||
self.initial_stop_loss = price_to_precision(
|
||||
new_loss, self.price_precision, self.precision_mode)
|
||||
self.initial_stop_loss_pct = -1 * abs(stoploss)
|
||||
|
||||
# evaluate if the stop loss needs to be updated
|
||||
@@ -617,7 +623,8 @@ class LocalTrade():
|
||||
else:
|
||||
logger.warning(
|
||||
f'Got different open_order_id {self.open_order_id} != {order.order_id}')
|
||||
if isclose(order.safe_amount_after_fee, self.amount, abs_tol=MATH_CLOSE_PREC):
|
||||
amount_tr = amount_to_precision(self.amount, self.amount_precision, self.precision_mode)
|
||||
if isclose(order.safe_amount_after_fee, amount_tr, abs_tol=MATH_CLOSE_PREC):
|
||||
self.close(order.safe_price)
|
||||
else:
|
||||
self.recalc_trade_from_orders()
|
||||
@@ -694,8 +701,8 @@ class LocalTrade():
|
||||
Calculate the open_rate including open_fee.
|
||||
:return: Price in of the open trade incl. Fees
|
||||
"""
|
||||
open_trade = Decimal(amount) * Decimal(open_rate)
|
||||
fees = open_trade * Decimal(self.fee_open)
|
||||
open_trade = FtPrecise(amount) * FtPrecise(open_rate)
|
||||
fees = open_trade * FtPrecise(self.fee_open)
|
||||
if self.is_short:
|
||||
return float(open_trade - fees)
|
||||
else:
|
||||
@@ -708,30 +715,30 @@ class LocalTrade():
|
||||
"""
|
||||
self.open_trade_value = self._calc_open_trade_value(self.amount, self.open_rate)
|
||||
|
||||
def calculate_interest(self) -> Decimal:
|
||||
def calculate_interest(self) -> FtPrecise:
|
||||
"""
|
||||
Calculate interest for this trade. Only applicable for Margin trading.
|
||||
"""
|
||||
zero = Decimal(0.0)
|
||||
zero = FtPrecise(0.0)
|
||||
# If nothing was borrowed
|
||||
if self.trading_mode != TradingMode.MARGIN or self.has_no_leverage:
|
||||
return zero
|
||||
|
||||
open_date = self.open_date.replace(tzinfo=None)
|
||||
now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None)
|
||||
sec_per_hour = Decimal(3600)
|
||||
total_seconds = Decimal((now - open_date).total_seconds())
|
||||
sec_per_hour = FtPrecise(3600)
|
||||
total_seconds = FtPrecise((now - open_date).total_seconds())
|
||||
hours = total_seconds / sec_per_hour or zero
|
||||
|
||||
rate = Decimal(self.interest_rate)
|
||||
borrowed = Decimal(self.borrowed)
|
||||
rate = FtPrecise(self.interest_rate)
|
||||
borrowed = FtPrecise(self.borrowed)
|
||||
|
||||
return interest(exchange_name=self.exchange, borrowed=borrowed, rate=rate, hours=hours)
|
||||
|
||||
def _calc_base_close(self, amount: Decimal, rate: float, fee: float) -> Decimal:
|
||||
def _calc_base_close(self, amount: FtPrecise, rate: float, fee: float) -> FtPrecise:
|
||||
|
||||
close_trade = amount * Decimal(rate)
|
||||
fees = close_trade * Decimal(fee)
|
||||
close_trade = amount * FtPrecise(rate)
|
||||
fees = close_trade * FtPrecise(fee)
|
||||
|
||||
if self.is_short:
|
||||
return close_trade + fees
|
||||
@@ -747,7 +754,7 @@ class LocalTrade():
|
||||
if rate is None and not self.close_rate:
|
||||
return 0.0
|
||||
|
||||
amount1 = Decimal(amount or self.amount)
|
||||
amount1 = FtPrecise(amount or self.amount)
|
||||
trading_mode = self.trading_mode or TradingMode.SPOT
|
||||
|
||||
if trading_mode == TradingMode.SPOT:
|
||||
@@ -826,12 +833,12 @@ class LocalTrade():
|
||||
|
||||
return float(f"{profit_ratio:.8f}")
|
||||
|
||||
def recalc_trade_from_orders(self, is_closing: bool = False):
|
||||
|
||||
current_amount = 0.0
|
||||
current_stake = 0.0
|
||||
def recalc_trade_from_orders(self, *, is_closing: bool = False):
|
||||
ZERO = FtPrecise(0.0)
|
||||
current_amount = FtPrecise(0.0)
|
||||
current_stake = FtPrecise(0.0)
|
||||
total_stake = 0.0 # Total stake after all buy orders (does not subtract!)
|
||||
avg_price = 0.0
|
||||
avg_price = FtPrecise(0.0)
|
||||
close_profit = 0.0
|
||||
close_profit_abs = 0.0
|
||||
|
||||
@@ -839,28 +846,29 @@ class LocalTrade():
|
||||
if o.ft_is_open or not o.filled:
|
||||
continue
|
||||
|
||||
tmp_amount = o.safe_amount_after_fee
|
||||
tmp_price = o.safe_price
|
||||
tmp_amount = FtPrecise(o.safe_amount_after_fee)
|
||||
tmp_price = FtPrecise(o.safe_price)
|
||||
|
||||
is_exit = o.ft_order_side != self.entry_side
|
||||
side = -1 if is_exit else 1
|
||||
if tmp_amount > 0.0 and tmp_price is not None:
|
||||
side = FtPrecise(-1 if is_exit else 1)
|
||||
if tmp_amount > ZERO and tmp_price is not None:
|
||||
current_amount += tmp_amount * side
|
||||
price = avg_price if is_exit else tmp_price
|
||||
current_stake += price * tmp_amount * side
|
||||
|
||||
if current_amount > 0:
|
||||
if current_amount > ZERO:
|
||||
avg_price = current_stake / current_amount
|
||||
|
||||
if is_exit:
|
||||
# Process partial exits
|
||||
exit_rate = o.safe_price
|
||||
exit_amount = o.safe_amount_after_fee
|
||||
profit = self.calc_profit(rate=exit_rate, amount=exit_amount, open_rate=avg_price)
|
||||
profit = self.calc_profit(rate=exit_rate, amount=exit_amount,
|
||||
open_rate=float(avg_price))
|
||||
close_profit_abs += profit
|
||||
close_profit = self.calc_profit_ratio(
|
||||
exit_rate, amount=exit_amount, open_rate=avg_price)
|
||||
if current_amount <= 0:
|
||||
if current_amount <= ZERO:
|
||||
profit = close_profit_abs
|
||||
else:
|
||||
total_stake = total_stake + self._calc_open_trade_value(tmp_amount, price)
|
||||
@@ -870,13 +878,15 @@ class LocalTrade():
|
||||
self.realized_profit = close_profit_abs
|
||||
self.close_profit_abs = profit
|
||||
|
||||
if current_amount > 0:
|
||||
current_amount_tr = amount_to_precision(float(current_amount),
|
||||
self.amount_precision, self.precision_mode)
|
||||
if current_amount_tr > 0.0:
|
||||
# Trade is still open
|
||||
# Leverage not updated, as we don't allow changing leverage through DCA at the moment.
|
||||
self.open_rate = current_stake / current_amount
|
||||
self.stake_amount = current_stake / (self.leverage or 1.0)
|
||||
self.amount = current_amount
|
||||
self.fee_open_cost = self.fee_open * current_stake
|
||||
self.open_rate = float(current_stake / current_amount)
|
||||
self.amount = current_amount_tr
|
||||
self.stake_amount = float(current_stake) / (self.leverage or 1.0)
|
||||
self.fee_open_cost = self.fee_open * float(current_stake)
|
||||
self.recalc_open_trade_value()
|
||||
if self.stop_loss_pct is not None and self.open_rate is not None:
|
||||
self.adjust_stop_loss(self.open_rate, self.stop_loss_pct)
|
||||
@@ -1119,6 +1129,9 @@ class Trade(_DECL_BASE, LocalTrade):
|
||||
timeframe = Column(Integer, nullable=True)
|
||||
|
||||
trading_mode = Column(Enum(TradingMode), nullable=True)
|
||||
amount_precision = Column(Float, nullable=True)
|
||||
price_precision = Column(Float, nullable=True)
|
||||
precision_mode = Column(Integer, nullable=True)
|
||||
|
||||
# Leverage trading properties
|
||||
leverage = Column(Float, nullable=True, default=1.0)
|
||||
|
Reference in New Issue
Block a user