Repository: freqtrade/freqtrade
Pull request: Backtest order timeout
## Summary
Allow backtest orders with custom pricing to fill later (or time out).

this accidentally (on purpose) also closes #6321 

## Quick changelog

- Don't autofill orders
- don't move price to be forced within opening candle

## TODO

* [x] call timeout functions
* [x] handle / respect timeout
* [x] Tests for timeout in backtesting  


Problem statement:
self.wallets.get_total_stake_amount() returns wrong amount
## Describe your environment

  * Operating system: Ubuntu 21.10 (GNU/Linux 5.13.0-27-generic x86_64)

  * python3 --version
Python 3.9.7
  * CCXT version: pip freeze | grep ccxt
ccxt==1.70.45

  * Freqtrade Version: freqtrade --version
freqtrade develop-aea84dc11

## Describe the problem: 

*Explain the problem you have encountered*
I use this stoploss function:

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
                            current_rate: float, current_profit: float, **kwargs) -> float:
        # max 2% of total wallet is at risk
        risk = 0.02
        num_trades = len(trade.get_trades_proxy())
        wallet = self.wallets.get_total_stake_amount()
        stop = (wallet * risk) / trade.stake_amount # / self.config["max_open_trades"]
        logger.info(" wallet " + str(wallet) + ", stake " + str(trade.stake_amount) + ", stop at " + str(stop) + " , trades: " + str(num_trades))
        return stop

During backtesting you can see that the amount returned from self.wallets.get_total_stake_amount() is much higher than the actual balance which seems wrong.

Backtesting with:

freqtrade backtesting -s Test01 -c config_bt.json --timeframe 5m --timerange=20210125- --starting-balance 1000 --max-open-trades 1

The relevant parts from the log:

2022-01-30 12:16:09,468 - freqtrade.configuration.configuration - INFO - Parameter --dry-run-wallet detected, overriding dry_run_wallet to: 1000.0 ...
[...]
2022-01-30 12:16:42,690 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
[...]
2022-01-30 12:16:42,707 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
[...]
2022-01-30 12:16:42,730 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
  
### Observed Results:

  * What happened?
  * self.wallets.get_total_stake_amount() > starting_balance
  * What did you expect to happen?
  * self.wallets.get_total_stake_amount() returns the correct value

  ```
$ freqtrade backtesting -s Test01 -c config_bt.json --timeframe 5m --timerange=20210125- --starting-balance 1000 --max-open-trades 1
2022-01-30 12:16:09,464 - freqtrade.configuration.configuration - INFO - Using config: config_bt.json ...
2022-01-30 12:16:09,467 - freqtrade.loggers - INFO - Verbosity set to 0
2022-01-30 12:16:09,467 - freqtrade.configuration.configuration - WARNING - `forcebuy` RPC message enabled.
2022-01-30 12:16:09,467 - freqtrade.configuration.configuration - INFO - Parameter -i/--timeframe detected ... Using timeframe: 5m ...
2022-01-30 12:16:09,468 - freqtrade.configuration.configuration - INFO - Parameter --max-open-trades detected, overriding max_open_trades to: 1 ...
2022-01-30 12:16:09,468 - freqtrade.configuration.configuration - INFO - Parameter --dry-run-wallet detected, overriding dry_run_wallet to: 1000.0 ...
2022-01-30 12:16:09,468 - freqtrade.configuration.configuration - INFO - Parameter --timerange detected: 20210125- ...
2022-01-30 12:16:09,469 - freqtrade.configuration.configuration - INFO - Using user-data directory: /home/ft/freqtrade/user_data ...
2022-01-30 12:16:09,470 - freqtrade.configuration.configuration - INFO - Using data directory: /home/ft/freqtrade/user_data/data/binance ...
2022-01-30 12:16:09,470 - freqtrade.configuration.configuration - INFO - Overriding timeframe with Command line argument
2022-01-30 12:16:09,470 - freqtrade.configuration.configuration - INFO - Parameter --cache=day detected ...
2022-01-30 12:16:09,471 - freqtrade.configuration.check_exchange - INFO - Checking exchange...
2022-01-30 12:16:09,489 - freqtrade.configuration.check_exchange - INFO - Exchange "binance" is officially supported by the Freqtrade development team.
2022-01-30 12:16:09,489 - freqtrade.configuration.configuration - INFO - Using pairlist from configuration.
2022-01-30 12:16:09,489 - freqtrade.configuration.config_validation - INFO - Validating configuration ...
2022-01-30 12:16:09,497 - freqtrade.commands.optimize_commands - INFO - Starting freqtrade in Backtesting mode
2022-01-30 12:16:09,498 - freqtrade.exchange.exchange - INFO - Instance is running with dry_run enabled
2022-01-30 12:16:09,498 - freqtrade.exchange.exchange - INFO - Using CCXT 1.70.45
2022-01-30 12:16:09,498 - freqtrade.exchange.exchange - INFO - Applying additional ccxt config: {'enableRateLimit': True}
2022-01-30 12:16:09,515 - freqtrade.exchange.exchange - INFO - Applying additional ccxt config: {'enableRateLimit': True, 'rateLimit': 200}
2022-01-30 12:16:09,530 - freqtrade.exchange.exchange - INFO - Using Exchange "Binance"
2022-01-30 12:16:11,367 - freqtrade.resolvers.exchange_resolver - INFO - Using resolved exchange 'Binance'...
2022-01-30 12:16:12,663 - freqtrade.resolvers.iresolver - INFO - Using resolved strategy Test01 from '/home/ft/freqtrade/user_data/strategies/Test01.py'...
2022-01-30 12:16:12,663 - freqtrade.strategy.hyper - INFO - Found no parameter file.
2022-01-30 12:16:12,664 - freqtrade.strategy.hyper - INFO - No params for buy found, using default values.
2022-01-30 12:16:12,664 - freqtrade.strategy.hyper - INFO - Strategy Parameter(default): buy_rsi = 30
2022-01-30 12:16:12,665 - freqtrade.strategy.hyper - INFO - No params for sell found, using default values.
2022-01-30 12:16:12,665 - freqtrade.strategy.hyper - INFO - Strategy Parameter(default): sell_rsi = 70
2022-01-30 12:16:12,665 - freqtrade.strategy.hyper - INFO - No params for protection found, using default values.
2022-01-30 12:16:12,666 - freqtrade.resolvers.strategy_resolver - INFO - Override strategy 'timeframe' with value in config file: 5m.
2022-01-30 12:16:12,666 - freqtrade.resolvers.strategy_resolver - INFO - Override strategy 'order_types' with value in config file: {'buy': 'market', 'sell': 'market', 'emergencysell': 'market', 'forcebuy': 'market', 'forcesell': 'market', 'stoploss': 'market', 'stoploss_on_exchange': False, 'stoploss_on_exchange_interval': 60}.
2022-01-30 12:16:12,666 - freqtrade.resolvers.strategy_resolver - INFO - Override strategy 'stake_currency' with value in config file: USDT.
2022-01-30 12:16:12,667 - freqtrade.resolvers.strategy_resolver - INFO - Override strategy 'stake_amount' with value in config file: unlimited.
2022-01-30 12:16:12,667 - freqtrade.resolvers.strategy_resolver - INFO - Override strategy 'unfilledtimeout' with value in config file: {'buy': 5, 'sell': 5, 'unit': 'minutes', 'exit_timeout_count': 0}.
2022-01-30 12:16:12,667 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using minimal_roi: {'60': 0.01, '30': 0.02, '0': 0.04}
2022-01-30 12:16:12,667 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using timeframe: 5m
2022-01-30 12:16:12,667 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using stoploss: -0.1
2022-01-30 12:16:12,667 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using trailing_stop: False
2022-01-30 12:16:12,667 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using trailing_stop_positive_offset: 0.0
2022-01-30 12:16:12,668 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using trailing_only_offset_is_reached: False
2022-01-30 12:16:12,668 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using use_custom_stoploss: True
2022-01-30 12:16:12,668 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using process_only_new_candles: False
2022-01-30 12:16:12,668 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using order_types: {'buy': 'market', 'sell': 'market', 'emergencysell': 'market', 'forcebuy': 'market', 'forcesell': 'market', 'stoploss': 'market', 'stoploss_on_exchange': False, 'stoploss_on_exchange_interval': 60}
2022-01-30 12:16:12,668 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using order_time_in_force: {'buy': 'gtc', 'sell': 'gtc'}
2022-01-30 12:16:12,668 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using stake_currency: USDT
2022-01-30 12:16:12,668 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using stake_amount: unlimited
2022-01-30 12:16:12,668 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using protections: []
2022-01-30 12:16:12,669 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using startup_candle_count: 30
2022-01-30 12:16:12,669 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using unfilledtimeout: {'buy': 5, 'sell': 5, 'unit': 'minutes', 'exit_timeout_count': 0}
2022-01-30 12:16:12,669 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using use_sell_signal: True
2022-01-30 12:16:12,669 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using sell_profit_only: False
2022-01-30 12:16:12,669 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using ignore_roi_if_buy_signal: False
2022-01-30 12:16:12,669 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using sell_profit_offset: 0.0
2022-01-30 12:16:12,677 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using disable_dataframe_checks: False
2022-01-30 12:16:12,677 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using ignore_buying_expired_candle_after: 0
2022-01-30 12:16:12,677 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using position_adjustment_enable: False
2022-01-30 12:16:12,677 - freqtrade.resolvers.strategy_resolver - INFO - Strategy using max_entry_position_adjustment: -1
2022-01-30 12:16:12,678 - freqtrade.configuration.config_validation - INFO - Validating configuration ...
2022-01-30 12:16:12,687 - freqtrade.resolvers.iresolver - INFO - Using resolved pairlist StaticPairList from '/home/ft/freqtrade/freqtrade/plugins/pairlist/StaticPairList.py'...
2022-01-30 12:16:12,701 - freqtrade.resolvers.iresolver - INFO - Using resolved pairlist ShuffleFilter from '/home/ft/freqtrade/freqtrade/plugins/pairlist/ShuffleFilter.py'...
2022-01-30 12:16:12,702 - ShuffleFilter - INFO - Backtesting mode detected, applying seed value: 3280
2022-01-30 12:16:12,704 - freqtrade.resolvers.iresolver - INFO - Using resolved pairlist PriceFilter from '/home/ft/freqtrade/freqtrade/plugins/pairlist/PriceFilter.py'...
2022-01-30 12:16:12,705 - freqtrade.resolvers.iresolver - INFO - Using resolved pairlist PriceFilter from '/home/ft/freqtrade/freqtrade/plugins/pairlist/PriceFilter.py'...
2022-01-30 12:16:12,713 - freqtrade.resolvers.iresolver - INFO - Using resolved pairlist RangeStabilityFilter from '/home/ft/freqtrade/freqtrade/plugins/pairlist/rangestabilityfilter.py'...
2022-01-30 12:16:12,718 - freqtrade.resolvers.iresolver - INFO - Using resolved pairlist AgeFilter from '/home/ft/freqtrade/freqtrade/plugins/pairlist/AgeFilter.py'...
2022-01-30 12:16:32,997 - freqtrade.data.history.history_utils - INFO - Using indicator startup period: 30 ...
2022-01-30 12:16:33,063 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair FTM/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,150 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair BAND/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,232 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair GTC/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,316 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair RVN/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,399 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair ROSE/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,622 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair RUNE/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,715 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair IOTX/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,808 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair KNC/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,892 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair AVAX/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:33,981 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair CHR/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:34,062 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair IOST/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:34,276 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair IOTA/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:34,363 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair CKB/USDT at 5m, data starts at 2021-12-05 00:00:00
2022-01-30 12:16:34,449 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair LINK/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:34,529 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair LUNA/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:34,611 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair MINA/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:34,694 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair ONE/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:34,913 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair 1INCH/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:35,014 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair AAVE/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:35,068 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair GLMR/USDT at 5m, data starts at 2022-01-11 16:00:00
2022-01-30 12:16:35,145 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair WAXP/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:35,232 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair HBAR/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:35,311 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair COMP/USDT at 5m, data starts at 2021-12-05 00:00:00
2022-01-30 12:16:35,519 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair ICP/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:35,595 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair DASH/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:35,668 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair ALGO/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:35,762 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair RNDR/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:35,839 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair GXS/USDT at 5m, data starts at 2021-12-05 00:00:00
2022-01-30 12:16:35,923 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair KLAY/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:36,119 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair NEAR/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:36,193 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair JST/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:36,278 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair ALICE/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:36,349 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair NMR/USDT at 5m, data starts at 2021-12-05 00:00:00
2022-01-30 12:16:36,424 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair STX/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:36,643 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair ATOM/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:36,722 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair MATIC/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:36,811 - freqtrade.data.history.idatahandler - WARNING - Missing data at start for pair DENT/USDT at 5m, data starts at 2021-12-02 00:00:00
2022-01-30 12:16:36,863 - freqtrade.optimize.backtesting - INFO - Loading data from 2021-12-02 00:00:00 up to 2022-01-30 11:45:00 (59 days).
2022-01-30 12:16:36,864 - freqtrade.configuration.timerange - WARNING - Moving start-date by 30 candles to account for startup time.
2022-01-30 12:16:36,864 - freqtrade.optimize.backtesting - INFO - Dataload complete. Calculating indicators
2022-01-30 12:16:36,866 - freqtrade.optimize.backtesting - WARNING - Backtest result caching disabled due to use of open-ended timerange.
2022-01-30 12:16:36,867 - freqtrade.optimize.backtesting - INFO - Running backtesting for Strategy Test01
2022-01-30 12:16:38,479 - freqtrade.optimize.backtesting - INFO - Backtesting with data from 2021-12-02 02:30:00 up to 2022-01-30 11:45:00 (59 days).
2022-01-30 12:16:42,690 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,691 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,693 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,694 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,695 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,696 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,697 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,698 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,699 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,700 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,701 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,702 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,703 - Test01 - INFO -  wallet 1970.1, stake 990.0, stop at 0.0398 , trades: 1
2022-01-30 12:16:42,707 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,708 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,709 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,710 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,711 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,712 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,713 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,714 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,714 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,715 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,716 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,717 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,718 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,719 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,720 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,721 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,722 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,722 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,723 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,725 - Test01 - INFO -  wallet 1998.0136276164899, stake 1004.026948551, stop at 0.039799999999999995 , trades: 2
2022-01-30 12:16:42,729 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
2022-01-30 12:16:42,730 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
2022-01-30 12:16:42,731 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
2022-01-30 12:16:42,732 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
2022-01-30 12:16:42,733 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
2022-01-30 12:16:42,734 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
2022-01-30 12:16:42,735 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
2022-01-30 12:16:42,736 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
2022-01-30 12:16:42,737 - Test01 - INFO -  wallet 2017.7939625387392, stake 1013.9668153461, stop at 0.0398 , trades: 3
....
  ```

Hints from issue discussion:
This contains the strategy and config i am using in case you need it.
[config_file+strategy.zip](https://github.com/freqtrade/freqtrade/files/7965943/config_file%2Bstrategy.zip)

A quick look at this shows that it's not 100% straightforward to fix without severe impact on performance - therefore i don't think it'll be a "very quick" fix.

A temporary workaround would be to call `self.wallets.update()` before fetching the balance.


Could you confirm that `get_available_stake_amount()` is not broken too?
`get_total_stake_amount()` is not per se broken - but in backtesting it returns "non-uptodate" results in some instances.
until now, there was no need to use these methods outside of `custom_stake_amount` (where they're correct).
Oh, they're correct in `custom_stake_amount()`? That's the most important thing I think at least for me. Thanks!

Commit message: Update missing doc segment

Patch to review:
diff --git a/docs/bot-basics.md b/docs/bot-basics.md
index a9a2628f62f..8c6303063a4 100644
--- a/docs/bot-basics.md
+++ b/docs/bot-basics.md
@@ -62,6 +62,7 @@ This loop will be repeated again and again until the bot is stopped.
   * Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested.
   * Call `custom_stoploss()` and `custom_sell()` to find custom exit points.
   * For sells based on sell-signal and custom-sell: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle).
+  * Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_buy_timeout()` / `check_sell_timeout()` strategy callbacks.
 * Generate backtest report output
 
 !!! Note
diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md
index bff5bd998e7..24b81d2dcf1 100644
--- a/docs/strategy-callbacks.md
+++ b/docs/strategy-callbacks.md
@@ -389,8 +389,8 @@ class AwesomeStrategy(IStrategy):
     If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98, which is 2% below the current (proposed) rate.
 
 !!! Warning "Backtesting"
-    While Custom prices are supported in backtesting (starting with 2021.12), prices will be moved to within the candle's high/low prices.
-    This behavior is currently being tested, and might be changed at a later point.
+    Custom prices are supported in backtesting (starting with 2021.12), and orders will fill if the price falls within the candle's low/high range.
+    Orders that don't fill immediately are subject to regular timeout handling.
     `custom_exit_price()` is only called for sells of type Sell_signal and Custom sell. All other sell-types will use regular backtesting prices.
 
 ## Custom order timeout rules
@@ -400,7 +400,8 @@ Simple, time-based order-timeouts can be configured either via strategy or in th
 However, freqtrade also offers a custom callback for both order types, which allows you to decide based on custom criteria if an order did time out or not.
 
 !!! Note
-    Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances.
+    Backtesting fills orders if their price falls within the candle's low/high range.
+    The below callbacks will be called for orders that don't fill automatically (which use custom pricing).
 
 ### Custom order timeout example
 
@@ -467,7 +468,8 @@ class AwesomeStrategy(IStrategy):
         'sell': 60 * 25
     }
 
-    def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool:
+    def check_buy_timeout(self, pair: str, trade: Trade, order: dict,
+                          current_time: datetime, **kwargs) -> bool:
         ob = self.dp.orderbook(pair, 1)
         current_price = ob['bids'][0][0]
         # Cancel buy order if price is more than 2% above the order.
@@ -476,7 +478,8 @@ class AwesomeStrategy(IStrategy):
         return False
 
 
-    def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool:
+    def check_sell_timeout(self, pair: str, trade: Trade, order: dict,
+                           current_time: datetime, **kwargs) -> bool:
         ob = self.dp.orderbook(pair, 1)
         current_price = ob['asks'][0][0]
         # Cancel sell order if price is more than 2% below the order.
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index d94e8d85062..d7ba0bf9869 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -456,6 +456,7 @@
     'dry_run_wallet',
     'dataformat_ohlcv',
     'dataformat_trades',
+    'unfilledtimeout',
 ]
 
 SCHEMA_MINIMAL_REQUIRED = [
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 572ceeabf07..279bb616150 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -987,18 +987,20 @@ def check_handle_timedout(self) -> None:
 
             fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
 
+            order_obj = trade.select_order_by_order_id(trade.open_order_id)
+
             if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and (
                     fully_cancelled
-                    or self.strategy.ft_check_timed_out(
-                        'buy', trade, order, datetime.now(timezone.utc))
-                        )):
+                    or (order_obj and self.strategy.ft_check_timed_out(
+                        'buy', trade, order_obj, datetime.now(timezone.utc))
+                        ))):
                 self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
 
             elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and (
                   fully_cancelled
-                  or self.strategy.ft_check_timed_out(
-                      'sell', trade, order, datetime.now(timezone.utc)))
-                  ):
+                  or (order_obj and self.strategy.ft_check_timed_out(
+                      'sell', trade, order_obj, datetime.now(timezone.utc))
+                      ))):
                 self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT'])
                 canceled_count = trade.get_exit_order_count()
                 max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py
index 3dd8986d340..cc4eb5351f7 100644
--- a/freqtrade/optimize/backtesting.py
+++ b/freqtrade/optimize/backtesting.py
@@ -63,6 +63,8 @@ def __init__(self, config: Dict[str, Any]) -> None:
         LoggingMixin.show_output = False
         self.config = config
         self.results: Dict[str, Any] = {}
+        self.trade_id_counter: int = 0
+        self.order_id_counter: int = 0
 
         config['dry_run'] = True
         self.run_ids: Dict[str, str] = {}
@@ -231,6 +233,7 @@ def prepare_backtest(self, enable_protections):
         PairLocks.reset_locks()
         Trade.reset_trades()
         self.rejected_trades = 0
+        self.timedout_orders = 0
         self.dataprovider.clear_cache()
         if enable_protections:
             self._load_protections(self.strategy)
@@ -353,7 +356,10 @@ def _get_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTup
                     # use Open rate if open_rate > calculated sell rate
                     return sell_row[OPEN_IDX]
 
-                return close_rate
+                # Use the maximum between close_rate and low as we
+                # cannot sell outside of a candle.
+                # Applies when a new ROI setting comes in place and the whole candle is above that.
+                return min(max(close_rate, sell_row[LOW_IDX]), sell_row[HIGH_IDX])
 
             else:
                 # This should not be reached...
@@ -376,10 +382,15 @@ def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple
         if stake_amount is not None and stake_amount > 0.0:
             pos_trade = self._enter_trade(trade.pair, row, stake_amount, trade)
             if pos_trade is not None:
+                self.wallets.update()
                 return pos_trade
 
         return trade
 
+    def _get_order_filled(self, rate: float, row: Tuple) -> bool:
+        """ Rate is within candle, therefore filled"""
+        return row[LOW_IDX] <= rate <= row[HIGH_IDX]
+
     def _get_sell_trade_entry_for_candle(self, trade: LocalTrade,
                                          sell_row: Tuple) -> Optional[LocalTrade]:
 
@@ -410,10 +421,8 @@ def _get_sell_trade_entry_for_candle(self, trade: LocalTrade,
                 closerate = strategy_safe_wrapper(self.strategy.custom_exit_price,
                                                   default_retval=closerate)(
                     pair=trade.pair, trade=trade,
-                    current_time=sell_row[DATE_IDX],
+                    current_time=sell_candle_time,
                     proposed_rate=closerate, current_profit=current_profit)
-            # Use the maximum between close_rate and low as we cannot sell outside of a candle.
-            closerate = min(max(closerate, sell_row[LOW_IDX]), sell_row[HIGH_IDX])
 
             # Confirm trade exit:
             time_in_force = self.strategy.order_time_in_force['sell']
@@ -436,7 +445,28 @@ def _get_sell_trade_entry_for_candle(self, trade: LocalTrade,
             ):
                 trade.sell_reason = sell_row[EXIT_TAG_IDX]
 
-            trade.close(closerate, show_msg=False)
+            self.order_id_counter += 1
+            order = Order(
+                id=self.order_id_counter,
+                ft_trade_id=trade.id,
+                order_date=sell_candle_time,
+                order_update_date=sell_candle_time,
+                ft_is_open=True,
+                ft_pair=trade.pair,
+                order_id=str(self.order_id_counter),
+                symbol=trade.pair,
+                ft_order_side="sell",
+                side="sell",
+                order_type=self.strategy.order_types['sell'],
+                status="open",
+                price=closerate,
+                average=closerate,
+                amount=trade.amount,
+                filled=0,
+                remaining=trade.amount,
+                cost=trade.amount * closerate,
+            )
+            trade.orders.append(order)
             return trade
 
         return None
@@ -475,13 +505,13 @@ def _enter_trade(self, pair: str, row: Tuple, stake_amount: Optional[float] = No
         current_time = row[DATE_IDX].to_pydatetime()
         entry_tag = row[BUY_TAG_IDX] if len(row) >= BUY_TAG_IDX + 1 else None
         # let's call the custom entry price, using the open price as default price
-        propose_rate = strategy_safe_wrapper(self.strategy.custom_entry_price,
-                                             default_retval=row[OPEN_IDX])(
-            pair=pair, current_time=current_time,
-            proposed_rate=row[OPEN_IDX], entry_tag=entry_tag)  # default value is the open rate
-
-        # Move rate to within the candle's low/high rate
-        propose_rate = min(max(propose_rate, row[LOW_IDX]), row[HIGH_IDX])
+        order_type = self.strategy.order_types['buy']
+        propose_rate = row[OPEN_IDX]
+        if order_type == 'limit':
+            propose_rate = strategy_safe_wrapper(self.strategy.custom_entry_price,
+                                                 default_retval=row[OPEN_IDX])(
+                pair=pair, current_time=current_time,
+                proposed_rate=row[OPEN_IDX], entry_tag=entry_tag)  # default value is the open rate
 
         min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0
         max_stake_amount = self.wallets.get_available_stake_amount()
@@ -489,9 +519,9 @@ def _enter_trade(self, pair: str, row: Tuple, stake_amount: Optional[float] = No
         pos_adjust = trade is not None
         if not pos_adjust:
             try:
-                stake_amount = self.wallets.get_trade_stake_amount(pair, None)
+                stake_amount = self.wallets.get_trade_stake_amount(pair, None, update=False)
             except DependencyException:
-                return trade
+                return None
 
             stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
                                                  default_retval=stake_amount)(
@@ -506,7 +536,6 @@ def _enter_trade(self, pair: str, row: Tuple, stake_amount: Optional[float] = No
             # If not pos adjust, trade is None
             return trade
 
-        order_type = self.strategy.order_types['buy']
         time_in_force = self.strategy.order_time_in_force['sell']
         # Confirm trade entry:
         if not pos_adjust:
@@ -517,15 +546,21 @@ def _enter_trade(self, pair: str, row: Tuple, stake_amount: Optional[float] = No
                 return None
 
         if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
+            self.order_id_counter += 1
             amount = round(stake_amount / propose_rate, 8)
             if trade is None:
                 # Enter trade
+                self.trade_id_counter += 1
                 trade = LocalTrade(
+                    id=self.trade_id_counter,
+                    open_order_id=self.order_id_counter,
                     pair=pair,
                     open_rate=propose_rate,
+                    open_rate_requested=propose_rate,
                     open_date=current_time,
                     stake_amount=stake_amount,
                     amount=amount,
+                    amount_requested=amount,
                     fee_open=self.fee,
                     fee_close=self.fee,
                     is_open=True,
@@ -533,28 +568,36 @@ def _enter_trade(self, pair: str, row: Tuple, stake_amount: Optional[float] = No
                     exchange='backtesting',
                     orders=[]
                 )
+
             trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True)
 
             order = Order(
-                ft_is_open=False,
+                id=self.order_id_counter,
+                ft_trade_id=trade.id,
+                ft_is_open=True,
                 ft_pair=trade.pair,
+                order_id=str(self.order_id_counter),
                 symbol=trade.pair,
                 ft_order_side="buy",
                 side="buy",
-                order_type="market",
-                status="closed",
+                order_type=order_type,
+                status="open",
                 order_date=current_time,
                 order_filled_date=current_time,
                 order_update_date=current_time,
                 price=propose_rate,
                 average=propose_rate,
                 amount=amount,
-                filled=amount,
-                cost=stake_amount + trade.fee_open
+                filled=0,
+                remaining=amount,
+                cost=stake_amount + trade.fee_open,
             )
+            if pos_adjust and self._get_order_filled(order.price, row):
+                order.close_bt_order(current_time)
+            else:
+                trade.open_order_id = str(self.order_id_counter)
             trade.orders.append(order)
-            if pos_adjust:
-                trade.recalc_trade_from_orders()
+            trade.recalc_trade_from_orders()
 
         return trade
 
@@ -567,6 +610,9 @@ def handle_left_open(self, open_trades: Dict[str, List[LocalTrade]],
         for pair in open_trades.keys():
             if len(open_trades[pair]) > 0:
                 for trade in open_trades[pair]:
+                    if trade.open_order_id and trade.nr_of_successful_buys == 0:
+                        # Ignore trade if buy-order did not fill yet
+                        continue
                     sell_row = data[pair][-1]
 
                     trade.close_date = sell_row[DATE_IDX].to_pydatetime()
@@ -587,6 +633,50 @@ def trade_slot_available(self, max_open_trades: int, open_trade_count: int) -> b
         self.rejected_trades += 1
         return False
 
+    def run_protections(self, enable_protections, pair: str, current_time: datetime):
+        if enable_protections:
+            self.protections.stop_per_pair(pair, current_time)
+            self.protections.global_stop(current_time)
+
+    def check_order_cancel(self, trade: LocalTrade, current_time) -> bool:
+        """
+        Check if an order has been canceled.
+        Returns True if the trade should be Deleted (initial order was canceled).
+        """
+        for order in [o for o in trade.orders if o.ft_is_open]:
+
+            timedout = self.strategy.ft_check_timed_out(order.side, trade, order, current_time)
+            if timedout:
+                self.timedout_orders += 1
+                if order.side == 'buy':
+                    if trade.nr_of_successful_buys == 0:
+                        # Remove trade due to buy timeout expiration.
+                        return True
+                    else:
+                        # Close additional buy order
+                        del trade.orders[trade.orders.index(order)]
+                if order.side == 'sell':
+                    # Close sell order and retry selling on next signal.
+                    del trade.orders[trade.orders.index(order)]
+
+        return False
+
+    def validate_row(
+            self, data: Dict, pair: str, row_index: int, current_time: datetime) -> Optional[Tuple]:
+        try:
+            # Row is treated as "current incomplete candle".
+            # Buy / sell signals are shifted by 1 to compensate for this.
+            row = data[pair][row_index]
+        except IndexError:
+            # missing Data for one pair at the end.
+            # Warnings for this are shown during data loading
+            return None
+
+        # Waits until the time-counter reaches the start of the data for this pair.
+        if row[DATE_IDX] > current_time:
+            return None
+        return row
+
     def backtest(self, processed: Dict,
                  start_date: datetime, end_date: datetime,
                  max_open_trades: int = 0, position_stacking: bool = False,
@@ -616,7 +706,7 @@ def backtest(self, processed: Dict,
 
         # Indexes per pair, so some pairs are allowed to have a missing start.
         indexes: Dict = defaultdict(int)
-        tmp = start_date + timedelta(minutes=self.timeframe_min)
+        current_time = start_date + timedelta(minutes=self.timeframe_min)
 
         open_trades: Dict[str, List[LocalTrade]] = defaultdict(list)
         open_trade_count = 0
@@ -625,35 +715,27 @@ def backtest(self, processed: Dict,
             (end_date - start_date) / timedelta(minutes=self.timeframe_min)))
 
         # Loop timerange and get candle for each pair at that point in time
-        while tmp <= end_date:
+        while current_time <= end_date:
             open_trade_count_start = open_trade_count
             self.check_abort()
             for i, pair in enumerate(data):
                 row_index = indexes[pair]
-                try:
-                    # Row is treated as "current incomplete candle".
-                    # Buy / sell signals are shifted by 1 to compensate for this.
-                    row = data[pair][row_index]
-                except IndexError:
-                    # missing Data for one pair at the end.
-                    # Warnings for this are shown during data loading
-                    continue
-
-                # Waits until the time-counter reaches the start of the data for this pair.
-                if row[DATE_IDX] > tmp:
+                row = self.validate_row(data, pair, row_index, current_time)
+                if not row:
                     continue
 
                 row_index += 1
                 indexes[pair] = row_index
                 self.dataprovider._set_dataframe_max_index(row_index)
 
+                # 1. Process buys.
                 # without positionstacking, we can only have one open trade per pair.
                 # max_open_trades must be respected
                 # don't open on the last row
                 if (
                     (position_stacking or len(open_trades[pair]) == 0)
                     and self.trade_slot_available(max_open_trades, open_trade_count_start)
-                    and tmp != end_date
+                    and current_time != end_date
                     and row[BUY_IDX] == 1
                     and row[SELL_IDX] != 1
                     and not PairLocks.is_pair_locked(pair, row[DATE_IDX])
@@ -661,32 +743,51 @@ def backtest(self, processed: Dict,
                     trade = self._enter_trade(pair, row)
                     if trade:
                         # TODO: hacky workaround to avoid opening > max_open_trades
-                        # This emulates previous behaviour - not sure if this is correct
+                        # This emulates previous behavior - not sure if this is correct
                         # Prevents buying if the trade-slot was freed in this candle
                         open_trade_count_start += 1
                         open_trade_count += 1
                         # logger.debug(f"{pair} - Emulate creation of new trade: {trade}.")
                         open_trades[pair].append(trade)
-                        LocalTrade.add_bt_trade(trade)
 
                 for trade in list(open_trades[pair]):
-                    # also check the buying candle for sell conditions.
-                    trade_entry = self._get_sell_trade_entry(trade, row)
-                    # Sell occurred
-                    if trade_entry:
+                    # 2. Process buy orders.
+                    order = trade.select_order('buy', is_open=True)
+                    if order and self._get_order_filled(order.price, row):
+                        order.close_bt_order(current_time)
+                        trade.open_order_id = None
+                        LocalTrade.add_bt_trade(trade)
+                        self.wallets.update()
+
+                    # 3. Create sell orders (if any)
+                    if not trade.open_order_id:
+                        self._get_sell_trade_entry(trade, row)  # Place sell order if necessary
+
+                    # 4. Process sell orders.
+                    order = trade.select_order('sell', is_open=True)
+                    if order and self._get_order_filled(order.price, row):
+                        trade.open_order_id = None
+                        trade.close_date = current_time
+                        trade.close(order.price, show_msg=False)
+
                         # logger.debug(f"{pair} - Backtesting sell {trade}")
                         open_trade_count -= 1
                         open_trades[pair].remove(trade)
-
                         LocalTrade.close_bt_trade(trade)
-                        trades.append(trade_entry)
-                        if enable_protections:
-                            self.protections.stop_per_pair(pair, row[DATE_IDX])
-                            self.protections.global_stop(tmp)
+                        trades.append(trade)
+                        self.wallets.update()
+                        self.run_protections(enable_protections, pair, current_time)
+
+                    # 5. Cancel expired buy/sell orders.
+                    if self.check_order_cancel(trade, current_time):
+                        # Close trade due to buy timeout expiration.
+                        open_trade_count -= 1
+                        open_trades[pair].remove(trade)
+                        self.wallets.update()
 
             # Move time one configured time_interval ahead.
             self.progress.increment()
-            tmp += timedelta(minutes=self.timeframe_min)
+            current_time += timedelta(minutes=self.timeframe_min)
 
         trades += self.handle_left_open(open_trades, data=data)
         self.wallets.update()
@@ -697,6 +798,8 @@ def backtest(self, processed: Dict,
             'config': self.strategy.config,
             'locks': PairLocks.get_all_locks(),
             'rejected_signals': self.rejected_trades,
+            # TODO: timedout_orders should be shown as part of results.
+            # 'timedout_orders': self.timedout_orders,
             'final_balance': self.wallets.get_total(self.strategy.config['stake_currency']),
         }
 
diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py
index ff500b54935..dfa98d97fbb 100644
--- a/freqtrade/persistence/models.py
+++ b/freqtrade/persistence/models.py
@@ -132,6 +132,10 @@ class Order(_DECL_BASE):
     order_filled_date = Column(DateTime, nullable=True)
     order_update_date = Column(DateTime, nullable=True)
 
+    @property
+    def order_date_utc(self):
+        return self.order_date.replace(tzinfo=timezone.utc)
+
     def __repr__(self):
 
         return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, '
@@ -188,6 +192,12 @@ def to_json(self) -> Dict[str, Any]:
             'status': self.status,
         }
 
+    def close_bt_order(self, close_date: datetime):
+        self.order_filled_date = close_date
+        self.filled = self.amount
+        self.status = 'closed'
+        self.ft_is_open = False
+
     @staticmethod
     def update_orders(orders: List['Order'], order: Dict[str, Any]):
         """
@@ -635,14 +645,27 @@ def recalc_trade_from_orders(self):
             if self.stop_loss_pct is not None and self.open_rate is not None:
                 self.adjust_stop_loss(self.open_rate, self.stop_loss_pct)
 
-    def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]:
+    def select_order_by_order_id(self, order_id: str) -> Optional[Order]:
+        """
+        Finds order object by Order id.
+        :param order_id: Exchange order id
+        """
+        for o in self.orders:
+            if o.order_id == order_id:
+                return o
+        return None
+
+    def select_order(
+            self, order_side: str = None, is_open: Optional[bool] = None) -> Optional[Order]:
         """
         Finds latest order for this orderside and status
         :param order_side: Side of the order (either 'buy' or 'sell')
         :param is_open: Only search for open orders?
         :return: latest Order object if it exists, else None
         """
-        orders = [o for o in self.orders if o.side == order_side]
+        orders = self.orders
+        if order_side:
+            orders = [o for o in self.orders if o.side == order_side]
         if is_open is not None:
             orders = [o for o in orders if o.ft_is_open == is_open]
         if len(orders) > 0:
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index 78dae6c5dec..0bd7834e23b 100644
--- a/freqtrade/strategy/interface.py
+++ b/freqtrade/strategy/interface.py
@@ -18,6 +18,7 @@
 from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
 from freqtrade.exchange.exchange import timeframe_to_next_date
 from freqtrade.persistence import PairLocks, Trade
+from freqtrade.persistence.models import LocalTrade, Order
 from freqtrade.strategy.hyper import HyperStrategyMixin
 from freqtrade.strategy.informative_decorator import (InformativeData, PopulateIndicators,
                                                       _create_and_merge_informative_pair,
@@ -862,23 +863,22 @@ def min_roi_reached(self, trade: Trade, current_profit: float, current_time: dat
         else:
             return current_profit > roi
 
-    def ft_check_timed_out(self, side: str, trade: Trade, order: Dict,
+    def ft_check_timed_out(self, side: str, trade: LocalTrade, order: Order,
                            current_time: datetime) -> bool:
         """
         FT Internal method.
         Check if timeout is active, and if the order is still open and timed out
         """
         timeout = self.config.get('unfilledtimeout', {}).get(side)
-        ordertime = arrow.get(order['datetime']).datetime
         if timeout is not None:
             timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
             timeout_kwargs = {timeout_unit: -timeout}
             timeout_threshold = current_time + timedelta(**timeout_kwargs)
-            timedout = (order['status'] == 'open' and order['side'] == side
-                        and ordertime < timeout_threshold)
+            timedout = (order.status == 'open' and order.side == side
+                        and order.order_date_utc < timeout_threshold)
             if timedout:
                 return True
-        time_method = self.check_sell_timeout if order['side'] == 'sell' else self.check_buy_timeout
+        time_method = self.check_sell_timeout if order.side == 'sell' else self.check_buy_timeout
 
         return strategy_safe_wrapper(time_method,
                                      default_retval=False)(
diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py
index e57739595ff..93f3d380091 100644
--- a/freqtrade/wallets.py
+++ b/freqtrade/wallets.py
@@ -211,7 +211,7 @@ def _check_available_stake_amount(self, stake_amount: float, available_amount: f
 
         return stake_amount
 
-    def get_trade_stake_amount(self, pair: str, edge=None) -> float:
+    def get_trade_stake_amount(self, pair: str, edge=None, update: bool = True) -> float:
         """
         Calculate stake amount for the trade
         :return: float: Stake amount
@@ -219,7 +219,8 @@ def get_trade_stake_amount(self, pair: str, edge=None) -> float:
         """
         stake_amount: float
         # Ensure wallets are uptodate.
-        self.update()
+        if update:
+            self.update()
         val_tied_up = Trade.total_open_trades_stakes()
         available_amount = self.get_available_stake_amount()
 
diff --git a/tests/conftest.py b/tests/conftest.py
index 20e027c2ed7..185ed5dc20d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -19,7 +19,7 @@
 from freqtrade.enums import RunMode
 from freqtrade.exchange import Exchange
 from freqtrade.freqtradebot import FreqtradeBot
-from freqtrade.persistence import LocalTrade, Trade, init_db
+from freqtrade.persistence import LocalTrade, Order, Trade, init_db
 from freqtrade.resolvers import ExchangeResolver
 from freqtrade.worker import Worker
 from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4,
@@ -1982,7 +1982,7 @@ def mockedimport(name, *args, **kwargs):
 
 @pytest.fixture(scope="function")
 def open_trade():
-    return Trade(
+    trade = Trade(
         pair='ETH/BTC',
         open_rate=0.00001099,
         exchange='binance',
@@ -1994,6 +1994,26 @@ def open_trade():
         open_date=arrow.utcnow().shift(minutes=-601).datetime,
         is_open=True
     )
+    trade.orders = [
+        Order(
+            ft_order_side='buy',
+            ft_pair=trade.pair,
+            ft_is_open=False,
+            order_id='123456789',
+            status="closed",
+            symbol=trade.pair,
+            order_type="market",
+            side="buy",
+            price=trade.open_rate,
+            average=trade.open_rate,
+            filled=trade.amount,
+            remaining=0,
+            cost=trade.open_rate * trade.amount,
+            order_date=trade.open_date,
+            order_filled_date=trade.open_date,
+        )
+    ]
+    return trade
 
 
 @pytest.fixture(scope="function")
diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py
index 68088d2d570..ce6ea0f0c4a 100644
--- a/tests/optimize/__init__.py
+++ b/tests/optimize/__init__.py
@@ -36,6 +36,8 @@ class BTContainer(NamedTuple):
     trailing_stop_positive_offset: float = 0.0
     use_sell_signal: bool = False
     use_custom_stoploss: bool = False
+    custom_entry_price: Optional[float] = None
+    custom_exit_price: Optional[float] = None
 
 
 def _get_frame_time_from_offset(offset):
diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py
index f41b6101cdc..42d68593a56 100644
--- a/tests/optimize/test_backtest_detail.py
+++ b/tests/optimize/test_backtest_detail.py
@@ -1,5 +1,6 @@
 # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, C0330, unused-argument
 import logging
+from unittest.mock import MagicMock
 
 import pytest
 
@@ -534,6 +535,78 @@
     )]
 )
 
+# Test 34: Custom-entry-price below all candles should timeout - so no trade happens.
+tc34 = BTContainer(data=[
+    # D   O     H     L     C    V    B  S
+    [0, 5000, 5050, 4950, 5000, 6172, 1, 0],
+    [1, 5000, 5500, 4951, 5000, 6172, 0, 0],    # timeout
+    [2, 4900, 5250, 4500, 5100, 6172, 0, 0],
+    [3, 5100, 5100, 4650, 4750, 6172, 0, 0],
+    [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
+    stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.0,
+    custom_entry_price=4200, trades=[]
+)
+
+# Test 35: Custom-entry-price above all candles should timeout - so no trade happens.
+tc35 = BTContainer(data=[
+    # D   O     H     L     C    V    B  S
+    [0, 5000, 5050, 4950, 5000, 6172, 1, 0],
+    [1, 5000, 5500, 4951, 5000, 6172, 0, 0],    # Timeout
+    [2, 4900, 5250, 4500, 5100, 6172, 0, 0],
+    [3, 5100, 5100, 4650, 4750, 6172, 0, 0],
+    [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
+    stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.0,
+    custom_entry_price=7200, trades=[]
+)
+
+# Test 36: Custom-entry-price around candle low
+# Causes immediate ROI exit. This is currently expected behavior (#6261)
+# https://github.com/freqtrade/freqtrade/issues/6261
+# But may change at a later point.
+tc36 = BTContainer(data=[
+    # D   O     H     L     C    V    B  S   BT
+    [0, 5000, 5050, 4950, 5000, 6172, 1, 0],
+    [1, 5000, 5500, 4951, 5000, 6172, 0, 0],    # Enter and immediate ROI
+    [2, 4900, 5250, 4500, 5100, 6172, 0, 0],
+    [3, 5100, 5100, 4650, 4750, 6172, 0, 0],
+    [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
+    stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.1,
+    custom_entry_price=4952,
+    trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
+)
+
+
+# Test 37: Custom exit price below all candles
+# causes sell signal timeout
+tc37 = BTContainer(data=[
+    # D   O     H     L     C    V    B  S   BT
+    [0, 5000, 5050, 4950, 5000, 6172, 1, 0],
+    [1, 5000, 5500, 4951, 5000, 6172, 0, 0],
+    [2, 4900, 5250, 4900, 5100, 6172, 0, 1],  # exit - but timeout
+    [3, 5100, 5100, 4950, 4950, 6172, 0, 0],
+    [4, 5000, 5100, 4950, 4950, 6172, 0, 0]],
+    stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
+    use_sell_signal=True,
+    custom_exit_price=4552,
+    trades=[BTrade(sell_reason=SellType.FORCE_SELL, open_tick=1, close_tick=4)]
+)
+
+# Test 38: Custom exit price above all candles
+# causes sell signal timeout
+tc38 = BTContainer(data=[
+    # D   O     H     L     C    V    B  S   BT
+    [0, 5000, 5050, 4950, 5000, 6172, 1, 0],
+    [1, 5000, 5500, 4951, 5000, 6172, 0, 0],
+    [2, 4900, 5250, 4900, 5100, 6172, 0, 1],  # exit - but timeout
+    [3, 5100, 5100, 4950, 4950, 6172, 0, 0],
+    [4, 5000, 5100, 4950, 4950, 6172, 0, 0]],
+    stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
+    use_sell_signal=True,
+    custom_exit_price=6052,
+    trades=[BTrade(sell_reason=SellType.FORCE_SELL, open_tick=1, close_tick=4)]
+)
+
+
 TESTS = [
     tc0,
     tc1,
@@ -569,6 +642,11 @@
     tc31,
     tc32,
     tc33,
+    tc34,
+    tc35,
+    tc36,
+    tc37,
+    tc38,
 ]
 
 
@@ -597,6 +675,10 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
     backtesting.required_startup = 0
     backtesting.strategy.advise_buy = lambda a, m: frame
     backtesting.strategy.advise_sell = lambda a, m: frame
+    if data.custom_entry_price:
+        backtesting.strategy.custom_entry_price = MagicMock(return_value=data.custom_entry_price)
+    if data.custom_exit_price:
+        backtesting.strategy.custom_exit_price = MagicMock(return_value=data.custom_exit_price)
     backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
     caplog.set_level(logging.DEBUG)
 
diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py
index 3af431f87ec..649a43b3265 100644
--- a/tests/optimize/test_backtesting.py
+++ b/tests/optimize/test_backtesting.py
@@ -521,6 +521,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
     # Fake 2 trades, so there's not enough amount for the next trade left.
     LocalTrade.trades_open.append(trade)
     LocalTrade.trades_open.append(trade)
+    backtesting.wallets.update()
     trade = backtesting._enter_trade(pair, row=row)
     assert trade is None
     LocalTrade.trades_open.pop()
@@ -528,6 +529,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
     assert trade is not None
 
     backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
+    backtesting.wallets.update()
     trade = backtesting._enter_trade(pair, row=row)
     assert trade
     assert trade.stake_amount == 123.5
@@ -635,7 +637,8 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
     assert res.sell_reason == SellType.ROI.value
     # Sell at minute 3 (not available above!)
     assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
-    assert round(res.close_rate, 3) == round(209.0225, 3)
+    sell_order = res.select_order('sell', True)
+    assert sell_order is not None
 
 
 def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 52369675984..a84616516f0 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -2042,6 +2042,7 @@ def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, li
 def test_check_handle_timedout_buy(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade,
                                    fee, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
+    limit_buy_order_old['id'] = open_trade.open_order_id
     limit_buy_cancel = deepcopy(limit_buy_order_old)
     limit_buy_cancel['status'] = 'canceled'
     cancel_order_mock = MagicMock(return_value=limit_buy_cancel)
@@ -2126,6 +2127,8 @@ def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt,
 def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, limit_sell_order_old,
                                                mocker, open_trade, caplog) -> None:
     default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440, "exit_timeout_count": 1}
+    limit_sell_order_old['id'] = open_trade.open_order_id
+
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
     patch_exchange(mocker)
@@ -2174,7 +2177,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
 
     # 2nd canceled trade - Fail execute sell
     caplog.clear()
-    open_trade.open_order_id = 'order_id_2'
+    open_trade.open_order_id = limit_sell_order_old['id']
     mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
                  side_effect=DependencyException)
@@ -2185,7 +2188,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
     caplog.clear()
 
     # 2nd canceled trade ...
-    open_trade.open_order_id = 'order_id_2'
+    open_trade.open_order_id = limit_sell_order_old['id']
     freqtrade.check_handle_timedout()
     assert log_has_re('Emergencyselling trade.*', caplog)
     assert et_mock.call_count == 1
@@ -2195,6 +2198,7 @@ def test_check_handle_timedout_sell(default_conf_usdt, ticker_usdt, limit_sell_o
                                     open_trade) -> None:
     rpc_mock = patch_RPCManager(mocker)
     cancel_order_mock = MagicMock()
+    limit_sell_order_old['id'] = open_trade.open_order_id
     patch_exchange(mocker)
     mocker.patch.multiple(
         'freqtrade.exchange.Exchange',
@@ -2253,6 +2257,7 @@ def test_check_handle_cancelled_sell(default_conf_usdt, ticker_usdt, limit_sell_
 def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy_order_old_partial,
                                        open_trade, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
+    limit_buy_order_old_partial['id'] = open_trade.open_order_id
     limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
     limit_buy_canceled['status'] = 'canceled'
 
@@ -2283,6 +2288,7 @@ def test_check_handle_timedout_partial_fee(default_conf_usdt, ticker_usdt, open_
                                            limit_buy_order_old_partial, trades_for_order,
                                            limit_buy_order_old_partial_canceled, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
+    limit_buy_order_old_partial['id'] = open_trade.open_order_id
     cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
     mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0))
     patch_exchange(mocker)
@@ -2322,6 +2328,8 @@ def test_check_handle_timedout_partial_except(default_conf_usdt, ticker_usdt, op
                                               fee, limit_buy_order_old_partial, trades_for_order,
                                               limit_buy_order_old_partial_canceled, mocker) -> None:
     rpc_mock = patch_RPCManager(mocker)
+    limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id
+    limit_buy_order_old_partial['id'] = open_trade.open_order_id
     cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
     patch_exchange(mocker)
     mocker.patch.multiple(


Code review comments:
diff_hunk: @@ -697,6 +798,8 @@ def backtest(self, processed: Dict,
             'config': self.strategy.config,
             'locks': PairLocks.get_all_locks(),
             'rejected_signals': self.rejected_trades,
+            # TODO: timedout_orders should be shown as part of results.
+            # 'timedout_orders': self.timedout_orders,
path: freqtrade/optimize/backtesting.py
text: @author:
@user1 asking your opinion again here ... you think a simple "cancelled" will suffice here - or should the value be split between entry and exit orders (that's the reason i've commented this out for now)

@user1:
I think the more statistics the better. Could be we have a rather reliable entry pricing and exits often timeout. If we lump those two together we would see just a number of timing out orders that looks about ok and would have no idea what actually is going on. We could then go on "improving" entry pricing only to make it worse (time out more) while we should have improved sell pricing instead..

@user1:
Err maybe if you are worried about statistics getting too crowded we could later on introduce a new command line switch to display advanced statistics. Then we can default to terse backtest result, but on user opting in we could display all kinds of stuff . cyberd3vil asked for more stats already.

@author:
i'm not yet at the "Output" phase - but at the "gather" phase :laughing: