opencode-pyneruntime 6.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
- opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
- opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
- opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
- opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
- pynecore/__init__.py +6 -0
- pynecore/cli/__init__.py +2 -0
- pynecore/cli/app.py +238 -0
- pynecore/cli/commands/__init__.py +343 -0
- pynecore/cli/commands/benchmark.py +186 -0
- pynecore/cli/commands/compile.py +198 -0
- pynecore/cli/commands/data.py +857 -0
- pynecore/cli/commands/debug.py +63 -0
- pynecore/cli/commands/optimize.py +956 -0
- pynecore/cli/commands/plugin.py +242 -0
- pynecore/cli/commands/run.py +2006 -0
- pynecore/cli/pluggable.py +132 -0
- pynecore/cli/utils/__init__.py +0 -0
- pynecore/cli/utils/api_error_handler.py +168 -0
- pynecore/cli/utils/broker_picker.py +330 -0
- pynecore/cli/utils/error_hook.py +28 -0
- pynecore/cli/utils/keyreader.py +178 -0
- pynecore/cli/utils/provider_picker.py +19 -0
- pynecore/cli/utils/symbol_browser.py +1149 -0
- pynecore/core/__init__.py +0 -0
- pynecore/core/aggregator.py +257 -0
- pynecore/core/bar_magnifier.py +168 -0
- pynecore/core/broker/__init__.py +64 -0
- pynecore/core/broker/defaults.py +113 -0
- pynecore/core/broker/disappearance.py +927 -0
- pynecore/core/broker/emulator.py +345 -0
- pynecore/core/broker/exceptions.py +346 -0
- pynecore/core/broker/idempotency.py +401 -0
- pynecore/core/broker/intent_builder.py +334 -0
- pynecore/core/broker/journal.py +1785 -0
- pynecore/core/broker/models.py +1600 -0
- pynecore/core/broker/native_failsafe_manager.py +1436 -0
- pynecore/core/broker/one_way_emulator.py +1128 -0
- pynecore/core/broker/position.py +787 -0
- pynecore/core/broker/run_identity.py +126 -0
- pynecore/core/broker/software_entry_stop_engine.py +351 -0
- pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
- pynecore/core/broker/spot_inventory.py +1327 -0
- pynecore/core/broker/storage.py +2655 -0
- pynecore/core/broker/store_helpers.py +2161 -0
- pynecore/core/broker/sync_engine.py +16070 -0
- pynecore/core/broker/validation.py +382 -0
- pynecore/core/class_property.py +7 -0
- pynecore/core/config.py +392 -0
- pynecore/core/csv_file.py +547 -0
- pynecore/core/currency.py +262 -0
- pynecore/core/data_converter.py +1002 -0
- pynecore/core/datetime.py +296 -0
- pynecore/core/download_info.py +71 -0
- pynecore/core/download_runner.py +274 -0
- pynecore/core/htf_aggregator.py +181 -0
- pynecore/core/import_hook.py +358 -0
- pynecore/core/instance_state.py +494 -0
- pynecore/core/live_ltf_collector.py +442 -0
- pynecore/core/live_ltf_window.py +189 -0
- pynecore/core/live_runner.py +1347 -0
- pynecore/core/module_property.py +26 -0
- pynecore/core/ohlcv_file.py +1888 -0
- pynecore/core/overload.py +371 -0
- pynecore/core/pine_cast.py +113 -0
- pynecore/core/pine_export.py +95 -0
- pynecore/core/pine_method.py +244 -0
- pynecore/core/pine_range.py +86 -0
- pynecore/core/pine_udt.py +69 -0
- pynecore/core/plugin/__init__.py +394 -0
- pynecore/core/plugin/broker.py +781 -0
- pynecore/core/plugin/cli.py +96 -0
- pynecore/core/plugin/live_provider.py +208 -0
- pynecore/core/plugin/provider.py +331 -0
- pynecore/core/provider_string.py +148 -0
- pynecore/core/random.py +40 -0
- pynecore/core/resampler.py +686 -0
- pynecore/core/safe_convert.py +64 -0
- pynecore/core/script.py +1011 -0
- pynecore/core/script_runner.py +3202 -0
- pynecore/core/security.py +1749 -0
- pynecore/core/security_process.py +1253 -0
- pynecore/core/security_shm.py +456 -0
- pynecore/core/series.py +417 -0
- pynecore/core/strategy_stats.py +669 -0
- pynecore/core/symbol_map.py +134 -0
- pynecore/core/syminfo.py +505 -0
- pynecore/core/viz.py +591 -0
- pynecore/lib/__init__.py +1771 -0
- pynecore/lib/_fixnan.py +32 -0
- pynecore/lib/_math_stateful.py +202 -0
- pynecore/lib/_timeframe_change.py +101 -0
- pynecore/lib/adjustment.py +6 -0
- pynecore/lib/alert.py +39 -0
- pynecore/lib/alert.pyi +14 -0
- pynecore/lib/array.py +1051 -0
- pynecore/lib/barmerge.py +60 -0
- pynecore/lib/barstate.py +30 -0
- pynecore/lib/box.py +415 -0
- pynecore/lib/chart.py +128 -0
- pynecore/lib/color.py +152 -0
- pynecore/lib/color.pyi +50 -0
- pynecore/lib/currency.py +62 -0
- pynecore/lib/dayofweek.py +36 -0
- pynecore/lib/dayofweek.pyi +18 -0
- pynecore/lib/display.py +8 -0
- pynecore/lib/dividends.py +9 -0
- pynecore/lib/earnings.py +11 -0
- pynecore/lib/extend.py +6 -0
- pynecore/lib/font.py +5 -0
- pynecore/lib/footprint.py +79 -0
- pynecore/lib/format.py +11 -0
- pynecore/lib/hline.py +67 -0
- pynecore/lib/hline.pyi +24 -0
- pynecore/lib/label.py +409 -0
- pynecore/lib/line.py +433 -0
- pynecore/lib/linefill.py +93 -0
- pynecore/lib/location.py +11 -0
- pynecore/lib/log.py +362 -0
- pynecore/lib/map.py +150 -0
- pynecore/lib/math.py +385 -0
- pynecore/lib/matrix.py +708 -0
- pynecore/lib/order.py +8 -0
- pynecore/lib/pivotpointtype.py +8 -0
- pynecore/lib/plot.py +95 -0
- pynecore/lib/plot.pyi +33 -0
- pynecore/lib/polyline.py +91 -0
- pynecore/lib/position.py +15 -0
- pynecore/lib/request.py +281 -0
- pynecore/lib/runtime.py +5 -0
- pynecore/lib/scale.py +9 -0
- pynecore/lib/session.py +267 -0
- pynecore/lib/session.pyi +12 -0
- pynecore/lib/shape.py +18 -0
- pynecore/lib/size.py +12 -0
- pynecore/lib/splits.py +4 -0
- pynecore/lib/strategy/__init__.py +4778 -0
- pynecore/lib/strategy/closedtrades.py +347 -0
- pynecore/lib/strategy/closedtrades.pyi +53 -0
- pynecore/lib/strategy/commission.py +9 -0
- pynecore/lib/strategy/direction.py +9 -0
- pynecore/lib/strategy/oca.py +13 -0
- pynecore/lib/strategy/opentrades.py +281 -0
- pynecore/lib/strategy/opentrades.pyi +49 -0
- pynecore/lib/strategy/risk.py +109 -0
- pynecore/lib/string.py +649 -0
- pynecore/lib/syminfo.py +84 -0
- pynecore/lib/ta.py +2230 -0
- pynecore/lib/table.py +290 -0
- pynecore/lib/text.py +17 -0
- pynecore/lib/ticker.py +207 -0
- pynecore/lib/timeframe.py +293 -0
- pynecore/lib/volume_row.py +67 -0
- pynecore/lib/xloc.py +4 -0
- pynecore/lib/yloc.py +5 -0
- pynecore/providers/__init__.py +0 -0
- pynecore/providers/ccxt.py +664 -0
- pynecore/providers/replay.py +187 -0
- pynecore/pynesys/__init__.py +0 -0
- pynecore/pynesys/api.py +498 -0
- pynecore/pynesys/compiler.py +112 -0
- pynecore/standalone.py +99 -0
- pynecore/testing/__init__.py +1 -0
- pynecore/testing/broker_lab/__init__.py +41 -0
- pynecore/testing/broker_lab/__main__.py +5 -0
- pynecore/testing/broker_lab/cli.py +87 -0
- pynecore/testing/broker_lab/generate.py +47 -0
- pynecore/testing/broker_lab/model.py +84 -0
- pynecore/testing/broker_lab/reference.py +645 -0
- pynecore/testing/broker_lab/runner.py +372 -0
- pynecore/testing/broker_lab/scheduler.py +50 -0
- pynecore/testing/broker_lab/subprocess.py +73 -0
- pynecore/transformers/__init__.py +0 -0
- pynecore/transformers/builtin_shadow.py +136 -0
- pynecore/transformers/closure_arguments_transformer.py +428 -0
- pynecore/transformers/display_rewrite.py +140 -0
- pynecore/transformers/dynamic_default.py +147 -0
- pynecore/transformers/function_isolation.py +757 -0
- pynecore/transformers/import_lifter.py +61 -0
- pynecore/transformers/import_normalizer.py +328 -0
- pynecore/transformers/inline_series_hoist.py +178 -0
- pynecore/transformers/input_transformer.py +175 -0
- pynecore/transformers/lib_series.py +201 -0
- pynecore/transformers/locations.py +70 -0
- pynecore/transformers/module_properties.json +3387 -0
- pynecore/transformers/module_property.py +221 -0
- pynecore/transformers/ne_guard.py +70 -0
- pynecore/transformers/persistent.py +320 -0
- pynecore/transformers/persistent_series.py +76 -0
- pynecore/transformers/safe_convert_transformer.py +97 -0
- pynecore/transformers/safe_division_transformer.py +95 -0
- pynecore/transformers/script_requirements.py +308 -0
- pynecore/transformers/security.py +752 -0
- pynecore/transformers/security_instantiation.py +274 -0
- pynecore/transformers/series.py +275 -0
- pynecore/transformers/slot_layout.py +381 -0
- pynecore/transformers/type_checking_stripper.py +25 -0
- pynecore/transformers/unused_series_detector.py +267 -0
- pynecore/types/__init__.py +21 -0
- pynecore/types/alert.py +5 -0
- pynecore/types/barmerge.py +5 -0
- pynecore/types/base.py +39 -0
- pynecore/types/box.py +37 -0
- pynecore/types/chart.py +17 -0
- pynecore/types/color.py +107 -0
- pynecore/types/currency.py +5 -0
- pynecore/types/datetime.py +6 -0
- pynecore/types/display.py +5 -0
- pynecore/types/dividends.py +5 -0
- pynecore/types/earnings.py +5 -0
- pynecore/types/extend.py +5 -0
- pynecore/types/font.py +5 -0
- pynecore/types/footprint.py +41 -0
- pynecore/types/format.py +5 -0
- pynecore/types/hline.py +24 -0
- pynecore/types/ib_persistent.py +8 -0
- pynecore/types/ib_persistent.pyi +10 -0
- pynecore/types/label.py +35 -0
- pynecore/types/line.py +32 -0
- pynecore/types/linefill.py +13 -0
- pynecore/types/location.py +5 -0
- pynecore/types/matrix.py +999 -0
- pynecore/types/na.py +237 -0
- pynecore/types/na.pyi +83 -0
- pynecore/types/ohlcv.py +12 -0
- pynecore/types/order.py +5 -0
- pynecore/types/persistent.py +8 -0
- pynecore/types/persistent.pyi +13 -0
- pynecore/types/pine_types.py +11 -0
- pynecore/types/pine_types.pyi +15 -0
- pynecore/types/pivotpointtype.py +5 -0
- pynecore/types/plot.py +12 -0
- pynecore/types/plot_meta.py +60 -0
- pynecore/types/polyline.py +40 -0
- pynecore/types/position.py +5 -0
- pynecore/types/scale.py +5 -0
- pynecore/types/script_type.py +15 -0
- pynecore/types/series.py +23 -0
- pynecore/types/series.pyi +19 -0
- pynecore/types/session.py +35 -0
- pynecore/types/shape.py +5 -0
- pynecore/types/size.py +5 -0
- pynecore/types/source.py +33 -0
- pynecore/types/splits.py +5 -0
- pynecore/types/strategy.py +45 -0
- pynecore/types/table.py +87 -0
- pynecore/types/text.py +13 -0
- pynecore/types/type_checker.py +7 -0
- pynecore/types/type_checker.pyi +48 -0
- pynecore/types/volume_row.py +36 -0
- pynecore/types/weekdays.py +11 -0
- pynecore/types/xloc.py +5 -0
- pynecore/types/yloc.py +5 -0
- pynecore/utils/__init__.py +0 -0
- pynecore/utils/file_utils.py +50 -0
- pynecore/utils/rich/__init__.py +0 -0
- pynecore/utils/rich/date_column.py +25 -0
- pynecore/utils/sequence_view.py +92 -0
- pynecore/utils/stdlib_checker.py +17 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Live state machine for ``request.security_lower_tf``.
|
|
3
|
+
|
|
4
|
+
Drives one chart period's lower-timeframe (LTF) intrabar executions and
|
|
5
|
+
accumulates their expression values into a :class:`LiveLtfWindow`. This is the
|
|
6
|
+
orchestration layer extracted from the security subprocess so the hard parts —
|
|
7
|
+
monotonic ``bar_index``, snapshot save/restore around the developing intrabar,
|
|
8
|
+
finalize-on-close, provisional rollover under a reordered feed, and
|
|
9
|
+
per-chart-period window reset — can be exercised in-process, with no subprocess,
|
|
10
|
+
by injecting the side effects as callbacks. The collector only *sequences* those
|
|
11
|
+
callbacks; it performs no I/O and touches no shared memory.
|
|
12
|
+
|
|
13
|
+
Model (TradingView, verified by a realtime probe on BINANCE:BTCUSDT 30m/3m):
|
|
14
|
+
while a chart bar develops, the LTF array is the closed intrabars of
|
|
15
|
+
``[period_start, period_end)`` plus the currently-developing intrabar as the
|
|
16
|
+
live *last* element; at chart close it converges to the full closed period. The
|
|
17
|
+
``bar_index`` is the continuous LTF series index — it never rewinds at a
|
|
18
|
+
chart-period boundary (only the window resets), so expression history such as a
|
|
19
|
+
``ta.sma`` inside the LTF expression stays intact across periods.
|
|
20
|
+
|
|
21
|
+
Reordered feed (provisional rollover): a live feed can deliver the forming tick
|
|
22
|
+
of intrabar ``N+1`` ahead of the late close of intrabar ``N`` (e.g. cTrader
|
|
23
|
+
closes a slot on the next slot's open, or plain stream reordering). The array
|
|
24
|
+
must still grow when ``N+1`` starts, so ``N`` is *promoted* to a provisional
|
|
25
|
+
element holding its last developing value at its original ``bar_index``; when
|
|
26
|
+
``N``'s close finally arrives it *replaces* that value at the same index (it is
|
|
27
|
+
never appended at a new index). Because the logical evaluation order is
|
|
28
|
+
timestamp order regardless of arrival order, a late close that shifts the
|
|
29
|
+
confirmed baseline triggers a *replay* of the still-pending provisional chain
|
|
30
|
+
and the developing tail on the corrected baseline — so a ``var``/``ta.*`` LTF
|
|
31
|
+
expression is never poisoned by a provisional intrabar whose close later
|
|
32
|
+
differs.
|
|
33
|
+
|
|
34
|
+
Baseline invariant: the saved baseline is the var state *after the latest
|
|
35
|
+
confirmed (closed) intrabar and before any provisional/developing execution*. It
|
|
36
|
+
is saved after every confirmed intrabar run and restored before every re-run at
|
|
37
|
+
an already-used ``bar_index`` — a developing re-tick, a provisional-chain
|
|
38
|
+
replay, or a developing/provisional intrabar's own confirmed close. A brand-new
|
|
39
|
+
intrabar at a fresh ``bar_index`` runs against the current baseline and never
|
|
40
|
+
saves afterwards (until it is confirmed). ``restore`` also resets the per-bar
|
|
41
|
+
add/set tracking so a ``Series.add`` at the reused index degrades to a ``set``
|
|
42
|
+
(overwrite), matching the proven live-HTF developing mechanism; a single restore
|
|
43
|
+
then lets a sequence of intrabars at distinct indices each append exactly once.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from collections.abc import Callable, Sequence
|
|
47
|
+
from dataclasses import dataclass
|
|
48
|
+
from typing import Any, Protocol
|
|
49
|
+
|
|
50
|
+
from .live_ltf_window import LiveLtfWindow
|
|
51
|
+
|
|
52
|
+
__all__ = ["LiveLtfCollector", "IntrabarLike"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class IntrabarLike(Protocol):
|
|
56
|
+
"""Minimal shape the collector needs from an intrabar (an ``OHLCV``).
|
|
57
|
+
|
|
58
|
+
Only the ``timestamp`` (seconds) is read here; the value an intrabar
|
|
59
|
+
contributes to the array comes from ``run_intrabar``, not from the OHLCV.
|
|
60
|
+
``OHLCV`` is an immutable ``NamedTuple``, so a reference kept for replay
|
|
61
|
+
cannot be mutated out from under the collector.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
timestamp: int
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class _Intrabar:
|
|
69
|
+
"""A replayable in-period intrabar slot.
|
|
70
|
+
|
|
71
|
+
Carries the last-known OHLCV alongside the assigned ``bar_index`` so a
|
|
72
|
+
provisional intrabar can be re-run on a corrected baseline after a late close
|
|
73
|
+
(replay needs the original input, not just the timestamp/index).
|
|
74
|
+
|
|
75
|
+
``is_new`` is the pending ``barstate.isnew`` flag: it is True only until the
|
|
76
|
+
slot has executed once. A bar promoted from the developing tail already fired
|
|
77
|
+
``is_new=True`` while it was developing, so its slot starts False; a bar that
|
|
78
|
+
is deferred straight into the chain without ever being a developing tail (a
|
|
79
|
+
newer closed bar held behind an unresolved gap) starts True so its very first
|
|
80
|
+
replay still reports a new bar, exactly like the normal closed-bar path. The
|
|
81
|
+
flag is consumed (cleared) by the first run, so later replays report False.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
ts_ms: int
|
|
85
|
+
bar_index: int
|
|
86
|
+
ohlcv: Any
|
|
87
|
+
is_new: bool = False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class LiveLtfCollector:
|
|
91
|
+
"""Sequences live LTF intrabar runs into a per-chart-period array.
|
|
92
|
+
|
|
93
|
+
:param run_intrabar: ``(ohlcv, bar_index, confirmed, is_new, islast) ->
|
|
94
|
+
value`` — runs the LTF script for one intrabar (sets lib properties +
|
|
95
|
+
barstate, executes ``main``) and returns the expression value it wrote.
|
|
96
|
+
``islast`` is the live last element flag (``barstate.islast``): true
|
|
97
|
+
only for the single developing tail, false for every closed and
|
|
98
|
+
provisional (rolled-over) intrabar — a provisional is no longer the
|
|
99
|
+
last element once a newer intrabar has started, even though it is still
|
|
100
|
+
unconfirmed.
|
|
101
|
+
:param save_baseline: Persist the current var state as the rollback
|
|
102
|
+
baseline (no-op acceptable when the script has no ``var`` slots).
|
|
103
|
+
:param restore_baseline: Restore the var state to the saved baseline and
|
|
104
|
+
reset per-bar add/set tracking, in preparation for a re-run at an
|
|
105
|
+
already-used ``bar_index``.
|
|
106
|
+
:param start_bar_index: The first ``bar_index`` to assign (default 0).
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
run_intrabar: Callable[[Any, int, bool, bool, bool], Any],
|
|
112
|
+
save_baseline: Callable[[], None],
|
|
113
|
+
restore_baseline: Callable[[], None],
|
|
114
|
+
*,
|
|
115
|
+
start_bar_index: int = 0,
|
|
116
|
+
) -> None:
|
|
117
|
+
self._run_intrabar = run_intrabar
|
|
118
|
+
self._save_baseline = save_baseline
|
|
119
|
+
self._restore_baseline = restore_baseline
|
|
120
|
+
|
|
121
|
+
self._window = LiveLtfWindow()
|
|
122
|
+
self._bar_index = start_bar_index
|
|
123
|
+
self._seen_any = False
|
|
124
|
+
self._has_baseline = False
|
|
125
|
+
|
|
126
|
+
self._period_start: int | None = None
|
|
127
|
+
self._developing: _Intrabar | None = None
|
|
128
|
+
self._pending: list[_Intrabar] = []
|
|
129
|
+
self._late_closes: dict[int, IntrabarLike] = {}
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def bar_index(self) -> int:
|
|
133
|
+
"""The most recently assigned LTF series index (monotonic)."""
|
|
134
|
+
return self._bar_index
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def window(self) -> LiveLtfWindow:
|
|
138
|
+
"""The underlying per-chart-period accumulator (for inspection)."""
|
|
139
|
+
return self._window
|
|
140
|
+
|
|
141
|
+
def _advance_index(self) -> int:
|
|
142
|
+
"""Assign the next ``bar_index``; the very first bar reuses the start."""
|
|
143
|
+
if self._seen_any:
|
|
144
|
+
self._bar_index += 1
|
|
145
|
+
self._seen_any = True
|
|
146
|
+
return self._bar_index
|
|
147
|
+
|
|
148
|
+
def _ensure_baseline(self) -> None:
|
|
149
|
+
"""Save an initial baseline if none exists (process opened on a dev bar)."""
|
|
150
|
+
if not self._has_baseline:
|
|
151
|
+
self._save_baseline()
|
|
152
|
+
self._has_baseline = True
|
|
153
|
+
|
|
154
|
+
def _is_pending_ts(self, ts_ms: int) -> bool:
|
|
155
|
+
"""Whether ``ts_ms`` belongs to a rolled-over provisional awaiting close."""
|
|
156
|
+
return any(p.ts_ms == ts_ms for p in self._pending)
|
|
157
|
+
|
|
158
|
+
def _run_pending(self, slot: _Intrabar, confirmed: bool) -> Any:
|
|
159
|
+
"""Run one pending provisional slot, consuming its first-run ``is_new``.
|
|
160
|
+
|
|
161
|
+
A deferred-into-the-chain slot must report ``barstate.isnew`` on its very
|
|
162
|
+
first execution (the normal closed-bar path does); the flag is cleared
|
|
163
|
+
here so every later replay of the same slot reports ``is_new=False``.
|
|
164
|
+
Pending slots are never the live last element, so ``islast`` is False.
|
|
165
|
+
"""
|
|
166
|
+
is_new = slot.is_new
|
|
167
|
+
slot.is_new = False
|
|
168
|
+
return self._run_intrabar(slot.ohlcv, slot.bar_index, confirmed, is_new, False)
|
|
169
|
+
|
|
170
|
+
def _drain_pending_closes(self) -> bool:
|
|
171
|
+
"""Finalize buffered late closes for the provisional chain, oldest first.
|
|
172
|
+
|
|
173
|
+
A provisional is finalized only once its own close is buffered AND it is
|
|
174
|
+
at the front of the chain (closes apply front-to-back so the confirmed
|
|
175
|
+
baseline advances contiguously). Each finalize restores the baseline,
|
|
176
|
+
re-runs the intrabar as confirmed at its ORIGINAL ``bar_index``, replaces
|
|
177
|
+
the provisional value with the confirmed one, and saves the new baseline.
|
|
178
|
+
|
|
179
|
+
:return: Whether at least one provisional was finalized.
|
|
180
|
+
"""
|
|
181
|
+
drained = False
|
|
182
|
+
while self._pending and self._pending[0].ts_ms in self._late_closes:
|
|
183
|
+
slot = self._pending.pop(0)
|
|
184
|
+
bar = self._late_closes.pop(slot.ts_ms)
|
|
185
|
+
slot.ohlcv = bar
|
|
186
|
+
self._restore_baseline()
|
|
187
|
+
value = self._run_pending(slot, True)
|
|
188
|
+
self._window.finalize_first_provisional(value)
|
|
189
|
+
self._save_baseline()
|
|
190
|
+
self._has_baseline = True
|
|
191
|
+
drained = True
|
|
192
|
+
return drained
|
|
193
|
+
|
|
194
|
+
def _rebuild_tail(self, *, run_developing: bool) -> None:
|
|
195
|
+
"""Replay the pending provisional chain (and optionally the developing
|
|
196
|
+
tail) on the current confirmed baseline.
|
|
197
|
+
|
|
198
|
+
Restores once to the post-confirmed baseline, then re-runs every pending
|
|
199
|
+
provisional in timestamp order (each at its own index, so each
|
|
200
|
+
``Series.add`` appends exactly once) and writes its value back in place;
|
|
201
|
+
finally re-runs the developing tail on top when requested. None of these
|
|
202
|
+
runs save a baseline — they are all unconfirmed.
|
|
203
|
+
"""
|
|
204
|
+
self._restore_baseline()
|
|
205
|
+
for i, slot in enumerate(self._pending):
|
|
206
|
+
value = self._run_pending(slot, False)
|
|
207
|
+
self._window.replace_provisional(i, value)
|
|
208
|
+
if run_developing and self._developing is not None:
|
|
209
|
+
value = self._run_intrabar(
|
|
210
|
+
self._developing.ohlcv, self._developing.bar_index, False, False, True
|
|
211
|
+
)
|
|
212
|
+
self._window.set_developing(value)
|
|
213
|
+
|
|
214
|
+
def process_round(
|
|
215
|
+
self,
|
|
216
|
+
period_start: int,
|
|
217
|
+
period_end_exclusive: int,
|
|
218
|
+
closed_bars: Sequence[IntrabarLike],
|
|
219
|
+
developing_bar: IntrabarLike | None,
|
|
220
|
+
chart_confirmed: bool,
|
|
221
|
+
) -> list[Any]:
|
|
222
|
+
"""Advance one chart tick and return the published LTF array (a copy).
|
|
223
|
+
|
|
224
|
+
``closed_bars`` are the newly available closed intrabars (ordered, and
|
|
225
|
+
already bounded above by ``period_end_exclusive`` by the caller — a bar
|
|
226
|
+
belonging to a future chart period must be withheld until that period is
|
|
227
|
+
current). Every closed intrabar is *run* to maintain expression history;
|
|
228
|
+
only those at or after ``period_start`` join the array. ``developing_bar``
|
|
229
|
+
is the currently-forming intrabar (``None`` when none is available, e.g.
|
|
230
|
+
right at period open); it is processed only while the chart bar itself is
|
|
231
|
+
still developing (``chart_confirmed`` is False).
|
|
232
|
+
|
|
233
|
+
:param period_start: The chart bar's open instant (ms).
|
|
234
|
+
:param period_end_exclusive: The chart bar's period end (ms, exclusive).
|
|
235
|
+
:param closed_bars: Newly available closed intrabars, time-ordered.
|
|
236
|
+
:param developing_bar: The currently-developing intrabar, or ``None``.
|
|
237
|
+
:param chart_confirmed: Whether the chart bar has closed (period complete).
|
|
238
|
+
:return: A fresh list — closed + provisional values plus the developing
|
|
239
|
+
tail, if any.
|
|
240
|
+
"""
|
|
241
|
+
# ── Chart-period roll ──
|
|
242
|
+
# The closing period's confirmed round always arrives (with its old
|
|
243
|
+
# ``period_start``) before the first round of the next period, so the
|
|
244
|
+
# window is already finalized here; the reset just clears it. Stale
|
|
245
|
+
# rollover tracking is dropped defensively.
|
|
246
|
+
if period_start != self._period_start:
|
|
247
|
+
self._window.reset()
|
|
248
|
+
self._period_start = period_start
|
|
249
|
+
self._developing = None
|
|
250
|
+
self._pending = []
|
|
251
|
+
self._late_closes = {}
|
|
252
|
+
|
|
253
|
+
# ── Closed intrabars (run all for history; window membership in-period) ──
|
|
254
|
+
# Tracks whether the confirmed baseline advanced inside this loop (an
|
|
255
|
+
# in-loop drain finalized a provisional, or a brand-new closed bar saved).
|
|
256
|
+
# A still-tracked developing tail computed against the OLD baseline is then
|
|
257
|
+
# stale and must be rebuilt below even if the post-loop drain finalizes
|
|
258
|
+
# nothing (it already drained in the loop).
|
|
259
|
+
baseline_advanced = False
|
|
260
|
+
for bar in closed_bars:
|
|
261
|
+
ts_ms = int(bar.timestamp * 1000)
|
|
262
|
+
if (self._developing is not None and ts_ms == self._developing.ts_ms
|
|
263
|
+
and not self._pending):
|
|
264
|
+
# In-order close of the current developing intrabar (no reorder):
|
|
265
|
+
# re-run as confirmed at the SAME index (its dev ticks restored
|
|
266
|
+
# away) and finalize the tail. The confirmed close may differ
|
|
267
|
+
# from the last developing tick.
|
|
268
|
+
self._restore_baseline()
|
|
269
|
+
value = self._run_intrabar(bar, self._developing.bar_index, True, False, False)
|
|
270
|
+
self._window.finalize_developing(value)
|
|
271
|
+
self._save_baseline()
|
|
272
|
+
self._has_baseline = True
|
|
273
|
+
self._developing = None
|
|
274
|
+
elif self._developing is not None and ts_ms == self._developing.ts_ms:
|
|
275
|
+
# Late close of the current developing intrabar arriving while a
|
|
276
|
+
# provisional chain still leads it (its close was batched behind
|
|
277
|
+
# the heads' closes). Promote it to the back of the chain — it has
|
|
278
|
+
# the largest timestamp — and buffer its close, so the drain below
|
|
279
|
+
# finalizes the whole chain front-to-back in timestamp order, each
|
|
280
|
+
# at its ORIGINAL bar_index, instead of appending a new index.
|
|
281
|
+
self._window.promote_developing_to_provisional()
|
|
282
|
+
self._pending.append(self._developing)
|
|
283
|
+
self._developing = None
|
|
284
|
+
self._late_closes[ts_ms] = bar
|
|
285
|
+
elif self._is_pending_ts(ts_ms):
|
|
286
|
+
# Late close of a rolled-over provisional: buffer it and finalize
|
|
287
|
+
# the provisional chain front-to-back below, so the close
|
|
288
|
+
# replaces the provisional value at its ORIGINAL bar_index rather
|
|
289
|
+
# than appending at a new one.
|
|
290
|
+
self._late_closes[ts_ms] = bar
|
|
291
|
+
else:
|
|
292
|
+
# A brand-new closed intrabar. The streamer can batch a pending
|
|
293
|
+
# provisional's late close ahead of (or behind) this newer bar in
|
|
294
|
+
# the same round; finalize every drainable provisional FIRST so the
|
|
295
|
+
# baseline advances and the chain becomes closed in timestamp order
|
|
296
|
+
# before this newer bar runs and appends. Without this drain the
|
|
297
|
+
# newer bar would run on the stale pre-close baseline, save it, and
|
|
298
|
+
# land in the closed segment ahead of the still-provisional chain —
|
|
299
|
+
# corrupting both the published order and the persisted var state.
|
|
300
|
+
if self._drain_pending_closes():
|
|
301
|
+
baseline_advanced = True
|
|
302
|
+
# If an older developing intrabar is still unconfirmed AND gets no
|
|
303
|
+
# fresh forming tick this round (its close was skipped — a gap),
|
|
304
|
+
# promote it to the BACK of the provisional chain so this newer bar
|
|
305
|
+
# is deferred behind it below instead of falling through to the
|
|
306
|
+
# closed segment. The developing tail always carries the largest
|
|
307
|
+
# timestamp of the tracked intrabars (it started after every pending
|
|
308
|
+
# provisional), so the back of the chain keeps timestamp order even
|
|
309
|
+
# when an earlier provisional gap left ``_pending`` non-empty.
|
|
310
|
+
# Without this the newer bar would publish ahead of the older
|
|
311
|
+
# developing tail (out of timestamp order) and advance the saved
|
|
312
|
+
# baseline past the gap. A developing that IS still forming this round
|
|
313
|
+
# (its tick is in ``developing_bar``) stays the live tail and the
|
|
314
|
+
# newer closed bar legitimately appends ahead of it.
|
|
315
|
+
if (self._developing is not None
|
|
316
|
+
and self._developing.ts_ms < ts_ms
|
|
317
|
+
and not (developing_bar is not None
|
|
318
|
+
and int(developing_bar.timestamp * 1000)
|
|
319
|
+
== self._developing.ts_ms)):
|
|
320
|
+
self._window.promote_developing_to_provisional()
|
|
321
|
+
self._pending.append(self._developing)
|
|
322
|
+
self._developing = None
|
|
323
|
+
idx = self._advance_index()
|
|
324
|
+
in_period = period_start <= ts_ms < period_end_exclusive
|
|
325
|
+
if self._pending and in_period:
|
|
326
|
+
# A provider gap left an older provisional's close missing, so
|
|
327
|
+
# the drain could not empty the chain. This newer bar's value
|
|
328
|
+
# cannot join the closed segment (it sits ahead of the still-
|
|
329
|
+
# pending chain in ``publish()``) nor advance the saved baseline
|
|
330
|
+
# past the unresolved earlier intrabar. Defer it into the
|
|
331
|
+
# provisional chain behind that gap: hold it at its own index,
|
|
332
|
+
# buffer its (already-arrived) close so it finalizes the moment
|
|
333
|
+
# the chain ahead drains, and replay the whole chain in place.
|
|
334
|
+
# It enters the chain as a brand-new bar, so its first replay
|
|
335
|
+
# must still report ``barstate.isnew`` like the normal closed
|
|
336
|
+
# path below — unlike a slot promoted from the developing tail.
|
|
337
|
+
self._pending.append(
|
|
338
|
+
_Intrabar(ts_ms=ts_ms, bar_index=idx, ohlcv=bar, is_new=True))
|
|
339
|
+
self._late_closes[ts_ms] = bar
|
|
340
|
+
self._window.append_provisional(None)
|
|
341
|
+
self._rebuild_tail(run_developing=True)
|
|
342
|
+
else:
|
|
343
|
+
value = self._run_intrabar(bar, idx, True, True, False)
|
|
344
|
+
if in_period:
|
|
345
|
+
self._window.append_closed(value)
|
|
346
|
+
self._save_baseline()
|
|
347
|
+
self._has_baseline = True
|
|
348
|
+
baseline_advanced = True
|
|
349
|
+
|
|
350
|
+
# ── Finalize buffered late closes; replay the rest of the chain ──
|
|
351
|
+
# After the baseline advances, the still-pending provisionals — and a
|
|
352
|
+
# developing tail computed against the pre-advance baseline — are stale.
|
|
353
|
+
# Rebuild them now unless the developing block below will rebuild the
|
|
354
|
+
# whole tail this round (a fresh developing tick is present), which avoids
|
|
355
|
+
# replaying the provisional chain twice. The post-loop drain alone is not
|
|
356
|
+
# enough to detect staleness: if the only baseline advance happened INSIDE
|
|
357
|
+
# the closed-bar loop (an in-loop drain, or a brand-new bar's save) this
|
|
358
|
+
# final drain finalizes nothing and returns False, yet a tail left from a
|
|
359
|
+
# prior round still sits on the old baseline — so ``baseline_advanced``
|
|
360
|
+
# forces the rebuild too. Skip entirely when nothing remains to replay (the
|
|
361
|
+
# whole chain closed and no developing tail) — a bare ``_rebuild_tail``
|
|
362
|
+
# would only restore the baseline, and that restore also resets
|
|
363
|
+
# function-instance state without a re-run to rebuild it, poisoning the
|
|
364
|
+
# next intrabar's child (``ta.*``-in-function) history.
|
|
365
|
+
if ((self._drain_pending_closes() or baseline_advanced) and developing_bar is None
|
|
366
|
+
and (self._pending or self._developing is not None)):
|
|
367
|
+
self._rebuild_tail(run_developing=True)
|
|
368
|
+
|
|
369
|
+
# ── Confirmed-round convergence ──
|
|
370
|
+
# A confirmed chart bar's array is exactly its closed intrabars. Any
|
|
371
|
+
# unconfirmed tail left from the last forming tick — a developing tail or
|
|
372
|
+
# a rolled-over provisional whose close never arrived (a provider gap or
|
|
373
|
+
# grace timeout; the caller publishes the round anyway rather than
|
|
374
|
+
# stalling the bot) — must not appear in a confirmed array. The discarded
|
|
375
|
+
# runs also mutated the script's var state without saving a baseline, so
|
|
376
|
+
# restore the post-confirmed baseline before dropping — otherwise the next
|
|
377
|
+
# period's intrabar would compound from the abandoned ticks. Then drop the
|
|
378
|
+
# tails and clear the rollover tracking so a late close in a later period
|
|
379
|
+
# is treated as its own intrabar, not a re-finalization at a stale index.
|
|
380
|
+
if chart_confirmed and (self._window.has_developing or self._window.has_provisional):
|
|
381
|
+
if self._has_baseline:
|
|
382
|
+
self._restore_baseline()
|
|
383
|
+
self._window.drop_developing()
|
|
384
|
+
self._window.drop_provisionals()
|
|
385
|
+
self._developing = None
|
|
386
|
+
self._pending = []
|
|
387
|
+
self._late_closes = {}
|
|
388
|
+
|
|
389
|
+
# ── Developing intrabar (live last element) ──
|
|
390
|
+
if not chart_confirmed and developing_bar is not None:
|
|
391
|
+
ts_ms = int(developing_bar.timestamp * 1000)
|
|
392
|
+
if period_start <= ts_ms < period_end_exclusive:
|
|
393
|
+
cur = self._developing
|
|
394
|
+
if cur is not None and ts_ms == cur.ts_ms:
|
|
395
|
+
# Another tick of the same developing intrabar: rewind to the
|
|
396
|
+
# baseline and re-run in place (replaying any provisional
|
|
397
|
+
# chain ahead of it so it accumulates from the confirmed
|
|
398
|
+
# prefix, not an abandoned tick).
|
|
399
|
+
cur.ohlcv = developing_bar
|
|
400
|
+
self._rebuild_tail(run_developing=True)
|
|
401
|
+
elif self._is_pending_ts(ts_ms):
|
|
402
|
+
# A stale forming update for an intrabar that already rolled
|
|
403
|
+
# over into the provisional chain (the feed reordered N's late
|
|
404
|
+
# tick behind N+1's open). It must NOT allocate a new index —
|
|
405
|
+
# refresh the provisional's stored OHLCV so its eventual close
|
|
406
|
+
# / replay uses the latest forming value, then rebuild the
|
|
407
|
+
# chain (and developing tail) in place at the same indices.
|
|
408
|
+
for slot in self._pending:
|
|
409
|
+
if slot.ts_ms == ts_ms:
|
|
410
|
+
slot.ohlcv = developing_bar
|
|
411
|
+
break
|
|
412
|
+
self._rebuild_tail(run_developing=True)
|
|
413
|
+
else:
|
|
414
|
+
# A fresh developing intrabar at a new timestamp.
|
|
415
|
+
if cur is not None:
|
|
416
|
+
# RACE: the previous developing intrabar never closed in
|
|
417
|
+
# order; promote it to a provisional element (preserve its
|
|
418
|
+
# slot + index) before advancing, so its late close can
|
|
419
|
+
# replace it in place rather than append at a new index.
|
|
420
|
+
self._window.promote_developing_to_provisional()
|
|
421
|
+
self._pending.append(cur)
|
|
422
|
+
self._ensure_baseline()
|
|
423
|
+
idx = self._advance_index()
|
|
424
|
+
self._developing = _Intrabar(ts_ms=ts_ms, bar_index=idx,
|
|
425
|
+
ohlcv=developing_bar)
|
|
426
|
+
if self._pending:
|
|
427
|
+
# Rebuild the provisional chain on the post-confirmed
|
|
428
|
+
# baseline, then run the NEW developing on top (first run
|
|
429
|
+
# at a fresh index -> is_new=True), without saving.
|
|
430
|
+
self._restore_baseline()
|
|
431
|
+
for i, slot in enumerate(self._pending):
|
|
432
|
+
v = self._run_pending(slot, False)
|
|
433
|
+
self._window.replace_provisional(i, v)
|
|
434
|
+
value = self._run_intrabar(developing_bar, idx, False, True, True)
|
|
435
|
+
self._window.set_developing(value)
|
|
436
|
+
else:
|
|
437
|
+
# No reorder in flight: run once at a new index against
|
|
438
|
+
# the post-confirmed baseline; do NOT save after.
|
|
439
|
+
value = self._run_intrabar(developing_bar, idx, False, True, True)
|
|
440
|
+
self._window.set_developing(value)
|
|
441
|
+
|
|
442
|
+
return self._window.publish()
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Per-chart-period accumulator for live ``request.security_lower_tf``.
|
|
3
|
+
|
|
4
|
+
In a live run the chart bar is *developing*: its lower-timeframe (LTF) array
|
|
5
|
+
grows as intrabars arrive and only becomes the full period ``[T, T+tf)`` once
|
|
6
|
+
the chart bar closes. TradingView includes the currently-developing LTF bar as
|
|
7
|
+
the live *last* element of the array (verified against TV: array size is
|
|
8
|
+
``floor(elapsed / ltf_span) + 1`` — the closed intrabars plus the one
|
|
9
|
+
developing intrabar, whose value tracks the live price tick-by-tick).
|
|
10
|
+
|
|
11
|
+
This container holds exactly that shape in three ordered segments:
|
|
12
|
+
|
|
13
|
+
* ``closed`` — finalized (confirmed-close) intrabar values;
|
|
14
|
+
* ``provisional`` — intrabars that have rolled over (a newer developing intrabar
|
|
15
|
+
started) but whose confirmed close has not arrived yet, so their value is the
|
|
16
|
+
last developing tick and is *replaced* in place when the late close lands;
|
|
17
|
+
* ``developing`` — the single currently-forming intrabar as the live tail.
|
|
18
|
+
|
|
19
|
+
The provisional segment exists only because a live feed can deliver the forming
|
|
20
|
+
tick of intrabar ``N+1`` ahead of the late close of intrabar ``N`` (e.g.
|
|
21
|
+
cTrader closes a slot on the next slot's open, or plain stream reordering). The
|
|
22
|
+
array must still grow by one when ``N+1`` starts — ``N`` is held provisionally
|
|
23
|
+
at its last developing value until its close replaces it at the same position.
|
|
24
|
+
|
|
25
|
+
It is pure value bookkeeping — it does NOT run the script or touch shared
|
|
26
|
+
memory; the security subprocess decides which intrabars to feed it, re-runs the
|
|
27
|
+
developing intrabar per tick, and replays the provisional chain when a late
|
|
28
|
+
close shifts the baseline. Keeping the accumulation logic here (rather than as
|
|
29
|
+
raw list surgery on the flush buffer) keeps the "closed + provisional +
|
|
30
|
+
developing" invariant in one place.
|
|
31
|
+
|
|
32
|
+
The child must still *execute* every LTF bar it needs to maintain expression
|
|
33
|
+
history (e.g. a ``ta.sma`` inside the LTF expression); only array *membership*
|
|
34
|
+
is bounded by the chart period, so the child appends to this accumulator only
|
|
35
|
+
for intrabars inside ``[period_start, period_end)``.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from typing import Any
|
|
39
|
+
|
|
40
|
+
__all__ = ["LiveLtfWindow"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LiveLtfWindow:
|
|
44
|
+
"""Accumulates one chart period's LTF intrabar values for the live path.
|
|
45
|
+
|
|
46
|
+
The array published to the script is ``closed + provisional + [developing]``.
|
|
47
|
+
A developing chart bar reports ``len(closed) + len(provisional) + 1`` elements
|
|
48
|
+
while a tail is forming and exactly ``len(closed)`` once every tail has been
|
|
49
|
+
finalized into the closed list at the chart period's end. The provisional
|
|
50
|
+
segment carries intrabars that have rolled over but whose confirmed close has
|
|
51
|
+
not yet arrived; their values are revised in place by a replay and become
|
|
52
|
+
closed (front-to-back) as their late closes land.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
self._closed: list[Any] = []
|
|
57
|
+
self._provisional: list[Any] = []
|
|
58
|
+
self._developing: Any = None
|
|
59
|
+
self._has_developing: bool = False
|
|
60
|
+
|
|
61
|
+
def reset(self) -> None:
|
|
62
|
+
"""Drop all state for the start of a new chart period."""
|
|
63
|
+
self._closed.clear()
|
|
64
|
+
self._provisional.clear()
|
|
65
|
+
self._developing = None
|
|
66
|
+
self._has_developing = False
|
|
67
|
+
|
|
68
|
+
def append_closed(self, value: Any) -> None:
|
|
69
|
+
"""Append a finalized (confirmed) intrabar value.
|
|
70
|
+
|
|
71
|
+
Used for closed intrabars that are neither the finalization of the
|
|
72
|
+
current developing tail (:meth:`finalize_developing`) nor of a pending
|
|
73
|
+
provisional (:meth:`finalize_first_provisional`).
|
|
74
|
+
"""
|
|
75
|
+
self._closed.append(value)
|
|
76
|
+
|
|
77
|
+
def set_developing(self, value: Any) -> None:
|
|
78
|
+
"""Set or replace the developing tail value.
|
|
79
|
+
|
|
80
|
+
Called on every tick of the currently-developing LTF intrabar; the
|
|
81
|
+
previous tick's value is discarded (the developing bar occupies a
|
|
82
|
+
single array slot whose value is revised in place).
|
|
83
|
+
"""
|
|
84
|
+
self._developing = value
|
|
85
|
+
self._has_developing = True
|
|
86
|
+
|
|
87
|
+
def finalize_developing(self, value: Any) -> None:
|
|
88
|
+
"""Finalize the developing tail into the closed list.
|
|
89
|
+
|
|
90
|
+
Called when the developing LTF intrabar closes in order (a confirmed bar
|
|
91
|
+
arrives for its timestamp with no provisional chain ahead of it); the
|
|
92
|
+
confirmed ``value`` may differ from the last developing tick, so it is
|
|
93
|
+
supplied explicitly rather than reusing the in-flight tail. The array
|
|
94
|
+
length is unchanged — the tail slot simply becomes a closed element.
|
|
95
|
+
"""
|
|
96
|
+
self._closed.append(value)
|
|
97
|
+
self._developing = None
|
|
98
|
+
self._has_developing = False
|
|
99
|
+
|
|
100
|
+
def promote_developing_to_provisional(self) -> None:
|
|
101
|
+
"""Roll the developing tail into the provisional segment.
|
|
102
|
+
|
|
103
|
+
Called when a newer developing intrabar starts before the current one's
|
|
104
|
+
close has arrived: the current tail keeps its (last developing) value
|
|
105
|
+
and position, becoming provisional, so the array still grows by one when
|
|
106
|
+
the new developing tail is set. A no-op if there is no developing tail.
|
|
107
|
+
"""
|
|
108
|
+
if self._has_developing:
|
|
109
|
+
self._provisional.append(self._developing)
|
|
110
|
+
self._developing = None
|
|
111
|
+
self._has_developing = False
|
|
112
|
+
|
|
113
|
+
def append_provisional(self, value: Any) -> None:
|
|
114
|
+
"""Append a new provisional entry behind the existing provisional chain.
|
|
115
|
+
|
|
116
|
+
Called when a brand-new closed intrabar arrives while an older provisional
|
|
117
|
+
is still unresolved (a provider gap left the chain head open). Its
|
|
118
|
+
confirmed value cannot join the closed segment yet — that sits ahead of
|
|
119
|
+
the provisional chain, so appending there would place the newer bar before
|
|
120
|
+
the older, still-pending one. It is held provisionally at the back instead,
|
|
121
|
+
in timestamp order, and finalized once the chain ahead of it drains.
|
|
122
|
+
"""
|
|
123
|
+
self._provisional.append(value)
|
|
124
|
+
|
|
125
|
+
def finalize_first_provisional(self, value: Any) -> None:
|
|
126
|
+
"""Promote the earliest provisional entry to a confirmed closed value.
|
|
127
|
+
|
|
128
|
+
Called when the late close of the oldest pending provisional arrives.
|
|
129
|
+
The provisional slot at the front becomes a closed element at the SAME
|
|
130
|
+
array position (closed entries sit ahead of provisional ones); the
|
|
131
|
+
confirmed ``value`` replaces the last-developing value it was holding.
|
|
132
|
+
"""
|
|
133
|
+
del self._provisional[0]
|
|
134
|
+
self._closed.append(value)
|
|
135
|
+
|
|
136
|
+
def replace_provisional(self, index: int, value: Any) -> None:
|
|
137
|
+
"""Revise a still-pending provisional value in place (replay).
|
|
138
|
+
|
|
139
|
+
After a late close shifts the baseline, the remaining provisional
|
|
140
|
+
intrabars are re-run on the corrected state; their values are written
|
|
141
|
+
back at their existing positions without changing the array length.
|
|
142
|
+
"""
|
|
143
|
+
self._provisional[index] = value
|
|
144
|
+
|
|
145
|
+
def drop_developing(self) -> None:
|
|
146
|
+
"""Discard the developing tail without finalizing it into the closed list.
|
|
147
|
+
|
|
148
|
+
Used when a chart bar confirms but the developing intrabar's close never
|
|
149
|
+
arrived (a provider gap): the unconfirmed tail must not remain in the
|
|
150
|
+
confirmed period array, and unlike :meth:`finalize_developing` there is no
|
|
151
|
+
confirmed value to keep — the array length shrinks by one.
|
|
152
|
+
"""
|
|
153
|
+
self._developing = None
|
|
154
|
+
self._has_developing = False
|
|
155
|
+
|
|
156
|
+
def drop_provisionals(self) -> None:
|
|
157
|
+
"""Discard every still-pending provisional entry.
|
|
158
|
+
|
|
159
|
+
Used at chart confirm when one or more rolled-over intrabars never
|
|
160
|
+
received their close (a provider gap or grace timeout): like an
|
|
161
|
+
unconfirmed developing tail, an unconfirmed provisional must not remain
|
|
162
|
+
in the confirmed period array. Already-finalized provisionals live in the
|
|
163
|
+
closed segment and are unaffected.
|
|
164
|
+
"""
|
|
165
|
+
self._provisional.clear()
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def has_developing(self) -> bool:
|
|
169
|
+
"""Whether a developing tail is currently present."""
|
|
170
|
+
return self._has_developing
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def has_provisional(self) -> bool:
|
|
174
|
+
"""Whether any pending (rolled-over, not-yet-closed) provisional remains."""
|
|
175
|
+
return bool(self._provisional)
|
|
176
|
+
|
|
177
|
+
def publish(self) -> list[Any]:
|
|
178
|
+
"""Return a copy of the current array (closed + provisional + developing).
|
|
179
|
+
|
|
180
|
+
A fresh list is returned each call so the consumer may hand it across
|
|
181
|
+
the result block without the accumulator's later mutations leaking in.
|
|
182
|
+
"""
|
|
183
|
+
out = [*self._closed, *self._provisional]
|
|
184
|
+
if self._has_developing:
|
|
185
|
+
out.append(self._developing)
|
|
186
|
+
return out
|
|
187
|
+
|
|
188
|
+
def __len__(self) -> int:
|
|
189
|
+
return len(self._closed) + len(self._provisional) + (1 if self._has_developing else 0)
|