根据今年Crypto的表现,其实从CoinGlass的持仓&多空比&资金费率、聚合现货订单薄或者是全网期货持仓、链上数据、稳定币发行量、市占率等数据,这些东西对于行情来说并没有指示,只能起事后分析的作用,看多了反而使信息接受冗余。


虽然很多策略从三四年前甚至更远开始算回测,可以有N倍收益,但如果只是测试从3月14日到现在这半年内的数据,你很难看到有表现好的。


是的,意思就是如果在2015年投入$1,今天将收获$10,116USD。

但实际上从2024年初至今总收益仅为140%,更不用提利润在2021~2022横盘了一整年。

根据我看过一些策略的实际情况,经常发现利润回撤和修复的时间相当久。

于是窝老另写Strategy做BTC回测,结果超乎预料:

历史回测开始时间结束时间
回测&交易范围2024-01-01 08:002024-09-18 00:00

可以看到,这个策略不管遇上暴涨暴跌的单边行情,还是来回洗的横盘震荡,都可以稳定盈利(也就是低点不断抬高)。

而最大回撤是9.45%,这个利润/亏损比相当优秀,因为策略的数据表现优异(特别是最大回撤控制)意味着可以加大风险来换取超额复利利润。

DetailsVer. IVer. IIVer. IIIVer. IVVer. Ⅴ
净利润211.79%183.62%218.15%260.64%279.78%
最大亏损9.45%7.95%6.63%6.63%6.63%
胜率66.67%57.34%53.52%55.48%56.08%
夏普比率1.881.4831.8431.7911.678
索提诺比率N/AN/AN/AN/AN/A
盈利因子2.262.2192.3212.5392.652

设定两倍杠杆:

当杠杆x2,虽然此时的理论最高回撤相比原来提升了一倍,但因为杠杆*胜率*多次交易积累所带来的复利效应的原因,收益由211.79%超额提升到了779.78%。

由于极高的净利润/最大亏损比,所以数据层面的损失很小:

DetailsVer. IVer. IIVer. IIIVer. IVVer. Ⅴ
净利润779.78%643.80%827.34%1,089.60%1,216.98%
最大亏损18.56%15.50%12.93%12.93%12.93%
胜率66.67%57.34%53.52%55.48%56.08%
夏普比率1.7661.4331.7651.6841.583
索提诺比率N/AN/AN/AN/AN/A
盈利因子2.2262.0862.252.4592.63

Post Script:收益曲线顺滑版(单利)↓

DetailsVer. IVer. IIVer. IIIVer. IVVer. Ⅴ
净利润500.41%704.50%722.08%797.83%820.88%
最大亏损18.00%19.64%18.13%18.13%18.13%
胜率66.67%57.34%53.52%55.48%56.08%
夏普比率1.3930.780.8830.9230.861
索提诺比率N/AN/AN/AN/AN/A
盈利因子2.2952.4022.4272.6422.682

平衡最大可接受回撤的杠杆:

可以看到,继续放大回撤风险的情况下,收益直接来到了13,547.06%,只算纯利润是1倍杠杆情况下的63.96倍(不考虑投资基数),而最大回撤仅仅为原来(9.45%)的5.27倍。

这就是高胜率,优秀数据和风险控制好的情况下,放大杠杆带来的复利效应;坏处是随时有可能一笔交易让你亏损超过20%。

不过就超过2/3的胜率,以及各项优秀数据来看,你确实可以说,这种杠杆其实是可以上实盘的。

因为量化策略要解决的问题就是:

如何在控制亏损的情况下,最多的保留策略运行过程中获得的利润。

如果你的胜率足够高,能够有效限制最大回撤,开高杠杆(其实图中杠杆也不算高)是完全可行的。

DetailsVer. IVer. IIVer. IIIVer. IVVer. Ⅴ
净利润13,547.06%44,077.06%173,836.83%423,370.89%625,045. 00%
最大亏损49.86%49.86%49.85%49.85%49.85%
胜率66.67%57.34%53.52%55.48%56.08%
夏普比率1.4191.1921.3991.0971.048
索提诺比率N/A36.147N/AN/AN/A
盈利因子2.0511.7312.0272.1752.734

DetailsVer. IVer. IIVer. IIIVer. IVVer. Ⅴ
净利润26,290.76%123,504.04%485,789.49%1,497,634.32%2,393,310. 15%
最大亏损59.87%59.84%59.86%59.86%59.86%
胜率66.67%57.34%53.52%55.48%56.08%
夏普比率1.3151.1281.3320.9510.912
索提诺比率N/A27.655N/AN/AN/A
盈利因子1.9821.6531.9672.1162.783

数据堆彻

前面可以看到,在1倍杠杆的时候,夏普比率还能接近1.9,随着杠杆拉升,当回撤放大至60%,数据同时也降低到了1.3。

那么,有没有夏普比率奇高的策略呢?

窝老刚好也写出了一种可能:

1倍杠杆的情况下,和前面的对比其实没有区别,但是:

不夸张的说,就我有限的认知来看,这的确是我见过数据最好的Pine Script策略。

并且,这个版本的策略无限拉高杠杆时数据损失也很小:

直接回撤80%,看一眼数据:

这个夏普比率是怎么能维持在1.8的?

毕竟,前面的版本,回撤放大到60%,Sharpe就已经降到1.3了…


隐性未来预见:

量化策略回测中有很多陷阱,其中「未来函数」就是最容易导致误解的。

未来函数会引发重绘(Repainting),指的是图表数据后指针或是策略进出场点不一致,如果代码里有参数引发了重绘,策略的收益就会相差几倍。

也许不是刻舟求剑式指定历史高低点的过拟合来优化策略,但是不经意间会导致疏忽。

举例:参数「calc_on_order_fills」如果你设置为勾选,那么每一次的进场点会从OPEN而不是CLOSE计算,结果就是每一次交易至少有0.*%甚至*%的领先优势,以此为计算的亏损也会更少。

运行时如果你的代码有重绘的可能就会引发提示,同一个策略上引用未来函数会让收益率大幅增长。

比如:

如图所示,引用未来函数之后,净利润从1,100.09%直飙到1,930.45%,不仅改变了部分曲线,也放大了收益,显然脱离了真实情况,实盘运行起来的收益更接近图1。


量化策略的好处也许就在于:

你不用特别去关注Fed的动向、加息降息周期、扩表缩表进程、TGA抽取ON RRP的流动性给市场放水、耶伦偏短期的调整债券发行方式、美元指数、国债收益率、宏观经济以及美国通胀、失业率、泡沫化水平和市场流动性等数据。

也不会被4月13日凌晨的以色列导弹袭击以及7月31日BOJ加息导致的日元Carry Trade套利交易平仓带来的暴跌影响。

所有这些,5月1日洗盘,和5月20日放出的ETH ETF炒作消息的拉盘,都不会在情绪上另外作用到策略。

所谓量化策略,就是「计划你的交易,交易你的计划」。

忽略情绪,所有逻辑都基于冰冷的代码来执行。

完美符合经济学意义上的纯粹理性人。

37 responses to “Pine Script 策略”

  1. wp178245861 Avatar
    wp178245861

    量化和AI的普及终将改变整个金融市场的交易方式,遗憾的是大部分普通参与者都会成为时代的垫脚石。最近本人在大A为国化债,被庄家和量化反复收割,属实让人泪目。本着打不过就加入的心态开始学习Pine Script,但仓促间写成的代码回测总是惨不忍睹,又不死心,于是斗胆向博主请教Strategy,能否分享源码,以资共勉。
    感激不敬。

    此致 !

    敬礼!

    PowerArmor
    [email protected]

    1. Keihou Shuu Avatar
      Keihou Shuu

      我觉得你可以在Pine Script策略最下方加入如下代码来显示阶段收益,这样能更好优化:
      // 新增:每月收益率显示
      showMonthlyReturn = input.bool(true, “Show monthly earnings”, group = groupParameterSettings)
      fontSizeOptions = input.string(“large”, title = “Font Size”, options = [“tiny”, “small”, “normal”, “large”, “huge”], group = groupParameterSettings)

      var month_return = map.new<int, float>()
      var month_strat_money = map.new<int, float>()
      var years = array.new()

      int now = year * 10000 + month
      flag = true
      for mon in map.keys(month_strat_money)
      if now == mon
      flag := false
      if flag == true
      map.put(month_strat_money, now, strategy.equity)
      start_money = map.get(month_strat_money, now)
      map.put(month_return, now, (strategy.equity / start_money – 1.0) * 100)
      if years.includes(year) == false
      years.push(year)
      labels = array.from(“”, “Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”, “Q1”, “Q2”, “Q3”, “Q4”, “Annual Return”)
      make_table(years, labels) =>
      rows = array.size(years) + 1
      columns = array.size(labels)
      backgroundColor = color.new(color.rgb(212, 212, 212), 0)
      winColor = color.rgb(81, 127, 70)
      lossColor = color.rgb(167, 76, 70)
      e = table.new(position.bottom_right, rows = rows, columns = columns, bgcolor = backgroundColor, border_width = 2, border_color = color.black, frame_width = 2, frame_color = color.black)
      for i = 0 to rows – 1
      for j = 0 to columns – 1
      table.cell_set_text_color(e, j, i, color.black)
      table.cell_set_text_size(e, j, i, fontSizeOptions)
      if i > 0 and j > 0
      table.cell_set_bgcolor(e, j, i, color.rgb(161, 162, 165))
      table.cell_set_text_color(e, j, i, color.white)
      for j = 0 to columns – 1
      table.cell_set_text(e, j, 0, array.get(labels, j))
      for i = 1 to rows – 1
      table.cell_set_text(e, 0, i, str.tostring(array.get(years, i – 1)))
      start_year = array.get(years, 0)
      sum_year = array.new(array.size(years), 1.0)
      quarterly_sum = array.new
      (4 * array.size(years), 1.0)
      for before in map.keys(month_return)
      nowyear = before / 10000
      nowmonth = before % 10000
      beforevalue = array.get(sum_year, nowyear – start_year)
      beforevalue *= (1.0 + map.get(month_return, before) / 100)
      array.set(sum_year, nowyear – start_year, beforevalue)
      return_value = map.get(month_return, before)
      return_value_str = return_value >= 0 ? “+” + str.tostring(return_value, “#.##”) : str.tostring(return_value, “#.##”)
      table.cell_set_bgcolor(e, nowmonth, (nowyear – start_year + 1), return_value >= 0 ? winColor : lossColor)
      table.cell_set_text(e, nowmonth, (nowyear – start_year + 1), return_value_str + “%”) // 添加百分比符号
      // 计算季度收益
      quarter = (nowmonth – 1) / 3
      quarterly_index = (nowyear – start_year) * 4 + quarter
      quarterly_value = array.get(quarterly_sum, quarterly_index)
      quarterly_value *= (1.0 + map.get(month_return, before) / 100)
      array.set(quarterly_sum, quarterly_index, quarterly_value)
      for i = 1 to rows – 1
      return_value = (array.get(sum_year, i – 1) – 1.0) * 100
      return_value_str = return_value >= 0 ? “+” + str.tostring(return_value, “#.##”) : str.tostring(return_value, “#.##”)
      table.cell_set_bgcolor(e, columns – 1, i, return_value >= 0 ? winColor : lossColor)
      table.cell_set_text(e, columns – 1, i, return_value_str + “%”) // 添加百分比符号
      // 显示季度收益
      for q = 0 to 3
      quarterly_return = (array.get(quarterly_sum, (i – 1) * 4 + q) – 1.0) * 100
      quarterly_return_str = quarterly_return >= 0 ? “+” + str.tostring(quarterly_return, “#.##”) : str.tostring(quarterly_return, “#.##”)
      table.cell_set_bgcolor(e, 12 + q + 1, i, quarterly_return >= 0 ? winColor : lossColor)
      table.cell_set_text(e, 12 + q + 1, i, quarterly_return_str + “%”) // 添加百分比符号
      if showMonthlyReturn
      make_table(years, labels)

    2. Keihou Shuu Avatar
      Keihou Shuu

      对了,如果是单利情况下,也就是除了百分比以外还显示详细P&L金额的,可以用这个版本:
      // 新增:每月收益金额显示
      showMonthlyReturn = input.bool(true, “Show monthly earnings”, group = groupParameterSettings)
      fontSizeOptions = input.string(“normal”, title = “Font Size”, options = [“tiny”, “small”, “normal”, “large”, “huge”], group = groupParameterSettings)

      // 初始化变量
      var month_return = map.new<int, float>()
      var month_strat_money = map.new<int, float>()
      var month_return_percent = map.new<int, float>()
      var years = array.new()

      // 计算当前月份的收益
      int now = year * 10000 + month
      if not map.contains(month_strat_money, now)
      map.put(month_strat_money, now, strategy.equity)
      start_money = map.get(month_strat_money, now)
      month_change = strategy.equity – start_money
      month_return_percent_value = (strategy.equity / start_money – 1.0) * 100
      map.put(month_return, now, month_change)
      map.put(month_return_percent, now, month_return_percent_value)
      if not years.includes(year)
      years.push(year)

      // 标签数组
      labels = array.from(“”, “Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”, “Q1”, “Q2”, “Q3”, “Q4”, “Annual Return”)

      // 创建表格函数
      make_table(years, labels) =>
      rows = array.size(years) + 1
      columns = array.size(labels)
      backgroundColor = color.new(color.rgb(212, 212, 212), 0)
      winColor = color.rgb(81, 127, 70)
      lossColor = color.rgb(167, 76, 70)
      e = table.new(position.bottom_right, rows = rows, columns = columns, bgcolor = backgroundColor, border_width = 2, border_color = color.black, frame_width = 2, frame_color = color.black)

      // 设置表格样式
      for i = 0 to rows - 1
      for j = 0 to columns - 1
      table.cell_set_text_color(e, j, i, color.black)
      table.cell_set_text_size(e, j, i, fontSizeOptions)
      if i > 0 and j > 0
      table.cell_set_bgcolor(e, j, i, color.rgb(161, 162, 165))
      table.cell_set_text_color(e, j, i, color.white)
      for j = 0 to columns - 1
      table.cell_set_text(e, j, 0, array.get(labels, j))
      for i = 1 to rows - 1
      table.cell_set_text(e, 0, i, str.tostring(array.get(years, i - 1)))

      // 初始化年度和季度收益数组
      start_year = array.get(years, 0)
      sum_year = array.new<float>(array.size(years), 0.0)
      quarterly_sum = array.new<float>(4 * array.size(years), 0.0)
      quarterly_sum_percent = array.new<float>(4 * array.size(years), 1.0)
      annual_sum_percent = array.new<float>(array.size(years), 1.0)

      // 计算收益
      for before in map.keys(month_return)
      nowyear = before / 10000
      nowmonth = before % 10000
      year_index = nowyear - start_year
      quarter = (nowmonth - 1) / 3
      quarterly_index = year_index * 4 + quarter

      // 更新年度收益
      array.set(sum_year, year_index, array.get(sum_year, year_index) + map.get(month_return, before))
      array.set(annual_sum_percent, year_index, array.get(annual_sum_percent, year_index) * (1.0 + map.get(month_return_percent, before) / 100))

      // 更新季度收益
      array.set(quarterly_sum, quarterly_index, array.get(quarterly_sum, quarterly_index) + map.get(month_return, before))
      array.set(quarterly_sum_percent, quarterly_index, array.get(quarterly_sum_percent, quarterly_index) * (1.0 + map.get(month_return_percent, before) / 100))

      // 设置单元格内容
      return_value = map.get(month_return, before)
      return_percent = map.get(month_return_percent, before)
      return_value_str = return_value >= 0 ? "+" + str.tostring(return_value, "#.##") : str.tostring(return_value, "#.##")
      return_percent_str = return_percent >= 0 ? "+" + str.tostring(return_percent, "#.##") : str.tostring(return_percent, "#.##")
      table.cell_set_bgcolor(e, nowmonth, year_index + 1, return_value >= 0 ? winColor : lossColor)
      table.cell_set_text(e, nowmonth, year_index + 1, return_value_str + "\n(" + return_percent_str + "%)")

      // 显示季度和年度收益
      for i = 1 to rows - 1
      year_index = i - 1
      return_value = array.get(sum_year, year_index)
      return_percent = (array.get(annual_sum_percent, year_index) - 1.0) * 100
      return_value_str = return_value >= 0 ? "+" + str.tostring(return_value, "#.##") : str.tostring(return_value, "#.##")
      return_percent_str = return_percent >= 0 ? "+" + str.tostring(return_percent, "#.##") : str.tostring(return_percent, "#.##")
      table.cell_set_bgcolor(e, columns - 1, i, return_value >= 0 ? winColor : lossColor)
      table.cell_set_text(e, columns - 1, i, return_value_str + "\n(" + return_percent_str + "%)")
      for q = 0 to 3
      quarterly_return = array.get(quarterly_sum, year_index * 4 + q)
      quarterly_return_percent = (array.get(quarterly_sum_percent, year_index * 4 + q) - 1.0) * 100
      quarterly_return_str = quarterly_return >= 0 ? "+" + str.tostring(quarterly_return, "#.##") : str.tostring(quarterly_return, "#.##")
      quarterly_return_percent_str = quarterly_return_percent >= 0 ? "+" + str.tostring(quarterly_return_percent, "#.##") : str.tostring(quarterly_return_percent, "#.##")
      table.cell_set_bgcolor(e, 12 + q + 1, i, quarterly_return >= 0 ? winColor : lossColor)
      table.cell_set_text(e, 12 + q + 1, i, quarterly_return_str + "\n(" + quarterly_return_percent_str + "%)")

      if showMonthlyReturn
      make_table(years, labels)

  2. tradeswoman Avatar
    tradeswoman

    5Gd9uUIOk7t

  3. inhumanities Avatar
    inhumanities

    HpzNK1O6xwQ

  4. asthores Avatar
    asthores

    rJ0drvFn2Vo

  5. outcount Avatar
    outcount

    LwJf0p7ccKA

  6. thong Avatar
    thong

    y9p8og68D4b

  7. outwardly Avatar
    outwardly

    9ME2WEh5a8w

  8. anterograde Avatar
    anterograde

    2wnIaPqlinv

  9. エボニーポルノ Avatar
    エボニーポルノ

    SKZV5Vg0lAv

  10. shaggymanes Avatar
    shaggymanes

    JmPIfU5b11C

  11. dopesheet Avatar
    dopesheet

    2CXEuH1DoBq

  12. tortor Avatar
    tortor

    Ah22QYhE3lq

  13. bryology Avatar
    bryology

    iRqO7y1j4zP

  14. potenti Avatar
    potenti

    PVw3QSxb1o3

  15. interdeals Avatar
    interdeals

    8KZYZdgsHnE

  16. 同性愛者のポルノ Avatar
    同性愛者のポルノ

    x2s2b8dQHVQ

  17. pepper Avatar
    pepper

    5MrIzPOxXkt

  18. 二穴同時挿入ポルノ Avatar
    二穴同時挿入ポルノ

    vyfTY5jtAnN

  19. lacinia Avatar
    lacinia

    yhKLR0ubA0V

  20. catarrhous Avatar
    catarrhous

    K92gS1884QS

  21. pestiferously Avatar
    pestiferously

    J7HokbuW0UL

  22. amphiprotic Avatar
    amphiprotic

    iNa6eEAS8YN

  23. overproportioned Avatar
    overproportioned

    UtLRFe2k0Ta

  24. damnation Avatar
    damnation

    4yyYcuNxgLI

  25. fairnitickles Avatar
    fairnitickles

    Bz1gGQBTTA2

  26. flours Avatar
    flours

    FcebPX8SBjX

  27. heliocentric Avatar
    heliocentric

    kG04UuYJzuA

  28. heliocentric Avatar
    heliocentric

    euTt1BlpUaf

  29. anhidrosis Avatar
    anhidrosis

    L2eLwmDWXTe

  30. salt Avatar
    salt

    SxLVyBrg4pY

  31. a Avatar
    a

    L8Qvbt6idRa

  32. meteoritic Avatar
    meteoritic

    NsEZT7Qkydf

  33. hoodwinker Avatar
    hoodwinker

    czH2Nc7JROF

  34. donec Avatar
    donec

    qpE3c25lTiT

  35. lexicographical Avatar
    lexicographical

    IEK3k9qH2co

Leave a Reply


Producer's Personal Blog

Copyright © 2024 Keihou Shuu