mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 18:23:55 +00:00
Merge branch 'freqtrade:develop' into develop
This commit is contained in:
commit
759a350d73
Binary file not shown.
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.21-cp38-cp38-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.21-cp38-cp38-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.21-cp39-cp39-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.21-cp39-cp39-win_amd64.whl
Normal file
Binary file not shown.
|
@ -6,10 +6,13 @@ python -m pip install --upgrade pip
|
||||||
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||||
|
|
||||||
if ($pyv -eq '3.7') {
|
if ($pyv -eq '3.7') {
|
||||||
pip install build_helpers\TA_Lib-0.4.20-cp37-cp37m-win_amd64.whl
|
pip install build_helpers\TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl
|
||||||
}
|
}
|
||||||
if ($pyv -eq '3.8') {
|
if ($pyv -eq '3.8') {
|
||||||
pip install build_helpers\TA_Lib-0.4.20-cp38-cp38-win_amd64.whl
|
pip install build_helpers\TA_Lib-0.4.21-cp38-cp38-win_amd64.whl
|
||||||
|
}
|
||||||
|
if ($pyv -eq '3.9') {
|
||||||
|
pip install build_helpers\TA_Lib-0.4.21-cp39-cp39-win_amd64.whl
|
||||||
}
|
}
|
||||||
|
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
|
|
|
@ -42,7 +42,7 @@ docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${TAG_ARM
|
||||||
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
||||||
|
|
||||||
# Run backtest
|
# Run backtest
|
||||||
docker run --rm -v $(pwd)/config_bittrex.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy
|
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "failed running backtest"
|
echo "failed running backtest"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mkdocs==1.2.1
|
mkdocs==1.2.2
|
||||||
mkdocs-material==7.1.10
|
mkdocs-material==7.1.11
|
||||||
mdx_truly_sane_lists==1.2
|
mdx_truly_sane_lists==1.2
|
||||||
pymdown-extensions==8.2
|
pymdown-extensions==8.2
|
||||||
|
|
|
@ -23,7 +23,7 @@ git clone https://github.com/freqtrade/freqtrade.git
|
||||||
|
|
||||||
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
|
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
|
||||||
|
|
||||||
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib‑0.4.20‑cp38‑cp38‑win_amd64.whl` (make sure to use the version matching your python version).
|
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.21-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version).
|
||||||
|
|
||||||
Freqtrade provides these dependencies for the latest 2 Python versions (3.7 and 3.8) and for 64bit Windows.
|
Freqtrade provides these dependencies for the latest 2 Python versions (3.7 and 3.8) and for 64bit Windows.
|
||||||
Other versions must be downloaded from the above link.
|
Other versions must be downloaded from the above link.
|
||||||
|
|
|
@ -551,7 +551,7 @@ class Exchange:
|
||||||
amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent',
|
amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent',
|
||||||
DEFAULT_AMOUNT_RESERVE_PERCENT)
|
DEFAULT_AMOUNT_RESERVE_PERCENT)
|
||||||
amount_reserve_percent = (
|
amount_reserve_percent = (
|
||||||
amount_reserve_percent / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
|
amount_reserve_percent / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
|
||||||
)
|
)
|
||||||
# it should not be more than 50%
|
# it should not be more than 50%
|
||||||
amount_reserve_percent = max(min(amount_reserve_percent, 1.5), 1)
|
amount_reserve_percent = max(min(amount_reserve_percent, 1.5), 1)
|
||||||
|
@ -999,94 +999,64 @@ class Exchange:
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
def get_buy_rate(self, pair: str, refresh: bool) -> float:
|
def get_rate(self, pair: str, refresh: bool, side: str) -> float:
|
||||||
"""
|
"""
|
||||||
Calculates bid target between current ask price and last price
|
Calculates bid/ask target
|
||||||
|
bid rate - between current ask price and last price
|
||||||
|
ask rate - either using ticker bid or first bid based on orderbook
|
||||||
|
or remain static in any other case since it's not updating.
|
||||||
:param pair: Pair to get rate for
|
:param pair: Pair to get rate for
|
||||||
:param refresh: allow cached data
|
:param refresh: allow cached data
|
||||||
|
:param side: "buy" or "sell"
|
||||||
:return: float: Price
|
:return: float: Price
|
||||||
:raises PricingError if orderbook price could not be determined.
|
:raises PricingError if orderbook price could not be determined.
|
||||||
"""
|
"""
|
||||||
|
cache_rate: TTLCache = self._buy_rate_cache if side == "buy" else self._sell_rate_cache
|
||||||
|
[strat_name, name] = ['bid_strategy', 'Buy'] if side == "buy" else ['ask_strategy', 'Sell']
|
||||||
|
|
||||||
if not refresh:
|
if not refresh:
|
||||||
rate = self._buy_rate_cache.get(pair)
|
rate = cache_rate.get(pair)
|
||||||
# Check if cache has been invalidated
|
# Check if cache has been invalidated
|
||||||
if rate:
|
if rate:
|
||||||
logger.debug(f"Using cached buy rate for {pair}.")
|
logger.debug(f"Using cached {side} rate for {pair}.")
|
||||||
return rate
|
return rate
|
||||||
|
|
||||||
bid_strategy = self._config.get('bid_strategy', {})
|
conf_strategy = self._config.get(strat_name, {})
|
||||||
if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False):
|
|
||||||
|
|
||||||
order_book_top = bid_strategy.get('order_book_top', 1)
|
if conf_strategy.get('use_order_book', False) and ('use_order_book' in conf_strategy):
|
||||||
|
|
||||||
|
order_book_top = conf_strategy.get('order_book_top', 1)
|
||||||
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
||||||
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_from_l2 = order_book[f"{bid_strategy['price_side']}s"][order_book_top - 1][0]
|
rate = order_book[f"{conf_strategy['price_side']}s"][order_book_top - 1][0]
|
||||||
except (IndexError, KeyError) as e:
|
except (IndexError, KeyError) as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Buy Price from orderbook could not be determined."
|
f"{name} Price at location {order_book_top} from orderbook could not be "
|
||||||
f"Orderbook: {order_book}"
|
|
||||||
)
|
|
||||||
raise PricingError from e
|
|
||||||
logger.info(f"Buy price from orderbook {bid_strategy['price_side'].capitalize()} side "
|
|
||||||
f"- top {order_book_top} order book buy rate {rate_from_l2:.8f}")
|
|
||||||
used_rate = rate_from_l2
|
|
||||||
else:
|
|
||||||
logger.info(f"Using Last {bid_strategy['price_side'].capitalize()} / Last Price")
|
|
||||||
ticker = self.fetch_ticker(pair)
|
|
||||||
ticker_rate = ticker[bid_strategy['price_side']]
|
|
||||||
if ticker['last'] and ticker_rate > ticker['last']:
|
|
||||||
balance = bid_strategy['ask_last_balance']
|
|
||||||
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
|
||||||
used_rate = ticker_rate
|
|
||||||
|
|
||||||
self._buy_rate_cache[pair] = used_rate
|
|
||||||
|
|
||||||
return used_rate
|
|
||||||
|
|
||||||
def get_sell_rate(self, pair: str, refresh: bool) -> float:
|
|
||||||
"""
|
|
||||||
Get sell rate - either using ticker bid or first bid based on orderbook
|
|
||||||
or remain static in any other case since it's not updating.
|
|
||||||
:param pair: Pair to get rate for
|
|
||||||
:param refresh: allow cached data
|
|
||||||
:return: Bid rate
|
|
||||||
:raises PricingError if price could not be determined.
|
|
||||||
"""
|
|
||||||
if not refresh:
|
|
||||||
rate = self._sell_rate_cache.get(pair)
|
|
||||||
# Check if cache has been invalidated
|
|
||||||
if rate:
|
|
||||||
logger.debug(f"Using cached sell rate for {pair}.")
|
|
||||||
return rate
|
|
||||||
|
|
||||||
ask_strategy = self._config.get('ask_strategy', {})
|
|
||||||
if ask_strategy.get('use_order_book', False):
|
|
||||||
logger.debug(
|
|
||||||
f"Getting price from order book {ask_strategy['price_side'].capitalize()} side."
|
|
||||||
)
|
|
||||||
order_book_top = ask_strategy.get('order_book_top', 1)
|
|
||||||
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
|
||||||
try:
|
|
||||||
rate = order_book[f"{ask_strategy['price_side']}s"][order_book_top - 1][0]
|
|
||||||
except (IndexError, KeyError) as e:
|
|
||||||
logger.warning(
|
|
||||||
f"Sell Price at location {order_book_top} from orderbook could not be "
|
|
||||||
f"determined. Orderbook: {order_book}"
|
f"determined. Orderbook: {order_book}"
|
||||||
)
|
)
|
||||||
raise PricingError from e
|
raise PricingError from e
|
||||||
|
|
||||||
|
logger.info(f"{name} price from orderbook {conf_strategy['price_side'].capitalize()}"
|
||||||
|
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
|
||||||
else:
|
else:
|
||||||
|
logger.info(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price")
|
||||||
ticker = self.fetch_ticker(pair)
|
ticker = self.fetch_ticker(pair)
|
||||||
ticker_rate = ticker[ask_strategy['price_side']]
|
ticker_rate = ticker[conf_strategy['price_side']]
|
||||||
if ticker['last'] and ticker_rate < ticker['last']:
|
if ticker['last']:
|
||||||
balance = ask_strategy.get('bid_last_balance', 0.0)
|
if side == 'buy' and ticker_rate > ticker['last']:
|
||||||
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
|
balance = conf_strategy['ask_last_balance']
|
||||||
|
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
||||||
|
elif side == 'sell' and ticker_rate < ticker['last']:
|
||||||
|
balance = conf_strategy.get('bid_last_balance', 0.0)
|
||||||
|
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
|
||||||
rate = ticker_rate
|
rate = ticker_rate
|
||||||
|
|
||||||
if rate is None:
|
if rate is None:
|
||||||
raise PricingError(f"Sell-Rate for {pair} was empty.")
|
raise PricingError(f"{name}-Rate for {pair} was empty.")
|
||||||
self._sell_rate_cache[pair] = rate
|
cache_rate[pair] = rate
|
||||||
|
|
||||||
return rate
|
return rate
|
||||||
|
|
||||||
# Fee handling
|
# Fee handling
|
||||||
|
@ -1318,8 +1288,8 @@ class Exchange:
|
||||||
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
|
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
|
||||||
# keeping parsed dataframe in cache
|
# keeping parsed dataframe in cache
|
||||||
ohlcv_df = ohlcv_to_dataframe(
|
ohlcv_df = ohlcv_to_dataframe(
|
||||||
ticks, timeframe, pair=pair, fill_missing=True,
|
ticks, timeframe, pair=pair, fill_missing=True,
|
||||||
drop_incomplete=self._ohlcv_partial_candle)
|
drop_incomplete=self._ohlcv_partial_candle)
|
||||||
results_df[(pair, timeframe)] = ohlcv_df
|
results_df[(pair, timeframe)] = ohlcv_df
|
||||||
if cache:
|
if cache:
|
||||||
self._klines[(pair, timeframe)] = ohlcv_df
|
self._klines[(pair, timeframe)] = ohlcv_df
|
||||||
|
|
|
@ -475,7 +475,7 @@ class FreqtradeBot(LoggingMixin):
|
||||||
buy_limit_requested = price
|
buy_limit_requested = price
|
||||||
else:
|
else:
|
||||||
# Calculate price
|
# Calculate price
|
||||||
buy_limit_requested = self.exchange.get_buy_rate(pair, True)
|
buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy")
|
||||||
|
|
||||||
if not buy_limit_requested:
|
if not buy_limit_requested:
|
||||||
raise PricingError('Could not determine buy price.')
|
raise PricingError('Could not determine buy price.')
|
||||||
|
@ -609,7 +609,7 @@ class FreqtradeBot(LoggingMixin):
|
||||||
"""
|
"""
|
||||||
Sends rpc notification when a buy cancel occurred.
|
Sends rpc notification when a buy cancel occurred.
|
||||||
"""
|
"""
|
||||||
current_rate = self.exchange.get_buy_rate(trade.pair, False)
|
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy")
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
|
@ -695,7 +695,7 @@ class FreqtradeBot(LoggingMixin):
|
||||||
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df)
|
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df)
|
||||||
|
|
||||||
logger.debug('checking sell')
|
logger.debug('checking sell')
|
||||||
sell_rate = self.exchange.get_sell_rate(trade.pair, True)
|
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
|
||||||
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
|
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -1132,7 +1132,8 @@ class FreqtradeBot(LoggingMixin):
|
||||||
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||||
# Use cached rates here - it was updated seconds ago.
|
# Use cached rates here - it was updated seconds ago.
|
||||||
current_rate = self.exchange.get_sell_rate(trade.pair, False) if not fill else None
|
current_rate = self.exchange.get_rate(
|
||||||
|
trade.pair, refresh=False, side="sell") if not fill else None
|
||||||
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
|
@ -1177,7 +1178,7 @@ class FreqtradeBot(LoggingMixin):
|
||||||
|
|
||||||
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||||
current_rate = self.exchange.get_sell_rate(trade.pair, False)
|
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="sell")
|
||||||
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
|
|
|
@ -154,7 +154,8 @@ class RPC:
|
||||||
# calculate profit and send message to user
|
# calculate profit and send message to user
|
||||||
if trade.is_open:
|
if trade.is_open:
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_sell_rate(trade.pair, False)
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
|
trade.pair, refresh=False, side="sell")
|
||||||
except (ExchangeError, PricingError):
|
except (ExchangeError, PricingError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
else:
|
else:
|
||||||
|
@ -213,7 +214,8 @@ class RPC:
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
# calculate profit and send message to user
|
# calculate profit and send message to user
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_sell_rate(trade.pair, False)
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
|
trade.pair, refresh=False, side="sell")
|
||||||
except (PricingError, ExchangeError):
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
trade_percent = (100 * trade.calc_profit_ratio(current_rate))
|
trade_percent = (100 * trade.calc_profit_ratio(current_rate))
|
||||||
|
@ -272,10 +274,10 @@ class RPC:
|
||||||
'date': key,
|
'date': key,
|
||||||
'abs_profit': value["amount"],
|
'abs_profit': value["amount"],
|
||||||
'fiat_value': self._fiat_converter.convert_amount(
|
'fiat_value': self._fiat_converter.convert_amount(
|
||||||
value['amount'],
|
value['amount'],
|
||||||
stake_currency,
|
stake_currency,
|
||||||
fiat_display_currency
|
fiat_display_currency
|
||||||
) if self._fiat_converter else 0,
|
) if self._fiat_converter else 0,
|
||||||
'trade_count': value["trades"],
|
'trade_count': value["trades"],
|
||||||
}
|
}
|
||||||
for key, value in profit_days.items()
|
for key, value in profit_days.items()
|
||||||
|
@ -372,7 +374,8 @@ class RPC:
|
||||||
else:
|
else:
|
||||||
# Get current rate
|
# Get current rate
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.exchange.get_sell_rate(trade.pair, False)
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
|
trade.pair, refresh=False, side="sell")
|
||||||
except (PricingError, ExchangeError):
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
||||||
|
@ -551,7 +554,8 @@ class RPC:
|
||||||
|
|
||||||
if not fully_canceled:
|
if not fully_canceled:
|
||||||
# Get current rate and execute sell
|
# Get current rate and execute sell
|
||||||
current_rate = self._freqtrade.exchange.get_sell_rate(trade.pair, False)
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
|
trade.pair, refresh=False, side="sell")
|
||||||
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
||||||
self._freqtrade.execute_sell(trade, current_rate, sell_reason)
|
self._freqtrade.execute_sell(trade, current_rate, sell_reason)
|
||||||
# ---- EOF def _exec_forcesell ----
|
# ---- EOF def _exec_forcesell ----
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
numpy==1.21.0
|
numpy==1.21.1
|
||||||
pandas==1.3.0
|
pandas==1.3.0
|
||||||
|
|
||||||
ccxt==1.52.83
|
ccxt==1.53.25
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==3.4.7
|
cryptography==3.4.7
|
||||||
aiohttp==3.7.4.post0
|
aiohttp==3.7.4.post0
|
||||||
SQLAlchemy==1.4.20
|
SQLAlchemy==1.4.21
|
||||||
python-telegram-bot==13.7
|
python-telegram-bot==13.7
|
||||||
arrow==1.1.1
|
arrow==1.1.1
|
||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
requests==2.25.1
|
requests==2.26.0
|
||||||
urllib3==1.26.6
|
urllib3==1.26.6
|
||||||
wrapt==1.12.1
|
wrapt==1.12.1
|
||||||
jsonschema==3.2.0
|
jsonschema==3.2.0
|
||||||
TA-Lib==0.4.20
|
TA-Lib==0.4.21
|
||||||
technical==1.3.0
|
technical==1.3.0
|
||||||
tabulate==0.8.9
|
tabulate==0.8.9
|
||||||
pycoingecko==2.2.0
|
pycoingecko==2.2.0
|
||||||
|
@ -39,5 +39,5 @@ aiofiles==0.7.0
|
||||||
# Support for colorized terminal output
|
# Support for colorized terminal output
|
||||||
colorama==0.4.4
|
colorama==0.4.4
|
||||||
# Building config files interactively
|
# Building config files interactively
|
||||||
questionary==1.9.0
|
questionary==1.10.0
|
||||||
prompt-toolkit==3.0.19
|
prompt-toolkit==3.0.19
|
||||||
|
|
27
setup.sh
27
setup.sh
|
@ -4,8 +4,12 @@
|
||||||
function check_installed_pip() {
|
function check_installed_pip() {
|
||||||
${PYTHON} -m pip > /dev/null
|
${PYTHON} -m pip > /dev/null
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "pip not found (called as '${PYTHON} -m pip'). Please make sure that pip is available for ${PYTHON}."
|
echo "-----------------------------"
|
||||||
exit 1
|
echo "Installing Pip for ${PYTHON}"
|
||||||
|
echo "-----------------------------"
|
||||||
|
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||||
|
${PYTHON} get-pip.py
|
||||||
|
rm get-pip.py
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,13 +21,12 @@ function check_installed_python() {
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for v in 8 9 7
|
for v in 9 8 7
|
||||||
do
|
do
|
||||||
PYTHON="python3.${v}"
|
PYTHON="python3.${v}"
|
||||||
which $PYTHON
|
which $PYTHON
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "using ${PYTHON}"
|
echo "using ${PYTHON}"
|
||||||
|
|
||||||
check_installed_pip
|
check_installed_pip
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
@ -136,7 +139,7 @@ function install_macos() {
|
||||||
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||||
fi
|
fi
|
||||||
#Gets number after decimal in python version
|
#Gets number after decimal in python version
|
||||||
version=$(egrep -o 3.\[0-9\]+ <<< $PYTHON | sed 's/3.//g' )
|
version=$(egrep -o 3.\[0-9\]+ <<< $PYTHON | sed 's/3.//g')
|
||||||
|
|
||||||
if [[ $version -ge 9 ]]; then #Checks if python version >= 3.9
|
if [[ $version -ge 9 ]]; then #Checks if python version >= 3.9
|
||||||
install_mac_newer_python_dependencies
|
install_mac_newer_python_dependencies
|
||||||
|
@ -147,7 +150,7 @@ function install_macos() {
|
||||||
# Install bot Debian_ubuntu
|
# Install bot Debian_ubuntu
|
||||||
function install_debian() {
|
function install_debian() {
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential autoconf libtool pkg-config make wget git libpython3-dev
|
sudo apt-get install -y build-essential autoconf libtool pkg-config make wget git $(echo lib${PYTHON}-dev ${PYTHON}-venv)
|
||||||
install_talib
|
install_talib
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,12 +239,12 @@ function install() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function plot() {
|
function plot() {
|
||||||
echo "
|
echo "
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
Installing dependencies for Plotting scripts
|
Installing dependencies for Plotting scripts
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
"
|
"
|
||||||
${PYTHON} -m pip install plotly --upgrade
|
${PYTHON} -m pip install plotly --upgrade
|
||||||
}
|
}
|
||||||
|
|
||||||
function help() {
|
function help() {
|
||||||
|
|
|
@ -1783,14 +1783,14 @@ def test_get_buy_rate(mocker, default_conf, caplog, side, ask, bid,
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
return_value={'ask': ask, 'last': last, 'bid': bid})
|
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||||
|
|
||||||
assert exchange.get_buy_rate('ETH/BTC', True) == expected
|
assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
|
||||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||||
|
|
||||||
assert exchange.get_buy_rate('ETH/BTC', False) == expected
|
assert exchange.get_rate('ETH/BTC', refresh=False, side="buy") == expected
|
||||||
assert log_has("Using cached buy rate for ETH/BTC.", caplog)
|
assert log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||||
# Running a 2nd time with Refresh on!
|
# Running a 2nd time with Refresh on!
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
assert exchange.get_buy_rate('ETH/BTC', True) == expected
|
assert exchange.get_rate('ETH/BTC', refresh=True, side="buy") == expected
|
||||||
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
assert not log_has("Using cached buy rate for ETH/BTC.", caplog)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1825,12 +1825,12 @@ def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask,
|
||||||
|
|
||||||
# Test regular mode
|
# Test regular mode
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
rate = exchange.get_sell_rate(pair, True)
|
rate = exchange.get_rate(pair, refresh=True, side="sell")
|
||||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||||
assert isinstance(rate, float)
|
assert isinstance(rate, float)
|
||||||
assert rate == expected
|
assert rate == expected
|
||||||
# Use caching
|
# Use caching
|
||||||
rate = exchange.get_sell_rate(pair, False)
|
rate = exchange.get_rate(pair, refresh=False, side="sell")
|
||||||
assert rate == expected
|
assert rate == expected
|
||||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||||
|
|
||||||
|
@ -1848,11 +1848,11 @@ def test_get_sell_rate_orderbook(default_conf, mocker, caplog, side, expected, o
|
||||||
pair = "ETH/BTC"
|
pair = "ETH/BTC"
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', order_book_l2)
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
rate = exchange.get_sell_rate(pair, True)
|
rate = exchange.get_rate(pair, refresh=True, side="sell")
|
||||||
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
assert not log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||||
assert isinstance(rate, float)
|
assert isinstance(rate, float)
|
||||||
assert rate == expected
|
assert rate == expected
|
||||||
rate = exchange.get_sell_rate(pair, False)
|
rate = exchange.get_rate(pair, refresh=False, side="sell")
|
||||||
assert rate == expected
|
assert rate == expected
|
||||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||||
|
|
||||||
|
@ -1868,7 +1868,7 @@ def test_get_sell_rate_orderbook_exception(default_conf, mocker, caplog):
|
||||||
return_value={'bids': [[]], 'asks': [[]]})
|
return_value={'bids': [[]], 'asks': [[]]})
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
with pytest.raises(PricingError):
|
with pytest.raises(PricingError):
|
||||||
exchange.get_sell_rate(pair, True)
|
exchange.get_rate(pair, refresh=True, side="sell")
|
||||||
assert log_has_re(r"Sell Price at location 1 from orderbook could not be determined\..*",
|
assert log_has_re(r"Sell Price at location 1 from orderbook could not be determined\..*",
|
||||||
caplog)
|
caplog)
|
||||||
|
|
||||||
|
@ -1881,18 +1881,18 @@ def test_get_sell_rate_exception(default_conf, mocker, caplog):
|
||||||
return_value={'ask': None, 'bid': 0.12, 'last': None})
|
return_value={'ask': None, 'bid': 0.12, 'last': None})
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
||||||
exchange.get_sell_rate(pair, True)
|
exchange.get_rate(pair, refresh=True, side="sell")
|
||||||
|
|
||||||
exchange._config['ask_strategy']['price_side'] = 'bid'
|
exchange._config['ask_strategy']['price_side'] = 'bid'
|
||||||
assert exchange.get_sell_rate(pair, True) == 0.12
|
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.12
|
||||||
# Reverse sides
|
# Reverse sides
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
return_value={'ask': 0.13, 'bid': None, 'last': None})
|
return_value={'ask': 0.13, 'bid': None, 'last': None})
|
||||||
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
with pytest.raises(PricingError, match=r"Sell-Rate for ETH/BTC was empty."):
|
||||||
exchange.get_sell_rate(pair, True)
|
exchange.get_rate(pair, refresh=True, side="sell")
|
||||||
|
|
||||||
exchange._config['ask_strategy']['price_side'] = 'ask'
|
exchange._config['ask_strategy']['price_side'] = 'ask'
|
||||||
assert exchange.get_sell_rate(pair, True) == 0.13
|
assert exchange.get_rate(pair, refresh=True, side="sell") == 0.13
|
||||||
|
|
||||||
|
|
||||||
def make_fetch_ohlcv_mock(data):
|
def make_fetch_ohlcv_mock(data):
|
||||||
|
@ -2203,7 +2203,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
|
||||||
({'status': 'canceled', 'filled': 10.0}, False),
|
({'status': 'canceled', 'filled': 10.0}, False),
|
||||||
({'status': 'unknown', 'filled': 10.0}, False),
|
({'status': 'unknown', 'filled': 10.0}, False),
|
||||||
({'result': 'testest123'}, False),
|
({'result': 'testest123'}, False),
|
||||||
])
|
])
|
||||||
def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, result):
|
def test_check_order_canceled_empty(mocker, default_conf, exchange_name, order, result):
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||||
assert exchange.check_order_canceled_empty(order) == result
|
assert exchange.check_order_canceled_empty(order) == result
|
||||||
|
|
|
@ -109,7 +109,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||||
'exchange': 'binance',
|
'exchange': 'binance',
|
||||||
}
|
}
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
results = rpc._rpc_trade_status()
|
results = rpc._rpc_trade_status()
|
||||||
assert isnan(results[0]['current_profit'])
|
assert isnan(results[0]['current_profit'])
|
||||||
|
@ -217,7 +217,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
assert '-0.41% (-0.06)' == result[0][3]
|
assert '-0.41% (-0.06)' == result[0][3]
|
||||||
assert '-0.06' == f'{fiat_profit_sum:.2f}'
|
assert '-0.06' == f'{fiat_profit_sum:.2f}'
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||||
assert 'instantly' == result[0][2]
|
assert 'instantly' == result[0][2]
|
||||||
|
@ -427,7 +427,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||||
|
|
||||||
# Test non-available pair
|
# Test non-available pair
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||||
assert stats['trade_count'] == 2
|
assert stats['trade_count'] == 2
|
||||||
|
|
|
@ -879,7 +879,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||||
'exchange': 'binance',
|
'exchange': 'binance',
|
||||||
}
|
}
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
|
|
||||||
rc = client_get(client, f"{BASE_URI}/status")
|
rc = client_get(client, f"{BASE_URI}/status")
|
||||||
|
|
|
@ -161,7 +161,7 @@ def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None:
|
||||||
(True, 0.0022, 3, 0.5, [0.001, 0.001, 0.0]),
|
(True, 0.0022, 3, 0.5, [0.001, 0.001, 0.0]),
|
||||||
(True, 0.0027, 3, 0.5, [0.001, 0.001, 0.000673]),
|
(True, 0.0027, 3, 0.5, [0.001, 0.001, 0.000673]),
|
||||||
(True, 0.0022, 3, 1, [0.001, 0.001, 0.0]),
|
(True, 0.0022, 3, 1, [0.001, 0.001, 0.0]),
|
||||||
])
|
])
|
||||||
def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_buy_order_open,
|
def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_buy_order_open,
|
||||||
amend_last, wallet, max_open, lsamr, expected) -> None:
|
amend_last, wallet, max_open, lsamr, expected) -> None:
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
|
@ -784,7 +784,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||||
buy_mm = MagicMock(return_value=limit_buy_order_open)
|
buy_mm = MagicMock(return_value=limit_buy_order_open)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
get_buy_rate=buy_rate_mock,
|
get_rate=buy_rate_mock,
|
||||||
fetch_ticker=MagicMock(return_value={
|
fetch_ticker=MagicMock(return_value={
|
||||||
'bid': 0.00001172,
|
'bid': 0.00001172,
|
||||||
'ask': 0.00001173,
|
'ask': 0.00001173,
|
||||||
|
@ -824,7 +824,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||||
limit_buy_order_open['id'] = '33'
|
limit_buy_order_open['id'] = '33'
|
||||||
fix_price = 0.06
|
fix_price = 0.06
|
||||||
assert freqtrade.execute_buy(pair, stake_amount, fix_price)
|
assert freqtrade.execute_buy(pair, stake_amount, fix_price)
|
||||||
# Make sure get_buy_rate wasn't called again
|
# Make sure get_rate wasn't called again
|
||||||
assert buy_rate_mock.call_count == 0
|
assert buy_rate_mock.call_count == 0
|
||||||
|
|
||||||
assert buy_mm.call_count == 2
|
assert buy_mm.call_count == 2
|
||||||
|
@ -893,7 +893,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
assert not freqtrade.execute_buy(pair, stake_amount)
|
||||||
|
|
||||||
# Fail to get price...
|
# Fail to get price...
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_buy_rate', MagicMock(return_value=0.0))
|
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
||||||
|
|
||||||
with pytest.raises(PricingError, match="Could not determine buy price."):
|
with pytest.raises(PricingError, match="Could not determine buy price."):
|
||||||
freqtrade.execute_buy(pair, stake_amount)
|
freqtrade.execute_buy(pair, stake_amount)
|
||||||
|
@ -909,7 +909,7 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -
|
||||||
'last': 0.00001172
|
'last': 0.00001172
|
||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value=limit_buy_order),
|
buy=MagicMock(return_value=limit_buy_order),
|
||||||
get_buy_rate=MagicMock(return_value=0.11),
|
get_rate=MagicMock(return_value=0.11),
|
||||||
get_min_pair_stake_amount=MagicMock(return_value=1),
|
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
)
|
)
|
||||||
|
@ -2513,7 +2513,7 @@ def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
cancel_order=cancel_order_mock,
|
cancel_order=cancel_order_mock,
|
||||||
)
|
)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate', return_value=0.245441)
|
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.245441)
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
|
@ -3956,7 +3956,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o
|
||||||
|
|
||||||
def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
|
def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
|
||||||
"""
|
"""
|
||||||
test if function get_buy_rate will return the order book price
|
test if function get_rate will return the order book price
|
||||||
instead of the ask rate
|
instead of the ask rate
|
||||||
"""
|
"""
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
@ -3974,7 +3974,7 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
|
||||||
default_conf['telegram']['enabled'] = False
|
default_conf['telegram']['enabled'] = False
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
assert freqtrade.exchange.get_buy_rate('ETH/BTC', True) == 0.043935
|
assert freqtrade.exchange.get_rate('ETH/BTC', refresh=True, side="buy") == 0.043935
|
||||||
assert ticker_mock.call_count == 0
|
assert ticker_mock.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -3996,8 +3996,8 @@ def test_order_book_bid_strategy_exception(mocker, default_conf, caplog) -> None
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
# orderbook shall be used even if tickers would be lower.
|
# orderbook shall be used even if tickers would be lower.
|
||||||
with pytest.raises(PricingError):
|
with pytest.raises(PricingError):
|
||||||
freqtrade.exchange.get_buy_rate('ETH/BTC', refresh=True)
|
freqtrade.exchange.get_rate('ETH/BTC', refresh=True, side="buy")
|
||||||
assert log_has_re(r'Buy Price from orderbook could not be determined.', caplog)
|
assert log_has_re(r'Buy Price at location 1 from orderbook could not be determined.', caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2) -> None:
|
def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2) -> None:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user