Compare commits
70 Commits
feat/cross
...
2022.6
Author | SHA1 | Date | |
---|---|---|---|
|
2db5cc177d | ||
|
c1d4078518 | ||
|
d25ec6d0b8 | ||
|
07aa372e2a | ||
|
c5e6520fee | ||
|
f2fdc21374 | ||
|
906c7b92fe | ||
|
df8c9fc4e1 | ||
|
3c1380fbc6 | ||
|
86f4077024 | ||
|
f2bc35e058 | ||
|
0a5225695a | ||
|
74471e41db | ||
|
8b1798522c | ||
|
7de7425e24 | ||
|
37dff8dc82 | ||
|
0c69a08863 | ||
|
f6e058a327 | ||
|
d60127a6d8 | ||
|
11a8151653 | ||
|
e3abaaa1b7 | ||
|
82ef97af7e | ||
|
74fdda6846 | ||
|
9eaf0400fa | ||
|
01185ab483 | ||
|
8405bf767b | ||
|
9a9d1a8974 | ||
|
0ef2c812db | ||
|
85d1b433bc | ||
|
d8f616cf35 | ||
|
870c25c81f | ||
|
fb3bc189b5 | ||
|
6510c8d330 | ||
|
efee148e43 | ||
|
8b7dc031f7 | ||
|
963f38a690 | ||
|
45db2347dc | ||
|
4840c7d2fd | ||
|
92dbb0d366 | ||
|
b5d0bc997d | ||
|
ca88ea50c5 | ||
|
2b07d34611 | ||
|
8bf0bf10c5 | ||
|
c57db0a330 | ||
|
f5087a82dc | ||
|
eed0d67005 | ||
|
a1d54f5ae0 | ||
|
a4a7c6536d | ||
|
2b61aa282a | ||
|
340a97d1df | ||
|
fab197edf2 | ||
|
851c5dad30 | ||
|
5b76ae452f | ||
|
2c750fdb09 | ||
|
e7f5252074 | ||
|
dfbd1c34c4 | ||
|
7615c4e904 | ||
|
e9b78bf3ae | ||
|
2e397a88e1 | ||
|
fe6c62e144 | ||
|
f0db721f05 | ||
|
4d8d30ea39 | ||
|
e90e3cead0 | ||
|
a568548192 | ||
|
f9d10a7fad | ||
|
cbc2b00ee6 | ||
|
8f7b857ae9 | ||
|
e88b022cd4 | ||
|
bfb738f69f | ||
|
00dd8e76ee |
@@ -13,11 +13,11 @@ repos:
|
||||
- id: mypy
|
||||
exclude: build_helpers
|
||||
additional_dependencies:
|
||||
- types-cachetools==5.0.2
|
||||
- types-cachetools==5.2.1
|
||||
- types-filelock==3.2.7
|
||||
- types-requests==2.27.30
|
||||
- types-tabulate==0.8.9
|
||||
- types-python-dateutil==2.8.17
|
||||
- types-requests==2.28.0
|
||||
- types-tabulate==0.8.11
|
||||
- types-python-dateutil==2.8.18
|
||||
# stages: [push]
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
|
@@ -20,7 +20,9 @@ All profit calculations of Freqtrade include fees. For Backtesting / Hyperopt /
|
||||
## Bot execution logic
|
||||
|
||||
Starting freqtrade in dry-run or live mode (using `freqtrade trade`) will start the bot and start the bot iteration loop.
|
||||
By default, loop runs every few seconds (`internals.process_throttle_secs`) and does roughly the following in the following sequence:
|
||||
This will also run the `bot_start()` callback.
|
||||
|
||||
By default, the bot loop runs every few seconds (`internals.process_throttle_secs`) and performs the following actions:
|
||||
|
||||
* Fetch open trades from persistence.
|
||||
* Calculate current list of tradable pairs.
|
||||
@@ -54,6 +56,7 @@ This loop will be repeated again and again until the bot is stopped.
|
||||
[backtesting](backtesting.md) or [hyperopt](hyperopt.md) do only part of the above logic, since most of the trading operations are fully simulated.
|
||||
|
||||
* Load historic data for configured pairlist.
|
||||
* Calls `bot_start()` once.
|
||||
* Calls `bot_loop_start()` once.
|
||||
* Calculate indicators (calls `populate_indicators()` once per pair).
|
||||
* Calculate entry / exit signals (calls `populate_entry_trend()` and `populate_exit_trend()` once per pair).
|
||||
|
@@ -1,5 +1,5 @@
|
||||
mkdocs==1.3.0
|
||||
mkdocs-material==8.3.6
|
||||
mkdocs-material==8.3.8
|
||||
mdx_truly_sane_lists==1.2
|
||||
pymdown-extensions==9.5
|
||||
jinja2==3.1.2
|
||||
|
@@ -130,7 +130,7 @@ In summary: The stoploss will be adjusted to be always be -10% of the highest ob
|
||||
|
||||
### Trailing stop loss, custom positive loss
|
||||
|
||||
It is also possible to have a default stop loss, when you are in the red with your buy (buy - fee), but once you hit positive result the system will utilize a new stop loss, which can have a different value.
|
||||
You could also have a default stop loss when you are in the red with your buy (buy - fee), but once you hit a positive result (or an offset you define) the system will utilize a new stop loss, which can have a different value.
|
||||
For example, your default stop loss is -10%, but once you have more than 0% profit (example 0.1%) a different trailing stoploss will be used.
|
||||
|
||||
!!! Note
|
||||
@@ -142,6 +142,8 @@ Both values require `trailing_stop` to be set to true and `trailing_stop_positiv
|
||||
stoploss = -0.10
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.0
|
||||
trailing_only_offset_is_reached = False # Default - not necessary for this example
|
||||
```
|
||||
|
||||
For example, simplified math:
|
||||
@@ -156,11 +158,31 @@ For example, simplified math:
|
||||
The 0.02 would translate to a -2% stop loss.
|
||||
Before this, `stoploss` is used for the trailing stoploss.
|
||||
|
||||
!!! Tip "Use an offset to change your stoploss"
|
||||
Use `trailing_stop_positive_offset` to ensure that your new trailing stoploss will be in profit by setting `trailing_stop_positive_offset` higher than `trailing_stop_positive`. Your first new stoploss value will then already have locked in profits.
|
||||
|
||||
Example with simplified math:
|
||||
|
||||
``` python
|
||||
stoploss = -0.10
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02
|
||||
trailing_stop_positive_offset = 0.03
|
||||
```
|
||||
|
||||
* the bot buys an asset at a price of 100$
|
||||
* the stop loss is defined at -10%, so the stop loss would get triggered once the asset drops below 90$
|
||||
* assuming the asset now increases to 102$
|
||||
* the stoploss will now be at 91.8$ - 10% below the highest observed rate
|
||||
* assuming the asset now increases to 103.5$ (above the offset configured)
|
||||
* the stop loss will now be -2% of 103$ = 101.42$
|
||||
* now the asset drops in value to 102\$, the stop loss will still be 101.42$ and would trigger once price breaks below 101.42$
|
||||
|
||||
### Trailing stop loss only once the trade has reached a certain offset
|
||||
|
||||
It is also possible to use a static stoploss until the offset is reached, and then trail the trade to take profits once the market turns.
|
||||
You can also keep a static stoploss until the offset is reached, and then trail the trade to take profits once the market turns.
|
||||
|
||||
If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured `stoploss`.
|
||||
If `trailing_only_offset_is_reached = True` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured `stoploss`.
|
||||
This option can be used with or without `trailing_stop_positive`, but uses `trailing_stop_positive_offset` as offset.
|
||||
|
||||
``` python
|
||||
@@ -203,7 +225,6 @@ If price moves 1% - you've lost 10$ of your own capital - therfore stoploss will
|
||||
|
||||
Make sure to be aware of this, and avoid using too tight stoploss (at 10x leverage, 10% risk may be too little to allow the trade to "breath" a little).
|
||||
|
||||
|
||||
## Changing stoploss on open trades
|
||||
|
||||
A stoploss on an open trade can be changed by changing the value in the configuration or strategy and use the `/reload_config` command (alternatively, completely stopping and restarting the bot also works).
|
||||
|
@@ -1,5 +1,5 @@
|
||||
""" Freqtrade bot """
|
||||
__version__ = 'develop'
|
||||
__version__ = '2022.6'
|
||||
|
||||
if 'dev' in __version__:
|
||||
try:
|
||||
|
@@ -24,7 +24,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
||||
|
||||
print_colorized = config.get('print_colorized', False)
|
||||
print_json = config.get('print_json', False)
|
||||
export_csv = config.get('export_csv', None)
|
||||
export_csv = config.get('export_csv')
|
||||
no_details = config.get('hyperopt_list_no_details', False)
|
||||
no_header = False
|
||||
|
||||
|
@@ -129,7 +129,7 @@ class Configuration:
|
||||
# Default to in-memory db for dry_run if not specified
|
||||
config['db_url'] = constants.DEFAULT_DB_DRYRUN_URL
|
||||
else:
|
||||
if not config.get('db_url', None):
|
||||
if not config.get('db_url'):
|
||||
config['db_url'] = constants.DEFAULT_DB_PROD_URL
|
||||
logger.info('Dry run is disabled')
|
||||
|
||||
@@ -182,7 +182,7 @@ class Configuration:
|
||||
config['user_data_dir'] = create_userdata_dir(config['user_data_dir'], create_dir=False)
|
||||
logger.info('Using user-data directory: %s ...', config['user_data_dir'])
|
||||
|
||||
config.update({'datadir': create_datadir(config, self.args.get('datadir', None))})
|
||||
config.update({'datadir': create_datadir(config, self.args.get('datadir'))})
|
||||
logger.info('Using data directory: %s ...', config.get('datadir'))
|
||||
|
||||
if self.args.get('exportfilename'):
|
||||
@@ -221,7 +221,7 @@ class Configuration:
|
||||
if config.get('max_open_trades') == -1:
|
||||
config['max_open_trades'] = float('inf')
|
||||
|
||||
if self.args.get('stake_amount', None):
|
||||
if self.args.get('stake_amount'):
|
||||
# Convert explicitly to float to support CLI argument for both unlimited and value
|
||||
try:
|
||||
self.args['stake_amount'] = float(self.args['stake_amount'])
|
||||
@@ -474,7 +474,7 @@ class Configuration:
|
||||
configuration instead of the content)
|
||||
"""
|
||||
if (argname in self.args and self.args[argname] is not None
|
||||
and self.args[argname] is not False):
|
||||
and self.args[argname] is not False):
|
||||
|
||||
config.update({argname: self.args[argname]})
|
||||
if logfun:
|
||||
|
@@ -387,7 +387,7 @@ class Exchange:
|
||||
and market.get('base', None) is not None
|
||||
and (self.precisionMode != TICK_SIZE
|
||||
# Too low precision will falsify calculations
|
||||
or market.get('precision', {}).get('price', None) > 1e-11)
|
||||
or market.get('precision', {}).get('price') > 1e-11)
|
||||
and ((self.trading_mode == TradingMode.SPOT and self.market_is_spot(market))
|
||||
or (self.trading_mode == TradingMode.MARGIN and self.market_is_margin(market))
|
||||
or (self.trading_mode == TradingMode.FUTURES and self.market_is_future(market)))
|
||||
@@ -537,7 +537,7 @@ class Exchange:
|
||||
# The internal info array is different for each particular market,
|
||||
# its contents depend on the exchange.
|
||||
# It can also be a string or similar ... so we need to verify that first.
|
||||
elif (isinstance(self.markets[pair].get('info', None), dict)
|
||||
elif (isinstance(self.markets[pair].get('info'), dict)
|
||||
and self.markets[pair].get('info', {}).get('prohibitedIn', False)):
|
||||
# Warn users about restricted pairs in whitelist.
|
||||
# We cannot determine reliably if Users are affected.
|
||||
|
@@ -17,13 +17,12 @@ from freqtrade.constants import BuySell, LongShort
|
||||
from freqtrade.data.converter import order_book_to_dataframe
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.edge import Edge
|
||||
from freqtrade.enums import (ExitCheckTuple, ExitType, MarginMode, RPCMessageType, RunMode,
|
||||
SignalDirection, State, TradingMode)
|
||||
from freqtrade.enums import (ExitCheckTuple, ExitType, RPCMessageType, RunMode, SignalDirection,
|
||||
State, TradingMode)
|
||||
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
|
||||
InvalidOrderException, PricingError)
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||
from freqtrade.maintenance_margin import MaintenanceMargin
|
||||
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
|
||||
from freqtrade.mixins import LoggingMixin
|
||||
from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db
|
||||
@@ -68,7 +67,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
|
||||
|
||||
init_db(self.config.get('db_url', None))
|
||||
init_db(self.config['db_url'])
|
||||
|
||||
self.wallets = Wallets(self.config, self.exchange)
|
||||
|
||||
@@ -105,11 +104,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
||||
|
||||
self.trading_mode: TradingMode = self.config.get('trading_mode', TradingMode.SPOT)
|
||||
self.margin_mode: MarginMode = (
|
||||
MarginMode(config.get('margin_mode'))
|
||||
if config.get('margin_mode')
|
||||
else MarginMode.NONE
|
||||
)
|
||||
|
||||
self._schedule = Scheduler()
|
||||
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
@@ -130,16 +125,6 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Initialize protections AFTER bot start - otherwise parameters are not loaded.
|
||||
self.protections = ProtectionManager(self.config, self.strategy.protections)
|
||||
|
||||
# Start calculating maintenance margin if on cross margin
|
||||
# TODO-lev: finish the below...
|
||||
if self.margin_mode == MarginMode.CROSS:
|
||||
|
||||
self.maintenance_margin = MaintenanceMargin(
|
||||
exchange_name=self.exchange.name,
|
||||
trading_mode=self.trading_mode)
|
||||
|
||||
self.maintenance_margin.run()
|
||||
|
||||
def notify_status(self, msg: str) -> None:
|
||||
"""
|
||||
Public method for users of this class (worker, etc.) to send notifications
|
||||
@@ -663,7 +648,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
order_obj = Order.parse_from_ccxt_object(order, pair, side)
|
||||
order_id = order['id']
|
||||
order_status = order.get('status', None)
|
||||
order_status = order.get('status')
|
||||
logger.info(f"Order #{order_id} was created for {pair} and status is {order_status}.")
|
||||
|
||||
# we assume the order is executed at the price requested
|
||||
@@ -736,10 +721,6 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
trade.orders.append(order_obj)
|
||||
trade.recalc_trade_from_orders()
|
||||
|
||||
if self.margin_mode == MarginMode.CROSS:
|
||||
self.maintenance_margin.add_new_trade(trade)
|
||||
|
||||
Trade.query.session.add(trade)
|
||||
Trade.commit()
|
||||
|
||||
@@ -978,6 +959,29 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.debug(f'Found no {exit_signal_type} signal for %s.', trade)
|
||||
return False
|
||||
|
||||
def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
|
||||
enter: bool, exit_: bool, exit_tag: Optional[str]) -> bool:
|
||||
"""
|
||||
Check and execute trade exit
|
||||
"""
|
||||
exits: List[ExitCheckTuple] = self.strategy.should_exit(
|
||||
trade,
|
||||
exit_rate,
|
||||
datetime.now(timezone.utc),
|
||||
enter=enter,
|
||||
exit_=exit_,
|
||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
||||
)
|
||||
for should_exit in exits:
|
||||
if should_exit.exit_flag:
|
||||
exit_tag1 = exit_tag if should_exit.exit_type == ExitType.EXIT_SIGNAL else None
|
||||
logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}'
|
||||
f'{f" Tag: {exit_tag1}" if exit_tag1 is not None else ""}')
|
||||
exited = self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag1)
|
||||
if exited:
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool:
|
||||
"""
|
||||
Abstracts creating stoploss orders from the logic.
|
||||
@@ -1129,28 +1133,6 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.warning(f"Could not create trailing stoploss order "
|
||||
f"for pair {trade.pair}.")
|
||||
|
||||
def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
|
||||
enter: bool, exit_: bool, exit_tag: Optional[str]) -> bool:
|
||||
"""
|
||||
Check and execute trade exit
|
||||
"""
|
||||
exits: List[ExitCheckTuple] = self.strategy.should_exit(
|
||||
trade,
|
||||
exit_rate,
|
||||
datetime.now(timezone.utc),
|
||||
enter=enter,
|
||||
exit_=exit_,
|
||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
||||
)
|
||||
for should_exit in exits:
|
||||
if should_exit.exit_flag:
|
||||
logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}'
|
||||
f'{f" Tag: {exit_tag}" if exit_tag is not None else ""}')
|
||||
exited = self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag)
|
||||
if exited:
|
||||
return True
|
||||
return False
|
||||
|
||||
def manage_open_orders(self) -> None:
|
||||
"""
|
||||
Management of open orders on exchange. Unfilled orders might be cancelled if timeout
|
||||
@@ -1520,21 +1502,10 @@ class FreqtradeBot(LoggingMixin):
|
||||
# In case of market sell orders the order can be closed immediately
|
||||
if order.get('status', 'unknown') in ('closed', 'expired'):
|
||||
self.update_trade_state(trade, trade.open_order_id, order)
|
||||
|
||||
Trade.commit()
|
||||
|
||||
self._remove_maintenance_trade(trade)
|
||||
|
||||
return True
|
||||
|
||||
def _remove_maintenance_trade(self, trade: Trade):
|
||||
"""
|
||||
Removes a trade from the maintenance margin object
|
||||
:param trade: The trade to remove from the maintenance margin
|
||||
"""
|
||||
if self.margin_mode == MarginMode.CROSS:
|
||||
self.maintenance_margin.remove_trade(trade)
|
||||
|
||||
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
||||
"""
|
||||
Sends rpc notification when a sell occurred.
|
||||
@@ -1571,7 +1542,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
'open_date': trade.open_date,
|
||||
'close_date': trade.close_date or datetime.utcnow(),
|
||||
'stake_currency': self.config['stake_currency'],
|
||||
'fiat_currency': self.config.get('fiat_display_currency', None),
|
||||
'fiat_currency': self.config.get('fiat_display_currency'),
|
||||
}
|
||||
|
||||
if 'fiat_display_currency' in self.config:
|
||||
@@ -1682,7 +1653,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
if order['status'] in constants.NON_OPEN_EXCHANGE_STATES:
|
||||
# If a entry order was closed, force update on stoploss on exchange
|
||||
if order.get('side', None) == trade.entry_side:
|
||||
if order.get('side') == trade.entry_side:
|
||||
trade = self.cancel_stoploss_on_exchange(trade)
|
||||
# TODO: Margin will need to use interest_rate as well.
|
||||
# interest_rate = self.exchange.get_interest_rate()
|
||||
|
@@ -1,52 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.leverage import liquidation_price
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
|
||||
class MaintenanceMargin:
|
||||
|
||||
trades: List[Trade]
|
||||
exchange_name: str
|
||||
trading_mode: TradingMode
|
||||
|
||||
@property
|
||||
def margin_level(self):
|
||||
# This is the current value of all assets,
|
||||
# and if you pass below liq_level, you are liquidated
|
||||
# TODO-lev: Add args to formula
|
||||
return liquidation_price(
|
||||
trading_mode=self.trading_mode,
|
||||
exchange_name=self.exchange_name
|
||||
)
|
||||
|
||||
@property
|
||||
def liq_level(self): # This may be a constant value and may not need a function
|
||||
# TODO-lev: The is the value that you are liquidated at
|
||||
return # If constant, would need to be recalculated after each new trade
|
||||
|
||||
def __init__(self, exchange_name: str, trading_mode: TradingMode):
|
||||
self.exchange_name = exchange_name
|
||||
self.trading_mode = trading_mode
|
||||
return
|
||||
|
||||
def add_new_trade(self, trade):
|
||||
self.trades.append(trade)
|
||||
|
||||
def remove_trade(self, trade):
|
||||
self.trades.remove(trade)
|
||||
|
||||
# ? def update_trade_pric(self):
|
||||
|
||||
def sell_all(self):
|
||||
# TODO-lev
|
||||
return
|
||||
|
||||
def run(self):
|
||||
# TODO-lev: implement a thread that constantly updates with every price change,
|
||||
# TODO-lev: must update at least every few seconds or so
|
||||
# while true:
|
||||
# if self.margin_level <= self.liq_level:
|
||||
# self.sell_all()
|
||||
return
|
@@ -87,7 +87,7 @@ class Backtesting:
|
||||
self.exchange = ExchangeResolver.load_exchange(self._exchange_name, self.config)
|
||||
self.dataprovider = DataProvider(self.config, self.exchange)
|
||||
|
||||
if self.config.get('strategy_list', None):
|
||||
if self.config.get('strategy_list'):
|
||||
for strat in list(self.config['strategy_list']):
|
||||
stratconf = deepcopy(self.config)
|
||||
stratconf['strategy'] = strat
|
||||
@@ -189,6 +189,7 @@ class Backtesting:
|
||||
self.strategy.order_types['stoploss_on_exchange'] = False
|
||||
|
||||
self.strategy.ft_bot_start()
|
||||
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)()
|
||||
|
||||
def _load_protections(self, strategy: IStrategy):
|
||||
if self.config.get('enable_protections', False):
|
||||
@@ -1140,8 +1141,6 @@ class Backtesting:
|
||||
backtest_start_time = datetime.now(timezone.utc)
|
||||
self._set_strategy(strat)
|
||||
|
||||
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)()
|
||||
|
||||
# Use max_open_trades in backtesting, except --disable-max-market-positions is set
|
||||
if self.config.get('use_max_market_positions', True):
|
||||
# Must come from strategy config, as the strategy may modify this setting.
|
||||
|
@@ -455,7 +455,7 @@ class Hyperopt:
|
||||
return self.opt.ask(n_points=n_points), [False for _ in range(n_points)]
|
||||
|
||||
def start(self) -> None:
|
||||
self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None))
|
||||
self.random_state = self._set_random_state(self.config.get('hyperopt_random_state'))
|
||||
logger.info(f"Using optimizer random state: {self.random_state}")
|
||||
self.hyperopt_table_header = -1
|
||||
# Initialize spaces ...
|
||||
|
@@ -127,14 +127,14 @@ class HyperoptTools():
|
||||
'only_profitable': config.get('hyperopt_list_profitable', False),
|
||||
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
|
||||
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
|
||||
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
|
||||
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
|
||||
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
|
||||
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
|
||||
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
|
||||
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None),
|
||||
'filter_min_objective': config.get('hyperopt_list_min_objective', None),
|
||||
'filter_max_objective': config.get('hyperopt_list_max_objective', None),
|
||||
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time'),
|
||||
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time'),
|
||||
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit'),
|
||||
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit'),
|
||||
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit'),
|
||||
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit'),
|
||||
'filter_min_objective': config.get('hyperopt_list_min_objective'),
|
||||
'filter_max_objective': config.get('hyperopt_list_max_objective'),
|
||||
}
|
||||
if not HyperoptTools._test_hyperopt_results_exist(results_file):
|
||||
# No file found.
|
||||
|
@@ -30,7 +30,7 @@ class AgeFilter(IPairList):
|
||||
self._symbolsCheckFailed = PeriodicCache(maxsize=1000, ttl=86_400)
|
||||
|
||||
self._min_days_listed = pairlistconfig.get('min_days_listed', 10)
|
||||
self._max_days_listed = pairlistconfig.get('max_days_listed', None)
|
||||
self._max_days_listed = pairlistconfig.get('max_days_listed')
|
||||
|
||||
candle_limit = exchange.ohlcv_candle_limit('1d', self._config['candle_type_def'])
|
||||
if self._min_days_listed < 1:
|
||||
|
@@ -21,7 +21,7 @@ class PerformanceFilter(IPairList):
|
||||
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
|
||||
|
||||
self._minutes = pairlistconfig.get('minutes', 0)
|
||||
self._min_profit = pairlistconfig.get('min_profit', None)
|
||||
self._min_profit = pairlistconfig.get('min_profit')
|
||||
|
||||
@property
|
||||
def needstickers(self) -> bool:
|
||||
|
@@ -27,7 +27,7 @@ class RangeStabilityFilter(IPairList):
|
||||
|
||||
self._days = pairlistconfig.get('lookback_days', 10)
|
||||
self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01)
|
||||
self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', None)
|
||||
self._max_rate_of_change = pairlistconfig.get('max_rate_of_change')
|
||||
self._refresh_period = pairlistconfig.get('refresh_period', 1440)
|
||||
self._def_candletype = self._config['candle_type_def']
|
||||
|
||||
|
@@ -28,7 +28,7 @@ class PairListManager(LoggingMixin):
|
||||
self._blacklist = self._config['exchange'].get('pair_blacklist', [])
|
||||
self._pairlist_handlers: List[IPairList] = []
|
||||
self._tickers_needed = False
|
||||
for pairlist_handler_config in self._config.get('pairlists', None):
|
||||
for pairlist_handler_config in self._config.get('pairlists', []):
|
||||
pairlist_handler = PairListResolver.load_pairlist(
|
||||
pairlist_handler_config['method'],
|
||||
exchange=exchange,
|
||||
|
@@ -282,7 +282,7 @@ def get_strategy(strategy: str, config=Depends(get_config)):
|
||||
def list_available_pairs(timeframe: Optional[str] = None, stake_currency: Optional[str] = None,
|
||||
candletype: Optional[CandleType] = None, config=Depends(get_config)):
|
||||
|
||||
dh = get_datahandler(config['datadir'], config.get('dataformat_ohlcv', None))
|
||||
dh = get_datahandler(config['datadir'], config.get('dataformat_ohlcv'))
|
||||
trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT)
|
||||
pair_interval = dh.ohlcv_get_available_data(config['datadir'], trading_mode)
|
||||
|
||||
|
@@ -97,7 +97,7 @@ class RPC:
|
||||
"""
|
||||
self._freqtrade = freqtrade
|
||||
self._config: Dict[str, Any] = freqtrade.config
|
||||
if self._config.get('fiat_display_currency', None):
|
||||
if self._config.get('fiat_display_currency'):
|
||||
self._fiat_converter = CryptoToFiatConverter()
|
||||
|
||||
@staticmethod
|
||||
@@ -566,7 +566,7 @@ class RPC:
|
||||
else:
|
||||
try:
|
||||
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency)
|
||||
rate = tickers.get(pair, {}).get('last', None)
|
||||
rate = tickers.get(pair, {}).get('last')
|
||||
if rate:
|
||||
if pair.startswith(stake_currency) and not pair.endswith(stake_currency):
|
||||
rate = 1.0 / rate
|
||||
|
@@ -259,7 +259,7 @@ class Telegram(RPCHandler):
|
||||
f" {entry_side['entered'] if is_fill else entry_side['enter']} {msg['pair']}"
|
||||
f" (#{msg['trade_id']})\n"
|
||||
)
|
||||
message += f"*Enter Tag:* `{msg['enter_tag']}`\n" if msg.get('enter_tag', None) else ""
|
||||
message += f"*Enter Tag:* `{msg['enter_tag']}`\n" if msg.get('enter_tag') else ""
|
||||
message += f"*Amount:* `{msg['amount']:.8f}`\n"
|
||||
if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0:
|
||||
message += f"*Leverage:* `{msg['leverage']}`\n"
|
||||
@@ -272,7 +272,7 @@ class Telegram(RPCHandler):
|
||||
|
||||
message += f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}"
|
||||
|
||||
if msg.get('fiat_currency', None):
|
||||
if msg.get('fiat_currency'):
|
||||
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
||||
|
||||
message += ")`"
|
||||
@@ -288,7 +288,7 @@ class Telegram(RPCHandler):
|
||||
msg['enter_tag'] = msg['enter_tag'] if "enter_tag" in msg.keys() else None
|
||||
msg['emoji'] = self._get_sell_emoji(msg)
|
||||
msg['leverage_text'] = (f"*Leverage:* `{msg['leverage']:.1f}`\n"
|
||||
if msg.get('leverage', None) and msg.get('leverage', 1.0) != 1.0
|
||||
if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0
|
||||
else "")
|
||||
|
||||
# Check if all sell properties are available.
|
||||
|
@@ -45,21 +45,21 @@ class Webhook(RPCHandler):
|
||||
try:
|
||||
whconfig = self._config['webhook']
|
||||
if msg['type'] in [RPCMessageType.ENTRY]:
|
||||
valuedict = whconfig.get('webhookentry', None)
|
||||
valuedict = whconfig.get('webhookentry')
|
||||
elif msg['type'] in [RPCMessageType.ENTRY_CANCEL]:
|
||||
valuedict = whconfig.get('webhookentrycancel', None)
|
||||
valuedict = whconfig.get('webhookentrycancel')
|
||||
elif msg['type'] in [RPCMessageType.ENTRY_FILL]:
|
||||
valuedict = whconfig.get('webhookentryfill', None)
|
||||
valuedict = whconfig.get('webhookentryfill')
|
||||
elif msg['type'] == RPCMessageType.EXIT:
|
||||
valuedict = whconfig.get('webhookexit', None)
|
||||
valuedict = whconfig.get('webhookexit')
|
||||
elif msg['type'] == RPCMessageType.EXIT_FILL:
|
||||
valuedict = whconfig.get('webhookexitfill', None)
|
||||
valuedict = whconfig.get('webhookexitfill')
|
||||
elif msg['type'] == RPCMessageType.EXIT_CANCEL:
|
||||
valuedict = whconfig.get('webhookexitcancel', None)
|
||||
valuedict = whconfig.get('webhookexitcancel')
|
||||
elif msg['type'] in (RPCMessageType.STATUS,
|
||||
RPCMessageType.STARTUP,
|
||||
RPCMessageType.WARNING):
|
||||
valuedict = whconfig.get('webhookstatus', None)
|
||||
valuedict = whconfig.get('webhookstatus')
|
||||
else:
|
||||
raise NotImplementedError('Unknown message type: {}'.format(msg['type']))
|
||||
if not valuedict:
|
||||
|
@@ -131,9 +131,9 @@ class Wallets:
|
||||
if isinstance(balances[currency], dict):
|
||||
self._wallets[currency] = Wallet(
|
||||
currency,
|
||||
balances[currency].get('free', None),
|
||||
balances[currency].get('used', None),
|
||||
balances[currency].get('total', None)
|
||||
balances[currency].get('free'),
|
||||
balances[currency].get('used'),
|
||||
balances[currency].get('total')
|
||||
)
|
||||
# Remove currencies no longer in get_balances output
|
||||
for currency in deepcopy(self._wallets):
|
||||
|
@@ -12,18 +12,18 @@ pre-commit==2.19.0
|
||||
pytest==7.1.2
|
||||
pytest-asyncio==0.18.3
|
||||
pytest-cov==3.0.0
|
||||
pytest-mock==3.7.0
|
||||
pytest-mock==3.8.1
|
||||
pytest-random-order==1.0.4
|
||||
isort==5.10.1
|
||||
# For datetime mocking
|
||||
time-machine==2.7.0
|
||||
time-machine==2.7.1
|
||||
|
||||
# Convert jupyter notebooks to markdown documents
|
||||
nbconvert==6.5.0
|
||||
|
||||
# mypy types
|
||||
types-cachetools==5.0.2
|
||||
types-cachetools==5.2.1
|
||||
types-filelock==3.2.7
|
||||
types-requests==2.27.30
|
||||
types-tabulate==0.8.9
|
||||
types-python-dateutil==2.8.17
|
||||
types-requests==2.28.0
|
||||
types-tabulate==0.8.11
|
||||
types-python-dateutil==2.8.18
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Include all requirements to run the bot.
|
||||
-r requirements.txt
|
||||
|
||||
plotly==5.8.2
|
||||
plotly==5.9.0
|
||||
|
@@ -1,12 +1,12 @@
|
||||
numpy==1.23.0
|
||||
pandas==1.4.2
|
||||
pandas==1.4.3
|
||||
pandas-ta==0.3.14b
|
||||
|
||||
ccxt==1.88.15
|
||||
ccxt==1.89.14
|
||||
# Pin cryptography for now due to rust build errors with piwheels
|
||||
cryptography==37.0.2
|
||||
aiohttp==3.8.1
|
||||
SQLAlchemy==1.4.37
|
||||
SQLAlchemy==1.4.39
|
||||
python-telegram-bot==13.12
|
||||
arrow==1.2.2
|
||||
cachetools==4.2.2
|
||||
@@ -15,7 +15,7 @@ urllib3==1.26.9
|
||||
jsonschema==4.6.0
|
||||
TA-Lib==0.4.24
|
||||
technical==1.3.0
|
||||
tabulate==0.8.9
|
||||
tabulate==0.8.10
|
||||
pycoingecko==2.2.0
|
||||
jinja2==3.1.2
|
||||
tables==3.7.0
|
||||
@@ -28,14 +28,14 @@ py_find_1st==1.1.5
|
||||
# Load ticker files 30% faster
|
||||
python-rapidjson==1.6
|
||||
# Properly format api responses
|
||||
orjson==3.7.2
|
||||
orjson==3.7.3
|
||||
|
||||
# Notify systemd
|
||||
sdnotify==0.3.2
|
||||
|
||||
# API Server
|
||||
fastapi==0.78.0
|
||||
uvicorn==0.17.6
|
||||
uvicorn==0.18.1
|
||||
pyjwt==2.4.0
|
||||
aiofiles==0.8.0
|
||||
psutil==5.9.1
|
||||
|
@@ -861,6 +861,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0)
|
||||
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
|
||||
assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
|
||||
assert hyperopt.backtesting.strategy.bot_loop_started is True
|
||||
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value == 35
|
||||
|
@@ -44,6 +44,11 @@ class HyperoptableStrategy(StrategyTestV2):
|
||||
})
|
||||
return prot
|
||||
|
||||
bot_loop_started = False
|
||||
|
||||
def bot_loop_start(self):
|
||||
self.bot_loop_started = True
|
||||
|
||||
def bot_start(self, **kwargs) -> None:
|
||||
"""
|
||||
Parameters can also be defined here ...
|
||||
|
@@ -3951,9 +3951,9 @@ def test_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limit_order_
|
||||
|
||||
# Test if entry-signal is absent (should sell due to roi = true)
|
||||
if is_short:
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_short=False)
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_short=False, exit_tag='something')
|
||||
else:
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=False, exit_tag='something')
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.exit_reason == ExitType.ROI.value
|
||||
|
||||
|
Reference in New Issue
Block a user