悲观胜率PWP及其实现
悲观胜率(Pessimistic Win Percentage)是一种用于衡量盈利交易占比的保守评估标准。相较于常规的胜率,它旨在提供一个对策略表现更具稳健性的考量。接下来,让我们深入了解这一概念,并学习如何在PineScript中实现它。
什么是悲观胜率
悲观胜率(Pessimistic Win Percentage, PWP)是基于Robert Pardo的悲观保证金回报率(Pessimistic Return On Margin, PROM)理念所衍生出的一个回测指标。PROM通过对总利润和总亏损进行悲观的调整,从而得出一个更低、但期望中也更贴近真实的保证金回报率。
当我们将相同的原则应用于胜率计算时,便可以定义出悲观胜率的计算公式:
公式中的两个变量含义如下:win count(盈利笔数)是策略所执行的盈利交易总数;total trade count(总交易笔数)是策略的已平仓交易总数。
PWP的核心思想是在数学上减少盈利交易的笔数。由于计算时总的交易笔数保持不变,因此最终得出的胜率自然也就降低了。
悲观胜率的优势
悲观胜率具备两大重要优势。其一是预留安全边际:它基于一个核心假设——策略在未来实盘中的表现会劣于其经过优化的历史回测表现,从而主动调低胜率。其二是惩罚小样本:相比于拥有大量交易记录的策略,PWP对交易样本量小的策略惩罚更重。盈利交易的笔数越缺乏统计显著性,策略调整后的胜率就变得越差。
许多交易策略都面临一个共同的困境:回测时表现优异,实盘时却大打折扣。PWP正是试图通过注入一份怀疑主义来缓解这一问题。如果一个策略在经过PWP的严苛评估后,其结果依然令人满意,那么我们就有更强的信心认为,我们找到了一个真正稳健的策略。
示例
让我们通过两个案例来感受PWP的作用。这两个案例的常规胜率完全相同,但正如我们将看到的,它们的PWP值却截然不同。
案例一,假设我们的回测结果如下:盈利交易80笔,总交易150笔,常规胜率53.33%。该策略的PWP计算结果为47.37%,比常规胜率降低了超过6个百分点:
案例二,我们保持常规胜率不变,但减少交易样本量:盈利交易24笔,总交易45笔,常规胜率同样是53.33%。对于这份数据,PWP进一步下跌至42.45%(比回测结果恶化了超过10个百分点):
样本量的影响
在上述第二个案例中,随着样本量的减小,PWP出现了更大幅度的下降。其背后的数学原理是:PWP通过减去盈利笔数的平方根来惩罚盈利总数。盈利交易的笔数越少,其平方根在数值上所占的比重就越大,从而导致从盈利总数中减去的有效笔数更多。
下表清晰地展示了盈利笔数如何影响PWP。在表中,常规胜率始终保持在50%,但随着盈利笔数的减少,PWP也每况愈下:
| 盈利交易 | 总交易 | 胜率 | 悲观胜率 (PWP) | 差异 | 差异 (%) |
|---|---|---|---|---|---|
| 300 | 600 | 50.0% | 47.1% | -2.9% | -5.8% |
| 200 | 400 | 50.0% | 46.5% | -3.5% | -7.1% |
| 150 | 300 | 50.0% | 45.9% | -4.1% | -8.2% |
| 100 | 200 | 50.0% | 45.0% | -5.0% | -10.0% |
| 75 | 150 | 50.0% | 44.2% | -5.8% | -11.5% |
| 50 | 100 | 50.0% | 42.9% | -7.1% | -14.1% |
| 30 | 60 | 50.0% | 40.9% | -9.1% | -18.3% |
| 10 | 20 | 50.0% | 34.2% | -15.8% | -31.6% |
对于一个拥有300笔盈利交易的策略,PWP的起始值为47.1%。然而,当表格拉到最后,由于样本量过小,胜率被压缩至34.2%。值得注意的是,常规胜率始终保持在50%,它并不会因为样本中仅有10笔盈利交易而受到任何惩罚。
自定义PineScript函数
现在,让我们看看如何在TradingView策略中应用悲观胜率。以下是一个用于计算策略PWP的自定义函数:
// PessimisticWinPercentage() 函数返回策略的胜率,
// 该胜率通过将盈利交易数减去其自身的平方根来进行悲观调整
// (这种方法同时也惩罚了小交易样本)。
// 注意:若策略尚未有任何平仓交易,则返回 'na'。
PessimisticWinPercentage() =>
((strategy.wintrades - math.sqrt(strategy.wintrades)) / strategy.closedtrades) * 100
在这个 PessimisticWinPercentage() 自定义函数中,我们首先从策略的盈利交易总数(strategy.wintrades)中,减去它的平方根(通过 math.sqrt() 函数计算得出)。接着,我们将这个经过悲观调整后的盈利数,除以策略的总平仓交易数(strategy.closedtrades)。最后,为了将结果转换为百分比形式,我们乘以100。
函数应用示例
将上述函数复制到我们的策略代码中后,便可以多种方式来运用它。要获取策略当前的PWP值,只需这样调用函数:
// 获取当前的悲观胜率
pwpValue = PessimisticWinPercentage()
要观察PWP在策略回测过程中的动态变化,我们可以使用 plot() 函数将其绘制出来:
// 将当前的PWP以柱状图形式展示
plot(PessimisticWinPercentage(), style=plot.style_columns, title="PWP")
PessimisticWinPercentage() 函数在每根K线上都返回一个值,这意味着我们可以比较当前值与历史值。例如,要判断PWP在过去10根K线内是否有所下降,可以这样写:
// 检查悲观胜率在过去10根K线内是否有所下降
if PessimisticWinPercentage() < PessimisticWinPercentage()[10]
label.new(bar_index, high, text="PWP有所下降!")
PessimisticWinPercentage() 的一个妙用是,策略可以在回测期间就使用它来辅助决策。假设我们想设定一条风控规则:当PWP降至20%以下时便停止交易。同时,为了避免因样本过小而过早触发,我们还要求策略的已平仓交易(strategy.closedtrades)必须超过30笔。实现代码如下:
var okayToTrade = true
// 当策略已平仓交易超过30笔,且悲观胜率
// 低于20%时,将 'okayToTrade' 设为 false 以停止交易。
if strategy.closedtrades > 30 and PessimisticWinPercentage() < 20
okayToTrade := false
// 只有在 'okayToTrade' 为 true 的情况下,策略才允许开立多头仓位
if okayToTrade and ta.crossover(close, ta.ema(close, 20))
strategy.entry("Enter Long", strategy.long)
另一种可能是将 PessimisticWinPercentage() 与其他PineScript函数结合。例如,我们可以用 ta.crossover() 函数来捕捉PWP上穿70%的时刻:
// 检查当前的悲观胜率相较于上一根K线,
// 是否上穿了70%。
if ta.crossover(PessimisticWinPercentage(), 70)
label.new(bar_index, high, text="PWP已回升至70%以上!")
策略范例
让我们通过一个完整的策略来演示如何使用 PessimisticWinPercentage() 函数。以下脚本基于钱德动量振荡器(Chande Momentum Oscillator, CMO)进行交易。当该指标上穿零线时做多,下穿零线时做空。在图表末尾,我们会用一个文本标签来同时报告策略的常规胜率及其悲观胜率。策略的完整代码如下:
//@version=5
strategy(title="悲观胜率示例", overlay=true)
// PessimisticWinPercentage() 函数返回策略的胜率,
// 该胜率通过将盈利交易数减去其自身的平方根来进行悲观调整
// (这种方法同时也惩罚了小交易样本)。
// 注意:若策略尚未有任何平仓交易,则返回 'na'。
PessimisticWinPercentage() =>
((strategy.wintrades - math.sqrt(strategy.wintrades)) / strategy.closedtrades) * 100
// GetWinRate() 函数返回策略的胜率(即盈利交易的百分比)。
// 可将 'includeEvens' 参数设置为 'true',
// 以便将盈亏两平的交易也视作盈利。
GetWinRate(includeEvens = false) =>
winTradeCount = strategy.wintrades + (includeEvens ? strategy.eventrades : 0)
(winTradeCount / strategy.closedtrades) * 100
// 计算钱德动量振荡器 (CMO)
cmoValue = ta.cmo(close, 9)
// 生成多头和空头交易信号
if ta.crossover(cmoValue, 0)
strategy.entry("Enter Long", strategy.long)
if ta.crossunder(cmoValue, 0)
strategy.entry("Enter Short", strategy.short)
// 在图表的最后一条确认的历史K线上,创建标签以显示
// 常规胜率和悲观胜率
if barstate.islastconfirmedhistory
label.new(bar_index + 3, hl2, style=label.style_label_left,
color=#FADFAD, text="常规胜率: " +
str.tostring(GetWinRate(), "0.00") + "%" +
"\n悲观胜率: " +
str.tostring(PessimisticWinPercentage(), "0.00") + "%")
我们以 strategy() 函数开始,为脚本命名并使其叠加在主图表上。接着,我们引入了上面讨论过的 PessimisticWinPercentage() 函数,以及用于计算常规胜率的自定义函数 GetWinRate()。
然后,ta.cmo() 函数计算了周期为9的钱德动量振荡器。随后的两个 if 语句负责生成交易信号。第一个使用 ta.crossover() 判断CMO是否上穿了0,若是,则 strategy.entry() 函数建立一个多头仓位。第二个 if 语句则使用 ta.crossunder() 判断CMO是否下穿了0,若是,则建立一个空头仓位。
在图表末尾,我们创建显示悲观胜率的标签:
// 在图表的最后一条确认的历史K线上,创建标签以显示
// 常规胜率和悲观胜率
if barstate.islastconfirmedhistory
label.new(bar_index + 3, hl2, style=label.style_label_left,
color=#FADFAD, text="常规胜率: " +
str.tostring(GetWinRate(), "0.00") + "%" +
"\n悲观胜率: " +
str.tostring(PessimisticWinPercentage(), "0.00") + "%")
此处的 if 语句通过 barstate.islastconfirmedhistory 变量判断当前是否为图表的最后一条历史K线。
在该K线上,label.new() 函数创建一个文本标签。标签显示在当前K线向右3根的位置(bar_index + 3),垂直位置在K线的中点(hl2)。标签箭头朝左(label.style_label_left),背景色为 #FADFAD(桃黄色的十六进制代码)。
标签的文本内容是将字符串与 GetWinRate()(常规胜率)和 PessimisticWinPercentage()(悲观胜率)的返回值拼接而成。由于这两个函数返回的是数值,而 label.new() 需要文本,我们使用 str.tostring() 函数将数值转换为文本。
当我们在图表上运行该策略时,它会创建一个包含悲观胜率的标签,效果如下:
简单总结一下:悲观胜率(PWP)的核心假设是,策略在未来实盘交易中的获胜频率,会低于其回测结果所显示的水平。为了计算PWP,我们将盈利交易的总笔数减去其自身的平方根,然后将这个被调低了的盈利数除以总的交易笔数。最终得到的是一个经过数学调整、数值更低的胜率,该指标同时还能有效惩罚那些交易样本量过小的策略。
悲观亏损百分比PLP及其实现
悲观亏损百分比(Pessimistic Lose Percentage, PLP)是一种衡量策略非盈利交易的保守型绩效指标。其设计目标是提供一个比常规亏损率更可靠的评估依据。让我们深入了解这个绩效指标的内涵,然后用PineScript将其付诸实践。
什么是悲观亏损百分比
PLP指标的灵感来源于Robert Pardo在其著作中提出的悲观保证金回报率(PROM)。PROM的核心思想是通过调低总盈利、调高总亏损来更准确地预测策略未来的实际表现。将这一思想应用于亏损率,我们便可以定义出悲观亏损百分比的计算公式:
其中,亏损笔数是策略中所有非盈利交易的总数量,总交易笔数是策略完成的所有交易的总数量。
PLP的本质,就是通过数学方法人为地增加亏损交易的数量。由于分母(总交易数)保持不变,最终计算出的亏损率自然会相应提高。
PLP的优势
采用PLP进行评估主要有两个显著的好处。其一是正视现实差距:它基于一个普遍共识——策略的实盘表现几乎总会劣于其在历史数据上(尤其是经过优化后)的回测表现。PLP主动提高了亏损率,以弥合这种预期差距。其二是惩罚小样本:它对交易样本量较小的策略施加更重的惩罚。亏损交易的样本量越小,其统计显著性就越低,PLP调整后的亏损率也就会变得越差。
我们宁愿高估未来的亏损率,也不愿盲目乐观地相信策略在未知市场上的表现能完美复刻历史回测。因此,如果一个策略在经过PLP的悲观调整后,其结果依然稳健,那么我们就有更强的信心认为它是一个真正有效的策略。
示例对比
为了更直观地理解PLP,我们来看两个例子。这两个策略的常规亏损率完全相同,但PLP却截然不同。
情况一,样本量较大:亏损交易35笔,总交易105笔,常规亏损率33.33%。该策略的PLP计算如下,结果为38.97%,比常规亏损率差了约5.5个百分点:
情况二,样本量较小:亏损交易10笔,总交易30笔,常规亏损率同样是33.33%。现在,PLP飙升至43.87%,比常规亏损率恶化了超过10个百分点:
样本量的影响
从上面的例子可以看出,PLP因为较小的样本量而急剧恶化。其背后的数学原理在于,PLP通过加上亏损数的平方根来进行调整。平方根函数的一个特性是,对于较小的数值,其增长是相对显著的。
下表清晰地展示了,即使在常规亏损率恒定为50%的情况下,仅仅因为亏损交易的样本量不断减小,PLP是如何被逐步放大的:
| 亏损交易 | 总交易 | 常规亏损率 | 悲观亏损百分比 (PLP) | 差异 | 差异 (%) |
|---|---|---|---|---|---|
| 300 | 600 | 50.0% | 52.9% | 2.9% | 5.8% |
| 200 | 400 | 50.0% | 53.5% | 3.5% | 7.1% |
| 150 | 300 | 50.0% | 54.1% | 4.1% | 8.2% |
| 100 | 200 | 50.0% | 55.0% | 5.0% | 10.0% |
| 75 | 150 | 50.0% | 55.8% | 5.8% | 11.5% |
| 50 | 100 | 50.0% | 57.1% | 7.1% | 14.1% |
| 30 | 60 | 50.0% | 59.1% | 9.1% | 18.3% |
| 10 | 20 | 50.0% | 65.8% | 15.8% | 31.6% |
值得注意的是,常规亏损率始终是50%,它完全没有体现出仅有10笔亏损交易的样本是多么不可靠。
PineScript实现
让我们看看如何在TradingView策略中计算并使用PLP。下面的自定义函数可以实现这一功能:
// PessimisticLosePercentage() 函数返回策略经过悲观调整后的亏损率。
// 它通过增加亏损交易数的平方根来实现调整,这种方法对小样本的惩罚更重。
// 注意:若策略无任何平仓交易,则返回'na'。
PessimisticLosePercentage() =>
((strategy.losstrades + math.sqrt(strategy.losstrades)) /
strategy.closedtrades) * 100
在这个函数中,我们首先获取策略的总亏损交易数(strategy.losstrades),并加上它的平方根(通过 math.sqrt() 函数计算)。然后,将这个调整后的亏损数除以总平仓交易数(strategy.closedtrades),最后乘以100得到百分比。
函数应用示例
将上述函数代码复制到你的策略脚本后,你就可以在多处灵活地调用它了。获取当前PLP值:
// 获取当前K线的悲观亏损百分比
currentPLP = PessimisticLosePercentage()
绘制PLP随时间变化的曲线:
// 将当前PLP以面积图形式绘制出来
plot(PessimisticLosePercentage(), style=plot.style_area, title="PLP")
比较PLP的变化趋势:
// 检查PLP相较于20根K线前是否有所改善
if PessimisticLosePercentage() < PessimisticLosePercentage()[20]
label.new(bar_index, high, text="PLP在最近20根K线内有所改善!")
将PLP用作交易决策的过滤器:
// 仅当PLP低于80%时,才允许开立新的多头仓位
if PessimisticLosePercentage() < 80 and ta.crossover(close, ta.wma(close, 20))
strategy.entry("Enter Long", strategy.long)
与其他函数结合使用,进行更复杂的分析:
// 检查当前PLP是否创下过去100根K线的新高
if PessimisticLosePercentage() > ta.highest(PessimisticLosePercentage(), 100)[1]
label.new(bar_index, high, text="PLP创下100根K线新高!")
策略范例
让我们通过一个完整的策略来看看如何应用 PessimisticLosePercentage() 函数。下面的脚本使用7周期的相对强弱指数(RSI)进行交易:RSI跌破30做多,升破70做空。我们会在回测结束时,创建一个文本标签,同时显示常规亏损率和悲观亏损率,以便直观对比。策略的完整代码如下:
//@version=5
strategy(title="悲观亏损百分比示例", overlay=true)
// 实现PLP计算的函数
PessimisticLosePercentage() =>
((strategy.losstrades + math.sqrt(strategy.losstrades)) /
strategy.closedtrades) * 100
// 计算常规亏损率的函数 (为对比而引入)
GetLoseRate(includeEvens = false) =>
loseTradeCount = strategy.losstrades +
(includeEvens ? strategy.eventrades : 0)
(loseTradeCount / strategy.closedtrades) * 100
// 计算RSI指标
rsiValue = ta.rsi(close, 7)
// 定义交易逻辑
if ta.crossunder(rsiValue, 30)
strategy.entry("Enter Long", strategy.long)
if ta.crossover(rsiValue, 70)
strategy.entry("Enter Short", strategy.short)
// 在回测的最后一根K线上,创建标签显示两种亏损率
if barstate.islastconfirmedhistory
label.new(bar_index + 3, hl2, style=label.style_label_left,
color=#FDDDE6, text="常规亏损率: " +
str.tostring(GetLoseRate(), "0.00") + "%" +
"\n悲观亏损率 (PLP): " +
str.tostring(PessimisticLosePercentage(), "0.00") + "%")
在这个策略中,我们首先定义了所需的函数和指标。交易逻辑很简单,基于RSI的超买超卖区进行反转操作。
关键在于最后一部分代码:if barstate.islastconfirmedhistory 语句确保只在回测结束时执行一次。label.new() 函数负责创建标签,其中 text 参数拼接了描述性文字以及通过调用 GetLoseRate() 和 PessimisticLosePercentage() 函数得到的两个亏损率。我们使用 str.tostring() 将数值格式化为两位小数的字符串。
当策略应用于图表时,最终生成的标签效果如下:
简单总结一下:悲观亏损百分比(PLP)的核心假设是,策略在未来实盘中的非盈利交易会比历史回测显示的更多。它的计算方法是在原有亏损交易数的基础上,加上其平方根,然后再除以总交易数。这种经过数学调整的方法,不仅得出了一个更保守的亏损率,更重要的是,它有效地惩罚了那些基于小样本得出的看似美好的回测结果。










