Describe the bug
When strategy.entry(..., stop=) fills on a bar and the attached strategy.exit(..., profit=) level is also reached on that same bar, TradingView closes the trade on the entry bar. PyneCore fills the entry but carries the exit forward, closing it on a later bar. The exit price is correct; only the bar on which it fills is wrong.
Verified on PyneCore 6.8.4 (latest at the time of writing), and previously on 6.8.2.
To Reproduce
- Save the following 7 synthetic bars as
workdir/data/SYNTH_TEST.csv:
time,open,high,low,close,volume
0,101.00,101.20,100.80,101.00,0
60,100.50,100.60,99.30,99.80,0
120,99.80,99.90,99.70,99.85,0
180,99.85,99.90,99.60,99.65,0
240,99.60,99.65,99.40,99.45,0
300,99.45,99.50,98.90,99.10,0
360,99.10,99.20,99.00,99.15,0
- Convert it. The generated TOML is used unmodified; it sets
mintick = 0.01, so profit=50 is 0.50.
pyne data convert-from workdir/data/SYNTH_TEST.csv -p synth -s SYNTH_TEST
- Save this script as
workdir/scripts/repro_same_bar_exit.py:
# /// script
# requires-python = ">=3.11"
# dependencies = ["pynesys-pynecore[cli]"]
# ///
"""
@pyne
"""
from pynecore.lib import bar_index, script, strategy
@script.strategy("Repro same-bar exit", overlay=True,
default_qty_type=strategy.fixed, default_qty_value=1000)
def main():
if bar_index == 0:
strategy.entry('S1', strategy.short, stop=100.00)
strategy.exit('S1X', from_entry='S1', profit=50) # ticks
if __name__ == "__main__":
from pynecore.standalone import run
run(__file__)
- Run it:
pyne run workdir/scripts/repro_same_bar_exit.py workdir/data/SYNTH_TEST.ohlcv
- Open
workdir/output/repro_same_bar_exit_trade.csv and compare the bar index of the entry and the exit.
Expected behavior
Both fills should land on bar 1.
The Pine Script User Manual FAQ, under "How can I exit a trade in the same bar as it opens?", states that the default of closing at the next bar's open can be overridden by specifying exit prices, and that a trade can exit during the same bar that it opens when the entry command also sets stop-loss or take-profit orders at price levels. The example given there has the same shape as the reproduction above:
strategy.entry("buy", strategy.long)
strategy.exit("exit", "buy", profit = exitTickSizeInput, loss = exitTickSizeInput)
The same page, under "How can I execute a trade partway through a bar?", also notes that stop and limit orders at predefined prices can fill partway through a bar on historical data, because the emulator simulates a fill at the predefined level whenever it determines price reached that level during the bar.
Source: https://www.tradingview.com/pine-script-docs/faq/strategies/
Screenshots
N/A
Environment:
- PyneCore branch: main (PyPI release 6.8.4,
pynesys-pynecore[cli])
- OS: Linux
- Version: Ubuntu 24.04
- Python version: 3.12.3
- How you ran the script: CLI
pyne run
Additional context
Actual output
Trade #,Bar Index,Type,Signal,Date/Time,Price USD,Contracts
1,1,Entry short,S1,1970-01-01T00:01:00+0000,100.0,1000.0
1,4,Exit short,S1X,1970-01-01T00:04:00+0000,99.5,1000.0
The entry fills on bar 1 as expected. The take-profit is not filled on that bar even though the bar's low reaches it; it fills on bar 4, the next bar whose range reaches the level.
Why bar 1 should contain both fills
Bar 1 is O=100.50 H=100.60 L=99.30 C=99.80. The high sits closer to the open than the low does, so the emulator's assumed intrabar path is open to high to low to close. Walking that path:
- descending from 100.60, price reaches the short stop entry at 100.00, so the entry fills
- the same descent continues to the bar low of 99.30, passing the take-profit at 99.50, so the exit fills
The two fills are correctly ordered within the assumed path, and the take-profit is reached strictly after the entry. This case needs no intrabar data beyond the OHLC the emulator already assumes.
Frequency
This is not an edge case for strategies whose take-profit is tight relative to bar range. On a 15-minute FX dataset of 20,430 bars, 12 of 51 entries (23.5%) had the take-profit level inside the entry bar's range, all of them stop entries.
Limits of this report
I do not have a TradingView run on this synthetic dataset to attach; the expected behaviour above is taken from the documentation rather than from a side-by-side run. If a reference run on a shared symbol would help, I can produce one.
Describe the bug
When
strategy.entry(..., stop=)fills on a bar and the attachedstrategy.exit(..., profit=)level is also reached on that same bar, TradingView closes the trade on the entry bar. PyneCore fills the entry but carries the exit forward, closing it on a later bar. The exit price is correct; only the bar on which it fills is wrong.Verified on PyneCore 6.8.4 (latest at the time of writing), and previously on 6.8.2.
To Reproduce
workdir/data/SYNTH_TEST.csv:mintick = 0.01, soprofit=50is 0.50.workdir/scripts/repro_same_bar_exit.py:workdir/output/repro_same_bar_exit_trade.csvand compare the bar index of the entry and the exit.Expected behavior
Both fills should land on bar 1.
The Pine Script User Manual FAQ, under "How can I exit a trade in the same bar as it opens?", states that the default of closing at the next bar's open can be overridden by specifying exit prices, and that a trade can exit during the same bar that it opens when the entry command also sets stop-loss or take-profit orders at price levels. The example given there has the same shape as the reproduction above:
The same page, under "How can I execute a trade partway through a bar?", also notes that stop and limit orders at predefined prices can fill partway through a bar on historical data, because the emulator simulates a fill at the predefined level whenever it determines price reached that level during the bar.
Source: https://www.tradingview.com/pine-script-docs/faq/strategies/
Screenshots
N/A
Environment:
pynesys-pynecore[cli])pyne runAdditional context
Actual output
The entry fills on bar 1 as expected. The take-profit is not filled on that bar even though the bar's low reaches it; it fills on bar 4, the next bar whose range reaches the level.
Why bar 1 should contain both fills
Bar 1 is
O=100.50 H=100.60 L=99.30 C=99.80. The high sits closer to the open than the low does, so the emulator's assumed intrabar path is open to high to low to close. Walking that path:The two fills are correctly ordered within the assumed path, and the take-profit is reached strictly after the entry. This case needs no intrabar data beyond the OHLC the emulator already assumes.
Frequency
This is not an edge case for strategies whose take-profit is tight relative to bar range. On a 15-minute FX dataset of 20,430 bars, 12 of 51 entries (23.5%) had the take-profit level inside the entry bar's range, all of them stop entries.
Limits of this report
I do not have a TradingView run on this synthetic dataset to attach; the expected behaviour above is taken from the documentation rather than from a side-by-side run. If a reference run on a shared symbol would help, I can produce one.