The provided Pine Script is a visual tool designed to identify the highest and lowest price points within the user’s visible chart area. This functionality, relying on chart.left_visible_bar_time, is inherently subjective and cannot be used for automated execution, as a server-side algorithm has no concept of a “visible screen.”
To transform this into a professional trading algorithm, we must first translate its core intent—trading based on recent extremes—into an objective, quantifiable rule. We will replace the “visible range” with a fixed lookbackPeriod. This creates a classic “Donchian Channel” or “Price Channel” breakout strategy, which we will then build into a robust execution framework.
1. Execution Triggers (Entry & Direction)¶
The strategy will be a breakout system. We will enter a long position when the price breaks above the highest high of a defined lookback period and a short position when it breaks below the lowest low.
Objective Levels:
channelHigh: The highesthighover the pastlookbackPeriodbars.channelLow: The lowestlowover the pastlookbackPeriodbars.
Long Entry Condition:
longCondition = ta.crossover(close, channelHigh[1])This boolean is
trueonly on the bar that closes decisively above the high of the channel calculated on the previous bar. Using[1]is critical to prevent the strategy from entering and exiting on the same bar (repainting) and ensures we act on a confirmed breakout.
Short Entry Condition:
shortCondition = ta.crossunder(close, channelLow[1])This is
trueonly on the bar that closes decisively below the low of the channel calculated on the previous bar.
Execution Nuance:
Execute at “Close” of the bar. This is the most robust method for backtesting and automation (
process_orders_on_close=true). It ensures the breakout is confirmed by the bar’s close, filtering out intraday “wicks” and false breakouts that don’t hold. Real-time execution on every tick can lead to excessive trades and is less reliable in backtests.
Signal Reversals:
The system is designed to be “always in” the market after the first signal. If the strategy is in a long position and a
shortConditionbecomes true, the framework will automatically close the long position and initiate a new short position. This is handled efficiently by using the sameidfor bothstrategy.entrycalls.
2. Multi-Tiered Exit Logic¶
A professional strategy never enters a trade without a pre-defined exit plan. We will implement a three-pronged exit logic.
Initial Stop Loss (Volatility-Based):
Instead of a fixed percentage, the stop loss will be placed based on market volatility using the Average True Range (ATR). This adapts the stop distance to current market conditions (wider in volatile markets, tighter in quiet ones).
Calculation:
For a Long entry:
StopLossPrice = entry_price - (ATR * atrMultiplier)For a Short entry:
StopLossPrice = entry_price + (ATR * atrMultiplier)
A typical
atrMultiplieris between 2 and 3. This stop is calculated at the time of entry and submitted with the entry order.
Take Profit/Trailing (Dynamic Trailing Stop):
Static take-profit targets can cut winning trades short. A dynamic trailing stop allows us to capture the majority of a trend.
Mechanism: An ATR-based trailing stop. The stop will trail the price by a multiple of the ATR.
Logic: Once a trade is profitable by a certain amount (e.g., 1x ATR), the trailing stop is activated. It follows the highest price (for longs) or lowest price (for shorts) reached since the trade was opened, maintaining a fixed distance (
ATR * trailMultiplier). If the price reverses and hits this trailing level, the position is closed to lock in profit. Pine Script’sstrategy.exitfunction has built-in parameters (trail_price,trail_offset) to manage this automatically.
Time-Based Exits:
End of Session: For intraday strategies (e.g., on timeframes H1 or lower), it’s critical to square all positions before the market closes to avoid overnight risk. A time-based rule will close any open position 15 minutes before the session end.
Trade Stagnation: If a trade has been open for a specified number of bars (
maxBarsInTrade) without hitting either the stop loss or take profit, it can be considered “dead money.” We will implement a rule to exit the position automatically to free up capital for better opportunities.
3. Capital Allocation & Risk Management¶
Position sizing is arguably more important than the entry signal itself. We will use a fixed-fractional risk model.
Risk-Based Sizing:
The core principle is to risk a fixed percentage (e.g., 1%) of total account equity on every single trade, regardless of the trade’s parameters.
Logic:
Define
riskPercent(e.g., 1.0 for 1%).Calculate the distance in points between the entry price and the initial stop-loss price (
riskPerShare).Convert this to a currency value using
syminfo.pointvalue(riskInCurrencyPerShare).Determine the total capital to risk:
capitalToRisk = strategy.equity * (riskPercent / 100).Calculate Position Size:
positionSize = capitalToRisk / riskInCurrencyPerShare.
This ensures that a trade with a wide stop has a smaller position size, and a trade with a tight stop has a larger one, but the maximum potential loss from any single trade is always the same.
Pyramiding & Scaling:
Pyramiding (Scaling In): For this initial framework, pyramiding (adding to a winning position) will be disabled (
pyramiding = 0) to maintain simplicity and control risk. A more advanced version could include rules like: “Add a new position of 50% of the original size if the trade moves 2x ATR in profit, and move the entire position’s stop loss to breakeven.”Scaling Out: The proposed ATR trailing stop is a form of “all-out” exit. A multi-stage take-profit could be implemented by submitting multiple
strategy.exitorders with different quantity sizes and profit targets (e.g., exit 50% at a 2:1 Reward:Risk ratio, and trail the rest).
4. Implementation Snippet (Pine Logic)¶
This snippet demonstrates the transformation from the visual indicator to a professional strategy, incorporating the principles above.
//@version=5
// TRANSITIONED FROM A VISUAL TOOL TO AN AUTOMATED EXECUTION STRATEGY
strategy("Price Channel Breakout - Pro Framework",
overlay=true,
initial_capital=100000,
commission_type=strategy.commission.percent,
commission_value=0.075, // Example: 0.075% per trade
slippage=2, // Example: 2 ticks of slippage per order
process_orders_on_close=true, // For robust, non-repainting execution
pyramiding=0) // Disable adding to positions for risk control
// --- Inputs ---
var lookbackPeriod = input.int(50, title="Channel Lookback Period", minval=10)
var atrLength = input.int(14, title="ATR Length", minval=1)
var stopAtrMult = input.float(2.5, title="Stop Loss ATR Multiplier", minval=0.1, step=0.1)
var trailAtrMult = input.float(3.0, title="Trailing Stop ATR Multiplier", minval=0.1, step=0.1)
var riskPercent = input.float(1.0, title="Risk per Trade (%)", minval=0.1, maxval=10.0)
var maxBarsInTrade = input.int(100, title="Max Bars in Stagnant Trade", minval=1)
// --- Core Calculations ---
float channelHigh = ta.highest(high, lookbackPeriod)
float channelLow = ta.lowest(low, lookbackPeriod)
float atr = ta.atr(atrLength)
// --- Entry Conditions ---
bool longCondition = ta.crossover(close, channelHigh[1])
bool shortCondition = ta.crossunder(close, channelLow[1])
// --- Position Sizing & Risk Management ---
float pointValue = syminfo.pointvalue
float riskPerShare = atr * stopAtrMult
float riskInCurrency = riskPerShare * pointValue
float capitalToRisk = (riskPercent / 100.0) * strategy.equity
int positionSize = math.max(1, math.floor(capitalToRisk / riskInCurrency))
// --- Exit Conditions ---
// Time-based exit for stagnant trades
bool timeExitCondition = strategy.opentrades > 0 and (bar_index - strategy.opentrades.entry_bar_index(0)) > maxBarsInTrade
// --- Execution Logic ---
if (longCondition)
// Close any existing short position and go long
strategy.entry("Breakout", strategy.long, qty=positionSize, comment="Long Entry")
if (shortCondition)
// Close any existing long position and go short
strategy.entry("Breakout", strategy.short, qty=positionSize, comment="Short Entry")
// --- Exit Order Management ---
if (strategy.opentrades > 0)
// Define stop loss and trailing stop levels for the current open trade
float stopLossLevel = strategy.opentrades.entry_price(0) + (strategy.opentrades.direction(0) == strategy.long ? -riskPerShare : riskPerShare)
float trailOffset = atr * trailAtrMult
// Submit a versatile exit order that handles initial stop and dynamic trailing
strategy.exit("Exit SL/Trail", from_entry="Breakout", stop=stopLossLevel, trail_price=close, trail_offset=trailOffset)
// Close position if it's stagnant
strategy.close("Breakout", when=timeExitCondition, comment="Time Exit")
// --- Visuals ---
plot(channelHigh[1], "Channel High", color=color.new(color.red, 50))
plot(channelLow[1], "Channel Low", color=color.new(color.green, 50))