A lot of strategies only make sense at certain hours: a London breakout, a New York open scalp, a system that should stay out of the thin Asian session. Pine Script handles this with one function, time(), and a session string. The details that trip people up are time zones, daylight saving, and what happens to a position when the window closes.
Every complete script below is Pine Script v6 and passes Edgoro Pine Check.
The one-line filter
inSession = not na(time(timeframe.period, "0800-1600", "Europe/London"))
time() returns the bar's opening time when that time falls inside the window, and na when it doesn't. The three arguments:
- timeframe.period: check the chart's own bars.
- "0800-1600": the window, as a session string.
- "Europe/London": the time zone the window is written in.
The test is made on each bar's opening time. On a 1-hour chart, the 15:00 bar is inside a 0800-1600 window and the 16:00 bar isn't, even though the 15:00 bar runs until 16:00.
Session strings
| String | Meaning |
|---|---|
| "0800-1600" | 08:00 to 16:00 on every day the symbol trades |
| "0930-1600:23456" | Monday to Friday only (1 is Sunday, 7 is Saturday) |
| "2200-0200" | Crosses midnight: 22:00 to 02:00 |
| "0800-1200,1300-1700" | Two windows with a break between them |
| "24x7" | Every minute of every day |
Use input.session() so the window shows up in the script's settings: sessWin = input.session("0800-1600", "Trading window").
Time zones and daylight saving
Always pass the time zone. Without it, the window is read in the exchange's time zone (syminfo.timezone), which on many forex and crypto feeds isn't London or New York.
Use a named zone such as "Europe/London" or "America/New_York". Named zones follow daylight saving. A fixed offset like "UTC+1" or "GMT+1" doesn't, so a London window written that way is an hour off for half the year.
The United States changes its clocks on the second Sunday of March and the first Sunday of November. The UK and the EU change on the last Sunday of March and the last Sunday of October. For a few weeks each spring and autumn, London and New York are four hours apart instead of five. A London–New York overlap hard-coded in UTC is wrong during those weeks. Testing each session in its own time zone, as below, is always right.
Highlight London, New York and the overlap
//@version=6
indicator("London and New York sessions", overlay = true)
londonSess = input.session("0800-1700", "London session (London time)")
nySess = input.session("0800-1700", "New York session (New York time)")
inLondon = not na(time(timeframe.period, londonSess, "Europe/London"))
inNewYork = not na(time(timeframe.period, nySess, "America/New_York"))
overlap = inLondon and inNewYork
sessionColor = overlap ? color.new(color.purple, 85) : inLondon ? color.new(color.blue, 92) : inNewYork ? color.new(color.orange, 92) : na
bgcolor(sessionColor, title = "Sessions")
alertcondition(overlap and not overlap[1], "Overlap starts", "London and New York are both open")
The overlap is simply the bars where both tests are true. The alert fires on the first bar of the overlap; create it in TradingView by picking "Overlap starts" in the alert dialog.
Filter a strategy, and close at the end of the window
Filtering entries is half the job. A position opened at 15:30 will otherwise stay open all night. This strategy trades an EMA cross only inside the window, skips Fridays if you want, and closes everything on the last bar of the window.
//@version=6
strategy("EMA cross, trading window only", overlay = true, initial_capital = 10000, default_qty_type = strategy.percent_of_equity, default_qty_value = 10, commission_type = strategy.commission.percent, commission_value = 0.01, slippage = 2)
fastLen = input.int(9, "Fast EMA", minval = 1)
slowLen = input.int(21, "Slow EMA", minval = 1)
sessWin = input.session("0800-1600", "Trading window")
sessTz = input.string("Europe/London", "Window time zone", options = ["Europe/London", "America/New_York", "Europe/Paris", "Asia/Tokyo", "UTC"])
endHour = input.int(16, "Window end hour", minval = 0, maxval = 23)
endMinute = input.int(0, "Window end minute", minval = 0, maxval = 59)
noFriday = input.bool(true, "No new trades on Friday")
stopPct = input.float(0.5, "Stop loss (%)", minval = 0.01, step = 0.05)
tpPct = input.float(1.0, "Take profit (%)", minval = 0.01, step = 0.05)
// The window, in its own time zone (daylight saving handled by the IANA name)
inWindow = timeframe.isintraday and not na(time(timeframe.period, sessWin, sessTz))
windowEnd = timestamp(sessTz, year(time, sessTz), month(time, sessTz), dayofmonth(time, sessTz), endHour, endMinute)
lastBar = inWindow and time_close >= windowEnd
dayOk = not (noFriday and dayofweek(time, sessTz) == dayofweek.friday)
fast = ta.ema(close, fastLen)
slow = ta.ema(close, slowLen)
longSignal = ta.crossover(fast, slow)
shortSignal = ta.crossunder(fast, slow)
if inWindow and dayOk and not lastBar
if longSignal
strategy.entry("Long", strategy.long)
if shortSignal
strategy.entry("Short", strategy.short)
if strategy.position_size > 0
strategy.exit("Long exit", "Long", stop = strategy.position_avg_price * (1 - stopPct / 100), limit = strategy.position_avg_price * (1 + tpPct / 100))
if strategy.position_size < 0
strategy.exit("Short exit", "Short", stop = strategy.position_avg_price * (1 + stopPct / 100), limit = strategy.position_avg_price * (1 - tpPct / 100))
// Flat at the end of the window, at the close of its last bar
if lastBar and strategy.position_size != 0
strategy.close_all(comment = "Window end", immediately = true)
plot(fast, "Fast EMA", color.teal)
plot(slow, "Slow EMA", color.orange)
bgcolor(inWindow ? color.new(color.blue, 92) : na, title = "Trading window")
How the end of the window is found: timestamp() builds today's end time in the window's time zone, and the last bar is the one inside the window whose close time (time_close) reaches it. On that bar strategy.close_all(immediately = true) fills at the bar's close. Without immediately, the order would fill at the next bar's open, and for a stock that next bar can be the following morning.
Two limits of this version. The end-time logic assumes the window doesn't cross midnight. And on daily or weekly charts inWindow is false, so the strategy doesn't trade at all: a window test means nothing when one bar covers the whole day.
Day-of-week filters
isFriday = dayofweek(time, "Europe/London") == dayofweek.friday
Pass the same time zone as the window, so the day changes at midnight London time and not at the exchange's midnight. The weekday suffix of a session string ("0800-1600:23456") does the same job when you want the rule inside the window itself.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
| No time zone argument | The window is read in the exchange's time zone | Always pass one |
| "UTC+1" for London | One hour off for half the year | "Europe/London" |
| Hard-coding the overlap in UTC | Wrong for a few weeks each spring and autumn | Test each session in its own zone |
hour(time) >= 8 | hour() also uses the exchange's time zone | hour(time, "Europe/London") |
| Filtering entries only | Positions opened in the window stay open overnight | Close on the window's last bar |
| Running the filter on a daily chart | One bar per day, so the test says little | Guard with timeframe.isintraday |
Related
- The session high and low indicator and the opening range breakout both build on session windows.
- The forex market hours tool shows the four main sessions live in your time zone.
- Stops and targets inside the window: strategy.exit() explained.
- Checking someone else's script: the free Pine Script checker.
Build it with Edgoro
Tell the Pine Script generator "only trade between 08:00 and 16:00 London time, no new trades on Friday, flat at the end of the window" and it writes the filter into your indicator or strategy, in Pine Script v6.
Code examples are for education. Test any strategy on historical data and on a demo account before relying on it. Trading involves substantial risk of loss.
Frequently asked questions
How do I make a Pine Script strategy trade only during certain hours?
Use a session filter: inSession = not na(time(timeframe.period, "0800-1600", "Europe/London")), then put your strategy.entry() calls inside if inSession. time() returns na for bars outside the window.
Does Pine Script handle daylight saving time?
Yes, if you pass a named time zone such as "Europe/London" or "America/New_York". Fixed offsets like "UTC+1" don't change with daylight saving.
What time zone does time() use if I leave it out?
The exchange's time zone, syminfo.timezone. That is often not the zone you have in mind, so pass the time zone explicitly.
How do I close all trades at the end of the session?
Find the window's last bar, for example by comparing time_close with the session end built by timestamp(), and call strategy.close_all(immediately = true) on it. The order then fills at that bar's close instead of the next bar's open.
How do I find the London and New York overlap in Pine Script?
Test each session in its own time zone and combine them: overlap = inLondon and inNewYork. This stays correct in the weeks when the US and the UK change their clocks on different dates.