Merge branch 'freqtrade:develop' into develop
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
""" Freqtrade bot """
|
||||
__version__ = '2023.3.dev'
|
||||
__version__ = '2023.4.dev'
|
||||
|
||||
if 'dev' in __version__:
|
||||
from pathlib import Path
|
||||
|
@@ -598,7 +598,7 @@ CONF_SCHEMA = {
|
||||
"model_type": {"type": "string", "default": "PPO"},
|
||||
"policy_type": {"type": "string", "default": "MlpPolicy"},
|
||||
"net_arch": {"type": "array", "default": [128, 128]},
|
||||
"randomize_startinng_position": {"type": "boolean", "default": False},
|
||||
"randomize_starting_position": {"type": "boolean", "default": False},
|
||||
"model_reward_parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@@ -8,15 +8,15 @@ 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_utils import (amount_to_contract_precision, amount_to_contracts,
|
||||
amount_to_precision, available_exchanges,
|
||||
ccxt_exchanges, contracts_to_amount,
|
||||
date_minus_candles, is_exchange_known_ccxt,
|
||||
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.exchange_utils import (ROUND_DOWN, ROUND_UP, amount_to_contract_precision,
|
||||
amount_to_contracts, amount_to_precision,
|
||||
available_exchanges, ccxt_exchanges,
|
||||
contracts_to_amount, date_minus_candles,
|
||||
is_exchange_known_ccxt, 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.gate import Gate
|
||||
from freqtrade.exchange.hitbtc import Hitbtc
|
||||
from freqtrade.exchange.huobi import Huobi
|
||||
|
@@ -30,13 +30,14 @@ from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFun
|
||||
RetryableOrderError, TemporaryError)
|
||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, remove_credentials, retrier,
|
||||
retrier_async)
|
||||
from freqtrade.exchange.exchange_utils import (CcxtModuleType, amount_to_contract_precision,
|
||||
amount_to_contracts, amount_to_precision,
|
||||
contracts_to_amount, date_minus_candles,
|
||||
is_exchange_known_ccxt, 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.exchange_utils import (ROUND, ROUND_DOWN, ROUND_UP, CcxtModuleType,
|
||||
amount_to_contract_precision, amount_to_contracts,
|
||||
amount_to_precision, contracts_to_amount,
|
||||
date_minus_candles, is_exchange_known_ccxt,
|
||||
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.types import OHLCVResponse, OrderBook, Ticker, Tickers
|
||||
from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json,
|
||||
safe_value_fallback2)
|
||||
@@ -734,12 +735,14 @@ class Exchange:
|
||||
"""
|
||||
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, *, rounding_mode: int = ROUND) -> float:
|
||||
"""
|
||||
Returns the price rounded up to the precision the Exchange accepts.
|
||||
Rounds up
|
||||
Returns the price rounded to the precision the Exchange accepts.
|
||||
The default price_rounding_mode in conf is ROUND.
|
||||
For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts.
|
||||
"""
|
||||
return price_to_precision(price, self.get_precision_price(pair), self.precisionMode)
|
||||
return price_to_precision(price, self.get_precision_price(pair),
|
||||
self.precisionMode, rounding_mode=rounding_mode)
|
||||
|
||||
def price_get_one_pip(self, pair: str, price: float) -> float:
|
||||
"""
|
||||
@@ -762,12 +765,12 @@ class Exchange:
|
||||
return self._get_stake_amount_limit(pair, price, stoploss, 'min', leverage)
|
||||
|
||||
def get_max_pair_stake_amount(self, pair: str, price: float, leverage: float = 1.0) -> float:
|
||||
max_stake_amount = self._get_stake_amount_limit(pair, price, 0.0, 'max')
|
||||
max_stake_amount = self._get_stake_amount_limit(pair, price, 0.0, 'max', leverage)
|
||||
if max_stake_amount is None:
|
||||
# * Should never be executed
|
||||
raise OperationalException(f'{self.name}.get_max_pair_stake_amount should'
|
||||
'never set max_stake_amount to None')
|
||||
return max_stake_amount / leverage
|
||||
return max_stake_amount
|
||||
|
||||
def _get_stake_amount_limit(
|
||||
self,
|
||||
@@ -785,43 +788,41 @@ class Exchange:
|
||||
except KeyError:
|
||||
raise ValueError(f"Can't get market information for symbol {pair}")
|
||||
|
||||
if isMin:
|
||||
# reserve some percent defined in config (5% default) + stoploss
|
||||
margin_reserve: float = 1.0 + self._config.get('amount_reserve_percent',
|
||||
DEFAULT_AMOUNT_RESERVE_PERCENT)
|
||||
stoploss_reserve = (
|
||||
margin_reserve / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
|
||||
)
|
||||
# it should not be more than 50%
|
||||
stoploss_reserve = max(min(stoploss_reserve, 1.5), 1)
|
||||
else:
|
||||
margin_reserve = 1.0
|
||||
stoploss_reserve = 1.0
|
||||
|
||||
stake_limits = []
|
||||
limits = market['limits']
|
||||
if (limits['cost'][limit] is not None):
|
||||
stake_limits.append(
|
||||
self._contracts_to_amount(
|
||||
pair,
|
||||
limits['cost'][limit]
|
||||
)
|
||||
self._contracts_to_amount(pair, limits['cost'][limit]) * stoploss_reserve
|
||||
)
|
||||
|
||||
if (limits['amount'][limit] is not None):
|
||||
stake_limits.append(
|
||||
self._contracts_to_amount(
|
||||
pair,
|
||||
limits['amount'][limit] * price
|
||||
)
|
||||
self._contracts_to_amount(pair, limits['amount'][limit]) * price * margin_reserve
|
||||
)
|
||||
|
||||
if not stake_limits:
|
||||
return None if isMin else float('inf')
|
||||
|
||||
# reserve some percent defined in config (5% default) + stoploss
|
||||
amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent',
|
||||
DEFAULT_AMOUNT_RESERVE_PERCENT)
|
||||
amount_reserve_percent = (
|
||||
amount_reserve_percent / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
|
||||
)
|
||||
# it should not be more than 50%
|
||||
amount_reserve_percent = max(min(amount_reserve_percent, 1.5), 1)
|
||||
|
||||
# The value returned should satisfy both limits: for amount (base currency) and
|
||||
# for cost (quote, stake currency), so max() is used here.
|
||||
# See also #2575 at github.
|
||||
return self._get_stake_amount_considering_leverage(
|
||||
max(stake_limits) * amount_reserve_percent,
|
||||
max(stake_limits) if isMin else min(stake_limits),
|
||||
leverage or 1.0
|
||||
) if isMin else min(stake_limits)
|
||||
)
|
||||
|
||||
def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float) -> float:
|
||||
"""
|
||||
@@ -1185,12 +1186,12 @@ class Exchange:
|
||||
|
||||
user_order_type = order_types.get('stoploss', 'market')
|
||||
ordertype, user_order_type = self._get_stop_order_type(user_order_type)
|
||||
|
||||
stop_price_norm = self.price_to_precision(pair, stop_price)
|
||||
round_mode = ROUND_DOWN if side == 'buy' else ROUND_UP
|
||||
stop_price_norm = self.price_to_precision(pair, stop_price, rounding_mode=round_mode)
|
||||
limit_rate = None
|
||||
if user_order_type == 'limit':
|
||||
limit_rate = self._get_stop_limit_rate(stop_price, order_types, side)
|
||||
limit_rate = self.price_to_precision(pair, limit_rate)
|
||||
limit_rate = self.price_to_precision(pair, limit_rate, rounding_mode=round_mode)
|
||||
|
||||
if self._config['dry_run']:
|
||||
dry_order = self.create_dry_run_order(
|
||||
|
@@ -2,11 +2,12 @@
|
||||
Exchange support utils
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from math import ceil
|
||||
from math import ceil, floor
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import ccxt
|
||||
from ccxt import ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE, decimal_to_precision
|
||||
from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGITS, TICK_SIZE,
|
||||
TRUNCATE, decimal_to_precision)
|
||||
|
||||
from freqtrade.exchange.common import BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED
|
||||
from freqtrade.util import FtPrecise
|
||||
@@ -219,35 +220,51 @@ def amount_to_contract_precision(
|
||||
return amount
|
||||
|
||||
|
||||
def price_to_precision(price: float, price_precision: Optional[float],
|
||||
precisionMode: Optional[int]) -> float:
|
||||
def price_to_precision(
|
||||
price: float,
|
||||
price_precision: Optional[float],
|
||||
precisionMode: Optional[int],
|
||||
*,
|
||||
rounding_mode: int = ROUND,
|
||||
) -> float:
|
||||
"""
|
||||
Returns the price rounded up to the precision the Exchange accepts.
|
||||
Returns the price rounded to the precision the Exchange accepts.
|
||||
Partial Re-implementation of ccxt internal method decimal_to_precision(),
|
||||
which does not support rounding up
|
||||
which does not support rounding up.
|
||||
For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts.
|
||||
|
||||
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
|
||||
:param rounding_mode: rounding mode to use. Defaults to ROUND
|
||||
: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:
|
||||
if rounding_mode == ROUND:
|
||||
ticks = price / price_precision
|
||||
rounded_ticks = round(ticks)
|
||||
return rounded_ticks * price_precision
|
||||
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 round(float(str(price_str - missing + precision)), 14)
|
||||
return price
|
||||
elif precisionMode in (SIGNIFICANT_DIGITS, DECIMAL_PLACES):
|
||||
ndigits = round(price_precision)
|
||||
if rounding_mode == ROUND:
|
||||
return round(price, ndigits)
|
||||
ticks = price * (10**ndigits)
|
||||
if rounding_mode == ROUND_UP:
|
||||
return ceil(ticks) / (10**ndigits)
|
||||
if rounding_mode == TRUNCATE:
|
||||
return int(ticks) / (10**ndigits)
|
||||
if rounding_mode == ROUND_DOWN:
|
||||
return floor(ticks) / (10**ndigits)
|
||||
raise ValueError(f"Unknown rounding_mode {rounding_mode}")
|
||||
raise ValueError(f"Unknown precisionMode {precisionMode}")
|
||||
return price
|
||||
|
@@ -12,6 +12,7 @@ from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, Invali
|
||||
OperationalException, TemporaryError)
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.exchange.common import retrier
|
||||
from freqtrade.exchange.exchange_utils import ROUND_DOWN, ROUND_UP
|
||||
from freqtrade.exchange.types import Tickers
|
||||
|
||||
|
||||
@@ -109,6 +110,7 @@ class Kraken(Exchange):
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
params.update({'reduceOnly': True})
|
||||
|
||||
round_mode = ROUND_DOWN if side == 'buy' else ROUND_UP
|
||||
if order_types.get('stoploss', 'market') == 'limit':
|
||||
ordertype = "stop-loss-limit"
|
||||
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
|
||||
@@ -116,11 +118,11 @@ class Kraken(Exchange):
|
||||
limit_rate = stop_price * limit_price_pct
|
||||
else:
|
||||
limit_rate = stop_price * (2 - limit_price_pct)
|
||||
params['price2'] = self.price_to_precision(pair, limit_rate)
|
||||
params['price2'] = self.price_to_precision(pair, limit_rate, rounding_mode=round_mode)
|
||||
else:
|
||||
ordertype = "stop-loss"
|
||||
|
||||
stop_price = self.price_to_precision(pair, stop_price)
|
||||
stop_price = self.price_to_precision(pair, stop_price, rounding_mode=round_mode)
|
||||
|
||||
if self._config['dry_run']:
|
||||
dry_order = self.create_dry_run_order(
|
||||
|
@@ -66,7 +66,7 @@ class Base3ActionRLEnv(BaseEnvironment):
|
||||
elif action == Actions.Sell.value and not self.can_short:
|
||||
self._update_total_profit()
|
||||
self._position = Positions.Neutral
|
||||
trade_type = "neutral"
|
||||
trade_type = "exit"
|
||||
self._last_trade_tick = None
|
||||
else:
|
||||
print("case not defined")
|
||||
@@ -74,7 +74,7 @@ class Base3ActionRLEnv(BaseEnvironment):
|
||||
if trade_type is not None:
|
||||
self.trade_history.append(
|
||||
{'price': self.current_price(), 'index': self._current_tick,
|
||||
'type': trade_type})
|
||||
'type': trade_type, 'profit': self.get_unrealized_profit()})
|
||||
|
||||
if (self._total_profit < self.max_drawdown or
|
||||
self._total_unrealized_profit < self.max_drawdown):
|
||||
|
@@ -52,16 +52,6 @@ class Base4ActionRLEnv(BaseEnvironment):
|
||||
|
||||
trade_type = None
|
||||
if self.is_tradesignal(action):
|
||||
"""
|
||||
Action: Neutral, position: Long -> Close Long
|
||||
Action: Neutral, position: Short -> Close Short
|
||||
|
||||
Action: Long, position: Neutral -> Open Long
|
||||
Action: Long, position: Short -> Close Short and Open Long
|
||||
|
||||
Action: Short, position: Neutral -> Open Short
|
||||
Action: Short, position: Long -> Close Long and Open Short
|
||||
"""
|
||||
|
||||
if action == Actions.Neutral.value:
|
||||
self._position = Positions.Neutral
|
||||
@@ -69,16 +59,16 @@ class Base4ActionRLEnv(BaseEnvironment):
|
||||
self._last_trade_tick = None
|
||||
elif action == Actions.Long_enter.value:
|
||||
self._position = Positions.Long
|
||||
trade_type = "long"
|
||||
trade_type = "enter_long"
|
||||
self._last_trade_tick = self._current_tick
|
||||
elif action == Actions.Short_enter.value:
|
||||
self._position = Positions.Short
|
||||
trade_type = "short"
|
||||
trade_type = "enter_short"
|
||||
self._last_trade_tick = self._current_tick
|
||||
elif action == Actions.Exit.value:
|
||||
self._update_total_profit()
|
||||
self._position = Positions.Neutral
|
||||
trade_type = "neutral"
|
||||
trade_type = "exit"
|
||||
self._last_trade_tick = None
|
||||
else:
|
||||
print("case not defined")
|
||||
@@ -86,7 +76,7 @@ class Base4ActionRLEnv(BaseEnvironment):
|
||||
if trade_type is not None:
|
||||
self.trade_history.append(
|
||||
{'price': self.current_price(), 'index': self._current_tick,
|
||||
'type': trade_type})
|
||||
'type': trade_type, 'profit': self.get_unrealized_profit()})
|
||||
|
||||
if (self._total_profit < self.max_drawdown or
|
||||
self._total_unrealized_profit < self.max_drawdown):
|
||||
|
@@ -53,16 +53,6 @@ class Base5ActionRLEnv(BaseEnvironment):
|
||||
|
||||
trade_type = None
|
||||
if self.is_tradesignal(action):
|
||||
"""
|
||||
Action: Neutral, position: Long -> Close Long
|
||||
Action: Neutral, position: Short -> Close Short
|
||||
|
||||
Action: Long, position: Neutral -> Open Long
|
||||
Action: Long, position: Short -> Close Short and Open Long
|
||||
|
||||
Action: Short, position: Neutral -> Open Short
|
||||
Action: Short, position: Long -> Close Long and Open Short
|
||||
"""
|
||||
|
||||
if action == Actions.Neutral.value:
|
||||
self._position = Positions.Neutral
|
||||
@@ -70,21 +60,21 @@ class Base5ActionRLEnv(BaseEnvironment):
|
||||
self._last_trade_tick = None
|
||||
elif action == Actions.Long_enter.value:
|
||||
self._position = Positions.Long
|
||||
trade_type = "long"
|
||||
trade_type = "enter_long"
|
||||
self._last_trade_tick = self._current_tick
|
||||
elif action == Actions.Short_enter.value:
|
||||
self._position = Positions.Short
|
||||
trade_type = "short"
|
||||
trade_type = "enter_short"
|
||||
self._last_trade_tick = self._current_tick
|
||||
elif action == Actions.Long_exit.value:
|
||||
self._update_total_profit()
|
||||
self._position = Positions.Neutral
|
||||
trade_type = "neutral"
|
||||
trade_type = "exit_long"
|
||||
self._last_trade_tick = None
|
||||
elif action == Actions.Short_exit.value:
|
||||
self._update_total_profit()
|
||||
self._position = Positions.Neutral
|
||||
trade_type = "neutral"
|
||||
trade_type = "exit_short"
|
||||
self._last_trade_tick = None
|
||||
else:
|
||||
print("case not defined")
|
||||
@@ -92,7 +82,7 @@ class Base5ActionRLEnv(BaseEnvironment):
|
||||
if trade_type is not None:
|
||||
self.trade_history.append(
|
||||
{'price': self.current_price(), 'index': self._current_tick,
|
||||
'type': trade_type})
|
||||
'type': trade_type, 'profit': self.get_unrealized_profit()})
|
||||
|
||||
if (self._total_profit < self.max_drawdown or
|
||||
self._total_unrealized_profit < self.max_drawdown):
|
||||
|
@@ -21,7 +21,8 @@ from freqtrade.enums import (ExitCheckTuple, ExitType, RPCMessageType, RunMode,
|
||||
State, TradingMode)
|
||||
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
||||
InvalidOrderException, PricingError)
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
|
||||
from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, timeframe_to_minutes, timeframe_to_next_date,
|
||||
timeframe_to_seconds)
|
||||
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
||||
from freqtrade.mixins import LoggingMixin
|
||||
from freqtrade.persistence import Order, PairLocks, Trade, init_db
|
||||
@@ -853,7 +854,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.info(f"Canceling stoploss on exchange for {trade}")
|
||||
co = self.exchange.cancel_stoploss_order_with_result(
|
||||
trade.stoploss_order_id, trade.pair, trade.amount)
|
||||
trade.update_order(co)
|
||||
self.update_trade_state(trade, trade.stoploss_order_id, co, stoploss_order=True)
|
||||
|
||||
# Reset stoploss order id.
|
||||
trade.stoploss_order_id = None
|
||||
except InvalidOrderException:
|
||||
@@ -945,7 +947,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
return enter_limit_requested, stake_amount, leverage
|
||||
|
||||
def _notify_enter(self, trade: Trade, order: Order, order_type: Optional[str] = None,
|
||||
def _notify_enter(self, trade: Trade, order: Order, order_type: str,
|
||||
fill: bool = False, sub_trade: bool = False) -> None:
|
||||
"""
|
||||
Sends rpc notification when a entry order occurred.
|
||||
@@ -1171,7 +1173,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.warning('Unable to fetch stoploss order: %s', exception)
|
||||
|
||||
if stoploss_order:
|
||||
trade.update_order(stoploss_order)
|
||||
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
||||
stoploss_order=True)
|
||||
|
||||
# We check if stoploss order is fulfilled
|
||||
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
||||
@@ -1235,7 +1238,9 @@ class FreqtradeBot(LoggingMixin):
|
||||
:param order: Current on exchange stoploss order
|
||||
:return: None
|
||||
"""
|
||||
stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stoploss_or_liquidation)
|
||||
stoploss_norm = self.exchange.price_to_precision(
|
||||
trade.pair, trade.stoploss_or_liquidation,
|
||||
rounding_mode=ROUND_DOWN if trade.is_short else ROUND_UP)
|
||||
|
||||
if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side):
|
||||
# we check if the update is necessary
|
||||
@@ -1778,7 +1783,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
return False
|
||||
|
||||
# Update trade with order values
|
||||
logger.info(f'Found open order for {trade}')
|
||||
if not stoploss_order:
|
||||
logger.info(f'Found open order for {trade}')
|
||||
try:
|
||||
order = action_order or self.exchange.fetch_order_or_stoploss_order(order_id,
|
||||
trade.pair,
|
||||
@@ -1847,7 +1853,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.handle_protections(trade.pair, trade.trade_direction)
|
||||
elif send_msg and not trade.open_order_id and not stoploss_order:
|
||||
# Enter fill
|
||||
self._notify_enter(trade, order, fill=True, sub_trade=sub_trade)
|
||||
self._notify_enter(trade, order, order.order_type, fill=True, sub_trade=sub_trade)
|
||||
|
||||
def handle_protections(self, pair: str, side: LongShort) -> None:
|
||||
# Lock pair for one candle to prevent immediate rebuys
|
||||
|
@@ -15,7 +15,8 @@ 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_contract_precision, price_to_precision
|
||||
from freqtrade.exchange import (ROUND_DOWN, ROUND_UP, amount_to_contract_precision,
|
||||
price_to_precision)
|
||||
from freqtrade.leverage import interest
|
||||
from freqtrade.persistence.base import ModelBase, SessionType
|
||||
from freqtrade.util import FtPrecise
|
||||
@@ -597,7 +598,8 @@ class LocalTrade():
|
||||
"""
|
||||
Method used internally to set self.stop_loss.
|
||||
"""
|
||||
stop_loss_norm = price_to_precision(stop_loss, self.price_precision, self.precision_mode)
|
||||
stop_loss_norm = price_to_precision(stop_loss, self.price_precision, self.precision_mode,
|
||||
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP)
|
||||
if not self.stop_loss:
|
||||
self.initial_stop_loss = stop_loss_norm
|
||||
self.stop_loss = stop_loss_norm
|
||||
@@ -628,7 +630,8 @@ class LocalTrade():
|
||||
if self.initial_stop_loss_pct is None or refresh:
|
||||
self.__set_stop_loss(new_loss, stoploss)
|
||||
self.initial_stop_loss = price_to_precision(
|
||||
new_loss, self.price_precision, self.precision_mode)
|
||||
new_loss, self.price_precision, self.precision_mode,
|
||||
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP)
|
||||
self.initial_stop_loss_pct = -1 * abs(stoploss)
|
||||
|
||||
# evaluate if the stop loss needs to be updated
|
||||
@@ -692,21 +695,24 @@ class LocalTrade():
|
||||
else:
|
||||
logger.warning(
|
||||
f'Got different open_order_id {self.open_order_id} != {order.order_id}')
|
||||
|
||||
elif order.ft_order_side == 'stoploss' and order.status not in ('open', ):
|
||||
self.stoploss_order_id = None
|
||||
self.close_rate_requested = self.stop_loss
|
||||
self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||
if self.is_open:
|
||||
logger.info(f'{order.order_type.upper()} is hit for {self}.')
|
||||
else:
|
||||
raise ValueError(f'Unknown order type: {order.order_type}')
|
||||
|
||||
if order.ft_order_side != self.entry_side:
|
||||
amount_tr = amount_to_contract_precision(self.amount, self.amount_precision,
|
||||
self.precision_mode, self.contract_size)
|
||||
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()
|
||||
elif order.ft_order_side == 'stoploss' and order.status not in ('canceled', 'open'):
|
||||
self.stoploss_order_id = None
|
||||
self.close_rate_requested = self.stop_loss
|
||||
self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||
if self.is_open:
|
||||
logger.info(f'{order.order_type.upper()} is hit for {self}.')
|
||||
self.close(order.safe_price)
|
||||
else:
|
||||
raise ValueError(f'Unknown order type: {order.order_type}')
|
||||
|
||||
Trade.commit()
|
||||
|
||||
def close(self, rate: float, *, show_msg: bool = True) -> None:
|
||||
|
@@ -6,6 +6,7 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from freqtrade.constants import Config
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import ROUND_UP
|
||||
from freqtrade.exchange.types import Ticker
|
||||
from freqtrade.plugins.pairlist.IPairList import IPairList
|
||||
|
||||
@@ -61,9 +62,10 @@ class PrecisionFilter(IPairList):
|
||||
stop_price = ticker['last'] * self._stoploss
|
||||
|
||||
# Adjust stop-prices to precision
|
||||
sp = self._exchange.price_to_precision(pair, stop_price)
|
||||
sp = self._exchange.price_to_precision(pair, stop_price, rounding_mode=ROUND_UP)
|
||||
|
||||
stop_gap_price = self._exchange.price_to_precision(pair, stop_price * 0.99)
|
||||
stop_gap_price = self._exchange.price_to_precision(pair, stop_price * 0.99,
|
||||
rounding_mode=ROUND_UP)
|
||||
logger.debug(f"{pair} - {sp} : {stop_gap_price}")
|
||||
|
||||
if sp <= stop_gap_price:
|
||||
|
@@ -52,7 +52,7 @@ class __RPCBuyMsgBase(RPCSendMsgBase):
|
||||
direction: str
|
||||
limit: float
|
||||
open_rate: float
|
||||
order_type: Optional[str] # TODO: why optional??
|
||||
order_type: str
|
||||
stake_amount: float
|
||||
stake_currency: str
|
||||
fiat_currency: Optional[str]
|
||||
|
Reference in New Issue
Block a user