OsirisDailyV1 — session-based intraday, Mon-Fri.
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 V1 — Session-Based Intraday for Consistent Daily Profit
=====================================================================
Built from exhaustive 1h data analysis across 10 pairs (Jan 2024 — Mar 2026).
CORE EDGE: Specific hours have structural directional bias.
When combined with trend filter (SMA20), the edge multiplies.
SIGNALS (trend-filtered, Mon-Fri):
LONG at 21h UTC if Bull trend → Sharpe 5.06, WR 54.6%, WinDays 60%
LONG at 3-4h UTC if Bull trend → Sharpe 3.39, WR 52.4%, WinDays 57%
SHORT at 13h UTC if Bear trend → Sharpe 4.02, WR 61.6%, WinDays 62%
SHORT at 23h UTC if Bear trend → Sharpe 2.03, WR 54.5%, WinDays 54%
DOW-SPECIFIC (no trend filter needed):
Mon 07h LONG, Tue 13h SHORT, Wed 21h LONG,
Thu 19h SHORT, Thu 21h LONG, Fri 07h SHORT
EXECUTION: Enter at candle open, exit after 1h (next candle close).
No SL/TP — the hourly bias IS the edge. Holding longer hurts.
"""
import logging
import numpy as np
from datetime import datetime
from pandas import DataFrame
from freqtrade.strategy import IStrategy
logger = logging.getLogger(__name__)
class OsirisDailyV1(IStrategy):
"""OsirisDailyV1 — session-based intraday, Mon-Fri."""
INTERFACE_VERSION = 3
timeframe = "1h"
can_short = True
# No fixed ROI — custom exit handles everything
minimal_roi = {"0": 100}
# Emergency stoploss (should never trigger — we exit after 1-2 candles)
stoploss = -0.05
trailing_stop = False
process_only_new_candles = True
startup_candle_count: int = 25
# ─── Trade management ────────────────────────────────
max_entry_position_adjustment = 0
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Trend: SMA20 on close (20 hourly candles = ~1 day)
dataframe['sma20'] = dataframe['close'].rolling(20).mean()
dataframe['trend_bull'] = (dataframe['close'] > dataframe['sma20']).astype(int)
# Time features
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:
"""
DOW-specific signals. FT enters at OPEN of NEXT candle after signal.
So to trade hour X, we must signal at hour X-1.
"""
# ═══ LONGS ═══
long_mask = np.zeros(len(dataframe), dtype=bool)
# Mon 07h Long → signal at hour 6
long_mask |= (
(dataframe['dow'] == 0) & (dataframe['hour'] == 6)
).values
# Wed 21h Long → signal at hour 20
long_mask |= (
(dataframe['dow'] == 2) & (dataframe['hour'] == 20)
).values
# Thu 21h Long → signal at hour 20
long_mask |= (
(dataframe['dow'] == 3) & (dataframe['hour'] == 20)
).values
dataframe.loc[long_mask, 'enter_long'] = 1
# ═══ SHORTS ═══
short_mask = np.zeros(len(dataframe), dtype=bool)
# Tue 13h Short → signal at hour 12
short_mask |= (
(dataframe['dow'] == 1) & (dataframe['hour'] == 12)
).values
# Thu 19h Short → signal at hour 18
short_mask |= (
(dataframe['dow'] == 3) & (dataframe['hour'] == 18)
).values
# Fri 07h Short → signal at hour 6
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:
# No indicator-based exits — use custom_exit
return dataframe
def custom_exit(self, pair: str, trade, current_time: datetime,
current_rate: float, current_profit: float,
**kwargs) -> str | bool:
"""Exit after 1 candle (1 hour hold)."""
trade_duration_hours = (current_time - trade.open_date_utc).total_seconds() / 3600
# Exit after 1h (1 candle)
if trade_duration_hours >= 1.0:
return "1h_exit"
return False