
La stratégie est basée sur l’analyse graphique de l’humeur du marché et quantifie la psychologie du marché à l’aide de trois oscillateurs principaux: oscillateur de réticence, oscillateur de peur et oscillateur de cupidité. La stratégie intègre des indicateurs de dynamique et de tendance, tout en combinant la confirmation de transaction pour construire un système de trading complet.
Le cœur de la stratégie est de construire trois oscillateurs d’émotions en analysant les différentes configurations de l’image:
La moyenne de ces trois oscillateurs constitue l’indice de l’émotion des bulls ((CEI)). Le signal de négociation de bullish est déclenché lorsque le CEI franchit différents seuils et est confirmé par le volume des transactions.
Il s’agit d’une stratégie innovante qui combine l’analyse technique et la négociation quantitative. Grâce à une analyse des émotions systématique et à une gestion rigoureuse des risques, la stratégie est capable de fournir aux traders des signaux de négociation fiables. Bien qu’il existe une certaine marge d’optimisation, le cadre de base de la stratégie est robuste et adapté à un développement ultérieur et à une application sur le terrain.
/*backtest
start: 2024-03-09 18:40:00
end: 2025-02-19 08:00:00
period: 1d
basePeriod: 1d
exchanges: [{"eid":"Binance","currency":"SOL_USDT"}]
*/
//@version=6
strategy("Candle Emotion Index Strategy", shorttitle="CEI Strategy", overlay=true)
// User Inputs
length = input.int(14, title="Lookback Period", minval=1)
dojiThreshold = input.float(0.1, title="Doji Threshold", minval=0.01, maxval=0.5)
spinningTopThreshold = input.float(0.3, title="Spinning Top Threshold", minval=0.1, maxval=0.5)
shootingStarThreshold = input.float(0.5, title="Shooting Star Threshold", minval=0.1, maxval=1.0)
hangingManThreshold = input.float(0.5, title="Hanging Man Threshold", minval=0.1, maxval=1.0)
engulfingThreshold = input.float(0.5, title="Engulfing Threshold", minval=0.1, maxval=1.0)
marubozuThreshold = input.float(0.9, title="Marubozu Threshold", minval=0.5, maxval=1.0)
hammerThreshold = input.float(0.5, title="Hammer Threshold", minval=0.1, maxval=1.0)
threeWhiteSoldiersThreshold = input.float(0.5, title="Three White Soldiers Threshold", minval=0.1, maxval=1.0)
// Volume Multiplier Input
volumeMultiplier = input.float(1.5, title="Volume Multiplier", minval=1.0)
// Cooldown Period Input
cooldownPeriod = input.int(10, title="Cooldown Period (Candles)", minval=1)
// Maximum Holding Period Inputs
maxHoldingPeriod = input.int(20, title="Maximum Holding Period (Candles)", minval=1)
lossHoldingPeriod = input.int(10, title="Loss Exit Holding Period (Candles)", minval=1)
lossThreshold = input.float(0.02, title="Loss Threshold (as % of Entry Price)", minval=0.01, maxval=1.0)
// --- Indecision Oscillator Functions ---
isDoji(open, close, high, low, threshold) =>
bodySize = math.abs(close - open)
rangeSize = high - low
bodySize / rangeSize < threshold
isSpinningTop(open, close, high, low, threshold) =>
bodySize = math.abs(close - open)
rangeSize = high - low
bodySize / rangeSize < threshold and bodySize / rangeSize >= dojiThreshold
indecisionOscillator() =>
var float dojiScore = 0.0
var float spinningTopScore = 0.0
for i = 1 to length
if isDoji(open[i], close[i], high[i], low[i], dojiThreshold)
dojiScore := dojiScore + 1.0
if isSpinningTop(open[i], close[i], high[i], low[i], spinningTopThreshold)
spinningTopScore := spinningTopScore + 1.0
dojiScore := dojiScore / length
spinningTopScore := spinningTopScore / length
(dojiScore + spinningTopScore) / 2
// --- Fear Oscillator Functions ---
isShootingStar(open, close, high, low, threshold) =>
bodySize = math.abs(close - open)
upperWick = high - math.max(open, close)
lowerWick = math.min(open, close) - low
upperWick / bodySize > threshold and lowerWick < bodySize
isHangingMan(open, close, high, low, threshold) =>
bodySize = math.abs(close - open)
upperWick = high - math.max(open, close)
lowerWick = math.min(open, close) - low
lowerWick / bodySize > threshold and upperWick < bodySize
isBearishEngulfing(open, close, openPrev, closePrev, threshold) =>
bodySize = math.abs(close - open)
prevBodySize = math.abs(closePrev - openPrev)
close < openPrev and open > closePrev and bodySize / prevBodySize > threshold
fearOscillator() =>
var float shootingStarScore = 0.0
var float hangingManScore = 0.0
var float engulfingScore = 0.0
for i = 1 to length
if isShootingStar(open[i], close[i], high[i], low[i], shootingStarThreshold)
shootingStarScore := shootingStarScore + 1.0
if isHangingMan(open[i], close[i], high[i], low[i], hangingManThreshold)
hangingManScore := hangingManScore + 1.0
if isBearishEngulfing(open[i], close[i], open[i+1], close[i+1], engulfingThreshold)
engulfingScore := engulfingScore + 1.0
shootingStarScore := shootingStarScore / length
hangingManScore := hangingManScore / length
engulfingScore := engulfingScore / length
(shootingStarScore + hangingManScore + engulfingScore) / 3
// --- Greed Oscillator Functions ---
isMarubozu(open, close, high, low, threshold) =>
bodySize = math.abs(close - open)
totalRange = high - low
bodySize / totalRange > threshold
isHammer(open, close, high, low, threshold) =>
bodySize = math.abs(close - open)
lowerWick = math.min(open, close) - low
upperWick = high - math.max(open, close)
lowerWick / bodySize > threshold and upperWick < bodySize
isBullishEngulfing(open, close, openPrev, closePrev, threshold) =>
bodySize = math.abs(close - open)
prevBodySize = math.abs(closePrev - openPrev)
close > openPrev and open < closePrev and bodySize / prevBodySize > threshold
isThreeWhiteSoldiers(open, close, openPrev, closePrev, openPrev2, closePrev2, threshold) =>
close > open and closePrev > openPrev and closePrev2 > openPrev2 and close > closePrev and closePrev > closePrev2
greedOscillator() =>
var float marubozuScore = 0.0
var float hammerScore = 0.0
var float engulfingScore = 0.0
var float soldiersScore = 0.0
for i = 1 to length
if isMarubozu(open[i], close[i], high[i], low[i], marubozuThreshold)
marubozuScore := marubozuScore + 1.0
if isHammer(open[i], close[i], high[i], low[i], hammerThreshold)
hammerScore := hammerScore + 1.0
if isBullishEngulfing(open[i], close[i], open[i+1], close[i+1], engulfingThreshold)
engulfingScore := engulfingScore + 1.0
if isThreeWhiteSoldiers(open[i], close[i], open[i+1], close[i+1], open[i+2], close[i+2], threeWhiteSoldiersThreshold)
soldiersScore := soldiersScore + 1.0
marubozuScore := marubozuScore / length
hammerScore := hammerScore / length
engulfingScore := engulfingScore / length
soldiersScore := soldiersScore / length
(marubozuScore + hammerScore + engulfingScore + soldiersScore) / 4
// --- Final Calculations ---
indecision = indecisionOscillator()
fear = fearOscillator()
greed = greedOscillator()
// Calculate the average of the three oscillators
averageOscillator = (indecision + fear + greed) / 3
// --- Combined Strategy Logic ---
var float entryPriceLong = na
var float entryPriceShort = na
var int holdingPeriodLong = 0
var int holdingPeriodShort = 0
var int cooldownCounter = 0
// Buy Signal Logic for Long and Short
longBuySignal = ta.crossover(averageOscillator, 0.1)
shortBuySignal = ta.crossover(averageOscillator, 0.2)
// Calculate average volume over the lookback period
avgVolume = ta.sma(volume, length)
// Take Profit Conditions
longTakeProfitCondition = close > open and volume > avgVolume * volumeMultiplier
shortTakeProfitCondition = close < open and volume > avgVolume * volumeMultiplier
// Buy Logic for Long Positions
if longBuySignal and strategy.position_size == 0 and cooldownCounter <= 0
entryPriceLong := close
strategy.entry("Long Entry", strategy.long)
cooldownCounter := cooldownPeriod
holdingPeriodLong := 0
// Increment holding period if in a long position
if strategy.position_size > 0
holdingPeriodLong := holdingPeriodLong + 1
// Sell Logic for Long Positions
if longTakeProfitCondition and strategy.position_size > 0 and close > entryPriceLong
strategy.close_all()
cooldownCounter := cooldownPeriod
if holdingPeriodLong >= maxHoldingPeriod and strategy.position_size > 0 and close >= entryPriceLong
strategy.close_all()
cooldownCounter := cooldownPeriod
if holdingPeriodLong >= lossHoldingPeriod and strategy.position_size > 0 and close < entryPriceLong * (1 - lossThreshold)
strategy.close_all()
cooldownCounter := cooldownPeriod
// Short Logic for Short Positions
if shortBuySignal and strategy.position_size == 0 and cooldownCounter <= 0
entryPriceShort := close
strategy.entry("Short Entry", strategy.short)
cooldownCounter := cooldownPeriod
holdingPeriodShort := 0
// Increment holding period if in a short position
if strategy.position_size < 0
holdingPeriodShort := holdingPeriodShort + 1
// Cover Logic for Short Positions
if shortTakeProfitCondition and strategy.position_size < 0 and close < entryPriceShort
strategy.close_all()
cooldownCounter := cooldownPeriod
if holdingPeriodShort >= maxHoldingPeriod and strategy.position_size < 0 and close <= entryPriceShort
strategy.close_all()
cooldownCounter := cooldownPeriod
if holdingPeriodShort >= lossHoldingPeriod and strategy.position_size < 0 and close > entryPriceShort * (1 + lossThreshold)
strategy.close_all()
cooldownCounter := cooldownPeriod
// Decrement the cooldown counter each candle
if cooldownCounter > 0
cooldownCounter := cooldownCounter - 1