Timeframe
1h
Direction
Long & Short
Stoploss
-5.0%
Trailing Stop
No
ROI
0m: 10000.0%
Interface Version
3
Startup Candles
N/A
Indicators
0
freqtrade/freqtrade-strategies
freqtrade/freqtrade-strategies
freqtrade/freqtrade-strategies
this is an example class, implementing a PSAR based trailing stop loss you are supposed to take the `custom_stoploss()` and `populate_indicators()` parts and adapt it to your own strategy
freqtrade/freqtrade-strategies
"""
OSIRIS DAILY V2 — Session-Based Intraday (DOW×Hour)
====================================================
7 statistically significant DOW×Hour signals (all t-stat ≥ 2.0).
FT-aligned: signal at hour X-1, trade at hour X, 1h hold.
SCHEDULE (all hours UTC):
Mon: 07h Long (t=3.33)
Tue: 13h Short (t=2.89)
Wed: 21h Long (t=2.14)
Thu: 15h Short + 19h Short + 21h Long (t=2.54, 3.85, 4.40)
Fri: 07h Short (t=2.32)
7 entries/week × 10 pairs = ~70 trades/week
Backtest: +104%, Sharpe 13.33, DD 11.5%, 827 days
"""
import logging
import numpy as np
from datetime import datetime
from pandas import DataFrame
from freqtrade.strategy import IStrategy
logger = logging.getLogger(__name__)
class OsirisDailyV2(IStrategy):
INTERFACE_VERSION = 3
timeframe = "1h"
can_short = True
minimal_roi = {"0": 100}
stoploss = -0.05
trailing_stop = False
process_only_new_candles = True
startup_candle_count: int = 25
max_entry_position_adjustment = 0
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['hour'] = dataframe['date'].dt.hour
dataframe['dow'] = dataframe['date'].dt.dayofweek # 0=Mon
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Signal at hour X-1 → FT enters at open of hour X → exits after 1h.
Only signals with t-stat ≥ 2.0 from exhaustive 5×24 DOW×Hour scan.
LONGS:
Mon 07h → signal at 06h, dow=0 (t=3.33)
Wed 21h → signal at 20h, dow=2 (t=2.14)
Thu 21h → signal at 20h, dow=3 (t=4.40)
SHORTS:
Tue 13h → signal at 12h, dow=1 (t=2.89)
Thu 15h → signal at 14h, dow=3 (t=2.54)
Thu 19h → signal at 18h, dow=3 (t=3.85)
Fri 07h → signal at 06h, dow=4 (t=2.32)
"""
long_mask = np.zeros(len(dataframe), dtype=bool)
# Mon 07h Long → sig@06, dow=0
long_mask |= ((dataframe['dow'] == 0) & (dataframe['hour'] == 6)).values
# Wed 21h Long → sig@20, dow=2
long_mask |= ((dataframe['dow'] == 2) & (dataframe['hour'] == 20)).values
# Thu 21h Long → sig@20, dow=3
long_mask |= ((dataframe['dow'] == 3) & (dataframe['hour'] == 20)).values
dataframe.loc[long_mask, 'enter_long'] = 1
short_mask = np.zeros(len(dataframe), dtype=bool)
# Tue 13h Short → sig@12, dow=1
short_mask |= ((dataframe['dow'] == 1) & (dataframe['hour'] == 12)).values
# Thu 15h Short → sig@14, dow=3
short_mask |= ((dataframe['dow'] == 3) & (dataframe['hour'] == 14)).values
# Thu 19h Short → sig@18, dow=3
short_mask |= ((dataframe['dow'] == 3) & (dataframe['hour'] == 18)).values
# Fri 07h Short → sig@06, dow=4
short_mask |= ((dataframe['dow'] == 4) & (dataframe['hour'] == 6)).values
dataframe.loc[short_mask, 'enter_short'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return dataframe
def custom_exit(self, pair: str, trade, current_time: datetime,
current_rate: float, current_profit: float,
**kwargs) -> str | bool:
trade_duration_hours = (current_time - trade.open_date_utc).total_seconds() / 3600
if trade_duration_hours >= 1.0:
return "1h_exit"
return False