Merge branch 'feature' of github.com:LangLazy/freqtrade into feature
This commit is contained in:
commit
1a74ede126
@ -15,7 +15,7 @@ repos:
|
|||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- types-cachetools==5.3.0.0
|
- types-cachetools==5.3.0.0
|
||||||
- types-filelock==3.2.7
|
- types-filelock==3.2.7
|
||||||
- types-requests==2.28.11.8
|
- types-requests==2.28.11.12
|
||||||
- types-tabulate==0.9.0.0
|
- types-tabulate==0.9.0.0
|
||||||
- types-python-dateutil==2.8.19.6
|
- types-python-dateutil==2.8.19.6
|
||||||
# stages: [push]
|
# stages: [push]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.10.7-slim-bullseye as base
|
FROM python:3.10.10-slim-bullseye as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.9.12-slim-bullseye as base
|
FROM python:3.9.16-slim-bullseye as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
@ -363,7 +363,7 @@ from pathlib import Path
|
|||||||
exchange = ccxt.binance({
|
exchange = ccxt.binance({
|
||||||
'apiKey': '<apikey>',
|
'apiKey': '<apikey>',
|
||||||
'secret': '<secret>'
|
'secret': '<secret>'
|
||||||
'options': {'defaultType': 'future'}
|
'options': {'defaultType': 'swap'}
|
||||||
})
|
})
|
||||||
_ = exchange.load_markets()
|
_ = exchange.load_markets()
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, the FreqAI aims to be a sand-box for easily deploying robust machine-learning libraries on real-time data ([details])(#freqai-position-in-open-source-machine-learning-landscape).
|
FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, FreqAI aims to be a sand-box for easily deploying robust machine-learning libraries on real-time data ([details](#freqai-position-in-open-source-machine-learning-landscape)).
|
||||||
|
|
||||||
Features include:
|
Features include:
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
markdown==3.3.7
|
markdown==3.3.7
|
||||||
mkdocs==1.4.2
|
mkdocs==1.4.2
|
||||||
mkdocs-material==9.0.11
|
mkdocs-material==9.0.12
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==9.9.2
|
pymdown-extensions==9.9.2
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import signal
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
|
||||||
@ -12,15 +13,20 @@ def start_trading(args: Dict[str, Any]) -> int:
|
|||||||
# Import here to avoid loading worker module when it's not used
|
# Import here to avoid loading worker module when it's not used
|
||||||
from freqtrade.worker import Worker
|
from freqtrade.worker import Worker
|
||||||
|
|
||||||
|
def term_handler(signum, frame):
|
||||||
|
# Raise KeyboardInterrupt - so we can handle it in the same way as Ctrl-C
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
|
||||||
# Create and run worker
|
# Create and run worker
|
||||||
worker = None
|
worker = None
|
||||||
try:
|
try:
|
||||||
|
signal.signal(signal.SIGTERM, term_handler)
|
||||||
worker = Worker(args)
|
worker = Worker(args)
|
||||||
worker.run()
|
worker.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
logger.exception("Fatal exception!")
|
logger.exception("Fatal exception!")
|
||||||
except KeyboardInterrupt:
|
except (KeyboardInterrupt):
|
||||||
logger.info('SIGINT received, aborting ...')
|
logger.info('SIGINT received, aborting ...')
|
||||||
finally:
|
finally:
|
||||||
if worker:
|
if worker:
|
||||||
|
@ -681,6 +681,7 @@ EntryExit = Literal['entry', 'exit']
|
|||||||
BuySell = Literal['buy', 'sell']
|
BuySell = Literal['buy', 'sell']
|
||||||
MakerTaker = Literal['maker', 'taker']
|
MakerTaker = Literal['maker', 'taker']
|
||||||
BidAsk = Literal['bid', 'ask']
|
BidAsk = Literal['bid', 'ask']
|
||||||
|
OBLiteral = Literal['asks', 'bids']
|
||||||
|
|
||||||
Config = Dict[str, Any]
|
Config = Dict[str, Any]
|
||||||
IntOrInf = float
|
IntOrInf = float
|
||||||
|
@ -18,6 +18,7 @@ from freqtrade.data.history import load_pair_history
|
|||||||
from freqtrade.enums import CandleType, RPCMessageType, RunMode
|
from freqtrade.enums import CandleType, RPCMessageType, RunMode
|
||||||
from freqtrade.exceptions import ExchangeError, OperationalException
|
from freqtrade.exceptions import ExchangeError, OperationalException
|
||||||
from freqtrade.exchange import Exchange, timeframe_to_seconds
|
from freqtrade.exchange import Exchange, timeframe_to_seconds
|
||||||
|
from freqtrade.exchange.types import OrderBook
|
||||||
from freqtrade.misc import append_candles_to_dataframe
|
from freqtrade.misc import append_candles_to_dataframe
|
||||||
from freqtrade.rpc import RPCManager
|
from freqtrade.rpc import RPCManager
|
||||||
from freqtrade.util import PeriodicCache
|
from freqtrade.util import PeriodicCache
|
||||||
@ -489,7 +490,7 @@ class DataProvider:
|
|||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def orderbook(self, pair: str, maximum: int) -> Dict[str, List]:
|
def orderbook(self, pair: str, maximum: int) -> OrderBook:
|
||||||
"""
|
"""
|
||||||
Fetch latest l2 orderbook data
|
Fetch latest l2 orderbook data
|
||||||
Warning: Does a network request - so use with common sense.
|
Warning: Does a network request - so use with common sense.
|
||||||
|
@ -195,7 +195,7 @@ class Edge:
|
|||||||
|
|
||||||
def stake_amount(self, pair: str, free_capital: float,
|
def stake_amount(self, pair: str, free_capital: float,
|
||||||
total_capital: float, capital_in_trade: float) -> float:
|
total_capital: float, capital_in_trade: float) -> float:
|
||||||
stoploss = self.stoploss(pair)
|
stoploss = self.get_stoploss(pair)
|
||||||
available_capital = (total_capital + capital_in_trade) * self._capital_ratio
|
available_capital = (total_capital + capital_in_trade) * self._capital_ratio
|
||||||
allowed_capital_at_risk = available_capital * self._allowed_risk
|
allowed_capital_at_risk = available_capital * self._allowed_risk
|
||||||
max_position_size = abs(allowed_capital_at_risk / stoploss)
|
max_position_size = abs(allowed_capital_at_risk / stoploss)
|
||||||
@ -214,7 +214,7 @@ class Edge:
|
|||||||
)
|
)
|
||||||
return round(position_size, 15)
|
return round(position_size, 15)
|
||||||
|
|
||||||
def stoploss(self, pair: str) -> float:
|
def get_stoploss(self, pair: str) -> float:
|
||||||
if pair in self._cached_pairs:
|
if pair in self._cached_pairs:
|
||||||
return self._cached_pairs[pair].stoploss
|
return self._cached_pairs[pair].stoploss
|
||||||
else:
|
else:
|
||||||
|
@ -21,7 +21,7 @@ from pandas import DataFrame, concat
|
|||||||
|
|
||||||
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BidAsk,
|
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BidAsk,
|
||||||
BuySell, Config, EntryExit, ListPairsWithTimeframes, MakerTaker,
|
BuySell, Config, EntryExit, ListPairsWithTimeframes, MakerTaker,
|
||||||
PairWithTimeframe)
|
OBLiteral, PairWithTimeframe)
|
||||||
from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list
|
from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list
|
||||||
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode
|
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode
|
||||||
from freqtrade.enums.pricetype import PriceType
|
from freqtrade.enums.pricetype import PriceType
|
||||||
@ -37,7 +37,7 @@ from freqtrade.exchange.exchange_utils import (CcxtModuleType, amount_to_contrac
|
|||||||
price_to_precision, timeframe_to_minutes,
|
price_to_precision, timeframe_to_minutes,
|
||||||
timeframe_to_msecs, timeframe_to_next_date,
|
timeframe_to_msecs, timeframe_to_next_date,
|
||||||
timeframe_to_prev_date, timeframe_to_seconds)
|
timeframe_to_prev_date, timeframe_to_seconds)
|
||||||
from freqtrade.exchange.types import OHLCVResponse, Ticker, Tickers
|
from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers
|
||||||
from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json,
|
from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json,
|
||||||
safe_value_fallback2)
|
safe_value_fallback2)
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
@ -860,10 +860,13 @@ class Exchange:
|
|||||||
dry_order["stopPrice"] = dry_order["price"]
|
dry_order["stopPrice"] = dry_order["price"]
|
||||||
# Workaround to avoid filling stoploss orders immediately
|
# Workaround to avoid filling stoploss orders immediately
|
||||||
dry_order["ft_order_type"] = "stoploss"
|
dry_order["ft_order_type"] = "stoploss"
|
||||||
|
orderbook: Optional[OrderBook] = None
|
||||||
|
if self.exchange_has('fetchL2OrderBook'):
|
||||||
|
orderbook = self.fetch_l2_order_book(pair, 20)
|
||||||
|
|
||||||
if dry_order["type"] == "market" and not dry_order.get("ft_order_type"):
|
if dry_order["type"] == "market" and not dry_order.get("ft_order_type"):
|
||||||
# Update market order pricing
|
# Update market order pricing
|
||||||
average = self.get_dry_market_fill_price(pair, side, amount, rate)
|
average = self.get_dry_market_fill_price(pair, side, amount, rate, orderbook)
|
||||||
dry_order.update({
|
dry_order.update({
|
||||||
'average': average,
|
'average': average,
|
||||||
'filled': _amount,
|
'filled': _amount,
|
||||||
@ -873,7 +876,8 @@ class Exchange:
|
|||||||
# market orders will always incurr taker fees
|
# market orders will always incurr taker fees
|
||||||
dry_order = self.add_dry_order_fee(pair, dry_order, 'taker')
|
dry_order = self.add_dry_order_fee(pair, dry_order, 'taker')
|
||||||
|
|
||||||
dry_order = self.check_dry_limit_order_filled(dry_order, immediate=True)
|
dry_order = self.check_dry_limit_order_filled(
|
||||||
|
dry_order, immediate=True, orderbook=orderbook)
|
||||||
|
|
||||||
self._dry_run_open_orders[dry_order["id"]] = dry_order
|
self._dry_run_open_orders[dry_order["id"]] = dry_order
|
||||||
# Copy order and close it - so the returned order is open unless it's a market order
|
# Copy order and close it - so the returned order is open unless it's a market order
|
||||||
@ -895,20 +899,22 @@ class Exchange:
|
|||||||
})
|
})
|
||||||
return dry_order
|
return dry_order
|
||||||
|
|
||||||
def get_dry_market_fill_price(self, pair: str, side: str, amount: float, rate: float) -> float:
|
def get_dry_market_fill_price(self, pair: str, side: str, amount: float, rate: float,
|
||||||
|
orderbook: Optional[OrderBook]) -> float:
|
||||||
"""
|
"""
|
||||||
Get the market order fill price based on orderbook interpolation
|
Get the market order fill price based on orderbook interpolation
|
||||||
"""
|
"""
|
||||||
if self.exchange_has('fetchL2OrderBook'):
|
if self.exchange_has('fetchL2OrderBook'):
|
||||||
ob = self.fetch_l2_order_book(pair, 20)
|
if not orderbook:
|
||||||
ob_type = 'asks' if side == 'buy' else 'bids'
|
orderbook = self.fetch_l2_order_book(pair, 20)
|
||||||
|
ob_type: OBLiteral = 'asks' if side == 'buy' else 'bids'
|
||||||
slippage = 0.05
|
slippage = 0.05
|
||||||
max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage))
|
max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage))
|
||||||
|
|
||||||
remaining_amount = amount
|
remaining_amount = amount
|
||||||
filled_amount = 0.0
|
filled_amount = 0.0
|
||||||
book_entry_price = 0.0
|
book_entry_price = 0.0
|
||||||
for book_entry in ob[ob_type]:
|
for book_entry in orderbook[ob_type]:
|
||||||
book_entry_price = book_entry[0]
|
book_entry_price = book_entry[0]
|
||||||
book_entry_coin_volume = book_entry[1]
|
book_entry_coin_volume = book_entry[1]
|
||||||
if remaining_amount > 0:
|
if remaining_amount > 0:
|
||||||
@ -936,18 +942,20 @@ class Exchange:
|
|||||||
|
|
||||||
return rate
|
return rate
|
||||||
|
|
||||||
def _is_dry_limit_order_filled(self, pair: str, side: str, limit: float) -> bool:
|
def _is_dry_limit_order_filled(self, pair: str, side: str, limit: float,
|
||||||
|
orderbook: Optional[OrderBook] = None) -> bool:
|
||||||
if not self.exchange_has('fetchL2OrderBook'):
|
if not self.exchange_has('fetchL2OrderBook'):
|
||||||
return True
|
return True
|
||||||
ob = self.fetch_l2_order_book(pair, 1)
|
if not orderbook:
|
||||||
|
orderbook = self.fetch_l2_order_book(pair, 1)
|
||||||
try:
|
try:
|
||||||
if side == 'buy':
|
if side == 'buy':
|
||||||
price = ob['asks'][0][0]
|
price = orderbook['asks'][0][0]
|
||||||
logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}")
|
logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}")
|
||||||
if limit >= price:
|
if limit >= price:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
price = ob['bids'][0][0]
|
price = orderbook['bids'][0][0]
|
||||||
logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}")
|
logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}")
|
||||||
if limit <= price:
|
if limit <= price:
|
||||||
return True
|
return True
|
||||||
@ -957,7 +965,8 @@ class Exchange:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def check_dry_limit_order_filled(
|
def check_dry_limit_order_filled(
|
||||||
self, order: Dict[str, Any], immediate: bool = False) -> Dict[str, Any]:
|
self, order: Dict[str, Any], immediate: bool = False,
|
||||||
|
orderbook: Optional[OrderBook] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Check dry-run limit order fill and update fee (if it filled).
|
Check dry-run limit order fill and update fee (if it filled).
|
||||||
"""
|
"""
|
||||||
@ -965,7 +974,7 @@ class Exchange:
|
|||||||
and order['type'] in ["limit"]
|
and order['type'] in ["limit"]
|
||||||
and not order.get('ft_order_type')):
|
and not order.get('ft_order_type')):
|
||||||
pair = order['symbol']
|
pair = order['symbol']
|
||||||
if self._is_dry_limit_order_filled(pair, order['side'], order['price']):
|
if self._is_dry_limit_order_filled(pair, order['side'], order['price'], orderbook):
|
||||||
order.update({
|
order.update({
|
||||||
'status': 'closed',
|
'status': 'closed',
|
||||||
'filled': order['amount'],
|
'filled': order['amount'],
|
||||||
@ -1131,8 +1140,8 @@ class Exchange:
|
|||||||
return params
|
return params
|
||||||
|
|
||||||
@retrier(retries=0)
|
@retrier(retries=0)
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict,
|
def create_stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict,
|
||||||
side: BuySell, leverage: float) -> Dict:
|
side: BuySell, leverage: float) -> Dict:
|
||||||
"""
|
"""
|
||||||
creates a stoploss order.
|
creates a stoploss order.
|
||||||
requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market
|
requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market
|
||||||
@ -1511,7 +1520,7 @@ class Exchange:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> OrderBook:
|
||||||
"""
|
"""
|
||||||
Get L2 order book from exchange.
|
Get L2 order book from exchange.
|
||||||
Can be limited to a certain amount (if supported).
|
Can be limited to a certain amount (if supported).
|
||||||
@ -1554,7 +1563,7 @@ class Exchange:
|
|||||||
|
|
||||||
def get_rate(self, pair: str, refresh: bool,
|
def get_rate(self, pair: str, refresh: bool,
|
||||||
side: EntryExit, is_short: bool,
|
side: EntryExit, is_short: bool,
|
||||||
order_book: Optional[dict] = None, ticker: Optional[Ticker] = None) -> float:
|
order_book: Optional[OrderBook] = None, ticker: Optional[Ticker] = None) -> float:
|
||||||
"""
|
"""
|
||||||
Calculates bid/ask target
|
Calculates bid/ask target
|
||||||
bid rate - between current ask price and last price
|
bid rate - between current ask price and last price
|
||||||
@ -1592,7 +1601,8 @@ class Exchange:
|
|||||||
logger.debug('order_book %s', order_book)
|
logger.debug('order_book %s', order_book)
|
||||||
# top 1 = index 0
|
# top 1 = index 0
|
||||||
try:
|
try:
|
||||||
rate = order_book[f"{price_side}s"][order_book_top - 1][0]
|
obside: OBLiteral = 'bids' if price_side == 'bid' else 'asks'
|
||||||
|
rate = order_book[obside][order_book_top - 1][0]
|
||||||
except (IndexError, KeyError) as e:
|
except (IndexError, KeyError) as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{pair} - {name} Price at location {order_book_top} from orderbook "
|
f"{pair} - {name} Price at location {order_book_top} from orderbook "
|
||||||
|
@ -97,8 +97,8 @@ class Kraken(Exchange):
|
|||||||
))
|
))
|
||||||
|
|
||||||
@retrier(retries=0)
|
@retrier(retries=0)
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float,
|
def create_stoploss(self, pair: str, amount: float, stop_price: float,
|
||||||
order_types: Dict, side: BuySell, leverage: float) -> Dict:
|
order_types: Dict, side: BuySell, leverage: float) -> Dict:
|
||||||
"""
|
"""
|
||||||
Creates a stoploss market order.
|
Creates a stoploss market order.
|
||||||
Stoploss market orders is the only stoploss type supported by kraken.
|
Stoploss market orders is the only stoploss type supported by kraken.
|
||||||
|
@ -15,6 +15,15 @@ class Ticker(TypedDict):
|
|||||||
# Several more - only listing required.
|
# Several more - only listing required.
|
||||||
|
|
||||||
|
|
||||||
|
class OrderBook(TypedDict):
|
||||||
|
symbol: str
|
||||||
|
bids: List[Tuple[float, float]]
|
||||||
|
asks: List[Tuple[float, float]]
|
||||||
|
timestamp: Optional[int]
|
||||||
|
datetime: Optional[str]
|
||||||
|
nonce: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
Tickers = Dict[str, Ticker]
|
Tickers = Dict[str, Ticker]
|
||||||
|
|
||||||
# pair, timeframe, candleType, OHLCV, drop last?,
|
# pair, timeframe, candleType, OHLCV, drop last?,
|
||||||
|
@ -563,7 +563,13 @@ class IFreqaiModel(ABC):
|
|||||||
:return:
|
:return:
|
||||||
:boolean: whether the model file exists or not.
|
:boolean: whether the model file exists or not.
|
||||||
"""
|
"""
|
||||||
path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model.joblib")
|
if self.dd.model_type == 'joblib':
|
||||||
|
file_type = ".joblib"
|
||||||
|
elif self.dd.model_type == 'keras':
|
||||||
|
file_type = ".h5"
|
||||||
|
elif 'stable_baselines' in self.dd.model_type or 'sb3_contrib' == self.dd.model_type:
|
||||||
|
file_type = ".zip"
|
||||||
|
path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model.{file_type}")
|
||||||
file_exists = path_to_modelfile.is_file()
|
file_exists = path_to_modelfile.is_file()
|
||||||
if file_exists:
|
if file_exists:
|
||||||
logger.info("Found model at %s", dk.data_path / dk.model_filename)
|
logger.info("Found model at %s", dk.data_path / dk.model_filename)
|
||||||
|
@ -1078,7 +1078,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
datetime.now(timezone.utc),
|
datetime.now(timezone.utc),
|
||||||
enter=enter,
|
enter=enter,
|
||||||
exit_=exit_,
|
exit_=exit_,
|
||||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
force_stoploss=self.edge.get_stoploss(trade.pair) if self.edge else 0
|
||||||
)
|
)
|
||||||
for should_exit in exits:
|
for should_exit in exits:
|
||||||
if should_exit.exit_flag:
|
if should_exit.exit_flag:
|
||||||
@ -1098,7 +1098,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
:return: True if the order succeeded, and False in case of problems.
|
:return: True if the order succeeded, and False in case of problems.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
stoploss_order = self.exchange.stoploss(
|
stoploss_order = self.exchange.create_stoploss(
|
||||||
pair=trade.pair,
|
pair=trade.pair,
|
||||||
amount=trade.amount,
|
amount=trade.amount,
|
||||||
stop_price=stop_price,
|
stop_price=stop_price,
|
||||||
@ -1172,7 +1172,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if not stoploss_order:
|
if not stoploss_order:
|
||||||
stop_price = trade.stoploss_or_liquidation
|
stop_price = trade.stoploss_or_liquidation
|
||||||
if self.edge:
|
if self.edge:
|
||||||
stoploss = self.edge.stoploss(pair=trade.pair)
|
stoploss = self.edge.get_stoploss(pair=trade.pair)
|
||||||
stop_price = (
|
stop_price = (
|
||||||
trade.open_rate * (1 - stoploss) if trade.is_short
|
trade.open_rate * (1 - stoploss) if trade.is_short
|
||||||
else trade.open_rate * (1 + stoploss)
|
else trade.open_rate * (1 + stoploss)
|
||||||
|
@ -163,7 +163,7 @@ class HyperStrategyMixin:
|
|||||||
else:
|
else:
|
||||||
logger.info(f'Strategy Parameter(default): {attr_name} = {attr.value}')
|
logger.info(f'Strategy Parameter(default): {attr_name} = {attr.value}')
|
||||||
|
|
||||||
def get_no_optimize_params(self):
|
def get_no_optimize_params(self) -> Dict[str, Dict]:
|
||||||
"""
|
"""
|
||||||
Returns list of Parameters that are not part of the current optimize job
|
Returns list of Parameters that are not part of the current optimize job
|
||||||
"""
|
"""
|
||||||
@ -173,7 +173,7 @@ class HyperStrategyMixin:
|
|||||||
'protection': {},
|
'protection': {},
|
||||||
}
|
}
|
||||||
for name, p in self.enumerate_parameters():
|
for name, p in self.enumerate_parameters():
|
||||||
if not p.optimize or not p.in_space:
|
if p.category and (not p.optimize or not p.in_space):
|
||||||
params[p.category][name] = p.value
|
params[p.category][name] = p.value
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
@ -1086,10 +1086,10 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
|
|
||||||
trade.adjust_min_max_rates(high or current_rate, low or current_rate)
|
trade.adjust_min_max_rates(high or current_rate, low or current_rate)
|
||||||
|
|
||||||
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
|
stoplossflag = self.ft_stoploss_reached(current_rate=current_rate, trade=trade,
|
||||||
current_time=current_time,
|
current_time=current_time,
|
||||||
current_profit=current_profit,
|
current_profit=current_profit,
|
||||||
force_stoploss=force_stoploss, low=low, high=high)
|
force_stoploss=force_stoploss, low=low, high=high)
|
||||||
|
|
||||||
# Set current rate to high for backtesting exits
|
# Set current rate to high for backtesting exits
|
||||||
current_rate = (low if trade.is_short else high) or rate
|
current_rate = (low if trade.is_short else high) or rate
|
||||||
@ -1156,13 +1156,12 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
|
|
||||||
return exits
|
return exits
|
||||||
|
|
||||||
def stop_loss_reached(self, current_rate: float, trade: Trade,
|
def ft_stoploss_adjust(self, current_rate: float, trade: Trade,
|
||||||
current_time: datetime, current_profit: float,
|
current_time: datetime, current_profit: float,
|
||||||
force_stoploss: float, low: Optional[float] = None,
|
force_stoploss: float, low: Optional[float] = None,
|
||||||
high: Optional[float] = None) -> ExitCheckTuple:
|
high: Optional[float] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Based on current profit of the trade and configured (trailing) stoploss,
|
Adjust stop-loss dynamically if configured to do so.
|
||||||
decides to exit or not
|
|
||||||
:param current_profit: current profit as ratio
|
:param current_profit: current profit as ratio
|
||||||
:param low: Low value of this candle, only set in backtesting
|
:param low: Low value of this candle, only set in backtesting
|
||||||
:param high: High value of this candle, only set in backtesting
|
:param high: High value of this candle, only set in backtesting
|
||||||
@ -1208,6 +1207,20 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
|
|
||||||
trade.adjust_stop_loss(bound or current_rate, stop_loss_value)
|
trade.adjust_stop_loss(bound or current_rate, stop_loss_value)
|
||||||
|
|
||||||
|
def ft_stoploss_reached(self, current_rate: float, trade: Trade,
|
||||||
|
current_time: datetime, current_profit: float,
|
||||||
|
force_stoploss: float, low: Optional[float] = None,
|
||||||
|
high: Optional[float] = None) -> ExitCheckTuple:
|
||||||
|
"""
|
||||||
|
Based on current profit of the trade and configured (trailing) stoploss,
|
||||||
|
decides to exit or not
|
||||||
|
:param current_profit: current profit as ratio
|
||||||
|
:param low: Low value of this candle, only set in backtesting
|
||||||
|
:param high: High value of this candle, only set in backtesting
|
||||||
|
"""
|
||||||
|
self.ft_stoploss_adjust(current_rate, trade, current_time, current_profit,
|
||||||
|
force_stoploss, low, high)
|
||||||
|
|
||||||
sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short)
|
sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short)
|
||||||
sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short)
|
sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short)
|
||||||
liq_higher_long = (trade.liquidation_price
|
liq_higher_long = (trade.liquidation_price
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
coveralls==3.3.1
|
coveralls==3.3.1
|
||||||
flake8==6.0.0
|
flake8==6.0.0
|
||||||
flake8-tidy-imports==4.8.0
|
flake8-tidy-imports==4.8.0
|
||||||
mypy==0.991
|
mypy==1.0.0
|
||||||
pre-commit==3.0.4
|
pre-commit==3.0.4
|
||||||
pytest==7.2.1
|
pytest==7.2.1
|
||||||
pytest-asyncio==0.20.3
|
pytest-asyncio==0.20.3
|
||||||
@ -28,6 +28,6 @@ nbconvert==7.2.9
|
|||||||
# mypy types
|
# mypy types
|
||||||
types-cachetools==5.3.0.0
|
types-cachetools==5.3.0.0
|
||||||
types-filelock==3.2.7
|
types-filelock==3.2.7
|
||||||
types-requests==2.28.11.8
|
types-requests==2.28.11.12
|
||||||
types-tabulate==0.9.0.0
|
types-tabulate==0.9.0.0
|
||||||
types-python-dateutil==2.8.19.6
|
types-python-dateutil==2.8.19.6
|
||||||
|
@ -8,4 +8,4 @@ joblib==1.2.0
|
|||||||
catboost==1.1.1; platform_machine != 'aarch64'
|
catboost==1.1.1; platform_machine != 'aarch64'
|
||||||
lightgbm==3.3.5
|
lightgbm==3.3.5
|
||||||
xgboost==1.7.3
|
xgboost==1.7.3
|
||||||
tensorboard==2.11.2
|
tensorboard==2.12.0
|
||||||
|
@ -2,11 +2,11 @@ numpy==1.24.2
|
|||||||
pandas==1.5.3
|
pandas==1.5.3
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==2.7.80
|
ccxt==2.7.93
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==38.0.1; platform_machine == 'armv7l'
|
cryptography==38.0.1; platform_machine == 'armv7l'
|
||||||
cryptography==39.0.1; platform_machine != 'armv7l'
|
cryptography==39.0.1; platform_machine != 'armv7l'
|
||||||
aiohttp==3.8.3
|
aiohttp==3.8.4
|
||||||
SQLAlchemy==1.4.46
|
SQLAlchemy==1.4.46
|
||||||
python-telegram-bot==13.15
|
python-telegram-bot==13.15
|
||||||
arrow==1.2.3
|
arrow==1.2.3
|
||||||
@ -30,17 +30,17 @@ py_find_1st==1.1.5
|
|||||||
# Load ticker files 30% faster
|
# Load ticker files 30% faster
|
||||||
python-rapidjson==1.9
|
python-rapidjson==1.9
|
||||||
# Properly format api responses
|
# Properly format api responses
|
||||||
orjson==3.8.5
|
orjson==3.8.6
|
||||||
|
|
||||||
# Notify systemd
|
# Notify systemd
|
||||||
sdnotify==0.3.2
|
sdnotify==0.3.2
|
||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
fastapi==0.89.1
|
fastapi==0.91.0
|
||||||
pydantic==1.10.4
|
pydantic==1.10.4
|
||||||
uvicorn==0.20.0
|
uvicorn==0.20.0
|
||||||
pyjwt==2.6.0
|
pyjwt==2.6.0
|
||||||
aiofiles==22.1.0
|
aiofiles==23.1.0
|
||||||
psutil==5.9.4
|
psutil==5.9.4
|
||||||
|
|
||||||
# Support for colorized terminal output
|
# Support for colorized terminal output
|
||||||
|
@ -139,7 +139,7 @@ def test_adjust(mocker, edge_conf):
|
|||||||
assert (edge.adjust(pairs) == ['E/F', 'C/D'])
|
assert (edge.adjust(pairs) == ['E/F', 'C/D'])
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss(mocker, edge_conf):
|
def test_edge_get_stoploss(mocker, edge_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
@ -150,10 +150,10 @@ def test_stoploss(mocker, edge_conf):
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
assert edge.stoploss('E/F') == -0.01
|
assert edge.get_stoploss('E/F') == -0.01
|
||||||
|
|
||||||
|
|
||||||
def test_nonexisting_stoploss(mocker, edge_conf):
|
def test_nonexisting_get_stoploss(mocker, edge_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||||
@ -162,7 +162,7 @@ def test_nonexisting_stoploss(mocker, edge_conf):
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
assert edge.stoploss('N/O') == -0.1
|
assert edge.get_stoploss('N/O') == -0.1
|
||||||
|
|
||||||
|
|
||||||
def test_edge_stake_amount(mocker, edge_conf):
|
def test_edge_stake_amount(mocker, edge_conf):
|
||||||
|
@ -20,7 +20,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
|||||||
(0.99, 220 * 1.01, "buy"),
|
(0.99, 220 * 1.01, "buy"),
|
||||||
(0.98, 220 * 1.02, "buy"),
|
(0.98, 220 * 1.02, "buy"),
|
||||||
])
|
])
|
||||||
def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode):
|
def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||||
order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop'
|
order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop'
|
||||||
@ -40,7 +40,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
order = exchange.stoploss(
|
order = exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=190,
|
stop_price=190,
|
||||||
@ -54,7 +54,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
|
|||||||
if limitratio is not None:
|
if limitratio is not None:
|
||||||
order_types.update({'stoploss_on_exchange_limit_ratio': limitratio})
|
order_types.update({'stoploss_on_exchange_limit_ratio': limitratio})
|
||||||
|
|
||||||
order = exchange.stoploss(
|
order = exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
@ -82,7 +82,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
|
|||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
exchange.stoploss(
|
exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
@ -94,7 +94,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
|
|||||||
api_mock.create_order = MagicMock(
|
api_mock.create_order = MagicMock(
|
||||||
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
|
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
exchange.stoploss(
|
exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
@ -104,12 +104,12 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
|
|||||||
)
|
)
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
|
||||||
"stoploss", "create_order", retries=1,
|
"create_stoploss", "create_order", retries=1,
|
||||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
||||||
side=side, leverage=1.0)
|
side=side, leverage=1.0)
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
order_type = 'stop_loss_limit'
|
order_type = 'stop_loss_limit'
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
@ -119,7 +119,7 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
order = exchange.stoploss(
|
order = exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=190,
|
stop_price=190,
|
||||||
@ -130,7 +130,7 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
|||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
|
|
||||||
order = exchange.stoploss(
|
order = exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
|
@ -534,8 +534,7 @@ class TestCCXTExchange():
|
|||||||
|
|
||||||
def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
|
||||||
exc, exchangename = exchange
|
exc, exchangename = exchange
|
||||||
if exchangename in ('binanceus', 'bittrex'):
|
if exchangename in ('bittrex'):
|
||||||
# TODO: reenable binanceus test once downtime "ages out" (2023-02-06)
|
|
||||||
# For some weired reason, this test returns random lengths for bittrex.
|
# For some weired reason, this test returns random lengths for bittrex.
|
||||||
pytest.skip("Exchange doesn't provide stable ohlcv history")
|
pytest.skip("Exchange doesn't provide stable ohlcv history")
|
||||||
|
|
||||||
|
@ -3380,7 +3380,7 @@ def test_get_fee(default_conf, mocker, exchange_name):
|
|||||||
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
|
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
|
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
|
||||||
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
||||||
exchange.stoploss(
|
exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
@ -5318,7 +5318,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
|
|||||||
exchange.get_contract_size = MagicMock(return_value=contract_size)
|
exchange.get_contract_size = MagicMock(return_value=contract_size)
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
order = exchange.stoploss(
|
order = exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=100,
|
amount=100,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
|
@ -14,7 +14,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
|||||||
(0.99, 220 * 0.99, "sell"),
|
(0.99, 220 * 0.99, "sell"),
|
||||||
(0.98, 220 * 0.98, "sell"),
|
(0.98, 220 * 0.98, "sell"),
|
||||||
])
|
])
|
||||||
def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
|
def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||||
order_type = 'stop-limit'
|
order_type = 'stop-limit'
|
||||||
@ -32,15 +32,15 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
||||||
side=side,
|
side=side,
|
||||||
leverage=1.0)
|
leverage=1.0)
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
|
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types,
|
order = exchange.create_stoploss(
|
||||||
side=side, leverage=1.0)
|
pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types, side=side, leverage=1.0)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -59,23 +59,23 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
|
|||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
order_types={}, side=side, leverage=1.0)
|
order_types={}, side=side, leverage=1.0)
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.create_order = MagicMock(
|
api_mock.create_order = MagicMock(
|
||||||
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
|
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
order_types={}, side=side, leverage=1.0)
|
order_types={}, side=side, leverage=1.0)
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi",
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi",
|
||||||
"stoploss", "create_order", retries=1,
|
"create_stoploss", "create_order", retries=1,
|
||||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
||||||
side=side, leverage=1.0)
|
side=side, leverage=1.0)
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_order_dry_run_huobi(default_conf, mocker):
|
def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
order_type = 'stop-limit'
|
order_type = 'stop-limit'
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
@ -85,14 +85,14 @@ def test_stoploss_order_dry_run_huobi(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
|
||||||
side='sell', leverage=1.0)
|
side='sell', leverage=1.0)
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
|
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
order_types={}, side='sell', leverage=1.0)
|
order_types={}, side='sell', leverage=1.0)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
|
@ -179,7 +179,7 @@ def test_get_balances_prod(default_conf, mocker):
|
|||||||
("sell", 217.8),
|
("sell", 217.8),
|
||||||
("buy", 222.2),
|
("buy", 222.2),
|
||||||
])
|
])
|
||||||
def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice):
|
def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
|
|||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||||
|
|
||||||
order = exchange.stoploss(
|
order = exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
@ -230,7 +230,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
|
|||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||||
exchange.stoploss(
|
exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
@ -243,7 +243,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
|
|||||||
api_mock.create_order = MagicMock(
|
api_mock.create_order = MagicMock(
|
||||||
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
|
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||||
exchange.stoploss(
|
exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
@ -253,13 +253,13 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
|
|||||||
)
|
)
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
|
||||||
"stoploss", "create_order", retries=1,
|
"create_stoploss", "create_order", retries=1,
|
||||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
||||||
side=side, leverage=1.0)
|
side=side, leverage=1.0)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('side', ['buy', 'sell'])
|
@pytest.mark.parametrize('side', ['buy', 'sell'])
|
||||||
def test_stoploss_order_dry_run_kraken(default_conf, mocker, side):
|
def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
|
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
|
||||||
@ -269,7 +269,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker, side):
|
|||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
|
|
||||||
order = exchange.stoploss(
|
order = exchange.create_stoploss(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
amount=1,
|
amount=1,
|
||||||
stop_price=220,
|
stop_price=220,
|
||||||
|
@ -15,7 +15,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
|||||||
(0.99, 220 * 0.99, "sell"),
|
(0.99, 220 * 0.99, "sell"),
|
||||||
(0.98, 220 * 0.98, "sell"),
|
(0.98, 220 * 0.98, "sell"),
|
||||||
])
|
])
|
||||||
def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type):
|
def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||||
|
|
||||||
@ -32,18 +32,18 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side,
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
||||||
if order_type == 'limit':
|
if order_type == 'limit':
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||||
order_types={
|
order_types={
|
||||||
'stoploss': order_type,
|
'stoploss': order_type,
|
||||||
'stoploss_on_exchange_limit_ratio': 1.05},
|
'stoploss_on_exchange_limit_ratio': 1.05},
|
||||||
side=side, leverage=1.0)
|
side=side, leverage=1.0)
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
order_types = {'stoploss': order_type}
|
order_types = {'stoploss': order_type}
|
||||||
if limitratio is not None:
|
if limitratio is not None:
|
||||||
order_types.update({'stoploss_on_exchange_limit_ratio': limitratio})
|
order_types.update({'stoploss_on_exchange_limit_ratio': limitratio})
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
order_types=order_types, side=side, leverage=1.0)
|
order_types=order_types, side=side, leverage=1.0)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -67,18 +67,18 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side,
|
|||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
order_types={}, side=side, leverage=1.0)
|
order_types={}, side=side, leverage=1.0)
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.create_order = MagicMock(
|
api_mock.create_order = MagicMock(
|
||||||
side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately."))
|
side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately."))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
order_types={}, side=side, leverage=1.0)
|
order_types={}, side=side, leverage=1.0)
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin",
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin",
|
||||||
"stoploss", "create_order", retries=1,
|
"create_stoploss", "create_order", retries=1,
|
||||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={},
|
||||||
side=side, leverage=1.0)
|
side=side, leverage=1.0)
|
||||||
|
|
||||||
@ -93,15 +93,15 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
||||||
order_types={'stoploss': 'limit',
|
order_types={'stoploss': 'limit',
|
||||||
'stoploss_on_exchange_limit_ratio': 1.05},
|
'stoploss_on_exchange_limit_ratio': 1.05},
|
||||||
side='sell', leverage=1.0)
|
side='sell', leverage=1.0)
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
|
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
order_types={}, side='sell', leverage=1.0)
|
order_types={}, side='sell', leverage=1.0)
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
|
@ -452,8 +452,8 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
|||||||
(0.05, 0.9, ExitType.NONE, None, False, True, 0.09, 0.9, ExitType.NONE,
|
(0.05, 0.9, ExitType.NONE, None, False, True, 0.09, 0.9, ExitType.NONE,
|
||||||
lambda **kwargs: None),
|
lambda **kwargs: None),
|
||||||
])
|
])
|
||||||
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom,
|
def test_ft_stoploss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom,
|
||||||
profit2, adjusted2, expected2, custom_stop) -> None:
|
profit2, adjusted2, expected2, custom_stop) -> None:
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -477,9 +477,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, t
|
|||||||
|
|
||||||
now = arrow.utcnow().datetime
|
now = arrow.utcnow().datetime
|
||||||
current_rate = trade.open_rate * (1 + profit)
|
current_rate = trade.open_rate * (1 + profit)
|
||||||
sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade,
|
sl_flag = strategy.ft_stoploss_reached(current_rate=current_rate, trade=trade,
|
||||||
current_time=now, current_profit=profit,
|
current_time=now, current_profit=profit,
|
||||||
force_stoploss=0, high=None)
|
force_stoploss=0, high=None)
|
||||||
assert isinstance(sl_flag, ExitCheckTuple)
|
assert isinstance(sl_flag, ExitCheckTuple)
|
||||||
assert sl_flag.exit_type == expected
|
assert sl_flag.exit_type == expected
|
||||||
if expected == ExitType.NONE:
|
if expected == ExitType.NONE:
|
||||||
@ -489,9 +489,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, t
|
|||||||
assert round(trade.stop_loss, 2) == adjusted
|
assert round(trade.stop_loss, 2) == adjusted
|
||||||
current_rate2 = trade.open_rate * (1 + profit2)
|
current_rate2 = trade.open_rate * (1 + profit2)
|
||||||
|
|
||||||
sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade,
|
sl_flag = strategy.ft_stoploss_reached(current_rate=current_rate2, trade=trade,
|
||||||
current_time=now, current_profit=profit2,
|
current_time=now, current_profit=profit2,
|
||||||
force_stoploss=0, high=None)
|
force_stoploss=0, high=None)
|
||||||
assert sl_flag.exit_type == expected2
|
assert sl_flag.exit_type == expected2
|
||||||
if expected2 == ExitType.NONE:
|
if expected2 == ExitType.NONE:
|
||||||
assert sl_flag.exit_flag is False
|
assert sl_flag.exit_flag is False
|
||||||
@ -579,7 +579,7 @@ def test_should_sell(default_conf, fee) -> None:
|
|||||||
assert res == [ExitCheckTuple(exit_type=ExitType.ROI)]
|
assert res == [ExitCheckTuple(exit_type=ExitType.ROI)]
|
||||||
|
|
||||||
strategy.min_roi_reached = MagicMock(return_value=True)
|
strategy.min_roi_reached = MagicMock(return_value=True)
|
||||||
strategy.stop_loss_reached = MagicMock(
|
strategy.ft_stoploss_reached = MagicMock(
|
||||||
return_value=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
|
return_value=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
|
||||||
|
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
@ -603,7 +603,7 @@ def test_should_sell(default_conf, fee) -> None:
|
|||||||
ExitCheckTuple(exit_type=ExitType.ROI),
|
ExitCheckTuple(exit_type=ExitType.ROI),
|
||||||
]
|
]
|
||||||
|
|
||||||
strategy.stop_loss_reached = MagicMock(
|
strategy.ft_stoploss_reached = MagicMock(
|
||||||
return_value=ExitCheckTuple(exit_type=ExitType.TRAILING_STOP_LOSS))
|
return_value=ExitCheckTuple(exit_type=ExitType.TRAILING_STOP_LOSS))
|
||||||
# Regular exit signal
|
# Regular exit signal
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
|
@ -1070,7 +1070,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||||
|
|
||||||
stoploss = MagicMock(return_value={'id': 13434334})
|
stoploss = MagicMock(return_value={'id': 13434334})
|
||||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss)
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||||
@ -1109,7 +1109,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
|||||||
exit_order,
|
exit_order,
|
||||||
]),
|
]),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
stoploss=stoploss
|
create_stoploss=stoploss
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||||
@ -1191,7 +1191,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
|||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.exchange.Exchange.stoploss',
|
'freqtrade.exchange.Exchange.create_stoploss',
|
||||||
side_effect=ExchangeError()
|
side_effect=ExchangeError()
|
||||||
)
|
)
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
@ -1205,7 +1205,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
|||||||
stoploss.reset_mock()
|
stoploss.reset_mock()
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
|
||||||
side_effect=InvalidOrderException())
|
side_effect=InvalidOrderException())
|
||||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss)
|
||||||
freqtrade.handle_stoploss_on_exchange(trade)
|
freqtrade.handle_stoploss_on_exchange(trade)
|
||||||
assert stoploss.call_count == 1
|
assert stoploss.call_count == 1
|
||||||
|
|
||||||
@ -1215,7 +1215,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
|||||||
trade.is_open = False
|
trade.is_open = False
|
||||||
stoploss.reset_mock()
|
stoploss.reset_mock()
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order')
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss)
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
assert stoploss.call_count == 0
|
assert stoploss.call_count == 0
|
||||||
|
|
||||||
@ -1240,7 +1240,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result',
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result',
|
||||||
side_effect=InvalidOrderException())
|
side_effect=InvalidOrderException())
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss)
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
@ -1271,7 +1271,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Binance',
|
'freqtrade.exchange.Binance',
|
||||||
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}),
|
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}),
|
||||||
stoploss=MagicMock(side_effect=ExchangeError()),
|
create_stoploss=MagicMock(side_effect=ExchangeError()),
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||||
@ -1315,7 +1315,7 @@ def test_create_stoploss_order_invalid_order(
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Binance',
|
'freqtrade.exchange.Binance',
|
||||||
fetch_order=MagicMock(return_value={'status': 'canceled'}),
|
fetch_order=MagicMock(return_value={'status': 'canceled'}),
|
||||||
stoploss=MagicMock(side_effect=InvalidOrderException()),
|
create_stoploss=MagicMock(side_effect=InvalidOrderException()),
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||||
@ -1367,7 +1367,7 @@ def test_create_stoploss_order_insufficient_funds(
|
|||||||
)
|
)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Binance',
|
'freqtrade.exchange.Binance',
|
||||||
stoploss=MagicMock(side_effect=InsufficientFundsError()),
|
create_stoploss=MagicMock(side_effect=InsufficientFundsError()),
|
||||||
)
|
)
|
||||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||||
@ -1417,7 +1417,7 @@ def test_handle_stoploss_on_exchange_trailing(
|
|||||||
)
|
)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Binance',
|
'freqtrade.exchange.Binance',
|
||||||
stoploss=stoploss,
|
create_stoploss=stoploss,
|
||||||
stoploss_adjust=MagicMock(return_value=True),
|
stoploss_adjust=MagicMock(return_value=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1478,7 +1478,7 @@ def test_handle_stoploss_on_exchange_trailing(
|
|||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
|
stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
|
||||||
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
|
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
|
||||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
|
mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock)
|
||||||
|
|
||||||
# stoploss should not be updated as the interval is 60 seconds
|
# stoploss should not be updated as the interval is 60 seconds
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
@ -1542,7 +1542,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
|
|||||||
)
|
)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Binance',
|
'freqtrade.exchange.Binance',
|
||||||
stoploss=stoploss,
|
create_stoploss=stoploss,
|
||||||
stoploss_adjust=MagicMock(return_value=True),
|
stoploss_adjust=MagicMock(return_value=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1593,7 +1593,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
|
|||||||
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime
|
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
|
cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
|
||||||
mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError())
|
mocker.patch("freqtrade.exchange.Binance.create_stoploss", side_effect=ExchangeError())
|
||||||
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
||||||
assert cancel_mock.call_count == 1
|
assert cancel_mock.call_count == 1
|
||||||
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog)
|
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog)
|
||||||
@ -1611,7 +1611,7 @@ def test_stoploss_on_exchange_price_rounding(
|
|||||||
adjust_mock = MagicMock(return_value=False)
|
adjust_mock = MagicMock(return_value=False)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Binance',
|
'freqtrade.exchange.Binance',
|
||||||
stoploss=stoploss_mock,
|
create_stoploss=stoploss_mock,
|
||||||
stoploss_adjust=adjust_mock,
|
stoploss_adjust=adjust_mock,
|
||||||
price_to_precision=price_mock,
|
price_to_precision=price_mock,
|
||||||
)
|
)
|
||||||
@ -1650,7 +1650,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
|||||||
)
|
)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Binance',
|
'freqtrade.exchange.Binance',
|
||||||
stoploss=stoploss,
|
create_stoploss=stoploss,
|
||||||
stoploss_adjust=MagicMock(return_value=True),
|
stoploss_adjust=MagicMock(return_value=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1710,7 +1710,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
|||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
|
stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
|
||||||
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
|
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
|
||||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
|
mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock)
|
||||||
|
|
||||||
# stoploss should not be updated as the interval is 60 seconds
|
# stoploss should not be updated as the interval is 60 seconds
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
@ -1775,7 +1775,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
|
|||||||
{'id': exit_order['id']},
|
{'id': exit_order['id']},
|
||||||
]),
|
]),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
stoploss=stoploss,
|
create_stoploss=stoploss,
|
||||||
)
|
)
|
||||||
|
|
||||||
# enabling TSL
|
# enabling TSL
|
||||||
@ -1827,7 +1827,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
|
|||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
stoploss_order_mock = MagicMock()
|
stoploss_order_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
|
||||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
|
mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock)
|
||||||
|
|
||||||
# price goes down 5%
|
# price goes down 5%
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
|
||||||
@ -3607,7 +3607,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
|
|||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
amount_to_precision=lambda s, x, y: y,
|
amount_to_precision=lambda s, x, y: y,
|
||||||
price_to_precision=lambda s, x, y: y,
|
price_to_precision=lambda s, x, y: y,
|
||||||
stoploss=stoploss,
|
create_stoploss=stoploss,
|
||||||
cancel_stoploss_order=cancel_order,
|
cancel_stoploss_order=cancel_order,
|
||||||
_is_dry_limit_order_filled=MagicMock(side_effect=[True, False]),
|
_is_dry_limit_order_filled=MagicMock(side_effect=[True, False]),
|
||||||
)
|
)
|
||||||
@ -3668,7 +3668,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss)
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||||
@ -3902,7 +3902,7 @@ def test_exit_profit_only(
|
|||||||
if exit_type == ExitType.EXIT_SIGNAL.value:
|
if exit_type == ExitType.EXIT_SIGNAL.value:
|
||||||
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
|
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
|
||||||
else:
|
else:
|
||||||
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=ExitCheckTuple(
|
freqtrade.strategy.ft_stoploss_reached = MagicMock(return_value=ExitCheckTuple(
|
||||||
exit_type=ExitType.NONE))
|
exit_type=ExitType.NONE))
|
||||||
freqtrade.enter_positions()
|
freqtrade.enter_positions()
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
|
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
|
||||||
)
|
)
|
||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
|
Loading…
Reference in New Issue
Block a user