Improve login-mixin structure
This commit is contained in:
parent
8d9c66a638
commit
8f958ef723
@ -367,7 +367,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"but checking to sell open trades.")
|
"but checking to sell open trades.")
|
||||||
return trades_created
|
return trades_created
|
||||||
if PairLocks.is_global_lock():
|
if PairLocks.is_global_lock():
|
||||||
self.log_once(logger.info, "Global pairlock active. Not creating new trades.")
|
self.log_once("Global pairlock active. Not creating new trades.", logger.info)
|
||||||
return trades_created
|
return trades_created
|
||||||
# Create entity and execute trade for each pair from whitelist
|
# Create entity and execute trade for each pair from whitelist
|
||||||
for pair in whitelist:
|
for pair in whitelist:
|
||||||
@ -553,7 +553,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe)
|
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe)
|
||||||
if self.strategy.is_pair_locked(
|
if self.strategy.is_pair_locked(
|
||||||
pair, analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None):
|
pair, analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None):
|
||||||
self.log_once(logger.info, f"Pair {pair} is currently locked.")
|
self.log_once(f"Pair {pair} is currently locked.", logger.info)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# get_free_open_trades is checked before create_trade is called
|
# get_free_open_trades is checked before create_trade is called
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
from typing import Callable
|
||||||
from cachetools import TTLCache, cached
|
from cachetools import TTLCache, cached
|
||||||
|
|
||||||
|
|
||||||
@ -19,12 +20,12 @@ class LoggingMixin():
|
|||||||
self.refresh_period = refresh_period
|
self.refresh_period = refresh_period
|
||||||
self._log_cache: TTLCache = TTLCache(maxsize=1024, ttl=self.refresh_period)
|
self._log_cache: TTLCache = TTLCache(maxsize=1024, ttl=self.refresh_period)
|
||||||
|
|
||||||
def log_once(self, logmethod, message: str) -> None:
|
def log_once(self, message: str, logmethod: Callable) -> None:
|
||||||
"""
|
"""
|
||||||
Logs message - not more often than "refresh_period" to avoid log spamming
|
Logs message - not more often than "refresh_period" to avoid log spamming
|
||||||
Logs the log-message as debug as well to simplify debugging.
|
Logs the log-message as debug as well to simplify debugging.
|
||||||
:param logmethod: Function that'll be called. Most likely `logger.info`.
|
|
||||||
:param message: String containing the message to be sent to the function.
|
:param message: String containing the message to be sent to the function.
|
||||||
|
:param logmethod: Function that'll be called. Most likely `logger.info`.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
@cached(cache=self._log_cache)
|
@cached(cache=self._log_cache)
|
||||||
|
@ -76,9 +76,8 @@ class AgeFilter(IPairList):
|
|||||||
self._symbolsChecked[ticker['symbol']] = int(arrow.utcnow().float_timestamp) * 1000
|
self._symbolsChecked[ticker['symbol']] = int(arrow.utcnow().float_timestamp) * 1000
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.log_once(logger.info,
|
self.log_once(f"Removed {ticker['symbol']} from whitelist, because age "
|
||||||
f"Removed {ticker['symbol']} from whitelist, because age "
|
|
||||||
f"{len(daily_candles)} is less than {self._min_days_listed} "
|
f"{len(daily_candles)} is less than {self._min_days_listed} "
|
||||||
f"{plural(self._min_days_listed, 'day')}")
|
f"{plural(self._min_days_listed, 'day')}", logger.info)
|
||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
@ -59,8 +59,8 @@ class PrecisionFilter(IPairList):
|
|||||||
logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}")
|
logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}")
|
||||||
|
|
||||||
if sp <= stop_gap_price:
|
if sp <= stop_gap_price:
|
||||||
self.log_once(logger.info, f"Removed {ticker['symbol']} from whitelist, because "
|
self.log_once(f"Removed {ticker['symbol']} from whitelist, because "
|
||||||
f"stop price {sp} would be <= stop limit {stop_gap_price}")
|
f"stop price {sp} would be <= stop limit {stop_gap_price}", logger.info)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -64,9 +64,9 @@ class PriceFilter(IPairList):
|
|||||||
:return: True if the pair can stay, false if it should be removed
|
:return: True if the pair can stay, false if it should be removed
|
||||||
"""
|
"""
|
||||||
if ticker['last'] is None or ticker['last'] == 0:
|
if ticker['last'] is None or ticker['last'] == 0:
|
||||||
self.log_once(logger.info,
|
self.log_once(f"Removed {ticker['symbol']} from whitelist, because "
|
||||||
f"Removed {ticker['symbol']} from whitelist, because "
|
"ticker['last'] is empty (Usually no trade in the last 24h).",
|
||||||
"ticker['last'] is empty (Usually no trade in the last 24h).")
|
logger.info)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Perform low_price_ratio check.
|
# Perform low_price_ratio check.
|
||||||
@ -74,22 +74,22 @@ class PriceFilter(IPairList):
|
|||||||
compare = self._exchange.price_get_one_pip(ticker['symbol'], ticker['last'])
|
compare = self._exchange.price_get_one_pip(ticker['symbol'], ticker['last'])
|
||||||
changeperc = compare / ticker['last']
|
changeperc = compare / ticker['last']
|
||||||
if changeperc > self._low_price_ratio:
|
if changeperc > self._low_price_ratio:
|
||||||
self.log_once(logger.info, f"Removed {ticker['symbol']} from whitelist, "
|
self.log_once(f"Removed {ticker['symbol']} from whitelist, "
|
||||||
f"because 1 unit is {changeperc * 100:.3f}%")
|
f"because 1 unit is {changeperc * 100:.3f}%", logger.info)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Perform min_price check.
|
# Perform min_price check.
|
||||||
if self._min_price != 0:
|
if self._min_price != 0:
|
||||||
if ticker['last'] < self._min_price:
|
if ticker['last'] < self._min_price:
|
||||||
self.log_once(logger.info, f"Removed {ticker['symbol']} from whitelist, "
|
self.log_once(f"Removed {ticker['symbol']} from whitelist, "
|
||||||
f"because last price < {self._min_price:.8f}")
|
f"because last price < {self._min_price:.8f}", logger.info)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Perform max_price check.
|
# Perform max_price check.
|
||||||
if self._max_price != 0:
|
if self._max_price != 0:
|
||||||
if ticker['last'] > self._max_price:
|
if ticker['last'] > self._max_price:
|
||||||
self.log_once(logger.info, f"Removed {ticker['symbol']} from whitelist, "
|
self.log_once(f"Removed {ticker['symbol']} from whitelist, "
|
||||||
f"because last price > {self._max_price:.8f}")
|
f"because last price > {self._max_price:.8f}", logger.info)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -45,9 +45,9 @@ class SpreadFilter(IPairList):
|
|||||||
if 'bid' in ticker and 'ask' in ticker:
|
if 'bid' in ticker and 'ask' in ticker:
|
||||||
spread = 1 - ticker['bid'] / ticker['ask']
|
spread = 1 - ticker['bid'] / ticker['ask']
|
||||||
if spread > self._max_spread_ratio:
|
if spread > self._max_spread_ratio:
|
||||||
self.log_once(logger.info,
|
self.log_once(f"Removed {ticker['symbol']} from whitelist, because spread "
|
||||||
f"Removed {ticker['symbol']} from whitelist, because spread "
|
f"{spread * 100:.3f}% > {self._max_spread_ratio * 100}%",
|
||||||
f"{spread * 100:.3f}% > {self._max_spread_ratio * 100}%")
|
logger.info)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
@ -111,6 +111,6 @@ class VolumePairList(IPairList):
|
|||||||
# Limit pairlist to the requested number of pairs
|
# Limit pairlist to the requested number of pairs
|
||||||
pairs = pairs[:self._number_pairs]
|
pairs = pairs[:self._number_pairs]
|
||||||
|
|
||||||
self.log_once(logger.info, f"Searching {self._number_pairs} pairs: {pairs}")
|
self.log_once(f"Searching {self._number_pairs} pairs: {pairs}", logger.info)
|
||||||
|
|
||||||
return pairs
|
return pairs
|
||||||
|
@ -78,10 +78,10 @@ class RangeStabilityFilter(IPairList):
|
|||||||
if pct_change >= self._min_rate_of_change:
|
if pct_change >= self._min_rate_of_change:
|
||||||
result = True
|
result = True
|
||||||
else:
|
else:
|
||||||
self.log_once(logger.info,
|
self.log_once(f"Removed {pair} from whitelist, because rate of change "
|
||||||
f"Removed {pair} from whitelist, because rate of change "
|
|
||||||
f"over {plural(self._days, 'day')} is {pct_change:.3f}, "
|
f"over {plural(self._days, 'day')} is {pct_change:.3f}, "
|
||||||
f"which is below the threshold of {self._min_rate_of_change}.")
|
f"which is below the threshold of {self._min_rate_of_change}.",
|
||||||
|
logger.info)
|
||||||
result = False
|
result = False
|
||||||
self._pair_cache[pair] = result
|
self._pair_cache[pair] = result
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
@ -46,7 +46,7 @@ class CooldownPeriod(IProtection):
|
|||||||
]
|
]
|
||||||
trade = Trade.get_trades(filters).first()
|
trade = Trade.get_trades(filters).first()
|
||||||
if trade:
|
if trade:
|
||||||
self.log_once(logger.info, f"Cooldown for {pair} for {self._stop_duration}.")
|
self.log_once(f"Cooldown for {pair} for {self._stop_duration}.", logger.info)
|
||||||
until = self.calculate_lock_end([trade], self._stop_duration)
|
until = self.calculate_lock_end([trade], self._stop_duration)
|
||||||
|
|
||||||
return True, until, self._reason()
|
return True, until, self._reason()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod, abstractproperty
|
from abc import ABC, abstractmethod
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
@ -58,9 +58,8 @@ class LowProfitPairs(IProtection):
|
|||||||
profit = sum(trade.close_profit for trade in trades)
|
profit = sum(trade.close_profit for trade in trades)
|
||||||
if profit < self._required_profit:
|
if profit < self._required_profit:
|
||||||
self.log_once(
|
self.log_once(
|
||||||
logger.info,
|
|
||||||
f"Trading for {pair} stopped due to {profit:.2f} < {self._required_profit} "
|
f"Trading for {pair} stopped due to {profit:.2f} < {self._required_profit} "
|
||||||
f"within {self._lookback_period} minutes.")
|
f"within {self._lookback_period} minutes.", logger.info)
|
||||||
until = self.calculate_lock_end(trades, self._stop_duration)
|
until = self.calculate_lock_end(trades, self._stop_duration)
|
||||||
|
|
||||||
return True, until, self._reason(profit)
|
return True, until, self._reason(profit)
|
||||||
|
@ -58,8 +58,8 @@ class StoplossGuard(IProtection):
|
|||||||
trades = Trade.get_trades(filters).all()
|
trades = Trade.get_trades(filters).all()
|
||||||
|
|
||||||
if len(trades) > self._trade_limit:
|
if len(trades) > self._trade_limit:
|
||||||
self.log_once(logger.info, f"Trading stopped due to {self._trade_limit} "
|
self.log_once(f"Trading stopped due to {self._trade_limit} "
|
||||||
f"stoplosses within {self._lookback_period} minutes.")
|
f"stoplosses within {self._lookback_period} minutes.", logger.info)
|
||||||
until = self.calculate_lock_end(trades, self._stop_duration)
|
until = self.calculate_lock_end(trades, self._stop_duration)
|
||||||
return True, until, self._reason()
|
return True, until, self._reason()
|
||||||
|
|
||||||
|
@ -102,14 +102,14 @@ def test_log_cached(mocker, static_pl_conf, markets, tickers):
|
|||||||
logmock = MagicMock()
|
logmock = MagicMock()
|
||||||
# Assign starting whitelist
|
# Assign starting whitelist
|
||||||
pl = freqtrade.pairlists._pairlist_handlers[0]
|
pl = freqtrade.pairlists._pairlist_handlers[0]
|
||||||
pl.log_once(logmock, 'Hello world')
|
pl.log_once('Hello world', logmock)
|
||||||
assert logmock.call_count == 1
|
assert logmock.call_count == 1
|
||||||
pl.log_once(logmock, 'Hello world')
|
pl.log_once('Hello world', logmock)
|
||||||
assert logmock.call_count == 1
|
assert logmock.call_count == 1
|
||||||
assert pl._log_cache.currsize == 1
|
assert pl._log_cache.currsize == 1
|
||||||
assert ('Hello world',) in pl._log_cache._Cache__data
|
assert ('Hello world',) in pl._log_cache._Cache__data
|
||||||
|
|
||||||
pl.log_once(logmock, 'Hello world2')
|
pl.log_once('Hello world2', logmock)
|
||||||
assert logmock.call_count == 2
|
assert logmock.call_count == 2
|
||||||
assert pl._log_cache.currsize == 2
|
assert pl._log_cache.currsize == 2
|
||||||
|
|
||||||
|
@ -165,6 +165,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
|
|||||||
assert PairLocks.is_pair_locked('XRP/BTC')
|
assert PairLocks.is_pair_locked('XRP/BTC')
|
||||||
assert not PairLocks.is_global_lock()
|
assert not PairLocks.is_global_lock()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("protectionconf,desc_expected,exception_expected", [
|
@pytest.mark.parametrize("protectionconf,desc_expected,exception_expected", [
|
||||||
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2},
|
({"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2},
|
||||||
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
"[{'StoplossGuard': 'StoplossGuard - Frequent Stoploss Guard, "
|
||||||
|
Loading…
Reference in New Issue
Block a user