
Chiến lược này dựa trên phân tích hình đồ thị về cảm xúc thị trường, định lượng tâm lý thị trường thông qua ba dao động cốt lõi: dao động do dự, dao động sợ hãi và dao động tham lam. Chiến lược này tích hợp các chỉ số động lực và xu hướng, đồng thời kết hợp xác nhận giao dịch để xây dựng một hệ thống giao dịch hoàn chỉnh. Chiến lược này phù hợp cho các nhà giao dịch muốn xác định cơ hội giao dịch có khả năng cao thông qua phân tích cảm xúc thị trường.
Cốt lõi của chiến lược là xây dựng ba rung động cảm xúc bằng cách phân tích các hình dạng khác nhau của photon:
Các giá trị trung bình của ba dao động này tạo thành chỉ số tâm trạng thăng hoa ((CEI)). Khi CEI vượt qua các ngưỡng khác nhau, nó sẽ kích hoạt tín hiệu giao dịch đa thùng và xác nhận thông qua khối lượng giao dịch.
Đây là một chiến lược sáng tạo kết hợp phân tích kỹ thuật với giao dịch định lượng. Bằng cách phân tích cảm xúc có hệ thống và quản lý rủi ro nghiêm ngặt, chiến lược này có thể cung cấp tín hiệu giao dịch đáng tin cậy cho các nhà giao dịch. Mặc dù có một số không gian tối ưu hóa, nhưng khung cơ bản của chiến lược là vững chắc, phù hợp cho việc phát triển và ứng dụng thực tế hơn nữa.
/*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