ThinkScript转Pine Script无交易生成问题排查求助
问题背景
我正在将一个简单的ThinkScript策略转换为Pine Script。原策略在ThinkOrSwim的SPY标的5分钟图表上,过去几日会产生大量交易,但转换为Pine Script后,策略测试器无任何交易生成,且无语法或编译错误。策略使用request.security获取更高时间框架/聚合周期的数据,不确定是否为此问题根源。移除交易时段(RTH/InSession)逻辑后仅生成1笔交易,希望实现与ThinkOrSwim完全一致的交易表现,请求帮忙定位问题。
原ThinkScript代码
input agg = AggregationPeriod.HOUR; input TrendPeriods = 5; input use_trading_hours = yes; input spread = 0.15; ###################################################### # TIME ###################################################### def startTradingTime = if use_trading_hours == yes then 0930 else 0000; def noMorePositions = if use_trading_hours == yes then 1530 else 0000; def endTradingTime = if use_trading_hours == yes then 1600 else 0000; def isTradingSession = if use_trading_hours == yes then SecondsFromTime(startTradingTime) >= 0 and SecondsTillTime(endTradingTime) > 0 else yes; def positionsStillAllowed = if use_trading_hours == yes then SecondsTillTime(noMorePositions) > 0 else yes; def h = high(period = agg); def l = low(period = agg); def c = close(period = agg); def hh = highest(h, trendPeriods); def ll = lowest(l, trendPeriods); def trend = if ((c - ll) / (hh - ll)) > .5 then 1 else 0; // Enter long (flip from short if necessary) addorder(orderType.BUY_AUTO, trend[1] and isTradingSession and positionsStillAllowed and BarNumber()>1, tradesize=1, price=open[-1]+spread); // Exit long at EOD addorder(orderType.SELL_TO_CLOSE, !isTradingSession[-1], tradesize=1, price=open-spread); // Enter short (flip from long if necessary) addorder(orderType.SELL_AUTO, !trend[1] and isTradingSession and positionsStillAllowed and BarNumber()>1, tradesize=1, price=open[-1]-spread); // Exit short at EOD addorder(orderType.BUY_TO_CLOSE, !isTradingSession[-1], tradesize=1, price=open+spread);
注:由于使用更高聚合周期,通过trend[1]获取已完全闭合的前一个trend值以避免重绘。
当前无交易的Pine Script代码
// © windowshopr //@version=5 //=========================== IMPORTS AND STRATEGY ===========================// strategy("My Strategy", shorttitle="My Strategy", overlay=true, initial_capital=1000, pyramiding=1, calc_on_order_fills=true, calc_on_every_tick=true, backtest_fill_limits_assumption=10, default_qty_type=strategy.percent_of_equity, default_qty_value=100, currency=currency.NONE, slippage=1, commission_type=strategy.commission.cash_per_contract, commission_value=0.01) //=========================== INPUTS ===========================// //========= STRATEGY input_agg_period = input.timeframe( title = "Higher TimeFrame Aggregation Period", defval = "60", group = 'Strategy', tooltip = 'The higher timeframe to use with strategy') input_trend_periods = input.int( title = "Trend Periods", minval = 1, defval = 5, group = 'Strategy', tooltip = 'The number of bars/periods to use for calculating trend') //=========================== STRATEGY ===========================// var float h = request.security(syminfo.tickerid, input_agg_period, high) var float l = request.security(syminfo.tickerid, input_agg_period, low) var float c = request.security(syminfo.tickerid, input_agg_period, close) var float hh = ta.highest(h, input_trend_periods) var float ll = ta.lowest(l, input_trend_periods) var int trend = ((c - ll) / (hh - ll)) > .5 ? 1 : 0 longCondition = trend[1] == 1 shortCondition = trend[1] == 0 //=========================== TIME MANAGEMENT ===========================// // InSession() returns 'true' when the current bar happens inside // the specified session, corrected for the given time zone (optional). // Returns 'false' when the bar doesn't happen in that time period, // or when the chart's time frame is 1 day or higher. InSession(sessionTime, sessionTimeZone=syminfo.timezone) => not na(time(timeframe.period, sessionTime, sessionTimeZone)) session = input.session("0730-1400", title="Session") timeZone = input.string("GMT-6", title="Time Zone") var bool inRTH = InSession(session, timeZone) //=========================== TRADE MANAGEMENT ===========================// bool openLongPosition = longCondition and inRTH bool openShortPosition = shortCondition and inRTH bool closeLongPosition = shortCondition or not inRTH bool closeShortPosition = longCondition or not inRTH bool longIsActive = openLongPosition or strategy.position_size > 0 and not closeLongPosition bool shortIsActive = openShortPosition or strategy.position_size < 0 and not closeShortPosition // the open signals when not already into a position bool validOpenLongPosition = openLongPosition and not (strategy.position_size > 0) bool validOpenShortPosition = openShortPosition and not (strategy.position_size < 0) // Order Exits if closeLongPosition and longIsActive strategy.close(id = 'Long Entry', comment = 'Close Long', alert_message = 'Long: Closed at market price') // close on trend reversal if closeShortPosition and shortIsActive strategy.close(id = 'Short Entry', comment = 'Close Short', alert_message = 'Short: Closed at market price') // Order Entries if validOpenLongPosition strategy.entry(id = 'Long Entry', direction = strategy.long, alert_message = 'Long(' + syminfo.ticker + '): Started') // getting into SHORT position if validOpenShortPosition strategy.entry(id = 'Short Entry', direction = strategy.short, alert_message = 'Short(' + syminfo.ticker + '): Started')
核心问题定位与修正方案
1. 变量初始化错误(var关键字滥用)
所有和更高时间框架数据相关的变量(h、l、c、hh、ll、trend)都被错误地加上了var关键字。var会让变量仅在脚本首次运行时初始化,后续不再更新,导致趋势计算一直停留在初始值,完全失效。必须移除所有这些变量前的var。
2. 更高时间框架数据获取逻辑错误
原策略用trend[1]取已闭合的前一根高时间框架K线数据,避免重绘。Pine Script中request.security默认会返回当前未闭合的高时间框架数据,需要添加参数确保获取已闭合数据:
[h, l, c] = request.security(syminfo.tickerid, input_agg_period, [high, low, close], barmerge.gaps_on, barmerge.lookahead_on=false)
用解构赋值一次性获取三个值,同时关闭前瞻(lookahead),确保数据无重绘。
3. 时间逻辑未对齐原策略
原策略区分三个时间规则:
isTradingSession:当前是否在交易时段(0930-1600)positionsStillAllowed:是否还允许开新仓(1530前)- 出场逻辑用
!isTradingSession[-1](前一根K线已离开交易时段)
你当前的时段设置是0730-1400,和原策略的美股交易时段完全不符,需修正时间规则,还原原逻辑:
// 还原原策略的时间逻辑 input use_trading_hours = input.bool(title="Use Trading Hours", defval=true, group="Time") input spread = input.float(title="Spread", defval=0.15, group="Strategy") startTradingTime = use_trading_hours ? 0930 : 0000 noMorePositions = use_trading_hours ? 1530 : 0000 endTradingTime = use_trading_hours ? 1600 : 0000 isTradingSession = use_trading_hours ? (ta.secondssince(time("0930")) >= 0 and ta.secondstill(time("1600")) > 0) : true positionsStillAllowed = use_trading_hours ? (ta.secondstill(time("1530")) > 0) : true
4. 订单逻辑还原原策略
原策略用BUY_AUTO/SELL_AUTO自动反向平仓,且指定了入场/出场价格(包含spread),需用strategy.entry的limit参数还原价格逻辑,同时用strategy.close实现EOD平仓:
// 入场信号(需满足已闭合趋势、交易时段、允许开仓、非首根K线) longEntryCond = trend[1] == 1 and isTradingSession and positionsStillAllowed and bar_index > 0 shortEntryCond = trend[1] == 0 and isTradingSession and positionsStillAllowed and bar_index > 0 // 入场订单(带spread的限价单) if longEntryCond strategy.entry("Long", strategy.long, limit=open[1]+spread) if shortEntryCond strategy.entry("Short", strategy.short, limit=open[1]-spread) // EOD平仓(前一根K线已离开交易时段) eodCloseCond = not isTradingSession[1] if eodCloseCond strategy.close("Long", limit=open-spread) strategy.close("Short", limit=open+spread)
修正后的完整Pine Script代码
//@version=5 strategy("My Strategy", shorttitle="My Strategy", overlay=true, initial_capital=1000, pyramiding=1, calc_on_order_fills=true, calc_on_every_tick=true, backtest_fill_limits_assumption=10, default_qty_type=strategy.percent_of_equity, default_qty_value=100, currency=currency.NONE, slippage=1, commission_type=strategy.commission.cash_per_contract, commission_value=0.01) //=========================== INPUTS ===========================// input_agg_period = input.timeframe(title="Higher TimeFrame Aggregation Period", defval="60", group='Strategy') input_trend_periods = input.int(title="Trend Periods", minval=1, defval=5, group='Strategy') use_trading_hours = input.bool(title="Use Trading Hours", defval=true, group="Time") spread = input.float(title="Spread", defval=0.15, group="Strategy") //=========================== TIME MANAGEMENT ===========================// startTradingTime = use_trading_hours ? 0930 : 0000 noMorePositions = use_trading_hours ? 1530 : 0000 endTradingTime = use_trading_hours ? 1600 : 0000 isTradingSession = use_trading_hours ? (ta.secondssince(time("0930")) >= 0 and ta.secondstill(time("1600")) > 0) : true positionsStillAllowed = use_trading_hours ? (ta.secondstill(time("1530")) > 0) : true //=========================== STRATEGY ===========================// // 获取已闭合的高时间框架数据,避免重绘 [h, l, c] = request.security(syminfo.tickerid, input_agg_period, [high, low, close], barmerge.gaps_on, barmerge.lookahead_on=false) hh = ta.highest(h, input_trend_periods) ll = ta.lowest(l, input_trend_periods) trend = ((c - ll) / (hh - ll)) > 0.5 ? 1 : 0 //=========================== TRADE MANAGEMENT ===========================// // 入场条件:用前一根高时间框架的趋势值,避免重绘 longEntryCond = trend[1] == 1 and isTradingSession and positionsStillAllowed and bar_index > 0 shortEntryCond = trend[1] == 0 and isTradingSession and positionsStillAllowed and bar_index > 0 // 入场订单(带spread的限价单) if longEntryCond strategy.entry("Long", strategy.long, limit=open[1]+spread, comment="Long Entry") if shortEntryCond strategy.entry("Short", strategy.short, limit=open[1]-spread, comment="Short Entry") // EOD平仓条件:前一根K线已离开交易时段 eodCloseCond = not isTradingSession[1] if eodCloseCond strategy.close("Long", limit=open-spread, comment="Close Long EOD") strategy.close("Short", limit=open+spread, comment="Close Short EOD")
内容的提问来源于stack exchange,提问作者wildcat89

