Timeframe
5m
Direction
Long Only
Stoploss
-99.0%
Trailing Stop
No
ROI
0m: 1000.0%
Interface Version
3
Startup Candles
200
Indicators
0
freqtrade/freqtrade-strategies
author@: lenik
# user_data/strategies/PolusFinalVisualization_touchstop_v2.py
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import pandas as pd
import numpy as np
# Try to import user's PolusLevels, but don't rely on its internals.
try:
from polus_levels import PolusLevels as _ExternalPolusLevels
except Exception:
_ExternalPolusLevels = None
class PolusFinalVisualization_touchstop_v2(IStrategy):
INTERFACE_VERSION = 3
timeframe = '5m'
minimal_roi = {'0': 10}
stoploss = -0.99
use_custom_stoploss = False
process_only_new_candles = False
startup_candle_count = 200
# Neutral gray viz
plot_config = {
'main_plot': {
'polus_high_signal': {'type': 'scatter', 'plotly': {'mode': 'markers', 'marker': {'symbol': 'x', 'size': 8, 'color': '#888888'}}},
'polus_low_signal': {'type': 'scatter', 'plotly': {'mode': 'markers', 'marker': {'symbol': 'x', 'size': 8, 'color': '#888888'}}},
# Dynamically add 6 level lines for high/low
**{f'level_high_{i}': {'type': 'scatter', 'plotly': {'mode': 'lines', 'line': {'color': '#888888', 'width': 1}}} for i in range(6)},
**{f'level_low_{i}': {'type': 'scatter', 'plotly': {'mode': 'lines', 'line': {'color': '#888888', 'width': 1}}} for i in range(6)},
}
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# If external class exists, instantiate it. We'll still compute signals internally.
self._pl = _ExternalPolusLevels(use_tick_volume=False) if _ExternalPolusLevels else None
@staticmethod
def _compute_polus_signals(df: DataFrame, use_tick_volume: bool = False):
"""Self-contained signal detection compatible with the user's logic.
Returns 3 numpy arrays: high_levels, low_levels, close_ref (all length len(df))."""
high = df['high'].to_numpy(copy=False) if 'high' in df.columns else df['close'].to_numpy(copy=False)
low = df['low'].to_numpy(copy=False) if 'low' in df.columns else df['close'].to_numpy(copy=False)
close = df['close'].to_numpy(copy=False)
volcol = 'tick_volume' if use_tick_volume and 'tick_volume' in df.columns else ('volume' if 'volume' in df.columns else None)
vol = df[volcol].to_numpy(copy=False) if volcol else np.ones_like(close)
hi_out = np.full(len(df), np.nan, dtype='float64')
lo_out = np.full(len(df), np.nan, dtype='float64')
cl_out = np.full(len(df), np.nan, dtype='float64')
for i in range(1, len(df) - 1):
if vol[i - 1] > vol[i] < vol[i + 1]:
hi_out[i] = high[i]
lo_out[i] = low[i]
cl_out[i] = close[i - 1]
return hi_out, lo_out, cl_out
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
# Use internal computation (robust). If external is present and has compute(), we could cross-check.
hi, lo, cl = self._compute_polus_signals(df, use_tick_volume=False)
df['polus_high_signal'] = pd.Series(hi, index=df.index)
df['polus_low_signal'] = pd.Series(lo, index=df.index)
df['polus_close_ref'] = pd.Series(cl, index=df.index)
df = self._manage_levels(df, max_levels=6)
return df
def _manage_levels(self, dataframe: DataFrame, max_levels: int) -> DataFrame:
# Pre-create columns
for slot in range(max_levels):
dataframe[f'level_high_{slot}'] = np.nan
dataframe[f'level_low_{slot}'] = np.nan
active_high = {} # price -> slot
active_low = {}
eps = 1e-6
for i in range(len(dataframe)):
hi = float(dataframe.at[i, 'high']) if 'high' in dataframe.columns else float(dataframe.at[i, 'close'])
lo = float(dataframe.at[i, 'low']) if 'low' in dataframe.columns else float(dataframe.at[i, 'close'])
# draw-until-touch (exclusive): don't write value on the touching bar
to_remove = []
for price, slot in active_high.items():
if hi >= price * (1 - eps):
to_remove.append(price)
else:
dataframe.loc[i, f'level_high_{slot}'] = price
for price in to_remove:
active_high.pop(price, None)
to_remove = []
for price, slot in active_low.items():
if lo <= price * (1 + eps):
to_remove.append(price)
else:
dataframe.loc[i, f'level_low_{slot}'] = price
for price in to_remove:
active_low.pop(price, None)
# add new levels from signals
new_high = dataframe.at[i, 'polus_high_signal']
if pd.notna(new_high) and float(new_high) not in active_high:
used = set(active_high.values())
for slot in range(max_levels):
if slot not in used:
active_high[float(new_high)] = slot
break
new_low = dataframe.at[i, 'polus_low_signal']
if pd.notna(new_low) and float(new_low) not in active_low:
used = set(active_low.values())
for slot in range(max_levels):
if slot not in used:
active_low[float(new_low)] = slot
break
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['enter_long'] = 0
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['exit_long'] = 0
return dataframe