Merge pull request #10530 from freqtrade/feat/price_precision_mode
add price_precision_mode
This commit is contained in:
@@ -416,7 +416,17 @@ class Exchange:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def precisionMode(self) -> int:
|
def precisionMode(self) -> int:
|
||||||
"""exchange ccxt precisionMode"""
|
"""Exchange ccxt precisionMode"""
|
||||||
|
return self._api.precisionMode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def precision_mode_price(self) -> int:
|
||||||
|
"""
|
||||||
|
Exchange ccxt precisionMode used for price
|
||||||
|
Workaround for ccxt limitation to not have precisionMode for price
|
||||||
|
if it differs for an exchange
|
||||||
|
Might need to be updated if https://github.com/ccxt/ccxt/issues/20408 is fixed.
|
||||||
|
"""
|
||||||
return self._api.precisionMode
|
return self._api.precisionMode
|
||||||
|
|
||||||
def additional_exchange_init(self) -> None:
|
def additional_exchange_init(self) -> None:
|
||||||
@@ -913,7 +923,10 @@ class Exchange:
|
|||||||
For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts.
|
For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts.
|
||||||
"""
|
"""
|
||||||
return price_to_precision(
|
return price_to_precision(
|
||||||
price, self.get_precision_price(pair), self.precisionMode, rounding_mode=rounding_mode
|
price,
|
||||||
|
self.get_precision_price(pair),
|
||||||
|
self.precision_mode_price,
|
||||||
|
rounding_mode=rounding_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
def price_get_one_pip(self, pair: str, price: float) -> float:
|
def price_get_one_pip(self, pair: str, price: float) -> float:
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if trade.exchange != self.exchange.id:
|
if trade.exchange != self.exchange.id:
|
||||||
continue
|
continue
|
||||||
trade.precision_mode = self.exchange.precisionMode
|
trade.precision_mode = self.exchange.precisionMode
|
||||||
|
trade.precision_mode_price = self.exchange.precision_mode_price
|
||||||
trade.amount_precision = self.exchange.get_precision_amount(trade.pair)
|
trade.amount_precision = self.exchange.get_precision_amount(trade.pair)
|
||||||
trade.price_precision = self.exchange.get_precision_price(trade.pair)
|
trade.price_precision = self.exchange.get_precision_price(trade.pair)
|
||||||
trade.contract_size = self.exchange.get_contract_size(trade.pair)
|
trade.contract_size = self.exchange.get_contract_size(trade.pair)
|
||||||
@@ -992,6 +993,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
amount_precision=self.exchange.get_precision_amount(pair),
|
amount_precision=self.exchange.get_precision_amount(pair),
|
||||||
price_precision=self.exchange.get_precision_price(pair),
|
price_precision=self.exchange.get_precision_price(pair),
|
||||||
precision_mode=self.exchange.precisionMode,
|
precision_mode=self.exchange.precisionMode,
|
||||||
|
precision_mode_price=self.exchange.precision_mode_price,
|
||||||
contract_size=self.exchange.get_contract_size(pair),
|
contract_size=self.exchange.get_contract_size(pair),
|
||||||
)
|
)
|
||||||
stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair)
|
stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair)
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ class Backtesting:
|
|||||||
self.fee = max(fee for fee in fees if fee is not None)
|
self.fee = max(fee for fee in fees if fee is not None)
|
||||||
logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
|
logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
|
||||||
self.precision_mode = self.exchange.precisionMode
|
self.precision_mode = self.exchange.precisionMode
|
||||||
|
self.precision_mode_price = self.exchange.precision_mode_price
|
||||||
|
|
||||||
if self.config.get("freqai_backtest_live_models", False):
|
if self.config.get("freqai_backtest_live_models", False):
|
||||||
from freqtrade.freqai.utils import get_timerange_backtest_live_models
|
from freqtrade.freqai.utils import get_timerange_backtest_live_models
|
||||||
@@ -785,7 +786,7 @@ class Backtesting:
|
|||||||
)
|
)
|
||||||
if rate is not None and rate != close_rate:
|
if rate is not None and rate != close_rate:
|
||||||
close_rate = price_to_precision(
|
close_rate = price_to_precision(
|
||||||
rate, trade.price_precision, self.precision_mode
|
rate, trade.price_precision, self.precision_mode_price
|
||||||
)
|
)
|
||||||
# We can't place orders lower than current low.
|
# We can't place orders lower than current low.
|
||||||
# freqtrade does not support this in live, and the order would fill immediately
|
# freqtrade does not support this in live, and the order would fill immediately
|
||||||
@@ -929,7 +930,9 @@ class Backtesting:
|
|||||||
# We can't place orders higher than current high (otherwise it'd be a stop limit entry)
|
# We can't place orders higher than current high (otherwise it'd be a stop limit entry)
|
||||||
# which freqtrade does not support in live.
|
# which freqtrade does not support in live.
|
||||||
if new_rate is not None and new_rate != propose_rate:
|
if new_rate is not None and new_rate != propose_rate:
|
||||||
propose_rate = price_to_precision(new_rate, price_precision, self.precision_mode)
|
propose_rate = price_to_precision(
|
||||||
|
new_rate, price_precision, self.precision_mode_price
|
||||||
|
)
|
||||||
if direction == "short":
|
if direction == "short":
|
||||||
propose_rate = max(propose_rate, row[LOW_IDX])
|
propose_rate = max(propose_rate, row[LOW_IDX])
|
||||||
else:
|
else:
|
||||||
@@ -1109,6 +1112,7 @@ class Backtesting:
|
|||||||
amount_precision=precision_amount,
|
amount_precision=precision_amount,
|
||||||
price_precision=precision_price,
|
price_precision=precision_price,
|
||||||
precision_mode=self.precision_mode,
|
precision_mode=self.precision_mode,
|
||||||
|
precision_mode_price=self.precision_mode_price,
|
||||||
contract_size=contract_size,
|
contract_size=contract_size,
|
||||||
orders=[],
|
orders=[],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ def migrate_trades_and_orders_table(
|
|||||||
price_precision = get_column_def(cols, "price_precision", "null")
|
price_precision = get_column_def(cols, "price_precision", "null")
|
||||||
precision_mode = get_column_def(cols, "precision_mode", "null")
|
precision_mode = get_column_def(cols, "precision_mode", "null")
|
||||||
contract_size = get_column_def(cols, "contract_size", "null")
|
contract_size = get_column_def(cols, "contract_size", "null")
|
||||||
|
precision_mode_price = get_column_def(
|
||||||
|
cols, "precision_mode_price", get_column_def(cols, "precision_mode", "null")
|
||||||
|
)
|
||||||
|
|
||||||
# Schema migration necessary
|
# Schema migration necessary
|
||||||
with engine.begin() as connection:
|
with engine.begin() as connection:
|
||||||
@@ -177,7 +180,7 @@ def migrate_trades_and_orders_table(
|
|||||||
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, funding_fee_running, realized_profit,
|
interest_rate, funding_fees, funding_fee_running, realized_profit,
|
||||||
amount_precision, price_precision, precision_mode, contract_size,
|
amount_precision, price_precision, precision_mode, precision_mode_price, contract_size,
|
||||||
max_stake_amount
|
max_stake_amount
|
||||||
)
|
)
|
||||||
select id, lower(exchange), pair, {base_currency} base_currency,
|
select id, lower(exchange), pair, {base_currency} base_currency,
|
||||||
@@ -207,8 +210,8 @@ def migrate_trades_and_orders_table(
|
|||||||
{funding_fees} funding_fees, {funding_fee_running} funding_fee_running,
|
{funding_fees} funding_fees, {funding_fee_running} funding_fee_running,
|
||||||
{realized_profit} realized_profit,
|
{realized_profit} realized_profit,
|
||||||
{amount_precision} amount_precision, {price_precision} price_precision,
|
{amount_precision} amount_precision, {price_precision} price_precision,
|
||||||
{precision_mode} precision_mode, {contract_size} contract_size,
|
{precision_mode} precision_mode, {precision_mode_price} precision_mode_price,
|
||||||
{max_stake_amount} max_stake_amount
|
{contract_size} contract_size, {max_stake_amount} max_stake_amount
|
||||||
from {trade_back_name}
|
from {trade_back_name}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@@ -348,8 +351,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
|
|||||||
# if ('orders' not in previous_tables
|
# if ('orders' not in previous_tables
|
||||||
# or not has_column(cols_orders, 'funding_fee')):
|
# or not has_column(cols_orders, 'funding_fee')):
|
||||||
migrating = False
|
migrating = False
|
||||||
# if not has_column(cols_trades, 'funding_fee_running'):
|
if not has_column(cols_trades, "precision_mode_price"):
|
||||||
if not has_column(cols_orders, "ft_order_tag"):
|
# if not has_column(cols_orders, "ft_order_tag"):
|
||||||
migrating = True
|
migrating = True
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Running database migration for trades - "
|
f"Running database migration for trades - "
|
||||||
|
|||||||
@@ -433,6 +433,7 @@ class LocalTrade:
|
|||||||
amount_precision: Optional[float] = None
|
amount_precision: Optional[float] = None
|
||||||
price_precision: Optional[float] = None
|
price_precision: Optional[float] = None
|
||||||
precision_mode: Optional[int] = None
|
precision_mode: Optional[int] = None
|
||||||
|
precision_mode_price: Optional[int] = None
|
||||||
contract_size: Optional[float] = None
|
contract_size: Optional[float] = None
|
||||||
|
|
||||||
# Leverage trading properties
|
# Leverage trading properties
|
||||||
@@ -730,6 +731,7 @@ class LocalTrade:
|
|||||||
"amount_precision": self.amount_precision,
|
"amount_precision": self.amount_precision,
|
||||||
"price_precision": self.price_precision,
|
"price_precision": self.price_precision,
|
||||||
"precision_mode": self.precision_mode,
|
"precision_mode": self.precision_mode,
|
||||||
|
"precision_mode_price": self.precision_mode_price,
|
||||||
"contract_size": self.contract_size,
|
"contract_size": self.contract_size,
|
||||||
"has_open_orders": self.has_open_orders,
|
"has_open_orders": self.has_open_orders,
|
||||||
"orders": orders_json,
|
"orders": orders_json,
|
||||||
@@ -810,7 +812,7 @@ class LocalTrade:
|
|||||||
stop_loss_norm = price_to_precision(
|
stop_loss_norm = price_to_precision(
|
||||||
new_loss,
|
new_loss,
|
||||||
self.price_precision,
|
self.price_precision,
|
||||||
self.precision_mode,
|
self.precision_mode_price,
|
||||||
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP,
|
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP,
|
||||||
)
|
)
|
||||||
# no stop loss assigned yet
|
# no stop loss assigned yet
|
||||||
@@ -819,7 +821,7 @@ class LocalTrade:
|
|||||||
self.initial_stop_loss = price_to_precision(
|
self.initial_stop_loss = price_to_precision(
|
||||||
stop_loss_norm,
|
stop_loss_norm,
|
||||||
self.price_precision,
|
self.price_precision,
|
||||||
self.precision_mode,
|
self.precision_mode_price,
|
||||||
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP,
|
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP,
|
||||||
)
|
)
|
||||||
self.initial_stop_loss_pct = -1 * abs(stoploss)
|
self.initial_stop_loss_pct = -1 * abs(stoploss)
|
||||||
@@ -1562,6 +1564,7 @@ class LocalTrade:
|
|||||||
amount_precision=data.get("amount_precision", None),
|
amount_precision=data.get("amount_precision", None),
|
||||||
price_precision=data.get("price_precision", None),
|
price_precision=data.get("price_precision", None),
|
||||||
precision_mode=data.get("precision_mode", None),
|
precision_mode=data.get("precision_mode", None),
|
||||||
|
precision_mode_price=data.get("precision_mode_price", data.get("precision_mode", None)),
|
||||||
contract_size=data.get("contract_size", None),
|
contract_size=data.get("contract_size", None),
|
||||||
)
|
)
|
||||||
for order in data["orders"]:
|
for order in data["orders"]:
|
||||||
@@ -1695,6 +1698,9 @@ class Trade(ModelBase, LocalTrade):
|
|||||||
)
|
)
|
||||||
price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
|
price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
|
||||||
precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore
|
precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore
|
||||||
|
precision_mode_price: Mapped[Optional[int]] = mapped_column( # type: ignore
|
||||||
|
Integer, nullable=True
|
||||||
|
)
|
||||||
contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
|
contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
|
||||||
|
|
||||||
# Leverage trading properties
|
# Leverage trading properties
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ def patch_exchange(
|
|||||||
mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange))
|
mocker.patch(f"{EXMS}.id", PropertyMock(return_value=exchange))
|
||||||
mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title()))
|
mocker.patch(f"{EXMS}.name", PropertyMock(return_value=exchange.title()))
|
||||||
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2))
|
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=2))
|
||||||
|
mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=2))
|
||||||
# Temporary patch ...
|
# Temporary patch ...
|
||||||
mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers")
|
mocker.patch("freqtrade.exchange.bybit.Bybit.cache_leverage_tiers")
|
||||||
|
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ def test_price_get_one_pip(default_conf, mocker, price, precision_mode, precisio
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
|
exchange = get_patched_exchange(mocker, default_conf, exchange="binance")
|
||||||
mocker.patch(f"{EXMS}.markets", markets)
|
mocker.patch(f"{EXMS}.markets", markets)
|
||||||
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode))
|
mocker.patch(f"{EXMS}.precisionMode", PropertyMock(return_value=precision_mode))
|
||||||
|
mocker.patch(f"{EXMS}.precision_mode_price", PropertyMock(return_value=precision_mode))
|
||||||
pair = "ETH/BTC"
|
pair = "ETH/BTC"
|
||||||
assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected
|
assert pytest.approx(exchange.price_get_one_pip(pair, price)) == expected
|
||||||
|
|
||||||
|
|||||||
@@ -1404,6 +1404,7 @@ def test_to_json(fee):
|
|||||||
exchange="binance",
|
exchange="binance",
|
||||||
enter_tag=None,
|
enter_tag=None,
|
||||||
precision_mode=1,
|
precision_mode=1,
|
||||||
|
precision_mode_price=1,
|
||||||
amount_precision=8.0,
|
amount_precision=8.0,
|
||||||
price_precision=7.0,
|
price_precision=7.0,
|
||||||
contract_size=1,
|
contract_size=1,
|
||||||
@@ -1473,6 +1474,7 @@ def test_to_json(fee):
|
|||||||
"amount_precision": 8.0,
|
"amount_precision": 8.0,
|
||||||
"price_precision": 7.0,
|
"price_precision": 7.0,
|
||||||
"precision_mode": 1,
|
"precision_mode": 1,
|
||||||
|
"precision_mode_price": 1,
|
||||||
"contract_size": 1,
|
"contract_size": 1,
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"has_open_orders": False,
|
"has_open_orders": False,
|
||||||
@@ -1493,6 +1495,7 @@ def test_to_json(fee):
|
|||||||
enter_tag="buys_signal_001",
|
enter_tag="buys_signal_001",
|
||||||
exchange="binance",
|
exchange="binance",
|
||||||
precision_mode=2,
|
precision_mode=2,
|
||||||
|
precision_mode_price=1,
|
||||||
amount_precision=7.0,
|
amount_precision=7.0,
|
||||||
price_precision=8.0,
|
price_precision=8.0,
|
||||||
contract_size=1,
|
contract_size=1,
|
||||||
@@ -1562,6 +1565,7 @@ def test_to_json(fee):
|
|||||||
"amount_precision": 7.0,
|
"amount_precision": 7.0,
|
||||||
"price_precision": 8.0,
|
"price_precision": 8.0,
|
||||||
"precision_mode": 2,
|
"precision_mode": 2,
|
||||||
|
"precision_mode_price": 1,
|
||||||
"contract_size": 1,
|
"contract_size": 1,
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"has_open_orders": False,
|
"has_open_orders": False,
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
"amount_precision": 8.0,
|
"amount_precision": 8.0,
|
||||||
"price_precision": 8.0,
|
"price_precision": 8.0,
|
||||||
"precision_mode": 2,
|
"precision_mode": 2,
|
||||||
|
"precision_mode_price": 2,
|
||||||
"contract_size": 1,
|
"contract_size": 1,
|
||||||
"has_open_orders": False,
|
"has_open_orders": False,
|
||||||
"orders": [
|
"orders": [
|
||||||
|
|||||||
@@ -579,6 +579,7 @@ def test_ft_stoploss_reached(
|
|||||||
liquidation_price=liq,
|
liquidation_price=liq,
|
||||||
price_precision=4,
|
price_precision=4,
|
||||||
precision_mode=2,
|
precision_mode=2,
|
||||||
|
precision_mode_price=2,
|
||||||
)
|
)
|
||||||
trade.adjust_min_max_rates(trade.open_rate, trade.open_rate)
|
trade.adjust_min_max_rates(trade.open_rate, trade.open_rate)
|
||||||
strategy.trailing_stop = trailing
|
strategy.trailing_stop = trailing
|
||||||
|
|||||||
Reference in New Issue
Block a user