Skip to content

Stop entry and its price-specified exit bracket never fill on the same bar; the exit is deferred to a later bar #76

Description

@Ritz-T

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

  1. 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
  1. 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
  1. 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__)
  1. Run it:
pyne run workdir/scripts/repro_same_bar_exit.py workdir/data/SYNTH_TEST.ohlcv
  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions