该策略基于蜡烛图形态分析市场情绪,通过三个核心振荡器(犹豫振荡器、恐惧振荡器和贪婪振荡器)来量化市场心理。策略综合了动量和趋势指标,同时结合成交量确认,构建了一个完整的交易系统。该策略适用于希望通过市场情绪分析来识别高概率交易机会的交易者。
策略的核心是通过分析不同的蜡烛图形态来构建三个情绪振荡器: 1. 犹豫振荡器 - 通过十字星和陀螺形态来衡量市场的不确定性 2. 恐惧振荡器 - 通过流星、上吊线和看跌吞没形态来跟踪空头情绪 3. 贪婪振荡器 - 通过光头阳线、锤子线、看涨吞没和三白兵来检测多头情绪
这三个振荡器的平均值构成了蜡烛情绪指数(CEI)。当CEI突破不同阈值时触发多空交易信号,并通过成交量确认。
这是一个将技术分析与量化交易相结合的创新策略。通过系统化的情绪分析和严格的风险管理,该策略能够为交易者提供可靠的交易信号。虽然存在一定的优化空间,但策略的基本框架是稳健的,适合进一步开发和实盘应用。
/*backtest
start: 2024-03-13 09:40:00
end: 2025-02-23 15:00:00
period: 1h
basePeriod: 1m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
*/
//@version=6
strategy("Candle Emotion Index Strategy", shorttitle="CEI Strategy", default_qty_value=10,overlay=true)
// 用户输入参数
length = input.int(14, title="回看周期", minval=1) // 设置回看周期,默认为14
dojiThreshold = input.float(0.1, title="Doji 阈值", minval=0.01, maxval=0.5) // 设置Doji形态的阈值
spinningTopThreshold = input.float(0.3, title="旋转顶阈值", minval=0.1, maxval=0.5) // 设置旋转顶形态的阈值
shootingStarThreshold = input.float(0.5, title="射击之星阈值", minval=0.1, maxval=1.0) // 设置射击之星形态的阈值
hangingManThreshold = input.float(0.5, title="吊人线阈值", minval=0.1, maxval=1.0) // 设置吊人线形态的阈值
engulfingThreshold = input.float(0.5, title="吞没形态阈值", minval=0.1, maxval=1.0) // 设置吞没形态的阈值
marubozuThreshold = input.float(0.9, title="光头光脚阈值", minval=0.5, maxval=1.0) // 设置光头光脚形态的阈值
hammerThreshold = input.float(0.5, title="锤子线阈值", minval=0.1, maxval=1.0) // 设置锤子线形态的阈值
threeWhiteSoldiersThreshold = input.float(0.5, title="三只白兵阈值", minval=0.1, maxval=1.0) // 设置三只白兵形态的阈值
// 成交量倍数
volumeMultiplier = input.float(1.5, title="成交量倍数", minval=1.0) // 设置成交量倍数,用于平仓的条件判断
// 冷却期输入
cooldownPeriod = input.int(10, title="冷却期(蜡烛数)", minval=1) // 设置冷却期,表示每次交易后等待冷却的蜡烛数
// 最大持仓周期输入
maxHoldingPeriod = input.int(20, title="最大持仓周期(蜡烛数)", minval=1) // 设置最大持仓周期,限制持仓时间
lossHoldingPeriod = input.int(10, title="亏损退出持仓周期(蜡烛数)", minval=1) // 设置亏损退出持仓的最大周期
lossThreshold = input.float(0.02, title="亏损阈值(相对于开盘价的百分比)", minval=0.01, maxval=1.0) // 设置亏损阈值,当亏损达到此百分比时退出
// --- 犹豫震荡指标函数 ---
isDoji(open, close, high, low, threshold) =>
bodySize = math.abs(close - open) // 计算蜡烛实体大小
rangeSize = high - low // 计算蜡烛的价格区间
bodySize / rangeSize < threshold // 若实体占总范围的比值小于阈值,认为是Doji
isSpinningTop(open, close, high, low, threshold) =>
bodySize = math.abs(close - open) // 计算蜡烛实体大小
rangeSize = high - low // 计算蜡烛的价格区间
bodySize / rangeSize < threshold and bodySize / rangeSize >= dojiThreshold // 若实体占总范围的比值小于阈值,且大于Doji阈值,认为是旋转顶
indecisionOscillator() =>
var float dojiScore = 0.0 // 初始化Doji形态的得分
var float spinningTopScore = 0.0 // 初始化旋转顶形态的得分
for i = 1 to length
if isDoji(open[i], close[i], high[i], low[i], dojiThreshold) // 检测Doji形态
dojiScore := dojiScore + 1.0
if isSpinningTop(open[i], close[i], high[i], low[i], spinningTopThreshold) // 检测旋转顶形态
spinningTopScore := spinningTopScore + 1.0
dojiScore := dojiScore / length // 计算Doji形态得分的平均值
spinningTopScore := spinningTopScore / length // 计算旋转顶形态得分的平均值
(dojiScore + spinningTopScore) / 2 // 返回犹豫震荡指标的平均得分
// --- 恐惧震荡指标函数 ---
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 // 返回恐惧震荡指标的平均得分
// --- 贪婪震荡指标函数 ---
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 // 返回贪婪震荡指标的平均得分
// --- 最终计算 ---
indecision = indecisionOscillator() // 计算犹豫震荡指标
fear = fearOscillator() // 计算恐惧震荡指标
greed = greedOscillator() // 计算贪婪震荡指标
// 计算三个震荡指标的平均值
averageOscillator = (indecision + fear + greed) / 3 // 计算所有情绪指标的平均得分
// --- 组合策略逻辑 ---
var float entryPriceLong = na // 定义多头入场价格
var float entryPriceShort = na // 定义空头入场价格
var int holdingPeriodLong = 0 // 多头持仓周期
var int holdingPeriodShort = 0 // 空头持仓周期
var int cooldownCounter = 0 // 冷却期计数器
// 多头买入信号
longBuySignal = ta.crossover(averageOscillator, 0.1) // 当情绪震荡指标上穿0.1时,产生多头买入信号
// 空头买入信号
shortBuySignal = ta.crossover(averageOscillator, 0.2) // 当情绪震荡指标上穿0.2时,产生空头买入信号
// 计算回看期的平均成交量
avgVolume = ta.sma(volume, length) // 计算回看期内的平均成交量
// 多头平仓条件
longTakeProfitCondition = close > open and volume > avgVolume * volumeMultiplier // 如果当前价格上涨且成交量超过平均成交量的1.5倍,则平仓
// 空头平仓条件
shortTakeProfitCondition = close < open and volume > avgVolume * volumeMultiplier // 如果当前价格下跌且成交量超过平均成交量的1.5倍,则平仓
// 多头买入逻辑
if longBuySignal and strategy.position_size == 0 and cooldownCounter <= 0
entryPriceLong := close // 记录入场价格
strategy.entry("Long Entry", strategy.long) // 进场做多
cooldownCounter := cooldownPeriod // 进入冷却期
holdingPeriodLong := 0 // 重置持仓周期
// 增加多头持仓周期
if strategy.position_size > 0
holdingPeriodLong := holdingPeriodLong + 1
// 多头平仓逻辑
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
// 空头买入逻辑
if shortBuySignal and strategy.position_size == 0 and cooldownCounter <= 0
entryPriceShort := close // 记录入场价格
strategy.entry("Short Entry", strategy.short) // 进场做空
cooldownCounter := cooldownPeriod // 进入冷却期
holdingPeriodShort := 0 // 重置持仓周期
// 增加空头持仓周期
if strategy.position_size < 0
holdingPeriodShort := holdingPeriodShort + 1
// 空头平仓逻辑
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
// 每根蜡烛减少冷却期计数器
if cooldownCounter > 0
cooldownCounter := cooldownCounter - 1