TEST Simple

This commit is contained in:
Jérôme Delacotte
2025-10-20 20:54:24 +02:00
parent ee45dc890d
commit 4c0692426e
3 changed files with 963 additions and 20 deletions

265
Simple.py Normal file
View File

@@ -0,0 +1,265 @@
# Zeus Strategy: First Generation of GodStra Strategy with maximum
# AVG/MID profit in USDT
# Author: @Mablue (Masoud Azizi)
# github: https://github.com/mablue/
# IMPORTANT: INSTALL TA BEFOUR RUN(pip install ta)
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy sell roi --strategy Zeus
# --- Do not remove these libs ---
from datetime import timedelta, datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open,
IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute)
import pandas as pd
import numpy as np
from pandas import DataFrame
from typing import Optional, Union, Tuple
from typing import List
import logging
import configparser
from technical import pivots_points
# --------------------------------
# Add your lib to import here test git
import ta
import talib.abstract as talib
import freqtrade.vendor.qtpylib.indicators as qtpylib
import requests
from datetime import timezone, timedelta
from scipy.signal import savgol_filter
from ta.trend import SMAIndicator, EMAIndicator, MACD, ADXIndicator
from collections import Counter
logger = logging.getLogger(__name__)
from tabulate import tabulate
# Couleurs ANSI de base
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
MAGENTA = "\033[35m"
CYAN = "\033[36m"
RESET = "\033[0m"
def pprint_df(dframe):
print(tabulate(dframe, headers='keys', tablefmt='psql', showindex=False))
def normalize(df):
df = (df - df.min()) / (df.max() - df.min())
return df
"""
SMA z-score derivative strategy with trailing exit (large).
- timeframe: 1h
- sma5, relative derivatives, z-score normalization (rolling z over z_window)
- smoothing: ewm(span=5) on z-scores
- entry: z_d1_smooth > entry_z1 AND z_d2_smooth > entry_z2
- exit: inversion (z_d1_smooth < 0) AND retrace from highest since entry >= trailing_stop
"""
class Simple(IStrategy):
levels = [1, 2, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
startup_candle_count = 12 * 24 * 2
# ROI table:
minimal_roi = {
"0": 10
}
stakes = 40
# Stoploss:
stoploss = -1 # 0.256
# Custom stoploss
use_custom_stoploss = True
# Buy hypers
timeframe = '1h'
max_open_trades = 5
max_amount = 40
# DCA config
position_adjustment_enable = True
# Parameters (tweakable)
z_window = 50 # window for rolling mean/std to compute zscore
entry_z1 = 0.1 # threshold on z-score of first derivative
entry_z2 = 0.1 # threshold on z-score of second derivative
min_volume = 0.0 # minimal volume to accept an entry
min_relative_d1 = 1e-6 # clip tiny d1 relative values to reduce noise
# Trailing parameters for "large" trailing requested
trailing_stop_pct = 0.05 # 5% retracement from highest since entry
# Smoothing for z-scores
ewm_span = 5
# Plot config: price + sma5 + markers + slope/accel subplots
plot_config = {
"main_plot": {
"close": {"color": "blue"},
"sma5": {"color": "orange"},
},
"subplots": {
"slope_and_accel": {
"z_d1": {"color": "green"},
"z_d2": {"color": "red"},
}
},
# Markers (Freqtrade charting supports markers via these keys)
"markers": [
# buy marker: '^' green when enter_long==1
{"type": "buy", "column": "enter_long", "marker": "^", "color": "green", "markersize": 10},
# sell marker: 'v' red when exit_long==1
{"type": "sell", "column": "exit_long", "marker": "v", "color": "red", "markersize": 10},
],
}
def informative_pairs(self):
return []
def _zscore(self, series: pd.Series, window: int) -> pd.Series:
mean = series.rolling(window=window, min_periods=3).mean()
std = series.rolling(window=window, min_periods=3).std(ddof=0)
z = (series - mean) / std
z = z.replace([np.inf, -np.inf], np.nan)
return z
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe
# SMA(5)
df['sma5'] = df['close'].rolling(5, min_periods=1).mean()
# Absolute derivatives
df['sma5_d1'] = df['sma5'].diff().rolling(5).mean()
df['sma5_d2'] = df['sma5_d1'].diff().rolling(5).mean()
# Relative derivatives (percentage-like)
eps = 1e-9
df['sma5_d1_rel'] = df['sma5_d1'] / (df['sma5'].shift(1).replace(0, np.nan) + eps)
df['sma5_d2_rel'] = df['sma5_d2'] / (df['sma5'].shift(2).replace(0, np.nan) + eps)
# Clip micro-noise
df.loc[df['sma5_d1_rel'].abs() < self.min_relative_d1, 'sma5_d1_rel'] = 0.0
df.loc[df['sma5_d2_rel'].abs() < self.min_relative_d1, 'sma5_d2_rel'] = 0.0
# Z-scores on relative derivatives
df['z_d1'] = self._zscore(df['sma5_d1_rel'], self.z_window)
df['z_d2'] = self._zscore(df['sma5_d2_rel'], self.z_window)
# Smoothing z-scores with EWM to reduce jitter
df['z_d1_smooth'] = df['z_d1'].ewm(span=self.ewm_span, adjust=False).mean()
df['z_d2_smooth'] = df['z_d2'].ewm(span=self.ewm_span, adjust=False).mean()
# Prepare marker columns (for plots). They will be filled in populate_entry_trend/populate_exit_trend
df['enter_long'] = 0
df['exit_long'] = 0
return df
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
# Use last closed candle for signals -> shift(1)
cond_entry = (
(df['z_d1'] > self.entry_z1) &
(df['z_d2'] > self.entry_z2) &
(df['volume'].shift(1) > self.min_volume)
)
df.loc[cond_entry, 'enter_long'] = 1
# Ensure others are explicitly zero (for clean plotting)
df.loc[~cond_entry, 'enter_long'] = 0
return df
def custom_exit(self, pair: str, trade: Trade, current_time, current_rate, current_profit, **kwargs) -> \
Optional[str]:
"""
Exit policy (mode C - trailing large):
- Must detect inversion: z_d1_smooth < 0 on last closed candle
- Compute highest close since trade entry (inclusive)
- If price has retraced >= trailing_stop_pct from that highest point and we're in inversion -> exit
"""
df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last = df.iloc[-1].squeeze()
z1 = last.get('z_d1_smooth', None)
if z1 is None:
return None
# Only consider exits when inversion detected (z1 < 0)
inversion = (z1 < 0)
if not inversion:
return None
# If we don't have profit info, be conservative
if current_profit is None:
return None
# Determine highest close since entry
highest_since_entry = None
try:
# trade.open_date_utc is available: find rows after that timestamp
# df.index is expected to be pd.DatetimeIndex in UTC or local; convert safely
if hasattr(trade, 'open_date_utc') and trade.open_date_utc:
# pandas comparison: ensure same tz awareness
entry_time = pd.to_datetime(trade.open_date_utc)
# select rows with index >= entry_time
mask = df.index >= entry_time
if mask.any():
highest_since_entry = df.loc[mask, 'close'].max()
# fallback: use trade.open_rate or the max over full df
except Exception as e:
logger.debug(f"Couldn't compute highest_since_entry from open_date_utc: {e}")
if highest_since_entry is None:
# fallback: use the maximum close in the entire provided dataframe slice
highest_since_entry = df['close'].max() if not df['close'].empty else current_rate
# Calculate retracement ratio from the highest
if highest_since_entry and highest_since_entry > 0:
retrace = 1.0 - (current_rate / highest_since_entry)
else:
retrace = 0.0
# Exit if:
# - currently in profit AND
# - retracement >= trailing_stop_pct (i.e. price has fallen enough from top since entry) AND
# - inversion detected (z1 < 0)
if (current_profit > 0) and (retrace >= self.trailing_stop_pct):
# Mark the dataframe for plotting (if possible)
# Note: freqtrade expects strategies to set exit flags in populate_exit_trend,
# but we set exit via custom_exit return reason; plotting will read exit_long if populated.
return "zscore"
# Otherwise, do not exit yet
return None
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
For plotting only: mark exit points where our logic would trigger.
This is approximate: we mark an exit when z_d1_smooth < 0 and the price has retraced >= trailing_stop_pct
based on the available dataframe window (best-effort).
"""
df = dataframe
# df['exit_long'] = 0
#
# # compute highest close since each possible entry (best-effort: use rolling max up to current index)
# rolling_max = df['close'].cummax()
#
# # retracement relative to rolling max
# retrace = 1.0 - (df['close'] / rolling_max.replace(0, np.nan))
#
# # mark exits where inversion and retrace >= threshold
# cond_exit = (df['z_d1_smooth'] < 0) & (retrace >= self.trailing_stop_pct)
# # shift by 0: we mark the candle where the exit condition appears
# df.loc[cond_exit, 'exit_long'] = 1
return df