TEST Simple
This commit is contained in:
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']}"
|
||||
)
|
||||
Reference in New Issue
Block a user