commit
83ca168bb8
@ -9,12 +9,13 @@ from freqtrade.exchange.bitpanda import Bitpanda
|
|||||||
from freqtrade.exchange.bittrex import Bittrex
|
from freqtrade.exchange.bittrex import Bittrex
|
||||||
from freqtrade.exchange.bybit import Bybit
|
from freqtrade.exchange.bybit import Bybit
|
||||||
from freqtrade.exchange.coinbasepro import Coinbasepro
|
from freqtrade.exchange.coinbasepro import Coinbasepro
|
||||||
from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
|
from freqtrade.exchange.exchange import (amount_to_precision, available_exchanges, ccxt_exchanges,
|
||||||
is_exchange_known_ccxt, is_exchange_officially_supported,
|
date_minus_candles, is_exchange_known_ccxt,
|
||||||
market_is_active, timeframe_to_minutes, timeframe_to_msecs,
|
is_exchange_officially_supported, market_is_active,
|
||||||
timeframe_to_next_date, timeframe_to_prev_date,
|
price_to_precision, timeframe_to_minutes,
|
||||||
timeframe_to_seconds, validate_exchange,
|
timeframe_to_msecs, timeframe_to_next_date,
|
||||||
validate_exchanges)
|
timeframe_to_prev_date, timeframe_to_seconds,
|
||||||
|
validate_exchange, validate_exchanges)
|
||||||
from freqtrade.exchange.ftx import Ftx
|
from freqtrade.exchange.ftx import Ftx
|
||||||
from freqtrade.exchange.gateio import Gateio
|
from freqtrade.exchange.gateio import Gateio
|
||||||
from freqtrade.exchange.hitbtc import Hitbtc
|
from freqtrade.exchange.hitbtc import Hitbtc
|
||||||
|
@ -681,45 +681,35 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
return endpoint in self._api.has and self._api.has[endpoint]
|
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:
|
def amount_to_precision(self, pair: str, amount: float) -> float:
|
||||||
"""
|
"""
|
||||||
Returns the amount to buy or sell to a precision the Exchange accepts
|
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:
|
def price_to_precision(self, pair: str, price: float) -> float:
|
||||||
"""
|
"""
|
||||||
Returns the price rounded up to the precision the Exchange accepts.
|
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
|
Rounds up
|
||||||
"""
|
"""
|
||||||
if self.markets[pair]['precision']['price']:
|
return price_to_precision(price, self.get_precision_price(pair), self.precisionMode)
|
||||||
# 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
|
|
||||||
|
|
||||||
def price_get_one_pip(self, pair: str, price: float) -> float:
|
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,
|
# See https://github.com/ccxt/ccxt/issues/4874,
|
||||||
# https://github.com/ccxt/ccxt/issues/4075#issuecomment-434760520
|
# https://github.com/ccxt/ccxt/issues/4075#issuecomment-434760520
|
||||||
return market.get('active', True) is not False
|
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 import MarginMode, TradingMode
|
||||||
from freqtrade.enums.candletype import CandleType
|
from freqtrade.enums.candletype import CandleType
|
||||||
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
|
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.common import retrier
|
||||||
from freqtrade.exchange.exchange import date_minus_candles
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -5,7 +5,6 @@ import copy
|
|||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime, time, timedelta, timezone
|
from datetime import datetime, time, timedelta, timezone
|
||||||
from decimal import Decimal
|
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
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.rpc import RPCManager
|
||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||||
|
from freqtrade.util import FtPrecise
|
||||||
from freqtrade.wallets import Wallets
|
from freqtrade.wallets import Wallets
|
||||||
|
|
||||||
|
|
||||||
@ -159,6 +159,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
performs startup tasks
|
performs startup tasks
|
||||||
"""
|
"""
|
||||||
self.rpc.startup_messages(self.config, self.pairlists, self.protections)
|
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:
|
if not self.edge:
|
||||||
# Adjust stoploss if it was changed
|
# Adjust stoploss if it was changed
|
||||||
Trade.stoploss_reinitialization(self.strategy.stoploss)
|
Trade.stoploss_reinitialization(self.strategy.stoploss)
|
||||||
@ -286,6 +288,17 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
else:
|
else:
|
||||||
return 0.0
|
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):
|
def startup_update_open_orders(self):
|
||||||
"""
|
"""
|
||||||
Updates open orders based on order list kept in the database.
|
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:
|
if stake_amount is not None and stake_amount < 0.0:
|
||||||
# We should decrease our position
|
# 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:
|
if amount > trade.amount:
|
||||||
# This is currently ineffective as remaining would become < min tradable
|
# This is currently ineffective as remaining would become < min tradable
|
||||||
# Fixing this would require checking for 0.0 there -
|
# Fixing this would require checking for 0.0 there -
|
||||||
@ -738,7 +751,10 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
leverage=leverage,
|
leverage=leverage,
|
||||||
is_short=is_short,
|
is_short=is_short,
|
||||||
trading_mode=self.trading_mode,
|
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:
|
else:
|
||||||
# This is additional buy, we reset fee_open_currency so timeout checking can work
|
# 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 math import ceil
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from freqtrade.util import FtPrecise
|
||||||
|
|
||||||
|
|
||||||
one = Decimal(1.0)
|
one = FtPrecise(1.0)
|
||||||
four = Decimal(4.0)
|
four = FtPrecise(4.0)
|
||||||
twenty_four = Decimal(24.0)
|
twenty_four = FtPrecise(24.0)
|
||||||
|
|
||||||
|
|
||||||
def interest(
|
def interest(
|
||||||
exchange_name: str,
|
exchange_name: str,
|
||||||
borrowed: Decimal,
|
borrowed: FtPrecise,
|
||||||
rate: Decimal,
|
rate: FtPrecise,
|
||||||
hours: Decimal
|
hours: FtPrecise
|
||||||
) -> Decimal:
|
) -> FtPrecise:
|
||||||
"""
|
"""
|
||||||
Equation to calculate interest on margin trades
|
Equation to calculate interest on margin trades
|
||||||
|
|
||||||
@ -31,13 +31,13 @@ def interest(
|
|||||||
"""
|
"""
|
||||||
exchange_name = exchange_name.lower()
|
exchange_name = exchange_name.lower()
|
||||||
if exchange_name == "binance":
|
if exchange_name == "binance":
|
||||||
return borrowed * rate * ceil(hours) / twenty_four
|
return borrowed * rate * FtPrecise(ceil(hours)) / twenty_four
|
||||||
elif exchange_name == "kraken":
|
elif exchange_name == "kraken":
|
||||||
# Rounded based on https://kraken-fees-calculator.github.io/
|
# 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":
|
elif exchange_name == "ftx":
|
||||||
# As Explained under #Interest rates section in
|
# As Explained under #Interest rates section in
|
||||||
# https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer
|
# 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:
|
else:
|
||||||
raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade")
|
raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade")
|
||||||
|
@ -131,6 +131,7 @@ class Backtesting:
|
|||||||
self.fee = config['fee']
|
self.fee = config['fee']
|
||||||
else:
|
else:
|
||||||
self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0])
|
self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0])
|
||||||
|
self.precision_mode = self.exchange.precisionMode
|
||||||
|
|
||||||
self.timerange = TimeRange.parse_timerange(
|
self.timerange = TimeRange.parse_timerange(
|
||||||
None if self.config.get('timerange') is None else str(self.config.get('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,
|
trading_mode=self.trading_mode,
|
||||||
leverage=leverage,
|
leverage=leverage,
|
||||||
# interest_rate=interest_rate,
|
# 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=[],
|
orders=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -130,6 +130,10 @@ def migrate_trades_and_orders_table(
|
|||||||
get_column_def(cols, 'sell_order_status', 'null'))
|
get_column_def(cols, 'sell_order_status', 'null'))
|
||||||
amount_requested = get_column_def(cols, 'amount_requested', 'amount')
|
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
|
# Schema migration necessary
|
||||||
with engine.begin() as connection:
|
with engine.begin() as connection:
|
||||||
connection.execute(text(f"alter table trades rename to {trade_back_name}"))
|
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,
|
max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag,
|
||||||
timeframe, open_trade_value, close_profit_abs,
|
timeframe, open_trade_value, close_profit_abs,
|
||||||
trading_mode, leverage, liquidation_price, is_short,
|
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,
|
select id, lower(exchange), pair, {base_currency} base_currency,
|
||||||
{stake_currency} stake_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,
|
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
||||||
{trading_mode} trading_mode, {leverage} leverage, {liquidation_price} liquidation_price,
|
{trading_mode} trading_mode, {leverage} leverage, {liquidation_price} liquidation_price,
|
||||||
{is_short} is_short, {interest_rate} interest_rate,
|
{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}
|
from {trade_back_name}
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
@ -300,7 +307,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
|
|||||||
# Migrates both trades and orders table!
|
# Migrates both trades and orders table!
|
||||||
# if ('orders' not in previous_tables
|
# if ('orders' not in previous_tables
|
||||||
# or not has_column(cols_orders, 'stop_price')):
|
# 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 - "
|
logger.info(f"Running database migration for trades - "
|
||||||
f"backup: {table_back_name}, {order_table_bak_name}")
|
f"backup: {table_back_name}, {order_table_bak_name}")
|
||||||
migrate_trades_and_orders_table(
|
migrate_trades_and_orders_table(
|
||||||
|
@ -3,7 +3,6 @@ This module contains the class to persist trades into SQLite
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from decimal import Decimal
|
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from typing import Any, Dict, List, Optional
|
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)
|
BuySell, LongShort)
|
||||||
from freqtrade.enums import ExitType, TradingMode
|
from freqtrade.enums import ExitType, TradingMode
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
|
from freqtrade.exchange import amount_to_precision, price_to_precision
|
||||||
from freqtrade.leverage import interest
|
from freqtrade.leverage import interest
|
||||||
from freqtrade.persistence.base import _DECL_BASE
|
from freqtrade.persistence.base import _DECL_BASE
|
||||||
|
from freqtrade.util import FtPrecise
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -292,6 +293,9 @@ class LocalTrade():
|
|||||||
timeframe: Optional[int] = None
|
timeframe: Optional[int] = None
|
||||||
|
|
||||||
trading_mode: TradingMode = TradingMode.SPOT
|
trading_mode: TradingMode = TradingMode.SPOT
|
||||||
|
amount_precision: Optional[float] = None
|
||||||
|
price_precision: Optional[float] = None
|
||||||
|
precision_mode: Optional[int] = None
|
||||||
|
|
||||||
# Leverage trading properties
|
# Leverage trading properties
|
||||||
liquidation_price: Optional[float] = None
|
liquidation_price: Optional[float] = None
|
||||||
@ -523,9 +527,10 @@ class LocalTrade():
|
|||||||
"""
|
"""
|
||||||
Method used internally to set self.stop_loss.
|
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:
|
if not self.stop_loss:
|
||||||
self.initial_stop_loss = stop_loss
|
self.initial_stop_loss = stop_loss_norm
|
||||||
self.stop_loss = stop_loss
|
self.stop_loss = stop_loss_norm
|
||||||
|
|
||||||
self.stop_loss_pct = -1 * abs(percent)
|
self.stop_loss_pct = -1 * abs(percent)
|
||||||
self.stoploss_last_update = datetime.utcnow()
|
self.stoploss_last_update = datetime.utcnow()
|
||||||
@ -553,7 +558,8 @@ class LocalTrade():
|
|||||||
# no stop loss assigned yet
|
# no stop loss assigned yet
|
||||||
if self.initial_stop_loss_pct is None or refresh:
|
if self.initial_stop_loss_pct is None or refresh:
|
||||||
self.__set_stop_loss(new_loss, stoploss)
|
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)
|
self.initial_stop_loss_pct = -1 * abs(stoploss)
|
||||||
|
|
||||||
# evaluate if the stop loss needs to be updated
|
# evaluate if the stop loss needs to be updated
|
||||||
@ -617,7 +623,8 @@ class LocalTrade():
|
|||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'Got different open_order_id {self.open_order_id} != {order.order_id}')
|
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)
|
self.close(order.safe_price)
|
||||||
else:
|
else:
|
||||||
self.recalc_trade_from_orders()
|
self.recalc_trade_from_orders()
|
||||||
@ -694,8 +701,8 @@ class LocalTrade():
|
|||||||
Calculate the open_rate including open_fee.
|
Calculate the open_rate including open_fee.
|
||||||
:return: Price in of the open trade incl. Fees
|
:return: Price in of the open trade incl. Fees
|
||||||
"""
|
"""
|
||||||
open_trade = Decimal(amount) * Decimal(open_rate)
|
open_trade = FtPrecise(amount) * FtPrecise(open_rate)
|
||||||
fees = open_trade * Decimal(self.fee_open)
|
fees = open_trade * FtPrecise(self.fee_open)
|
||||||
if self.is_short:
|
if self.is_short:
|
||||||
return float(open_trade - fees)
|
return float(open_trade - fees)
|
||||||
else:
|
else:
|
||||||
@ -708,30 +715,30 @@ class LocalTrade():
|
|||||||
"""
|
"""
|
||||||
self.open_trade_value = self._calc_open_trade_value(self.amount, self.open_rate)
|
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.
|
Calculate interest for this trade. Only applicable for Margin trading.
|
||||||
"""
|
"""
|
||||||
zero = Decimal(0.0)
|
zero = FtPrecise(0.0)
|
||||||
# If nothing was borrowed
|
# If nothing was borrowed
|
||||||
if self.trading_mode != TradingMode.MARGIN or self.has_no_leverage:
|
if self.trading_mode != TradingMode.MARGIN or self.has_no_leverage:
|
||||||
return zero
|
return zero
|
||||||
|
|
||||||
open_date = self.open_date.replace(tzinfo=None)
|
open_date = self.open_date.replace(tzinfo=None)
|
||||||
now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None)
|
now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None)
|
||||||
sec_per_hour = Decimal(3600)
|
sec_per_hour = FtPrecise(3600)
|
||||||
total_seconds = Decimal((now - open_date).total_seconds())
|
total_seconds = FtPrecise((now - open_date).total_seconds())
|
||||||
hours = total_seconds / sec_per_hour or zero
|
hours = total_seconds / sec_per_hour or zero
|
||||||
|
|
||||||
rate = Decimal(self.interest_rate)
|
rate = FtPrecise(self.interest_rate)
|
||||||
borrowed = Decimal(self.borrowed)
|
borrowed = FtPrecise(self.borrowed)
|
||||||
|
|
||||||
return interest(exchange_name=self.exchange, borrowed=borrowed, rate=rate, hours=hours)
|
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)
|
close_trade = amount * FtPrecise(rate)
|
||||||
fees = close_trade * Decimal(fee)
|
fees = close_trade * FtPrecise(fee)
|
||||||
|
|
||||||
if self.is_short:
|
if self.is_short:
|
||||||
return close_trade + fees
|
return close_trade + fees
|
||||||
@ -747,7 +754,7 @@ class LocalTrade():
|
|||||||
if rate is None and not self.close_rate:
|
if rate is None and not self.close_rate:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
amount1 = Decimal(amount or self.amount)
|
amount1 = FtPrecise(amount or self.amount)
|
||||||
trading_mode = self.trading_mode or TradingMode.SPOT
|
trading_mode = self.trading_mode or TradingMode.SPOT
|
||||||
|
|
||||||
if trading_mode == TradingMode.SPOT:
|
if trading_mode == TradingMode.SPOT:
|
||||||
@ -826,12 +833,12 @@ class LocalTrade():
|
|||||||
|
|
||||||
return float(f"{profit_ratio:.8f}")
|
return float(f"{profit_ratio:.8f}")
|
||||||
|
|
||||||
def recalc_trade_from_orders(self, is_closing: bool = False):
|
def recalc_trade_from_orders(self, *, is_closing: bool = False):
|
||||||
|
ZERO = FtPrecise(0.0)
|
||||||
current_amount = 0.0
|
current_amount = FtPrecise(0.0)
|
||||||
current_stake = 0.0
|
current_stake = FtPrecise(0.0)
|
||||||
total_stake = 0.0 # Total stake after all buy orders (does not subtract!)
|
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 = 0.0
|
||||||
close_profit_abs = 0.0
|
close_profit_abs = 0.0
|
||||||
|
|
||||||
@ -839,28 +846,29 @@ class LocalTrade():
|
|||||||
if o.ft_is_open or not o.filled:
|
if o.ft_is_open or not o.filled:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tmp_amount = o.safe_amount_after_fee
|
tmp_amount = FtPrecise(o.safe_amount_after_fee)
|
||||||
tmp_price = o.safe_price
|
tmp_price = FtPrecise(o.safe_price)
|
||||||
|
|
||||||
is_exit = o.ft_order_side != self.entry_side
|
is_exit = o.ft_order_side != self.entry_side
|
||||||
side = -1 if is_exit else 1
|
side = FtPrecise(-1 if is_exit else 1)
|
||||||
if tmp_amount > 0.0 and tmp_price is not None:
|
if tmp_amount > ZERO and tmp_price is not None:
|
||||||
current_amount += tmp_amount * side
|
current_amount += tmp_amount * side
|
||||||
price = avg_price if is_exit else tmp_price
|
price = avg_price if is_exit else tmp_price
|
||||||
current_stake += price * tmp_amount * side
|
current_stake += price * tmp_amount * side
|
||||||
|
|
||||||
if current_amount > 0:
|
if current_amount > ZERO:
|
||||||
avg_price = current_stake / current_amount
|
avg_price = current_stake / current_amount
|
||||||
|
|
||||||
if is_exit:
|
if is_exit:
|
||||||
# Process partial exits
|
# Process partial exits
|
||||||
exit_rate = o.safe_price
|
exit_rate = o.safe_price
|
||||||
exit_amount = o.safe_amount_after_fee
|
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_abs += profit
|
||||||
close_profit = self.calc_profit_ratio(
|
close_profit = self.calc_profit_ratio(
|
||||||
exit_rate, amount=exit_amount, open_rate=avg_price)
|
exit_rate, amount=exit_amount, open_rate=avg_price)
|
||||||
if current_amount <= 0:
|
if current_amount <= ZERO:
|
||||||
profit = close_profit_abs
|
profit = close_profit_abs
|
||||||
else:
|
else:
|
||||||
total_stake = total_stake + self._calc_open_trade_value(tmp_amount, price)
|
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.realized_profit = close_profit_abs
|
||||||
self.close_profit_abs = profit
|
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
|
# Trade is still open
|
||||||
# Leverage not updated, as we don't allow changing leverage through DCA at the moment.
|
# Leverage not updated, as we don't allow changing leverage through DCA at the moment.
|
||||||
self.open_rate = current_stake / current_amount
|
self.open_rate = float(current_stake / current_amount)
|
||||||
self.stake_amount = current_stake / (self.leverage or 1.0)
|
self.amount = current_amount_tr
|
||||||
self.amount = current_amount
|
self.stake_amount = float(current_stake) / (self.leverage or 1.0)
|
||||||
self.fee_open_cost = self.fee_open * current_stake
|
self.fee_open_cost = self.fee_open * float(current_stake)
|
||||||
self.recalc_open_trade_value()
|
self.recalc_open_trade_value()
|
||||||
if self.stop_loss_pct is not None and self.open_rate is not None:
|
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)
|
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)
|
timeframe = Column(Integer, nullable=True)
|
||||||
|
|
||||||
trading_mode = Column(Enum(TradingMode), 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 trading properties
|
||||||
leverage = Column(Float, nullable=True, default=1.0)
|
leverage = Column(Float, nullable=True, default=1.0)
|
||||||
|
2
setup.py
2
setup.py
@ -49,7 +49,7 @@ setup(
|
|||||||
],
|
],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
# from requirements.txt
|
# from requirements.txt
|
||||||
'ccxt>=1.83.12',
|
'ccxt>=1.92.9',
|
||||||
'SQLAlchemy',
|
'SQLAlchemy',
|
||||||
'python-telegram-bot>=13.4',
|
'python-telegram-bot>=13.4',
|
||||||
'arrow>=0.17.0',
|
'arrow>=0.17.0',
|
||||||
|
@ -81,7 +81,7 @@ def mock_trade_usdt_1(fee, is_short: bool):
|
|||||||
def mock_order_usdt_2(is_short: bool):
|
def mock_order_usdt_2(is_short: bool):
|
||||||
return {
|
return {
|
||||||
'id': f'1235_{direc(is_short)}',
|
'id': f'1235_{direc(is_short)}',
|
||||||
'symbol': 'ETC/USDT',
|
'symbol': 'NEO/USDT',
|
||||||
'status': 'closed',
|
'status': 'closed',
|
||||||
'side': entry_side(is_short),
|
'side': entry_side(is_short),
|
||||||
'type': 'limit',
|
'type': 'limit',
|
||||||
@ -95,7 +95,7 @@ def mock_order_usdt_2(is_short: bool):
|
|||||||
def mock_order_usdt_2_exit(is_short: bool):
|
def mock_order_usdt_2_exit(is_short: bool):
|
||||||
return {
|
return {
|
||||||
'id': f'12366_{direc(is_short)}',
|
'id': f'12366_{direc(is_short)}',
|
||||||
'symbol': 'ETC/USDT',
|
'symbol': 'NEO/USDT',
|
||||||
'status': 'closed',
|
'status': 'closed',
|
||||||
'side': exit_side(is_short),
|
'side': exit_side(is_short),
|
||||||
'type': 'limit',
|
'type': 'limit',
|
||||||
@ -111,7 +111,7 @@ def mock_trade_usdt_2(fee, is_short: bool):
|
|||||||
Closed trade...
|
Closed trade...
|
||||||
"""
|
"""
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='ETC/USDT',
|
pair='NEO/USDT',
|
||||||
stake_amount=200.0,
|
stake_amount=200.0,
|
||||||
amount=100.0,
|
amount=100.0,
|
||||||
amount_requested=100.0,
|
amount_requested=100.0,
|
||||||
@ -132,10 +132,10 @@ def mock_trade_usdt_2(fee, is_short: bool):
|
|||||||
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
|
||||||
is_short=is_short,
|
is_short=is_short,
|
||||||
)
|
)
|
||||||
o = Order.parse_from_ccxt_object(mock_order_usdt_2(is_short), 'ETC/USDT', entry_side(is_short))
|
o = Order.parse_from_ccxt_object(mock_order_usdt_2(is_short), 'NEO/USDT', entry_side(is_short))
|
||||||
trade.orders.append(o)
|
trade.orders.append(o)
|
||||||
o = Order.parse_from_ccxt_object(
|
o = Order.parse_from_ccxt_object(
|
||||||
mock_order_usdt_2_exit(is_short), 'ETC/USDT', exit_side(is_short))
|
mock_order_usdt_2_exit(is_short), 'NEO/USDT', exit_side(is_short))
|
||||||
trade.orders.append(o)
|
trade.orders.append(o)
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
@ -205,7 +205,7 @@ def mock_trade_usdt_3(fee, is_short: bool):
|
|||||||
def mock_order_usdt_4(is_short: bool):
|
def mock_order_usdt_4(is_short: bool):
|
||||||
return {
|
return {
|
||||||
'id': f'prod_buy_12345_{direc(is_short)}',
|
'id': f'prod_buy_12345_{direc(is_short)}',
|
||||||
'symbol': 'ETC/USDT',
|
'symbol': 'NEO/USDT',
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'side': entry_side(is_short),
|
'side': entry_side(is_short),
|
||||||
'type': 'limit',
|
'type': 'limit',
|
||||||
@ -221,7 +221,7 @@ def mock_trade_usdt_4(fee, is_short: bool):
|
|||||||
Simulate prod entry
|
Simulate prod entry
|
||||||
"""
|
"""
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='ETC/USDT',
|
pair='NEO/USDT',
|
||||||
stake_amount=20.0,
|
stake_amount=20.0,
|
||||||
amount=10.0,
|
amount=10.0,
|
||||||
amount_requested=10.01,
|
amount_requested=10.01,
|
||||||
@ -236,7 +236,7 @@ def mock_trade_usdt_4(fee, is_short: bool):
|
|||||||
timeframe=5,
|
timeframe=5,
|
||||||
is_short=is_short,
|
is_short=is_short,
|
||||||
)
|
)
|
||||||
o = Order.parse_from_ccxt_object(mock_order_usdt_4(is_short), 'ETC/USDT', entry_side(is_short))
|
o = Order.parse_from_ccxt_object(mock_order_usdt_4(is_short), 'NEO/USDT', entry_side(is_short))
|
||||||
trade.orders.append(o)
|
trade.orders.append(o)
|
||||||
return trade
|
return trade
|
||||||
|
|
||||||
|
@ -78,3 +78,5 @@ def test_FtPrecise():
|
|||||||
assert FtPrecise(-213) == '-213'
|
assert FtPrecise(-213) == '-213'
|
||||||
assert str(FtPrecise(-213)) == '-213'
|
assert str(FtPrecise(-213)) == '-213'
|
||||||
assert FtPrecise(213.2) == '213.2'
|
assert FtPrecise(213.2) == '213.2'
|
||||||
|
assert float(FtPrecise(213.2)) == 213.2
|
||||||
|
assert float(FtPrecise(-213.2)) == -213.2
|
||||||
|
@ -14,12 +14,12 @@ from pandas import DataFrame
|
|||||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||||
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
|
||||||
OperationalException, PricingError, TemporaryError)
|
OperationalException, PricingError, TemporaryError)
|
||||||
from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken
|
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision,
|
||||||
|
date_minus_candles, market_is_active, price_to_precision,
|
||||||
|
timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
|
||||||
|
timeframe_to_prev_date, timeframe_to_seconds)
|
||||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
|
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
|
||||||
calculate_backoff, remove_credentials)
|
calculate_backoff, remove_credentials)
|
||||||
from freqtrade.exchange.exchange import (date_minus_candles, market_is_active, timeframe_to_minutes,
|
|
||||||
timeframe_to_msecs, timeframe_to_next_date,
|
|
||||||
timeframe_to_prev_date, timeframe_to_seconds)
|
|
||||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
|
||||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re
|
from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re
|
||||||
|
|
||||||
@ -279,62 +279,35 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog):
|
|||||||
ex.validate_order_time_in_force(tif2)
|
ex.validate_order_time_in_force(tif2)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("amount,precision_mode,precision,contract_size,expected,trading_mode", [
|
@pytest.mark.parametrize("amount,precision_mode,precision,expected", [
|
||||||
(2.34559, 2, 4, 1, 2.3455, 'spot'),
|
(2.34559, 2, 4, 2.3455),
|
||||||
(2.34559, 2, 5, 1, 2.34559, 'spot'),
|
(2.34559, 2, 5, 2.34559),
|
||||||
(2.34559, 2, 3, 1, 2.345, 'spot'),
|
(2.34559, 2, 3, 2.345),
|
||||||
(2.9999, 2, 3, 1, 2.999, 'spot'),
|
(2.9999, 2, 3, 2.999),
|
||||||
(2.9909, 2, 3, 1, 2.990, 'spot'),
|
(2.9909, 2, 3, 2.990),
|
||||||
(2.9909, 2, 0, 1, 2, 'spot'),
|
(2.9909, 2, 0, 2),
|
||||||
(29991.5555, 2, 0, 1, 29991, 'spot'),
|
(29991.5555, 2, 0, 29991),
|
||||||
(29991.5555, 2, -1, 1, 29990, 'spot'),
|
(29991.5555, 2, -1, 29990),
|
||||||
(29991.5555, 2, -2, 1, 29900, 'spot'),
|
(29991.5555, 2, -2, 29900),
|
||||||
# Tests for Tick-size
|
# Tests for Tick-size
|
||||||
(2.34559, 4, 0.0001, 1, 2.3455, 'spot'),
|
(2.34559, 4, 0.0001, 2.3455),
|
||||||
(2.34559, 4, 0.00001, 1, 2.34559, 'spot'),
|
(2.34559, 4, 0.00001, 2.34559),
|
||||||
(2.34559, 4, 0.001, 1, 2.345, 'spot'),
|
(2.34559, 4, 0.001, 2.345),
|
||||||
(2.9999, 4, 0.001, 1, 2.999, 'spot'),
|
(2.9999, 4, 0.001, 2.999),
|
||||||
(2.9909, 4, 0.001, 1, 2.990, 'spot'),
|
(2.9909, 4, 0.001, 2.990),
|
||||||
(2.9909, 4, 0.005, 0.01, 2.99, 'futures'),
|
(2.9909, 4, 0.005, 2.99),
|
||||||
(2.9999, 4, 0.005, 10, 2.995, 'futures'),
|
(2.9999, 4, 0.005, 2.995),
|
||||||
])
|
])
|
||||||
def test_amount_to_precision(
|
def test_amount_to_precision(amount, precision_mode, precision, expected,):
|
||||||
default_conf,
|
|
||||||
mocker,
|
|
||||||
amount,
|
|
||||||
precision_mode,
|
|
||||||
precision,
|
|
||||||
contract_size,
|
|
||||||
expected,
|
|
||||||
trading_mode
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Test rounds down
|
Test rounds down
|
||||||
"""
|
"""
|
||||||
|
|
||||||
markets = PropertyMock(return_value={
|
|
||||||
'ETH/BTC': {
|
|
||||||
'contractSize': contract_size,
|
|
||||||
'precision': {
|
|
||||||
'amount': precision
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
default_conf['trading_mode'] = trading_mode
|
|
||||||
default_conf['margin_mode'] = 'isolated'
|
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
|
||||||
# digits counting mode
|
# digits counting mode
|
||||||
# DECIMAL_PLACES = 2
|
# DECIMAL_PLACES = 2
|
||||||
# SIGNIFICANT_DIGITS = 3
|
# SIGNIFICANT_DIGITS = 3
|
||||||
# TICK_SIZE = 4
|
# TICK_SIZE = 4
|
||||||
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
|
|
||||||
PropertyMock(return_value=precision_mode))
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
|
|
||||||
|
|
||||||
pair = 'ETH/BTC'
|
assert amount_to_precision(amount, precision, precision_mode) == expected
|
||||||
assert exchange.amount_to_precision(pair, amount) == expected
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
||||||
@ -359,21 +332,13 @@ def test_amount_to_precision(
|
|||||||
(0.000000003483, 4, 1e-12, 0.000000003483),
|
(0.000000003483, 4, 1e-12, 0.000000003483),
|
||||||
|
|
||||||
])
|
])
|
||||||
def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected):
|
def test_price_to_precision(price, precision_mode, precision, expected):
|
||||||
"""Test price to precision"""
|
|
||||||
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}})
|
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id="binance")
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.markets', markets)
|
|
||||||
# digits counting mode
|
# digits counting mode
|
||||||
# DECIMAL_PLACES = 2
|
# DECIMAL_PLACES = 2
|
||||||
# SIGNIFICANT_DIGITS = 3
|
# SIGNIFICANT_DIGITS = 3
|
||||||
# TICK_SIZE = 4
|
# TICK_SIZE = 4
|
||||||
mocker.patch('freqtrade.exchange.Exchange.precisionMode',
|
|
||||||
PropertyMock(return_value=precision_mode))
|
|
||||||
|
|
||||||
pair = 'ETH/BTC'
|
assert price_to_precision(price, precision, precision_mode) == expected
|
||||||
assert exchange.price_to_precision(pair, price) == expected
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
@pytest.mark.parametrize("price,precision_mode,precision,expected", [
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
from decimal import Decimal
|
|
||||||
from math import isclose
|
from math import isclose
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.leverage import interest
|
from freqtrade.leverage import interest
|
||||||
|
from freqtrade.util import FtPrecise
|
||||||
|
|
||||||
|
|
||||||
ten_mins = Decimal(1 / 6)
|
ten_mins = FtPrecise(1 / 6)
|
||||||
five_hours = Decimal(5.0)
|
five_hours = FtPrecise(5.0)
|
||||||
twentyfive_hours = Decimal(25.0)
|
twentyfive_hours = FtPrecise(25.0)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('exchange,interest_rate,hours,expected', [
|
@pytest.mark.parametrize('exchange,interest_rate,hours,expected', [
|
||||||
@ -28,11 +28,11 @@ twentyfive_hours = Decimal(25.0)
|
|||||||
('ftx', 0.00025, twentyfive_hours, 0.015625),
|
('ftx', 0.00025, twentyfive_hours, 0.015625),
|
||||||
])
|
])
|
||||||
def test_interest(exchange, interest_rate, hours, expected):
|
def test_interest(exchange, interest_rate, hours, expected):
|
||||||
borrowed = Decimal(60.0)
|
borrowed = FtPrecise(60.0)
|
||||||
|
|
||||||
assert isclose(interest(
|
assert isclose(interest(
|
||||||
exchange_name=exchange,
|
exchange_name=exchange,
|
||||||
borrowed=borrowed,
|
borrowed=borrowed,
|
||||||
rate=Decimal(interest_rate),
|
rate=FtPrecise(interest_rate),
|
||||||
hours=hours
|
hours=hours
|
||||||
), expected)
|
), expected)
|
||||||
|
@ -735,7 +735,7 @@ def test_PerformanceFilter_lookback(mocker, default_conf_usdt, fee, caplog) -> N
|
|||||||
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||||
create_mock_trades_usdt(fee)
|
create_mock_trades_usdt(fee)
|
||||||
pm.refresh_pairlist()
|
pm.refresh_pairlist()
|
||||||
assert pm.whitelist == ['XRP/USDT']
|
assert pm.whitelist == ['XRP/USDT', 'NEO/USDT']
|
||||||
assert log_has_re(r'Removing pair .* since .* is below .*', caplog)
|
assert log_has_re(r'Removing pair .* since .* is below .*', caplog)
|
||||||
|
|
||||||
# Move to "outside" of lookback window, so original sorting is restored.
|
# Move to "outside" of lookback window, so original sorting is restored.
|
||||||
@ -762,8 +762,8 @@ def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog
|
|||||||
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||||
create_mock_trades_usdt(fee)
|
create_mock_trades_usdt(fee)
|
||||||
pm.refresh_pairlist()
|
pm.refresh_pairlist()
|
||||||
assert pm.whitelist == ['XRP/USDT', 'ETC/USDT', 'ETH/USDT', 'LTC/USDT',
|
assert pm.whitelist == ['XRP/USDT', 'NEO/USDT', 'ETH/USDT', 'LTC/USDT',
|
||||||
'NEO/USDT', 'TKN/USDT', 'ADA/USDT', ]
|
'TKN/USDT', 'ADA/USDT', 'ETC/USDT', ]
|
||||||
# assert log_has_re(r'Removing pair .* since .* is below .*', caplog)
|
# assert log_has_re(r'Removing pair .* since .* is below .*', caplog)
|
||||||
|
|
||||||
# Move to "outside" of lookback window, so original sorting is restored.
|
# Move to "outside" of lookback window, so original sorting is restored.
|
||||||
|
@ -96,20 +96,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'profit_pct': -0.41,
|
'profit_pct': -0.41,
|
||||||
'profit_abs': -4.09e-06,
|
'profit_abs': -4.09e-06,
|
||||||
'profit_fiat': ANY,
|
'profit_fiat': ANY,
|
||||||
'stop_loss_abs': 9.882e-06,
|
'stop_loss_abs': 9.89e-06,
|
||||||
'stop_loss_pct': -10.0,
|
'stop_loss_pct': -10.0,
|
||||||
'stop_loss_ratio': -0.1,
|
'stop_loss_ratio': -0.1,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': ANY,
|
'stoploss_last_update': ANY,
|
||||||
'stoploss_last_update_timestamp': ANY,
|
'stoploss_last_update_timestamp': ANY,
|
||||||
'initial_stop_loss_abs': 9.882e-06,
|
'initial_stop_loss_abs': 9.89e-06,
|
||||||
'initial_stop_loss_pct': -10.0,
|
'initial_stop_loss_pct': -10.0,
|
||||||
'initial_stop_loss_ratio': -0.1,
|
'initial_stop_loss_ratio': -0.1,
|
||||||
'stoploss_current_dist': -1.1080000000000002e-06,
|
'stoploss_current_dist': pytest.approx(-1.0999999e-06),
|
||||||
'stoploss_current_dist_ratio': -0.10081893,
|
'stoploss_current_dist_ratio': -0.10009099,
|
||||||
'stoploss_current_dist_pct': -10.08,
|
'stoploss_current_dist_pct': -10.01,
|
||||||
'stoploss_entry_dist': -0.00010475,
|
'stoploss_entry_dist': -0.00010402,
|
||||||
'stoploss_entry_dist_ratio': -0.10448878,
|
'stoploss_entry_dist_ratio': -0.10376381,
|
||||||
'open_order': None,
|
'open_order': None,
|
||||||
'realized_profit': 0.0,
|
'realized_profit': 0.0,
|
||||||
'exchange': 'binance',
|
'exchange': 'binance',
|
||||||
@ -181,20 +181,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'profit_pct': ANY,
|
'profit_pct': ANY,
|
||||||
'profit_abs': ANY,
|
'profit_abs': ANY,
|
||||||
'profit_fiat': ANY,
|
'profit_fiat': ANY,
|
||||||
'stop_loss_abs': 9.882e-06,
|
'stop_loss_abs': 9.89e-06,
|
||||||
'stop_loss_pct': -10.0,
|
'stop_loss_pct': -10.0,
|
||||||
'stop_loss_ratio': -0.1,
|
'stop_loss_ratio': -0.1,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': ANY,
|
'stoploss_last_update': ANY,
|
||||||
'stoploss_last_update_timestamp': ANY,
|
'stoploss_last_update_timestamp': ANY,
|
||||||
'initial_stop_loss_abs': 9.882e-06,
|
'initial_stop_loss_abs': 9.89e-06,
|
||||||
'initial_stop_loss_pct': -10.0,
|
'initial_stop_loss_pct': -10.0,
|
||||||
'initial_stop_loss_ratio': -0.1,
|
'initial_stop_loss_ratio': -0.1,
|
||||||
'stoploss_current_dist': ANY,
|
'stoploss_current_dist': ANY,
|
||||||
'stoploss_current_dist_ratio': ANY,
|
'stoploss_current_dist_ratio': ANY,
|
||||||
'stoploss_current_dist_pct': ANY,
|
'stoploss_current_dist_pct': ANY,
|
||||||
'stoploss_entry_dist': -0.00010475,
|
'stoploss_entry_dist': -0.00010402,
|
||||||
'stoploss_entry_dist_ratio': -0.10448878,
|
'stoploss_entry_dist_ratio': -0.10376381,
|
||||||
'open_order': None,
|
'open_order': None,
|
||||||
'exchange': 'binance',
|
'exchange': 'binance',
|
||||||
'realized_profit': 0.0,
|
'realized_profit': 0.0,
|
||||||
@ -761,7 +761,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
|||||||
# and trade amount is updated
|
# and trade amount is updated
|
||||||
rpc._rpc_force_exit('3')
|
rpc._rpc_force_exit('3')
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert trade.amount == filled_amount
|
assert pytest.approx(trade.amount) == filled_amount
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.exchange.Exchange.fetch_order',
|
'freqtrade.exchange.Exchange.fetch_order',
|
||||||
@ -830,7 +830,7 @@ def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None:
|
|||||||
|
|
||||||
res = rpc._rpc_performance()
|
res = rpc._rpc_performance()
|
||||||
assert len(res) == 3
|
assert len(res) == 3
|
||||||
assert res[0]['pair'] == 'ETC/USDT'
|
assert res[0]['pair'] == 'NEO/USDT'
|
||||||
assert res[0]['count'] == 1
|
assert res[0]['count'] == 1
|
||||||
assert res[0]['profit_pct'] == 5.0
|
assert res[0]['profit_pct'] == 5.0
|
||||||
|
|
||||||
|
@ -892,7 +892,7 @@ def test_api_performance(botclient, fee):
|
|||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert len(rc.json()) == 2
|
assert len(rc.json()) == 2
|
||||||
assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_pct': 7.61,
|
assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_pct': 7.61,
|
||||||
'profit_ratio': 0.07609203, 'profit_abs': 0.01872279},
|
'profit_ratio': 0.07609203, 'profit_abs': 0.0187228},
|
||||||
{'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_pct': -5.57,
|
{'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_pct': -5.57,
|
||||||
'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}]
|
'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}]
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from math import isclose
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from unittest.mock import ANY, MagicMock, PropertyMock, patch
|
from unittest.mock import ANY, MagicMock, PropertyMock, patch
|
||||||
|
|
||||||
@ -12,7 +11,7 @@ import arrow
|
|||||||
import pytest
|
import pytest
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.constants import CANCEL_REASON, MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT
|
from freqtrade.constants import CANCEL_REASON, UNLIMITED_STAKE_AMOUNT
|
||||||
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RPCMessageType, RunMode,
|
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RPCMessageType, RunMode,
|
||||||
SignalDirection, State)
|
SignalDirection, State)
|
||||||
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
||||||
@ -23,9 +22,9 @@ from freqtrade.persistence import Order, PairLocks, Trade
|
|||||||
from freqtrade.persistence.models import PairLock
|
from freqtrade.persistence.models import PairLock
|
||||||
from freqtrade.plugins.protections.iprotection import ProtectionReturn
|
from freqtrade.plugins.protections.iprotection import ProtectionReturn
|
||||||
from freqtrade.worker import Worker
|
from freqtrade.worker import Worker
|
||||||
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker,
|
from tests.conftest import (create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot,
|
||||||
log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal,
|
get_patched_worker, log_has, log_has_re, patch_edge, patch_exchange,
|
||||||
patch_wallet, patch_whitelist)
|
patch_get_signal, patch_wallet, patch_whitelist)
|
||||||
from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_1,
|
from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_1,
|
||||||
mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell,
|
mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell,
|
||||||
mock_order_4, mock_order_5_stoploss, mock_order_6_sell)
|
mock_order_4, mock_order_5_stoploss, mock_order_6_sell)
|
||||||
@ -569,7 +568,7 @@ def test_process_trade_creation(default_conf_usdt, ticker_usdt, limit_order, lim
|
|||||||
assert trade.open_date is not None
|
assert trade.open_date is not None
|
||||||
assert trade.exchange == 'binance'
|
assert trade.exchange == 'binance'
|
||||||
assert trade.open_rate == ticker_usdt.return_value[ticker_side]
|
assert trade.open_rate == ticker_usdt.return_value[ticker_side]
|
||||||
assert isclose(trade.amount, 60 / ticker_usdt.return_value[ticker_side])
|
assert pytest.approx(trade.amount) == 60 / ticker_usdt.return_value[ticker_side]
|
||||||
|
|
||||||
assert log_has(
|
assert log_has(
|
||||||
f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
|
f'{"Short" if is_short else "Long"} signal found: about create a new trade for ETH/USDT '
|
||||||
@ -1801,7 +1800,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
|
|||||||
# stoploss initially at 20% as edge dictated it.
|
# stoploss initially at 20% as edge dictated it.
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
assert isclose(trade.stop_loss, 1.76)
|
assert pytest.approx(trade.stop_loss) == 1.76
|
||||||
|
|
||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
stoploss_order_mock = MagicMock()
|
stoploss_order_mock = MagicMock()
|
||||||
@ -1818,7 +1817,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
|
|||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
|
|
||||||
# stoploss should remain the same
|
# stoploss should remain the same
|
||||||
assert isclose(trade.stop_loss, 1.76)
|
assert pytest.approx(trade.stop_loss) == 1.76
|
||||||
|
|
||||||
# stoploss on exchange should not be canceled
|
# stoploss on exchange should not be canceled
|
||||||
cancel_order_mock.assert_not_called()
|
cancel_order_mock.assert_not_called()
|
||||||
@ -2172,7 +2171,7 @@ def test_handle_trade(
|
|||||||
|
|
||||||
assert trade.close_rate == (2.0 if is_short else 2.2)
|
assert trade.close_rate == (2.0 if is_short else 2.2)
|
||||||
assert pytest.approx(trade.close_profit) == close_profit
|
assert pytest.approx(trade.close_profit) == close_profit
|
||||||
assert trade.calc_profit(trade.close_rate) == 5.685
|
assert pytest.approx(trade.calc_profit(trade.close_rate)) == 5.685
|
||||||
assert trade.close_date is not None
|
assert trade.close_date is not None
|
||||||
assert trade.exit_reason == 'sell_signal1'
|
assert trade.exit_reason == 'sell_signal1'
|
||||||
|
|
||||||
@ -4144,6 +4143,7 @@ def test_trailing_stop_loss_positive(
|
|||||||
'last': enter_price + (-0.06 if is_short else 0.06),
|
'last': enter_price + (-0.06 if is_short else 0.06),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
caplog.clear()
|
||||||
# stop-loss not reached, adjusted stoploss
|
# stop-loss not reached, adjusted stoploss
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
caplog_text = (f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
|
caplog_text = (f"ETH/USDT - Using positive stoploss: 0.01 offset: {offset} profit: "
|
||||||
@ -4524,11 +4524,8 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord
|
|||||||
|
|
||||||
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
|
||||||
# Amount changes by fee amount.
|
# Amount changes by fee amount.
|
||||||
assert isclose(
|
assert pytest.approx(freqtrade.get_real_amount(
|
||||||
freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj),
|
trade, limit_buy_order_usdt, order_obj)) == amount - (amount * 0.001)
|
||||||
amount - (amount * 0.001),
|
|
||||||
abs_tol=MATH_CLOSE_PREC,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
|
def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker):
|
||||||
@ -4958,6 +4955,31 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s
|
|||||||
assert hto_mock.call_args_list[1][0][0]['status'] == 'canceled'
|
assert hto_mock.call_args_list[1][0][0]['status'] == 'canceled'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
|
def test_startup_backpopulate_precision(mocker, default_conf_usdt, fee, caplog):
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||||
|
create_mock_trades_usdt(fee)
|
||||||
|
|
||||||
|
trades = Trade.get_trades().all()
|
||||||
|
trades[-1].exchange = 'some_other_exchange'
|
||||||
|
for trade in trades:
|
||||||
|
assert trade.price_precision is None
|
||||||
|
assert trade.amount_precision is None
|
||||||
|
assert trade.precision_mode is None
|
||||||
|
|
||||||
|
freqtrade.startup_backpopulate_precision()
|
||||||
|
trades = Trade.get_trades().all()
|
||||||
|
for trade in trades:
|
||||||
|
if trade.exchange == 'some_other_exchange':
|
||||||
|
assert trade.price_precision is None
|
||||||
|
assert trade.amount_precision is None
|
||||||
|
assert trade.precision_mode is None
|
||||||
|
else:
|
||||||
|
assert trade.price_precision is not None
|
||||||
|
assert trade.amount_precision is not None
|
||||||
|
assert trade.precision_mode is not None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
@pytest.mark.parametrize("is_short", [False, True])
|
@pytest.mark.parametrize("is_short", [False, True])
|
||||||
def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short):
|
def test_update_closed_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_short):
|
||||||
|
@ -189,7 +189,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
|
|||||||
assert len(trades) == 5
|
assert len(trades) == 5
|
||||||
|
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
assert trade.stake_amount == result1
|
assert pytest.approx(trade.stake_amount) == result1
|
||||||
# Reset trade open order id's
|
# Reset trade open order id's
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
trades = Trade.get_open_trades()
|
trades = Trade.get_open_trades()
|
||||||
@ -220,8 +220,6 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker_usdt,
|
fetch_ticker=ticker_usdt,
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
amount_to_precision=lambda s, x, y: y,
|
|
||||||
price_to_precision=lambda s, x, y: y,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
@ -249,7 +247,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
|||||||
assert len(trade.orders) == 2
|
assert len(trade.orders) == 2
|
||||||
for o in trade.orders:
|
for o in trade.orders:
|
||||||
assert o.status == "closed"
|
assert o.status == "closed"
|
||||||
assert trade.stake_amount == 120
|
assert pytest.approx(trade.stake_amount) == 120
|
||||||
|
|
||||||
# Open-rate averaged between 2.0 and 2.0 * 0.995
|
# Open-rate averaged between 2.0 and 2.0 * 0.995
|
||||||
assert trade.open_rate < 2.0
|
assert trade.open_rate < 2.0
|
||||||
@ -259,11 +257,11 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
|||||||
freqtrade.process()
|
freqtrade.process()
|
||||||
trade = Trade.get_trades().first()
|
trade = Trade.get_trades().first()
|
||||||
assert len(trade.orders) == 2
|
assert len(trade.orders) == 2
|
||||||
assert trade.stake_amount == 120
|
assert pytest.approx(trade.stake_amount) == 120
|
||||||
assert trade.orders[0].amount == 30
|
assert trade.orders[0].amount == 30
|
||||||
assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid']
|
assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid']
|
||||||
|
|
||||||
assert trade.amount == trade.orders[0].amount + trade.orders[1].amount
|
assert pytest.approx(trade.amount) == trade.orders[0].amount + trade.orders[1].amount
|
||||||
assert trade.nr_of_successful_buys == 2
|
assert trade.nr_of_successful_buys == 2
|
||||||
assert trade.nr_of_successful_entries == 2
|
assert trade.nr_of_successful_entries == 2
|
||||||
|
|
||||||
@ -274,7 +272,7 @@ def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None:
|
|||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
assert trade.orders[0].amount == 30
|
assert trade.orders[0].amount == 30
|
||||||
assert trade.orders[0].side == 'buy'
|
assert trade.orders[0].side == 'buy'
|
||||||
assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid']
|
assert pytest.approx(trade.orders[1].amount) == 60 / ticker_usdt_modif['bid']
|
||||||
# Sold everything
|
# Sold everything
|
||||||
assert trade.orders[-1].side == 'sell'
|
assert trade.orders[-1].side == 'sell'
|
||||||
assert trade.orders[2].amount == trade.amount
|
assert trade.orders[2].amount == trade.amount
|
||||||
|
@ -1387,7 +1387,8 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
|||||||
assert log_has("trying trades_bak2", caplog)
|
assert log_has("trying trades_bak2", caplog)
|
||||||
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
|
assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0",
|
||||||
caplog)
|
caplog)
|
||||||
assert trade.open_trade_value == trade._calc_open_trade_value(trade.amount, trade.open_rate)
|
assert pytest.approx(trade.open_trade_value) == trade._calc_open_trade_value(
|
||||||
|
trade.amount, trade.open_rate)
|
||||||
assert trade.close_profit_abs is None
|
assert trade.close_profit_abs is None
|
||||||
|
|
||||||
orders = trade.orders
|
orders = trade.orders
|
||||||
|
Loading…
Reference in New Issue
Block a user