TEST Simple
This commit is contained in:
265
Simple.py
Normal file
265
Simple.py
Normal 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
|
||||||
633
Simple_01.py
Normal file
633
Simple_01.py
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
# sma_zscore_trailing_dca.py
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from freqtrade.persistence import Trade
|
||||||
|
from pandas import DataFrame
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from typing import Optional
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta, datetime
|
||||||
|
import talib.abstract as talib
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Simple_01(IStrategy):
|
||||||
|
"""
|
||||||
|
SMA z-score derivative strategy with trailing exit and intelligent DCA (large pullback).
|
||||||
|
- 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_pct
|
||||||
|
- adjust_trade_position: add to position on controlled pullback (large = 4-6%)
|
||||||
|
"""
|
||||||
|
|
||||||
|
timeframe = "1h"
|
||||||
|
startup_candle_count = 24
|
||||||
|
# Risk mgmt (we handle exit in custom_exit)
|
||||||
|
minimal_roi = {"0": 0.99}
|
||||||
|
stoploss = -0.99
|
||||||
|
use_custom_exit = True
|
||||||
|
position_adjustment_enable = True
|
||||||
|
|
||||||
|
columns_logged = False
|
||||||
|
|
||||||
|
# Parameters
|
||||||
|
z_window = 10 # window for rolling mean/std to compute zscore
|
||||||
|
entry_z1 = 0.1 # threshold on z-score of first derivative
|
||||||
|
entry_z2 = 0 # 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 (exit)
|
||||||
|
trailing_stop_pct = 0.01 # 5% retracement from highest since entry
|
||||||
|
|
||||||
|
# Smoothing for z-scores
|
||||||
|
ewm_span = 5
|
||||||
|
|
||||||
|
# DCA intelligent (adjust_trade_position) parameters for "large" pullback
|
||||||
|
dca_enabled = True
|
||||||
|
# Pullback bounds (large): allow adding when retrace is between 4% and 6%
|
||||||
|
dca_pullback_min = 0.01
|
||||||
|
dca_pullback_max = 0.02
|
||||||
|
# Maximum number of adds per trade (to avoid infinite pyramiding)
|
||||||
|
dca_max_adds = 8
|
||||||
|
# Percentage of base position to add on each reinforcement (50% of original size by default)
|
||||||
|
dca_add_ratio = 0.5
|
||||||
|
# Require momentum still positive to add
|
||||||
|
dca_require_z1_positive = True
|
||||||
|
dca_require_z2_positive = True
|
||||||
|
# Do not add if current_profit < min_profit_to_add (avoid averaging down when deep in loss)
|
||||||
|
min_profit_to_add = -0.02 # allow small loss but not big drawdown
|
||||||
|
|
||||||
|
pairs = {
|
||||||
|
pair: {
|
||||||
|
"first_buy": 0,
|
||||||
|
"last_buy": 0.0,
|
||||||
|
"first_amount": 0.0,
|
||||||
|
"last_min": 999999999999999.5,
|
||||||
|
"last_max": 0,
|
||||||
|
"trade_info": {},
|
||||||
|
"max_touch": 0.0,
|
||||||
|
"last_sell": 0.0,
|
||||||
|
'count_of_buys': 0,
|
||||||
|
'current_profit': 0,
|
||||||
|
'expected_profit': 0,
|
||||||
|
"last_candle": {},
|
||||||
|
"last_trade": None,
|
||||||
|
"last_count_of_buys": 0,
|
||||||
|
'base_stake_amount': 0,
|
||||||
|
'stop_buy': False,
|
||||||
|
'last_date': 0,
|
||||||
|
'stop': False,
|
||||||
|
'max_profit': 0,
|
||||||
|
'last_palier_index': -1,
|
||||||
|
'total_amount': 0,
|
||||||
|
'has_gain': 0,
|
||||||
|
'force_sell': False,
|
||||||
|
'force_buy': False
|
||||||
|
}
|
||||||
|
for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC",
|
||||||
|
"BTC/USDT", "ETH/USDT", "DOGE/USDT", "XRP/USDT", "SOL/USDT"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Plot config
|
||||||
|
plot_config = {
|
||||||
|
"main_plot": {
|
||||||
|
"close": {"color": "blue"},
|
||||||
|
"sma5": {"color": "orange"},
|
||||||
|
},
|
||||||
|
"subplots": {
|
||||||
|
"slope_and_accel": {
|
||||||
|
"z_d1_smooth": {"color": "green"},
|
||||||
|
"z_d2_smooth": {"color": "red"},
|
||||||
|
},
|
||||||
|
"sma_deriv": {
|
||||||
|
"sma5_deriv1_rel": {"color": "green"},
|
||||||
|
"sma5_deriv2_rel": {"color": "red"},
|
||||||
|
},
|
||||||
|
"rsi": {
|
||||||
|
"rsi": {"color": "blue"}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"markers": [
|
||||||
|
{"type": "buy", "column": "enter_long", "marker": "^", "color": "green", "markersize": 10},
|
||||||
|
{"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_deriv1'] = df['sma5'].diff()
|
||||||
|
df['sma5_deriv2'] = df['sma5_deriv1'].diff()
|
||||||
|
|
||||||
|
# Relative derivatives (percentage-like)
|
||||||
|
eps = 1e-9
|
||||||
|
df['sma5_deriv1_rel'] = df['sma5_deriv1'] / (df['sma5'].shift(1).replace(0, np.nan) + eps)
|
||||||
|
df['sma5_deriv2_rel'] = df['sma5_deriv2'] / (df['sma5'].shift(2).replace(0, np.nan) + eps)
|
||||||
|
|
||||||
|
# Clip micro-noise
|
||||||
|
df.loc[df['sma5_deriv1_rel'].abs() < self.min_relative_d1, 'sma5_deriv1_rel'] = 0.0
|
||||||
|
df.loc[df['sma5_deriv2_rel'].abs() < self.min_relative_d1, 'sma5_deriv2_rel'] = 0.0
|
||||||
|
|
||||||
|
# Z-scores on relative derivatives
|
||||||
|
df['z_d1'] = self._zscore(df['sma5_deriv1_rel'], self.z_window)
|
||||||
|
df['z_d2'] = self._zscore(df['sma5_deriv2_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)
|
||||||
|
df['enter_long'] = 0
|
||||||
|
df['exit_long'] = 0
|
||||||
|
df['rsi'] = talib.RSI(df['close'], timeperiod=14)
|
||||||
|
df['max_rsi_12'] = talib.MAX(df['rsi'], timeperiod=12)
|
||||||
|
df['min_rsi_12'] = talib.MIN(df['rsi'], timeperiod=12)
|
||||||
|
# self.calculeDerivees(df, 'rsi', horizon=12)
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
df = dataframe
|
||||||
|
|
||||||
|
# Use last closed candle for signals -> shift(1)
|
||||||
|
cond_entry = (
|
||||||
|
(df['z_d1_smooth'] > self.entry_z1) &
|
||||||
|
(df['z_d2_smooth'] > self.entry_z2) &
|
||||||
|
(df['max_rsi_12'] < 70) &
|
||||||
|
(df['volume'] > self.min_volume)
|
||||||
|
)
|
||||||
|
|
||||||
|
df.loc[cond_entry, 'enter_long'] = 1
|
||||||
|
df.loc[~cond_entry, 'enter_long'] = 0
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
df = dataframe
|
||||||
|
# df['exit_long'] = 0
|
||||||
|
#
|
||||||
|
# # compute rolling max (best-effort for plotting)
|
||||||
|
# rolling_max = df['close'].cummax()
|
||||||
|
# retrace = 1.0 - (df['close'] / rolling_max.replace(0, np.nan))
|
||||||
|
#
|
||||||
|
# cond_exit = (df['z_d1_smooth'] < 0) & (retrace >= self.trailing_stop_pct)
|
||||||
|
# df.loc[cond_exit, 'exit_long'] = 1
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float,
|
||||||
|
time_in_force: str,
|
||||||
|
exit_reason: str, current_time, **kwargs, ) -> bool:
|
||||||
|
# allow_to_sell = (minutes > 30)
|
||||||
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
|
||||||
|
force = self.pairs[pair]['force_sell']
|
||||||
|
allow_to_sell = True #(last_candle['percent'] < 0) #or force
|
||||||
|
|
||||||
|
minutes = int(round((current_time - trade.date_last_filled_utc).total_seconds() / 60, 0))
|
||||||
|
|
||||||
|
if allow_to_sell:
|
||||||
|
self.trades = list()
|
||||||
|
self.pairs[pair]['last_count_of_buys'] = trade.nr_of_successful_entries # self.pairs[pair]['count_of_buys']
|
||||||
|
self.pairs[pair]['last_sell'] = rate
|
||||||
|
self.pairs[pair]['last_trade'] = trade
|
||||||
|
self.pairs[pair]['last_candle'] = last_candle
|
||||||
|
self.trades = list()
|
||||||
|
dispo = round(self.wallets.get_available_stake_amount())
|
||||||
|
print(f"Sell {pair} {current_time} {exit_reason} dispo={dispo} amount={amount} rate={rate} open_rate={trade.open_rate}")
|
||||||
|
self.log_trade(
|
||||||
|
last_candle=last_candle,
|
||||||
|
date=current_time,
|
||||||
|
action="🟥Sell " + str(minutes),
|
||||||
|
pair=pair,
|
||||||
|
trade_type=exit_reason,
|
||||||
|
rate=last_candle['close'],
|
||||||
|
dispo=dispo,
|
||||||
|
profit=round(trade.calc_profit(rate, amount), 2)
|
||||||
|
)
|
||||||
|
self.pairs[pair]['max_profit'] = 0
|
||||||
|
self.pairs[pair]['force_sell'] = False
|
||||||
|
self.pairs[pair]['has_gain'] = 0
|
||||||
|
self.pairs[pair]['current_profit'] = 0
|
||||||
|
self.pairs[pair]['total_amount'] = 0
|
||||||
|
self.pairs[pair]['count_of_buys'] = 0
|
||||||
|
self.pairs[pair]['max_touch'] = 0
|
||||||
|
self.pairs[pair]['last_buy'] = 0
|
||||||
|
self.pairs[pair]['last_date'] = current_time
|
||||||
|
self.pairs[pair]['last_palier_index'] = -1
|
||||||
|
self.pairs[pair]['last_trade'] = trade
|
||||||
|
self.pairs[pair]['current_trade'] = None
|
||||||
|
|
||||||
|
return (allow_to_sell) | (exit_reason == 'force_exit')
|
||||||
|
|
||||||
|
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str,
|
||||||
|
current_time: datetime, entry_tag: Optional[str], **kwargs) -> bool:
|
||||||
|
|
||||||
|
minutes = 0
|
||||||
|
if self.pairs[pair]['last_date'] != 0:
|
||||||
|
minutes = round(int((current_time - self.pairs[pair]['last_date']).total_seconds() / 60))
|
||||||
|
|
||||||
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
last_candle_2 = dataframe.iloc[-2].squeeze()
|
||||||
|
last_candle_3 = dataframe.iloc[-3].squeeze()
|
||||||
|
# val = self.getProbaHausse144(last_candle)
|
||||||
|
|
||||||
|
# allow_to_buy = True #(not self.stop_all) #& (not self.all_down)
|
||||||
|
allow_to_buy = not self.pairs[pair]['stop'] # and val > self.buy_val.value #not last_candle['tendency'] in ('B-', 'B--') # (rate <= float(limit)) | (entry_tag == 'force_entry')
|
||||||
|
|
||||||
|
# force = self.pairs[pair]['force_buy']
|
||||||
|
# if self.pairs[pair]['force_buy']:
|
||||||
|
# self.pairs[pair]['force_buy'] = False
|
||||||
|
# allow_to_buy = True
|
||||||
|
# else:
|
||||||
|
# if not self.should_enter_trade(pair, last_candle, current_time):
|
||||||
|
# allow_to_buy = False
|
||||||
|
|
||||||
|
if allow_to_buy:
|
||||||
|
self.trades = list()
|
||||||
|
self.pairs[pair]['first_buy'] = rate
|
||||||
|
self.pairs[pair]['last_buy'] = rate
|
||||||
|
self.pairs[pair]['max_touch'] = last_candle['close']
|
||||||
|
self.pairs[pair]['last_candle'] = last_candle
|
||||||
|
self.pairs[pair]['count_of_buys'] = 1
|
||||||
|
self.pairs[pair]['current_profit'] = 0
|
||||||
|
self.pairs[pair]['last_palier_index'] = -1
|
||||||
|
self.pairs[pair]['last_max'] = max(last_candle['close'], self.pairs[pair]['last_max'])
|
||||||
|
self.pairs[pair]['last_min'] = min(last_candle['close'], self.pairs[pair]['last_min'])
|
||||||
|
|
||||||
|
dispo = round(self.wallets.get_available_stake_amount())
|
||||||
|
self.printLineLog()
|
||||||
|
|
||||||
|
stake_amount = self.adjust_stake_amount(pair, last_candle)
|
||||||
|
|
||||||
|
self.pairs[pair]['total_amount'] = stake_amount
|
||||||
|
|
||||||
|
self.log_trade(
|
||||||
|
last_candle=last_candle,
|
||||||
|
date=current_time,
|
||||||
|
action=("🟩Buy" if allow_to_buy else "Canceled") + " " + str(minutes),
|
||||||
|
pair=pair,
|
||||||
|
rate=rate,
|
||||||
|
dispo=dispo,
|
||||||
|
profit=0,
|
||||||
|
trade_type=entry_tag,
|
||||||
|
buys=1,
|
||||||
|
stake=round(stake_amount, 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
return allow_to_buy
|
||||||
|
|
||||||
|
def custom_exit(self, pair: str, trade: Trade, current_time, current_rate, current_profit, **kwargs) -> Optional[str]:
|
||||||
|
df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
|
last_candle = df.iloc[-1].squeeze()
|
||||||
|
|
||||||
|
self.pairs[pair]['last_max'] = max(last_candle['close'], self.pairs[pair]['last_max'])
|
||||||
|
self.pairs[pair]['last_min'] = min(last_candle['close'], self.pairs[pair]['last_min'])
|
||||||
|
self.pairs[pair]['current_trade'] = trade
|
||||||
|
count_of_buys = trade.nr_of_successful_entries
|
||||||
|
|
||||||
|
profit = round(current_profit * trade.stake_amount, 1)
|
||||||
|
self.pairs[pair]['max_profit'] = max(self.pairs[pair]['max_profit'], profit)
|
||||||
|
max_profit = self.pairs[pair]['max_profit']
|
||||||
|
baisse = 0
|
||||||
|
if profit > 0:
|
||||||
|
baisse = 100 * abs(max_profit - profit) / max_profit
|
||||||
|
mx = max_profit / 5
|
||||||
|
self.pairs[pair]['count_of_buys'] = count_of_buys
|
||||||
|
self.pairs[pair]['current_profit'] = profit
|
||||||
|
|
||||||
|
dispo = round(self.wallets.get_available_stake_amount())
|
||||||
|
hours_since_first_buy = (current_time - trade.open_date_utc).seconds / 3600.0
|
||||||
|
days_since_first_buy = (current_time - trade.open_date_utc).days
|
||||||
|
hours = (current_time - trade.date_last_filled_utc).total_seconds() / 3600.0
|
||||||
|
|
||||||
|
if hours % 4 == 0:
|
||||||
|
self.log_trade(
|
||||||
|
last_candle=last_candle,
|
||||||
|
date=current_time,
|
||||||
|
action="🔴 CURRENT" if self.pairs[pair]['stop'] else "🟢 CURRENT",
|
||||||
|
dispo=dispo,
|
||||||
|
pair=pair,
|
||||||
|
rate=last_candle['close'],
|
||||||
|
trade_type='',
|
||||||
|
profit=profit,
|
||||||
|
buys='',
|
||||||
|
stake=0
|
||||||
|
)
|
||||||
|
|
||||||
|
z1 = last_candle.get('z_d1_smooth', None)
|
||||||
|
if z1 is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if z1 >= 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if current_profit is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# highest close since entry
|
||||||
|
highest_since_entry = self.pairs[trade.pair]['max_touch']
|
||||||
|
# try:
|
||||||
|
# if hasattr(trade, 'open_date_utc') and trade.open_date_utc:
|
||||||
|
# entry_time = pd.to_datetime(trade.open_date_utc)
|
||||||
|
# mask = df.index >= entry_time
|
||||||
|
# if mask.any():
|
||||||
|
# highest_since_entry = df.loc[mask, 'close'].max()
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.debug(f"Couldn't compute highest_since_entry: {e}")
|
||||||
|
#
|
||||||
|
# if highest_since_entry is None:
|
||||||
|
# highest_since_entry = df['close'].max() if not df['close'].empty else current_rate
|
||||||
|
|
||||||
|
if highest_since_entry and highest_since_entry > 0:
|
||||||
|
retrace = 1.0 - (current_rate / highest_since_entry)
|
||||||
|
else:
|
||||||
|
retrace = 0.0
|
||||||
|
z1 = last_candle.get('z_d1_smooth', None)
|
||||||
|
z2 = last_candle.get('z_d2_smooth', None)
|
||||||
|
|
||||||
|
if (current_profit > 0) and (current_profit >= self.trailing_stop_pct) and last_candle['sma5_deriv1'] < -0.002:
|
||||||
|
return str(count_of_buys) + '_' + "zscore"
|
||||||
|
|
||||||
|
self.pairs[pair]['max_touch'] = max(last_candle['close'], self.pairs[pair]['max_touch'])
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def adjust_stake_amount(self, pair: str, last_candle: DataFrame):
|
||||||
|
# Calculer le minimum des 14 derniers jours
|
||||||
|
return self.config.get('stake_amount')
|
||||||
|
|
||||||
|
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
||||||
|
current_rate: float, current_profit: float, min_stake: float,
|
||||||
|
max_stake: float, **kwargs):
|
||||||
|
"""
|
||||||
|
DCA intelligent (mode C - 'large'):
|
||||||
|
- Only add if:
|
||||||
|
* DCA is enabled
|
||||||
|
* number of adds done so far < dca_max_adds
|
||||||
|
* retracement since highest_since_entry is between dca_pullback_min and dca_pullback_max
|
||||||
|
* momentum still positive (z_d1_smooth, z_d2_smooth) if required
|
||||||
|
* current_profit >= min_profit_to_add (avoid averaging down into large loss)
|
||||||
|
- Returns a dict describing the desired order for Freqtrade to place (common format).
|
||||||
|
Example returned dict: {'type': 'market', 'amount': 0.01}
|
||||||
|
"""
|
||||||
|
if not self.dca_enabled:
|
||||||
|
return None
|
||||||
|
pair = trade.pair
|
||||||
|
|
||||||
|
# Basic guards
|
||||||
|
if current_profit is None:
|
||||||
|
current_profit = 0.0
|
||||||
|
|
||||||
|
# Do not add if we're already deeply in loss
|
||||||
|
if current_profit < self.min_profit_to_add:
|
||||||
|
return None
|
||||||
|
|
||||||
|
df, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
|
||||||
|
last_candle = df.iloc[-1].squeeze()
|
||||||
|
|
||||||
|
# Compute highest close since entry
|
||||||
|
highest_since_entry = self.pairs[trade.pair]['last_buy']
|
||||||
|
|
||||||
|
last = df.iloc[-1]
|
||||||
|
z1 = last.get('z_d1_smooth', None)
|
||||||
|
z2 = last.get('z_d2_smooth', None)
|
||||||
|
|
||||||
|
# Count how many adds have been done for this trade.
|
||||||
|
# Trade.extra might contain meta; otherwise try trade.tags or use trade.open_rate/amount heuristics.
|
||||||
|
adds_done = trade.nr_of_successful_entries
|
||||||
|
# Calculate retracement since the highest
|
||||||
|
retrace = 0.0
|
||||||
|
if highest_since_entry and highest_since_entry > 0:
|
||||||
|
retrace = (last['close'] - highest_since_entry) / highest_since_entry
|
||||||
|
|
||||||
|
# logger.info(f"{pair} {current_rate} {current_time} {highest_since_entry} add: retrace={retrace:.4f}, adds_done={adds_done} z1={z1} z2={z2}")
|
||||||
|
|
||||||
|
# Enforce momentum requirements if requested
|
||||||
|
if self.dca_require_z1_positive and (z1 is None or z1 <= 0):
|
||||||
|
return None
|
||||||
|
if self.dca_require_z2_positive and (z2 is None or z2 <= 0):
|
||||||
|
return None
|
||||||
|
|
||||||
|
#
|
||||||
|
# try:
|
||||||
|
# meta = getattr(trade, 'meta', None) or {}
|
||||||
|
# adds_done = int(meta.get('adds_done', 0))
|
||||||
|
# except Exception:
|
||||||
|
# adds_done = 0
|
||||||
|
|
||||||
|
if adds_done >= self.dca_max_adds:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# if hasattr(trade, 'open_date_utc') and trade.open_date_utc:
|
||||||
|
# entry_time = pd.to_datetime(trade.open_date_utc)
|
||||||
|
# mask = df.index >= entry_time
|
||||||
|
# if mask.any():
|
||||||
|
# highest_since_entry = df.loc[mask, 'close'].max()
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.debug(f"adjust_trade_position: couldn't compute highest_since_entry: {e}")
|
||||||
|
#
|
||||||
|
# if highest_since_entry is None:
|
||||||
|
# highest_since_entry = df['close'].max() if not df['close'].empty else current_rate
|
||||||
|
|
||||||
|
# Check if retrace is inside the allowed DCA window (large)
|
||||||
|
if ((retrace >= self.dca_pullback_min) and (retrace <= self.dca_pullback_max)):
|
||||||
|
# Determine amount to add: a fraction of the original trade amount
|
||||||
|
# Try to get trade.amount (base asset amount). If not available, fall back to stake percentage
|
||||||
|
add_amount = None
|
||||||
|
try:
|
||||||
|
base_amount = self.config.get('stake_amount')
|
||||||
|
if base_amount:
|
||||||
|
add_amount = base_amount * self.dca_add_ratio
|
||||||
|
else:
|
||||||
|
# Fallback: attempt to compute amount from trade.open_rate and desired quote stake
|
||||||
|
# We'll propose to use a fraction of current rate worth (this is best-effort)
|
||||||
|
add_amount = None
|
||||||
|
except Exception:
|
||||||
|
add_amount = None
|
||||||
|
|
||||||
|
# If we couldn't compute an absolute amount, propose a relative size via a suggested stake (user must map)
|
||||||
|
if add_amount is None:
|
||||||
|
# Return a suggested instruction; adapt according to your freqtrade version.
|
||||||
|
suggested = {
|
||||||
|
'type': 'market',
|
||||||
|
'amount': None, # caller should compute actual amount from stake management
|
||||||
|
'note': f'suggest_add_ratio={self.dca_add_ratio}'
|
||||||
|
}
|
||||||
|
# logger.info(f"{pair} {current_rate} DCA suggestion (no absolute amount): retrace={retrace:.4f}, adds_done={adds_done}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
dispo = round(self.wallets.get_available_stake_amount())
|
||||||
|
trade_type = last_candle['enter_tag'] if last_candle['enter_long'] == 1 else 'pct48'
|
||||||
|
self.pairs[trade.pair]['count_of_buys'] += 1
|
||||||
|
self.pairs[pair]['total_amount'] += add_amount
|
||||||
|
self.log_trade(
|
||||||
|
last_candle=last_candle,
|
||||||
|
date=current_time,
|
||||||
|
action="🟧 Loss -",
|
||||||
|
dispo=dispo,
|
||||||
|
pair=trade.pair,
|
||||||
|
rate=current_rate,
|
||||||
|
trade_type=trade_type,
|
||||||
|
profit=round(current_profit * trade.stake_amount, 1),
|
||||||
|
buys=trade.nr_of_successful_entries + 1,
|
||||||
|
stake=round(add_amount, 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.pairs[trade.pair]['last_buy'] = current_rate
|
||||||
|
self.pairs[trade.pair]['max_touch'] = last_candle['close']
|
||||||
|
self.pairs[trade.pair]['last_candle'] = last_candle
|
||||||
|
|
||||||
|
# All checks passed -> create market order instruction
|
||||||
|
# logger.info(f"{pair} {current_rate} {current_time} {highest_since_entry} add: retrace={retrace:.4f}, adds_done={adds_done}, add_amount={add_amount:.8f}")
|
||||||
|
return add_amount
|
||||||
|
|
||||||
|
# Not in allowed retrace window -> no action
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getPctFirstBuy(self, pair, last_candle):
|
||||||
|
return round((last_candle['close'] - self.pairs[pair]['first_buy']) / self.pairs[pair]['first_buy'], 3)
|
||||||
|
|
||||||
|
def getPctLastBuy(self, pair, last_candle):
|
||||||
|
return round((last_candle['close'] - self.pairs[pair]['last_buy']) / self.pairs[pair]['last_buy'], 4)
|
||||||
|
|
||||||
|
def getPct60D(self, pair, last_candle):
|
||||||
|
return round((last_candle['max60'] - last_candle['min60']) / last_candle['max60'], 4)
|
||||||
|
|
||||||
|
def getPctClose60D(self, pair, last_candle):
|
||||||
|
if last_candle['close'] > last_candle['max12']:
|
||||||
|
return 1
|
||||||
|
if last_candle['close'] < last_candle['min12']:
|
||||||
|
return 0
|
||||||
|
return round(
|
||||||
|
(last_candle['close'] - last_candle['min12']) / (last_candle['max12'] - last_candle['min12']), 4)
|
||||||
|
|
||||||
|
|
||||||
|
def printLineLog(self):
|
||||||
|
self.printLog(
|
||||||
|
f"+{'-' * 18}+{'-' * 12}+{'-' * 5}+{'-' * 20}+{'-' * 9}+{'-' * 8}+{'-' * 12}+{'-' * 8}+{'-' * 13}+{'-' * 14}+{'-' * 9}{'-' * 9}+{'-' * 5}+{'-' * 7}+"
|
||||||
|
f"{'-' * 3}"
|
||||||
|
# "+{'-' * 3}+{'-' * 3}
|
||||||
|
f"+{'-' * 6}+{'-' * 7}+{'-' * 5}+{'-' * 5}+{'-' * 5}+{'-' * 5}+{'-' * 5}+{'-' * 5}+"
|
||||||
|
)
|
||||||
|
|
||||||
|
def printLog(self, str):
|
||||||
|
if self.config.get('runmode') == 'hyperopt' or self.dp.runmode.value in ('hyperopt'):
|
||||||
|
return
|
||||||
|
if not self.dp.runmode.value in ('backtest', 'hyperopt', 'lookahead-analysis'):
|
||||||
|
logger.info(str)
|
||||||
|
else:
|
||||||
|
if not self.dp.runmode.value in ('hyperopt'):
|
||||||
|
print(str)
|
||||||
|
|
||||||
|
def log_trade(self, action, pair, date, trade_type=None, rate=None, dispo=None, profit=None, buys=None,
|
||||||
|
stake=None,
|
||||||
|
last_candle=None):
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# Afficher les colonnes une seule fois
|
||||||
|
if self.config.get('runmode') == 'hyperopt' or self.dp.runmode.value in ('hyperopt'):
|
||||||
|
return
|
||||||
|
if self.columns_logged % 10 == 0:
|
||||||
|
self.printLog(
|
||||||
|
f"| {'Date':<16} | {'Action':<10} |{'Pair':<5}| {'Trade Type':<18} |{'Rate':>8} | {'Dispo':>6} | {'Profit':>8} | {'Pct':>6} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>7}| {'last_max':>7}|{'Buys':>5}| {'Stake':>5} |"
|
||||||
|
f"Tdc|{'val':>6}| RSI |s201d|s5_1d|s5_2d|s51h|s52h"
|
||||||
|
)
|
||||||
|
self.printLineLog()
|
||||||
|
df = pd.DataFrame.from_dict(self.pairs, orient='index')
|
||||||
|
colonnes_a_exclure = ['last_candle', 'last_trade', 'last_palier_index',
|
||||||
|
'trade_info', 'last_date', 'expected_profit', 'last_count_of_buys',
|
||||||
|
'base_stake_amount', 'stop_buy']
|
||||||
|
df_filtered = df[df['count_of_buys'] > 0].drop(columns=colonnes_a_exclure)
|
||||||
|
# df_filtered = df_filtered["first_buy", "last_max", "max_touch", "last_sell","last_buy", 'count_of_buys', 'current_profit']
|
||||||
|
|
||||||
|
print(df_filtered)
|
||||||
|
|
||||||
|
self.columns_logged += 1
|
||||||
|
date = str(date)[:16] if date else "-"
|
||||||
|
limit = None
|
||||||
|
# if buys is not None:
|
||||||
|
# limit = round(last_rate * (1 - self.fibo[buys] / 100), 4)
|
||||||
|
|
||||||
|
rsi = ''
|
||||||
|
rsi_pct = ''
|
||||||
|
sma5_1d = ''
|
||||||
|
sma5_1h = ''
|
||||||
|
|
||||||
|
sma5 = str(sma5_1d) + ' ' + str(sma5_1h)
|
||||||
|
|
||||||
|
last_lost = '' #self.getLastLost(last_candle, pair)
|
||||||
|
|
||||||
|
if buys is None:
|
||||||
|
buys = ''
|
||||||
|
|
||||||
|
max_touch = '' # round(last_candle['max12'], 1) #round(self.pairs[pair]['max_touch'], 1)
|
||||||
|
pct_max = '' #self.getPctFirstBuy(pair, last_candle)
|
||||||
|
|
||||||
|
total_counts = str(buys) + '/' + str(
|
||||||
|
sum(pair_data['count_of_buys'] for pair_data in self.pairs.values()))
|
||||||
|
|
||||||
|
dist_max = '' #self.getDistMax(last_candle, pair)
|
||||||
|
val = 0 #self.getProbaHausseSma5d(last_candle)
|
||||||
|
|
||||||
|
pct60 = 0 #round(100 * self.getPct60D(pair, last_candle), 2)
|
||||||
|
|
||||||
|
color = GREEN if profit > 0 else RED
|
||||||
|
# color_sma20 = GREEN if last_candle['sma20_deriv1'] > 0 else RED
|
||||||
|
# color_sma5 = GREEN if last_candle['mid_smooth_5_deriv1'] > 0 else RED
|
||||||
|
# color_sma5_2 = GREEN if last_candle['mid_smooth_5_deriv2'] > 0 else RED
|
||||||
|
# color_sma5_1h = GREEN if last_candle['sma5_deriveriv1'] > 0 else RED
|
||||||
|
# color_sma5_2h = GREEN if last_candle['sma5_deriveriv2'] > 0 else RED
|
||||||
|
|
||||||
|
last_max = int(self.pairs[pair]['last_max']) if self.pairs[pair]['last_max'] > 1 else round(
|
||||||
|
self.pairs[pair]['last_max'], 3)
|
||||||
|
last_min = int(self.pairs[pair]['last_min']) if self.pairs[pair]['last_min'] > 1 else round(
|
||||||
|
self.pairs[pair]['last_min'], 3)
|
||||||
|
|
||||||
|
profit = str(profit) + '/' + str(round(self.pairs[pair]['max_profit'], 2))
|
||||||
|
|
||||||
|
# 🟢 Dérivée 1 > 0 et dérivée 2 > 0: tendance haussière qui s’accélère.
|
||||||
|
# 🟡 Dérivée 1 > 0 et dérivée 2 < 0: tendance haussière qui ralentit → essoufflement potentiel.
|
||||||
|
# 🔴 Dérivée 1 < 0 et dérivée 2 < 0: tendance baissière qui s’accélère.
|
||||||
|
# 🟠 Dérivée 1 < 0 et dérivée 2 > 0: tendance baissière qui ralentit → possible bottom.
|
||||||
|
|
||||||
|
# tdc last_candle['tendency_12']
|
||||||
|
self.printLog(
|
||||||
|
f"| {date:<16} |{action:<10} | {pair[0:3]:<3} | {trade_type or '-':<18} |{rate or '-':>9}| {dispo or '-':>6} "
|
||||||
|
f"|{color}{profit or '-':>10}{RESET}| {pct_max or '-':>6} | {round(self.pairs[pair]['max_touch'], 2) or '-':>11} | {last_lost or '-':>12} "
|
||||||
|
f"| {last_max or '-':>7} | {last_min or '-':>7} |{total_counts or '-':>5}|{stake or '-':>7}"
|
||||||
|
f"|{'-':>3}|"
|
||||||
|
f"{round(val, 1) or '-' :>6}|"
|
||||||
|
# f"{round(last_candle['rsi'], 0):>7}|{color_sma20}{round(last_candle['sma20_deriv1'], 2):>5}{RESET}"
|
||||||
|
# f"|{color_sma5}{round(last_candle['mid_smooth_5_deriv1'], 2):>5}{RESET}|{color_sma5_2}{round(last_candle['mid_smooth_5_deriv2'], 2):>5}{RESET}"
|
||||||
|
# f"|{color_sma5_1h}{round(last_candle['sma5_deriveriv1'], 2):>5}{RESET}|{color_sma5_2h}{round(last_candle['sma5_deriveriv2'], 2):>5}{RESET}"
|
||||||
|
# f"|{last_candle['min60']}|{last_candle['max60']}"
|
||||||
|
)
|
||||||
85
Zeus_8_1d.py
85
Zeus_8_1d.py
@@ -99,7 +99,7 @@ class Zeus_8_1d(IStrategy):
|
|||||||
},
|
},
|
||||||
"subplots": {
|
"subplots": {
|
||||||
"Rsi": {
|
"Rsi": {
|
||||||
"max_rsi_12": {
|
"max_rsi_24": {
|
||||||
"color": "blue"
|
"color": "blue"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -145,6 +145,7 @@ class Zeus_8_1d(IStrategy):
|
|||||||
pair: {
|
pair: {
|
||||||
"first_buy": 0,
|
"first_buy": 0,
|
||||||
"last_buy": 0.0,
|
"last_buy": 0.0,
|
||||||
|
"first_amount": 0.0,
|
||||||
"last_min": 999999999999999.5,
|
"last_min": 999999999999999.5,
|
||||||
"last_max": 0,
|
"last_max": 0,
|
||||||
"trade_info": {},
|
"trade_info": {},
|
||||||
@@ -405,16 +406,19 @@ class Zeus_8_1d(IStrategy):
|
|||||||
# self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 5)
|
# self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 5)
|
||||||
# return 'Baisse_' + pair_name + '_' + str(count_of_buys) + '_' + str(self.pairs[pair]['has_gain']) + '_' + str(round(baisse, 2))
|
# return 'Baisse_' + pair_name + '_' + str(count_of_buys) + '_' + str(self.pairs[pair]['has_gain']) + '_' + str(round(baisse, 2))
|
||||||
|
|
||||||
if last_candle['mid_smooth_5_deriv1'] <= -0.1 and profit > expected_profit and last_candle['rsi'] > 65:
|
if last_candle['mid_smooth_5_deriv1'] <= -0.12 and profit > expected_profit and last_candle['max_rsi_24'] > 75 \
|
||||||
self.pairs[pair]['force_sell'] = False
|
and last_candle['mid_smooth_5_deriv2'] <= -0.15 :
|
||||||
self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 5)
|
return str(count_of_buys) + '_' + 'RSI80_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + '_' + str(round(baisse, 2))
|
||||||
return 'RSI_' + pair_name + '_' + str(count_of_buys) + '_' + str(self.pairs[pair]['has_gain']) + '_' + str(round(baisse, 2))
|
|
||||||
|
|
||||||
if last_candle['mid_smooth_24_deriv1'] <= -0.1 \
|
# if last_candle['mid_smooth_5_deriv1'] <= -0.1 and profit > expected_profit and last_candle['max_rsi_24'] > 65:
|
||||||
and profit > expected_profit:
|
# self.pairs[pair]['force_sell'] = False
|
||||||
|
# self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 5)
|
||||||
|
# return str(count_of_buys) + '_' + 'RSI65_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + '_' + str(round(baisse, 2))
|
||||||
|
|
||||||
|
if last_candle['mid_smooth_24_deriv1'] <= -0.1 and last_candle['mid_smooth_5_deriv1'] <= -0 and profit > expected_profit:
|
||||||
self.pairs[pair]['force_sell'] = False
|
self.pairs[pair]['force_sell'] = False
|
||||||
self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 5)
|
# self.pairs[pair]['force_buy'] = (self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain'] > 5)
|
||||||
return 'Drv3_' + pair_name + '_' + str(count_of_buys) + '_' + str(self.pairs[pair]['has_gain']) + '_' + str(round(baisse, 2))
|
return str(count_of_buys) + '_' + 'Drv3_' + pair_name + '_' + str(self.pairs[pair]['has_gain']) + '_' + str(round(baisse, 2))
|
||||||
|
|
||||||
self.pairs[pair]['max_touch'] = max(last_candle['close'], self.pairs[pair]['max_touch'])
|
self.pairs[pair]['max_touch'] = max(last_candle['close'], self.pairs[pair]['max_touch'])
|
||||||
|
|
||||||
@@ -591,8 +595,9 @@ class Zeus_8_1d(IStrategy):
|
|||||||
|
|
||||||
# print(metadata['pair'])
|
# print(metadata['pair'])
|
||||||
dataframe['rsi'] = talib.RSI(dataframe['close'], timeperiod=14)
|
dataframe['rsi'] = talib.RSI(dataframe['close'], timeperiod=14)
|
||||||
dataframe['max_rsi_12'] = talib.MAX(dataframe['rsi'], timeperiod=12)
|
dataframe['max_rsi_24'] = talib.MAX(dataframe['rsi'], timeperiod=24)
|
||||||
self.calculeDerivees(dataframe, 'rsi', horizon=12)
|
dataframe['min_rsi_24'] = talib.MIN(dataframe['rsi'], timeperiod=24)
|
||||||
|
self.calculeDerivees(dataframe, 'rsi', horizon=24)
|
||||||
|
|
||||||
dataframe['max12'] = talib.MAX(dataframe['close'], timeperiod=12)
|
dataframe['max12'] = talib.MAX(dataframe['close'], timeperiod=12)
|
||||||
dataframe['min12'] = talib.MIN(dataframe['close'], timeperiod=12)
|
dataframe['min12'] = talib.MIN(dataframe['close'], timeperiod=12)
|
||||||
@@ -663,6 +668,7 @@ class Zeus_8_1d(IStrategy):
|
|||||||
if count == 0:
|
if count == 0:
|
||||||
dataframe['first_price'] = buy.price
|
dataframe['first_price'] = buy.price
|
||||||
self.pairs[pair]['first_buy'] = buy.price
|
self.pairs[pair]['first_buy'] = buy.price
|
||||||
|
self.pairs[pair]['first_amount'] = buy.price * buy.filled
|
||||||
# dataframe['close01'] = buy.price * 1.01
|
# dataframe['close01'] = buy.price * 1.01
|
||||||
|
|
||||||
# Order(id=2396, trade=1019, order_id=29870026652, side=buy, filled=0.00078, price=63921.01,
|
# Order(id=2396, trade=1019, order_id=29870026652, side=buy, filled=0.00078, price=63921.01,
|
||||||
@@ -674,12 +680,10 @@ class Zeus_8_1d(IStrategy):
|
|||||||
# dataframe['mid_price'] = (dataframe['last_price'] + dataframe['first_price']) / 2
|
# dataframe['mid_price'] = (dataframe['last_price'] + dataframe['first_price']) / 2
|
||||||
count_buys = count
|
count_buys = count
|
||||||
# dataframe['limit'] = dataframe['last_price'] * (1 - self.baisse[count] / 100)
|
# dataframe['limit'] = dataframe['last_price'] * (1 - self.baisse[count] / 100)
|
||||||
# dataframe['amount'] = amount
|
|
||||||
self.pairs[pair]['total_amount'] = amount
|
self.pairs[pair]['total_amount'] = amount
|
||||||
|
|
||||||
# Compter les baisses / hausses consécutives
|
# Compter les baisses / hausses consécutives
|
||||||
self.calculateDownAndUp(dataframe, limit=0.0001)
|
# self.calculateDownAndUp(dataframe, limit=0.0001)
|
||||||
|
|
||||||
horizon_h = 12
|
horizon_h = 12
|
||||||
|
|
||||||
dataframe['close_smooth'] = self.conditional_smoothing(dataframe['mid'].rolling(3).mean().dropna(),
|
dataframe['close_smooth'] = self.conditional_smoothing(dataframe['mid'].rolling(3).mean().dropna(),
|
||||||
@@ -701,6 +705,32 @@ class Zeus_8_1d(IStrategy):
|
|||||||
|
|
||||||
self.calculeDerivees(dataframe, 'ema_volume', factor_1=10, factor_2=1, horizon=14)
|
self.calculeDerivees(dataframe, 'ema_volume', factor_1=10, factor_2=1, horizon=14)
|
||||||
|
|
||||||
|
dataframe['futur_percent_3'] = 100 * ((dataframe['sma5'].shift(-3) - dataframe['sma5']) / dataframe['sma5'])
|
||||||
|
futur_cols = ['futur_percent_3']
|
||||||
|
indic_1 = 'mid_smooth_5_deriv1'
|
||||||
|
indic_2 = 'mid_smooth_5_deriv2'
|
||||||
|
|
||||||
|
self.calculateProbabilite2Index(dataframe, futur_cols, indic_1, indic_2)
|
||||||
|
|
||||||
|
################### INFORMATIVE 1d
|
||||||
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d")
|
||||||
|
heikinashi = qtpylib.heikinashi(informative)
|
||||||
|
informative['haopen'] = heikinashi['open']
|
||||||
|
informative['haclose'] = heikinashi['close']
|
||||||
|
informative['hapercent'] = (informative['haclose'] - informative['haopen']) / informative['haclose']
|
||||||
|
informative = self.calculateDerivation(informative, window=5, suffixe="_5")
|
||||||
|
informative['sma5'] = talib.SMA(informative, timeperiod=5)
|
||||||
|
informative['sma20'] = talib.SMA(informative, timeperiod=20)
|
||||||
|
informative['max60'] = talib.MAX(informative['close'], timeperiod=60)
|
||||||
|
informative['min60'] = talib.MIN(informative['close'], timeperiod=60)
|
||||||
|
|
||||||
|
self.calculeDerivees(informative, 'sma5', factor_1=10, factor_2=1, horizon=5)
|
||||||
|
self.calculeDerivees(informative, 'sma20', factor_1=10, factor_2=1, horizon=20)
|
||||||
|
if self.dp.runmode.value in ('backtest'):
|
||||||
|
informative['futur_percent_3'] = 100 * (informative['close'].shift(-3) - informative['close']) / informative['close']
|
||||||
|
self.calculateProbabilite2Index(informative, futur_cols, indic_1, indic_2)
|
||||||
|
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True)
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def calculeDerivees(self, dataframe, indic, factor_1=100, factor_2=10, horizon=5):
|
def calculeDerivees(self, dataframe, indic, factor_1=100, factor_2=10, horizon=5):
|
||||||
@@ -801,13 +831,20 @@ class Zeus_8_1d(IStrategy):
|
|||||||
# (dataframe['mid_smooth_24_deriv1'].shift(1) <= 0)
|
# (dataframe['mid_smooth_24_deriv1'].shift(1) <= 0)
|
||||||
(dataframe['mid_smooth_24_deriv1'] >= 0.05)
|
(dataframe['mid_smooth_24_deriv1'] >= 0.05)
|
||||||
& (dataframe['mid_smooth_24_deriv2'] > 0)
|
& (dataframe['mid_smooth_24_deriv2'] > 0)
|
||||||
|
& (dataframe['mid_smooth_5_deriv1'] > 0.001)
|
||||||
& (dataframe['mid_smooth_5_deriv2'] > 0)
|
& (dataframe['mid_smooth_5_deriv2'] > 0)
|
||||||
# & (dataframe['hapercent'] > 0)
|
# & (dataframe['hapercent'] > 0)
|
||||||
#& (dataframe['max_rsi_12'] < 50)
|
#& (dataframe['max_rsi_24'] < 50)
|
||||||
# & (dataframe['open'] <= dataframe['bb_middleband'])
|
# & (dataframe['open'] <= dataframe['bb_middleband'])
|
||||||
|
|
||||||
), ['enter_long', 'enter_tag']] = (1, 'smth_12')
|
), ['enter_long', 'enter_tag']] = (1, 'smth_12')
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['min_rsi_24'] < 20)
|
||||||
|
& (dataframe['hapercent'] > 0)
|
||||||
|
), ['enter_long', 'enter_tag']] = (1, 'min_rsi_24')
|
||||||
|
|
||||||
dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.01, np.nan)
|
dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.01, np.nan)
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
@@ -990,12 +1027,12 @@ class Zeus_8_1d(IStrategy):
|
|||||||
return stake_amount
|
return stake_amount
|
||||||
return None
|
return None
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
# print(exception)
|
print(exception)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
last_lost = self.getLastLost(last_candle, pair)
|
last_lost = self.getLastLost(last_candle, pair)
|
||||||
|
|
||||||
if (False and hours > 6 and last_candle['mid_smooth_5_deriv1'] > 0 and last_candle['mid_smooth_5_deriv2'] > 0):
|
if (hours > 6 and last_candle['mid_smooth_24_deriv1'] > 0.03 and last_candle['mid_smooth_5_deriv2'] > 0):
|
||||||
try:
|
try:
|
||||||
stake_amount = self.pairs[pair]['first_amount'] / 4
|
stake_amount = self.pairs[pair]['first_amount'] / 4
|
||||||
if self.wallets.get_available_stake_amount() > stake_amount:
|
if self.wallets.get_available_stake_amount() > stake_amount:
|
||||||
@@ -1074,6 +1111,7 @@ class Zeus_8_1d(IStrategy):
|
|||||||
# pct60 = round(100 * self.getPctClose60D(pair, last_candle), 2)
|
# pct60 = round(100 * self.getPctClose60D(pair, last_candle), 2)
|
||||||
|
|
||||||
if True: # not pair in ('BTC/USDT', 'BTC/USDC'):
|
if True: # not pair in ('BTC/USDT', 'BTC/USDC'):
|
||||||
|
# factors = [1, 1.2, 1.3, 1.4]
|
||||||
# factors = [1, 1.2, 1.3, 1.4]
|
# factors = [1, 1.2, 1.3, 1.4]
|
||||||
if self.pairs[pair]['count_of_buys'] == 0:
|
if self.pairs[pair]['count_of_buys'] == 0:
|
||||||
# pctClose60 = self.getPctClose60D(pair, last_candle)
|
# pctClose60 = self.getPctClose60D(pair, last_candle)
|
||||||
@@ -1230,6 +1268,13 @@ class Zeus_8_1d(IStrategy):
|
|||||||
return self.labels[i]
|
return self.labels[i]
|
||||||
return self.labels[-1] # cas limite pour la borne max
|
return self.labels[-1] # cas limite pour la borne max
|
||||||
|
|
||||||
|
def informative_pairs(self):
|
||||||
|
# get access to all pairs available in whitelist.
|
||||||
|
pairs = self.dp.current_whitelist()
|
||||||
|
informative_pairs = [(pair, '1d') for pair in pairs]
|
||||||
|
|
||||||
|
return informative_pairs
|
||||||
|
|
||||||
def approx_val_from_bins(self, matrice, numeric_matrice, row_label, col_label):
|
def approx_val_from_bins(self, matrice, numeric_matrice, row_label, col_label):
|
||||||
"""
|
"""
|
||||||
Renvoie une approximation de la valeur à partir des labels binaires (e.g. B5, H1)
|
Renvoie une approximation de la valeur à partir des labels binaires (e.g. B5, H1)
|
||||||
@@ -1401,7 +1446,6 @@ class Zeus_8_1d(IStrategy):
|
|||||||
# print(pivot_mean.round(2))
|
# print(pivot_mean.round(2))
|
||||||
|
|
||||||
def should_enter_trade(self, pair: str, last_candle, current_time) -> bool:
|
def should_enter_trade(self, pair: str, last_candle, current_time) -> bool:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
limit = 3
|
limit = 3
|
||||||
|
|
||||||
@@ -1413,7 +1457,8 @@ class Zeus_8_1d(IStrategy):
|
|||||||
# if not pair.startswith('BTC'):
|
# if not pair.startswith('BTC'):
|
||||||
dispo = round(self.wallets.get_available_stake_amount())
|
dispo = round(self.wallets.get_available_stake_amount())
|
||||||
|
|
||||||
if self.pairs[pair]['stop'] and last_candle['mid_smooth_5_deriv1'] > -0.9 and last_candle['sma5_deriv1'] > 0 and last_candle['sma5_deriv2'] > 0:
|
if self.pairs[pair]['stop'] and last_candle['mid_smooth_5_deriv1_1d'] > -0.9 \
|
||||||
|
and last_candle['sma5_deriv1_1d'] > 0 and last_candle['sma5_deriv2_1d'] > 0:
|
||||||
self.pairs[pair]['stop'] = False
|
self.pairs[pair]['stop'] = False
|
||||||
self.log_trade(
|
self.log_trade(
|
||||||
last_candle=last_candle,
|
last_candle=last_candle,
|
||||||
@@ -1428,7 +1473,7 @@ class Zeus_8_1d(IStrategy):
|
|||||||
stake=0
|
stake=0
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if self.pairs[pair]['stop'] == False and (last_candle['sma5_deriv1'] < -0.2 or last_candle['sma5_deriv2'] < -3):
|
if self.pairs[pair]['stop'] == False and (last_candle['sma5_deriv1_1d'] < -0.2 or last_candle['sma5_deriv2_1d'] < -3):
|
||||||
self.pairs[pair]['stop'] = True
|
self.pairs[pair]['stop'] = True
|
||||||
# if self.pairs[pair]['current_profit'] > 0:
|
# if self.pairs[pair]['current_profit'] > 0:
|
||||||
# self.pairs[pair]['force_sell'] = True
|
# self.pairs[pair]['force_sell'] = True
|
||||||
|
|||||||
Reference in New Issue
Block a user