HourBasedStrategy
Tested 1 Jun 2023 – 30 May 2025
Rank 20 of 27 by return
HourBasedStrategy turned $1,000 into $475 (−52.5%) over 729 days, while holding Bitcoin returned +288.1% and an equal mix of the ten coins +194.8%. Its worst drawdown was a severe 69.7%, deep enough that most people would have switched it off. With a 64% win rate and a profit factor of 0.85, losses outweighed gains.
How HourBasedStrategy trades
HourBasedStrategy uses no indicators at all. It enters during one window of UTC hours and exits during another, with both windows chosen by hyperopt. A stepped take-profit and a 10% stoploss limit each trade.
This is a teaching example of how hyperopt can find patterns in noise. Any edge from trading by the clock should be confirmed on data the optimiser never saw.
Rules at a glance
- Indicators
- Hour of day (UTC)
- Take-profit (minimal ROI)
- 52.8% from entry; 11.3% after 2h 49m; 8.9% after 8h 48m; 0% after 1d 6h
- Stoploss
- −10%
- Trailing stop
- Off
- Exit signal
- Used
Results by month
Profit or loss in USDT for trades closed in each month.
Results by coin
| Pair | Trades | Win rate | Profit (USDT) | Avg. hold |
|---|---|---|---|---|
| SOL/USDT | 246 | 65.4% | +109.94 | 1d 14h |
| BTC/USDT | 215 | 69.3% | +53.68 | 2d 14h |
| BNB/USDT | 93 | 69.9% | +11.92 | 3d 8h |
| XRP/USDT | 93 | 59.1% | −4.65 | 2d 3h |
| ETH/USDT | 228 | 64.5% | −48.07 | 2d 6h |
| DOT/USDT | 6 | 0.0% | −81.49 | 2d 1h |
| LINK/USDT | 6 | 16.7% | −101.86 | 11h 40m |
| AVAX/USDT | 10 | 30.0% | −101.90 | 8h 24m |
| DOGE/USDT | 24 | 41.7% | −142.47 | 1d 5h |
| ADA/USDT | 58 | 56.9% | −220.28 | 1d 22h |
Why trades closed
| Exit reason | Trades | Win rate | Profit (USDT) |
|---|---|---|---|
| Take-profit (ROI) | 805 | 77.5% | +2,997.81 |
| Still open at end of test | 2 | 0.0% | −19.75 |
| Stoploss | 172 | 0.0% | −3,503.25 |
The code
HourBasedStrategy comes from the community freqtrade-strategies repository (GPL-3.0). These are the parts that decide when it trades; the full file is 104 lines.
Entry rules
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (dataframe['hour'].between(self.buy_hour_min.value, self.buy_hour_max.value)) ), 'enter_long'] = 1 return dataframe
Exit rules
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (dataframe['hour'].between(self.sell_hour_min.value, self.sell_hour_max.value)) ), 'exit_long'] = 1 return dataframe
Indicators
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['hour'] = dataframe['date'].dt.hour return dataframe
Reproduce this test
With freqtrade installed (see our Docker guide), download the data and run the same backtest:
freqtrade download-data --exchange binance --pairs ADA/USDT AVAX/USDT BNB/USDT BTC/USDT DOGE/USDT DOT/USDT ETH/USDT LINK/USDT SOL/USDT XRP/USDT --timeframes 1h --timerange 20230501-20250530
freqtrade backtesting --strategy HourBasedStrategy --timeframe 1h \
--timerange 20230601-20250530 --dry-run-wallet 1000 --stake-amount 200 --max-open-trades 5 --fee 0.001
Results can differ slightly with a different data source or freqtrade version. See how we test.