Backtest your data on TradingView

In the previous article, we introduced the use of Hummingbot. This time, we’ll discuss backtesting. Backtesting is a crucial task in quantitative trading, primarily used to verify the long-term effectiveness of your chosen strategy and parameters and their ability to handle extreme situations. Here, we’ll use TradingView and its Pine language to replicate and test our strategy.

在上一期文章中,我们介绍了hummingbot的使用,这一期我们来讲讲如何回测,回测是量化交易中非常重要的一项任务,主要用来测试你所选择策略,参数是否长期有效,是否能够应对极端情况.这里我们选择来平台TradingView,结合其提供的Pine语言来复现我们的策略,并进行测试.

TradingView is a powerful charting and social trading platform offering global market data, extensive technical indicators, customizable Pine Script, and a vibrant community for sharing ideas. It caters to both beginners and professional traders seeking to build, analyze, and share trading strategies.

TradingView 是一个强大的图表分析和社交交易平台,支持全球市场数据、丰富技术指标、自定义 Pine 脚本和社区分享。无论是初学者还是专业交易者,都能在此构建、分析并共享交易想法。

Here are some recommendations for cloud servers and exchanges that can run permanently.

这里推荐一下能够永久运行的云服务器和交易所.

Bybit 33% off

Gate.io 60% off

Amazon

Oracle

Azure

Below is a segmented explanation of the previous strategy simple_pmm and the full code display.

下面是之前策略simple_pmm的分段解释以及全部代码展示

1. Strategy Declaration

strategy("PMM (% of Equity Version)", overlay=true, margin_long=100, pyramiding=10, margin_short=100, default_qty_type=strategy.percent_of_equity, default_qty_value=20)

Defines the strategy metadata and behavior:

  • Overlay = true lets it draw on the price chart.
  • Uses 20% of account equity for each trade (percent_of_equity type).
  • Allows up to 10 accumulating entries in the same direction (pyramiding = 10).

strategy(...) 定义了策略元信息与默认行为:

  • 名称、是否覆盖在主图(overlay=true)。
  • default_qty_typedefault_qty_value 把每笔下单数量设为账户权益的 20%(percent_of_equity)。也就是说每次 entry 会尝试按 20% 的权益下单(注意交易所保证金和杠杆会影响实际仓位)。
  • pyramiding=10 允许最多叠加 10 次同方向持仓(这会影响做市时你可能叠仓的风险)。

2. Configurable Inputs

pmm_spread = input.float(0.5, title="Spread (%)", minval=0.01, step=0.01) / 100
refresh_interval_min = input.int(1, title="Order Refresh Interval (Minutes)", minval=1)
  • pmm_spread: Distance from mid-price to place buy/sell orders (default 0.5%).
  • refresh_interval_min: How often (in minutes) to cancel and re-post orders (default every minute).
  • pmm_spread:双边报价距离 mid 的比例。默认 0.5%(代码里最后除以 100)。
  • refresh_interval_min:每隔多少分钟刷新(取消并重下)订单。默认 1 分钟。

3.Time-Window Control

startTime = input.time(timestamp("1 Jan 2024 00:00:00"), title="Start Time")
endTime   = input.time(timestamp("8 Aug 2025 23:59:59"), title="End Time")
timeFilter = time >= startTime and time <= endTime
  • Restricts the strategy’s activity to a specific time range, useful for backtesting only during desired periods.
  • 允许你只在某个时间区间内运行策略(便于回测或实盘按时间窗限制)。

4.Price Calculations (Mid / Bid / Ask)

mid_price = hl2
bid_price = mid_price * (1 - pmm_spread)
ask_price = mid_price * (1 + pmm_spread)
  • Uses hl2 (high + low divided by 2) as mid-price.
  • Computes buy/sell prices by offsetting mid by the spread.
  • hl2(高低平均)作为 mid,向两边按 pmm_spread 给出限价。简单直接,但注意:不同交易品种的 spread/手续费/滑点会影响实际效果。

5. When to Refresh Orders

trigger_refresh = (bar_index % refresh_interval_bars == 0)
  • This line of code controls the refresh action to be triggered only once every N K-bars by checking whether the current K-bar index (bar_index) is divisible by the interval number you set (refresh_interval_bars).
  • 这行代码通过判断当前K线的序号 (bar_index) 能否被您设置的间隔数 (refresh_interval_bars) 整除,来控制刷新动作只在每N根K线上触发一次。

6.Order Entry & Cancellation

if (timeFilter)
    if (trigger_refresh)
        strategy.cancel_all()

    if (trigger_refresh)
        strategy.entry("PMM_Bid", strategy.long, limit=bid_price)
        strategy.entry("PMM_Ask", strategy.short, limit=ask_price)

Within the active time window:

  • On each refresh, cancel all pending orders.
  • Then post new limit orders: one long (buy) at bid_price, and one short (sell) at ask_price.
  • Trade size defaults to equity percentage defined above.
  • Since pyramiding=10, if you already have a directional position, placing repeated orders may result in an increase in position (be aware of the risk).
  • 在允许时间窗口内,每次触发刷新先 strategy.cancel_all() 再下新的 bid/ask 限价单。
  • 使用 strategy.entry() 提交限价单(ID 分别为 "PMM_Bid" / "PMM_Ask"),使用前面声明的默认百分比。
  • 由于 pyramiding=10,如果之前已经有方向性的持仓,重复下单可能造成加仓(注意风险)。

7.Visualization Enhancements

plotshape(trigger_refresh and timeFilter, title="Order Refresh Tick", style=shape.circle, location=location.belowbar, color=color.new(color.blue, 85), size=size.tiny)
bgcolor(timeFilter ? color.new(color.teal, 90) : na)
plot(strategy.position_size, "Position Size", color=color.new(color.purple, 0), style=plot.style_histogram, linewidth=4)
hline(0, "Zero Line", color=color.gray)
  • plotshape(...) marks each refresh tick on the chart—useful for debugging.
  • bgcolor(...) tints the background when the strategy is active.
  • plot(strategy.position_size, ...) plots position size over time—great for tracking inventory behavior.
  • hline(0, ..., color=...) draws a static horizontal line at value 0—essentially acting as a baseline to visually separate positive and negative values in the position-size plot.
  • plotshape 标记刷新点
  • bgcolor 高亮活动时间窗
  • plot(strategy.position_size) 绘制仓位变化(对做市策略分析库存波动很重要)。
  • hline(0, ..., color=...)在0值处绘制一条静态水平线 – 本质上充当基线,以在视觉上区分位置大小图中的正值和负值。

Complete code

//@version=5
// =================== 策略函数声明 ===================
strategy("PMM (% of Equity Version)", overlay=true, margin_long=100, pyramiding=3, margin_short=100, default_qty_type=strategy.percent_of_equity, default_qty_value=20)

// =================== PMM 参数输入 ===================
pmm_spread = input.float(0.5, title="Spread (%)", minval=0.01, step=0.01) / 100
// 注意:这里的数字现在代表“每隔N根K线”刷新一次
refresh_interval_bars = input.int(1, title="Order Refresh Interval (Bars)", minval=1)

// =================== 时间控制输入 ===================
startTime = input.time(timestamp("1 Jan 2024 00:00:00"), title="Start Time")
endTime   = input.time(timestamp("8 Aug 2025 23:59:59"), title="End Time")

// =================== 核心计算 ===================
// 价格计算
mid_price = hl2
bid_price = mid_price * (1 - pmm_spread)
ask_price = mid_price * (1 + pmm_spread)

// 时间过滤器
timeFilter = time >= startTime and time <= endTime

// =================== 新的订单刷新逻辑 ===================
// 使用 K线索引 (bar_index) 来触发刷新,更加直接可靠
// bar_index % N == 0 的意思是 “每 N 根 K线 执行一次”
// 我们用 N-1 是为了让它在第2、4、6根线上触发,而不是1、3、5,但这只是习惯问题,效果一样
trigger_refresh = (bar_index % refresh_interval_bars == 0)

// =================== 策略逻辑 ===================
if (timeFilter)
    // 1. 在刷新周期开始时,取消所有未成交的订单
    if (trigger_refresh)
        strategy.cancel_all()

    // 2. 持续报价核心逻辑
    if (trigger_refresh)
        strategy.entry("PMM_Bid", strategy.long, limit=bid_price)
        strategy.entry("PMM_Ask", strategy.short, limit=ask_price)

// =================== 可视化 ===================
// 标记刷新订单的时间点 (用于调试)
// 这个蓝色圆点现在应该每隔一根K线出现一次
plotshape(trigger_refresh and timeFilter, title="Order Refresh Tick", style=shape.circle, location=location.belowbar, color=color.new(color.blue, 85), size=size.tiny)

// 使用背景色突出显示策略活动的时间窗口
bgcolor(timeFilter ? color.new(color.teal, 90) : na)

// 在单独的窗格中绘制持仓大小
plot(strategy.position_size, "Position Size", color=color.new(color.purple, 0), style=plot.style_histogram, linewidth=4)
hline(0, "Zero Line", color=color.gray)

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注