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
pynecore/core/series.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TypeVar, Generic, Iterator, Callable, cast, overload
|
|
3
|
+
|
|
4
|
+
from types import ModuleType
|
|
5
|
+
|
|
6
|
+
# noinspection PyProtectedMember
|
|
7
|
+
from ..types.na import NA
|
|
8
|
+
|
|
9
|
+
__all__ = ['SeriesImpl', 'inline_series']
|
|
10
|
+
|
|
11
|
+
T = TypeVar('T')
|
|
12
|
+
|
|
13
|
+
# Interned na sentinel of untyped series (element type unknown at transform
|
|
14
|
+
# time). Hybrid guards (``isinstance(x, NA) or x != x``) treat it and the
|
|
15
|
+
# native nan as the same na.
|
|
16
|
+
_NA_UNTYPED: NA = NA(T)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SeriesImpl(Generic[T]):
|
|
20
|
+
"""
|
|
21
|
+
A dynamic circular buffer that behaves similarly to Pine Script's "Series".
|
|
22
|
+
Every docstring and comment is in English, as requested.
|
|
23
|
+
|
|
24
|
+
Note: max_bars_back means the maximum number of older bars that can be indexed.
|
|
25
|
+
Therefore, the actual buffer capacity is (max_bars_back + 1).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__slots__ = ('_buffer',
|
|
29
|
+
'_max_bars_back', '_max_bars_back_set',
|
|
30
|
+
'_capacity', '_write_pos', '_size',
|
|
31
|
+
'_last_bar_index', '_na')
|
|
32
|
+
|
|
33
|
+
DEFAULT_MAX_BARS_BACK = 500 # This can be set globally by indicator or strategy commands
|
|
34
|
+
MAXIMUM_MAX_BARS_BACK = 5000 # This is the maximum allowed value
|
|
35
|
+
|
|
36
|
+
_lib: ModuleType = None # Placeholder for the lib module
|
|
37
|
+
|
|
38
|
+
# noinspection PyMissingConstructor
|
|
39
|
+
def __init__(self, max_bars_back: int | None = None, na_value: T | None = None):
|
|
40
|
+
"""
|
|
41
|
+
:param max_bars_back: Optional initial capacity for historical lookback.
|
|
42
|
+
If not provided, DEFAULT_MAX_BARS_BACK is used.
|
|
43
|
+
The actual buffer capacity will be (max_bars_back + 1).
|
|
44
|
+
:param na_value: The na sentinel returned for out-of-range reads.
|
|
45
|
+
Float series pass the native nan here so a warmup /
|
|
46
|
+
out-of-history read behaves exactly like every other
|
|
47
|
+
float na; untyped series keep the interned NA object.
|
|
48
|
+
"""
|
|
49
|
+
# Importing the lib module here to avoid circular imports
|
|
50
|
+
if not SeriesImpl._lib:
|
|
51
|
+
from .. import lib
|
|
52
|
+
SeriesImpl._lib = lib
|
|
53
|
+
|
|
54
|
+
self._na: T | NA[T] = _NA_UNTYPED if na_value is None else na_value
|
|
55
|
+
|
|
56
|
+
self._max_bars_back = max_bars_back or self.DEFAULT_MAX_BARS_BACK
|
|
57
|
+
self._max_bars_back_set = max_bars_back or 0
|
|
58
|
+
# Internal capacity is max_bars_back + 1 (for current candle + historical bars)
|
|
59
|
+
self._capacity = self._max_bars_back + 1
|
|
60
|
+
|
|
61
|
+
# Pre-allocated buffer for better performance. The None placeholders
|
|
62
|
+
# only ever fill the unused tail (positions >= _size are never read),
|
|
63
|
+
# so the element type can omit None and reads stay cast-free.
|
|
64
|
+
self._buffer: list[T | NA[T]] = cast('list[T | NA[T]]', [None] * self._capacity)
|
|
65
|
+
|
|
66
|
+
# The next logical write position in a circular manner.
|
|
67
|
+
self._write_pos = 0
|
|
68
|
+
|
|
69
|
+
# The current number of valid elements in the buffer (<= self._capacity).
|
|
70
|
+
self._size = 0
|
|
71
|
+
|
|
72
|
+
# The last bar index that was accessed
|
|
73
|
+
self._last_bar_index = -1
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def max_bars_back(self) -> int:
|
|
77
|
+
"""
|
|
78
|
+
Returns the current max_bars_back, i.e. how many historical bars can be indexed.
|
|
79
|
+
"""
|
|
80
|
+
return self._max_bars_back
|
|
81
|
+
|
|
82
|
+
@max_bars_back.setter
|
|
83
|
+
def max_bars_back(self, new_max_bars_back: int) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Resizes the circular buffer capacity to (new_value + 1).
|
|
86
|
+
- If increased, old items are preserved (up to the old _size), and
|
|
87
|
+
the buffer is linearized.
|
|
88
|
+
- If decreased, only the most recent 'new_value + 1' items are kept.
|
|
89
|
+
|
|
90
|
+
:param new_max_bars_back: The new 'max_bars_back' value.
|
|
91
|
+
:raises ValueError: If new_value <= 0.
|
|
92
|
+
"""
|
|
93
|
+
if new_max_bars_back <= 0:
|
|
94
|
+
raise ValueError("The max_bars_back must be a positive integer!")
|
|
95
|
+
if new_max_bars_back > SeriesImpl.MAXIMUM_MAX_BARS_BACK:
|
|
96
|
+
raise ValueError(f"The max_bars_back cannot exceed {self.MAXIMUM_MAX_BARS_BACK}!")
|
|
97
|
+
|
|
98
|
+
if new_max_bars_back == self._max_bars_back:
|
|
99
|
+
return # No change
|
|
100
|
+
|
|
101
|
+
old_size = self._size
|
|
102
|
+
old_write_pos = self._write_pos
|
|
103
|
+
|
|
104
|
+
new_capacity = new_max_bars_back + 1
|
|
105
|
+
if new_capacity > old_size == old_write_pos:
|
|
106
|
+
# The buffer is not yet full, and the write position is at the end.
|
|
107
|
+
# We can just modify the max_bars_back and capacity, and extend the buffer
|
|
108
|
+
self._max_bars_back = new_max_bars_back
|
|
109
|
+
self._max_bars_back_set = new_max_bars_back
|
|
110
|
+
self._capacity = new_capacity
|
|
111
|
+
# Extend pre-allocated buffer with None placeholders (unused tail)
|
|
112
|
+
self._buffer.extend(cast('list[T | NA[T]]',
|
|
113
|
+
[None] * (new_capacity - len(self._buffer))))
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
old_buffer = self._buffer
|
|
117
|
+
old_capacity = self._capacity
|
|
118
|
+
|
|
119
|
+
# Number of items to keep: either all old items or new_capacity, whichever is smaller.
|
|
120
|
+
items_to_keep = min(old_size, new_capacity)
|
|
121
|
+
|
|
122
|
+
# Build a new pre-allocated buffer (None placeholders in the unused tail).
|
|
123
|
+
new_buffer: list[T | NA[T]] = cast('list[T | NA[T]]', [None] * new_capacity)
|
|
124
|
+
|
|
125
|
+
if items_to_keep > 0 and old_buffer:
|
|
126
|
+
# The newest item is at (old_write_pos - 1) in a circular manner.
|
|
127
|
+
# We'll copy the most recent 'items_to_keep' from the old buffer in linear order.
|
|
128
|
+
start_idx = (old_write_pos - items_to_keep) % old_capacity
|
|
129
|
+
|
|
130
|
+
for i in range(items_to_keep):
|
|
131
|
+
src_idx = (start_idx + i) % old_capacity
|
|
132
|
+
new_buffer[i] = old_buffer[src_idx]
|
|
133
|
+
|
|
134
|
+
# Update references
|
|
135
|
+
self._buffer = new_buffer
|
|
136
|
+
self._max_bars_back = new_max_bars_back
|
|
137
|
+
self._max_bars_back_set = new_max_bars_back
|
|
138
|
+
self._capacity = new_capacity
|
|
139
|
+
self._size = items_to_keep
|
|
140
|
+
self._write_pos = items_to_keep # The next free slot is after the last copied item
|
|
141
|
+
|
|
142
|
+
def add(self, value: T | NA[T]) -> T | NA[T]:
|
|
143
|
+
"""
|
|
144
|
+
Adds a new candle (data point) to the buffer.
|
|
145
|
+
- If the buffer is not yet at full capacity (size < capacity), it appends the new value.
|
|
146
|
+
- Otherwise, it overwrites the oldest data in a circular manner.
|
|
147
|
+
:param value: The new data to be added.
|
|
148
|
+
:return: The same value that was added (for chaining or inline usage).
|
|
149
|
+
"""
|
|
150
|
+
# Optimize attribute lookup by using local variable
|
|
151
|
+
lib = SeriesImpl._lib
|
|
152
|
+
|
|
153
|
+
# Set data instead of adding a new one if the bar index is the same
|
|
154
|
+
if self._last_bar_index == lib.bar_index:
|
|
155
|
+
return self.set(value)
|
|
156
|
+
|
|
157
|
+
if self._size < self._capacity:
|
|
158
|
+
# The buffer is not yet full - use direct indexing to pre-allocated buffer
|
|
159
|
+
self._buffer[self._write_pos] = value
|
|
160
|
+
self._size += 1
|
|
161
|
+
self._write_pos += 1
|
|
162
|
+
else:
|
|
163
|
+
# The buffer is full: overwrite in circular fashion
|
|
164
|
+
pos = self._write_pos
|
|
165
|
+
if pos >= self._capacity:
|
|
166
|
+
pos = 0
|
|
167
|
+
self._write_pos = 1
|
|
168
|
+
else:
|
|
169
|
+
self._write_pos += 1
|
|
170
|
+
self._buffer[pos] = value
|
|
171
|
+
|
|
172
|
+
# Store the last bar index to prevent adding more than one value per bar
|
|
173
|
+
self._last_bar_index = lib.bar_index
|
|
174
|
+
|
|
175
|
+
return value
|
|
176
|
+
|
|
177
|
+
def set(self, value: T | NA[T]) -> T | NA[T]:
|
|
178
|
+
"""
|
|
179
|
+
Overwrites the most recently added (current) candle value.
|
|
180
|
+
If there is no data yet, returns na.
|
|
181
|
+
:param value: The new value for the current candle.
|
|
182
|
+
:return: The value that was set, or na if buffer is empty.
|
|
183
|
+
"""
|
|
184
|
+
if self._size == 0:
|
|
185
|
+
return self._na
|
|
186
|
+
|
|
187
|
+
pos = self._write_pos - 1
|
|
188
|
+
if pos < 0:
|
|
189
|
+
pos += self._capacity
|
|
190
|
+
self._buffer[pos] = value
|
|
191
|
+
return value
|
|
192
|
+
|
|
193
|
+
def _snapshot(self) -> tuple[list[T | NA[T]], int, int, int, int, int, int]:
|
|
194
|
+
"""Capture the full buffer state for a rollback baseline.
|
|
195
|
+
|
|
196
|
+
Used by the live ``request.security_lower_tf`` LTF baseline to roll a
|
|
197
|
+
series back when a reordered feed forces a replay that re-runs an
|
|
198
|
+
*earlier* ``bar_index`` after a later one already ran (see
|
|
199
|
+
:class:`~pynecore.core.live_ltf_collector.LiveLtfCollector`). A cursor-only
|
|
200
|
+
snapshot is not enough: at capacity a stray :meth:`add` overwrites the
|
|
201
|
+
oldest element circularly, which the cursor alone cannot reconstruct — so
|
|
202
|
+
the buffer contents are copied too. Cheap at live cadence (one copy per
|
|
203
|
+
confirmed intrabar), never used on the warmup forward-only path.
|
|
204
|
+
|
|
205
|
+
:return: An opaque tuple consumed only by :meth:`_restore`.
|
|
206
|
+
"""
|
|
207
|
+
return (self._buffer.copy(), self._size, self._write_pos, self._last_bar_index,
|
|
208
|
+
self._max_bars_back, self._max_bars_back_set, self._capacity)
|
|
209
|
+
|
|
210
|
+
def _restore(self, snapshot: tuple[list[T | NA[T]], int, int, int, int, int, int]) -> None:
|
|
211
|
+
"""Restore the buffer state captured by :meth:`_snapshot` (in place).
|
|
212
|
+
|
|
213
|
+
The same snapshot may be restored repeatedly (one save, many replay
|
|
214
|
+
re-runs), so the saved buffer is copied back rather than aliased.
|
|
215
|
+
|
|
216
|
+
:param snapshot: A value returned by :meth:`_snapshot`.
|
|
217
|
+
"""
|
|
218
|
+
(buffer, self._size, self._write_pos, self._last_bar_index,
|
|
219
|
+
self._max_bars_back, self._max_bars_back_set, self._capacity) = snapshot
|
|
220
|
+
self._buffer = buffer.copy()
|
|
221
|
+
|
|
222
|
+
@overload
|
|
223
|
+
def __getitem__(self, key: int) -> T | NA[T]:
|
|
224
|
+
...
|
|
225
|
+
|
|
226
|
+
@overload
|
|
227
|
+
def __getitem__(self, key: slice) -> ReadOnlySeriesView[T]:
|
|
228
|
+
...
|
|
229
|
+
|
|
230
|
+
def __getitem__(self, key: int | slice) -> T | NA[T] | ReadOnlySeriesView[T]:
|
|
231
|
+
"""
|
|
232
|
+
Get item(s) using Pine indexing with slice support.
|
|
233
|
+
|
|
234
|
+
Pine semantics: subscripting with `na` returns the current bar's value
|
|
235
|
+
(offset 0) — TradingView's history-referencing operator treats an `na`
|
|
236
|
+
offset as 0. An out-of-range integer (negative = forward of the current
|
|
237
|
+
bar, positive >= size = before available history) returns `na` rather
|
|
238
|
+
than raising.
|
|
239
|
+
|
|
240
|
+
:param key: Integer index, NA, or slice
|
|
241
|
+
:return: Single value for integer index, ReadOnlySeriesView for slice
|
|
242
|
+
:raises TypeError: If key is not int, NA, or slice
|
|
243
|
+
"""
|
|
244
|
+
# Pine: series[na] -> series[0] (an na offset is treated as the current
|
|
245
|
+
# bar). Coerce to 0 here, before the int(key) path, since int(NA) raises;
|
|
246
|
+
# the bounds check below then returns na only for a genuinely empty series.
|
|
247
|
+
if isinstance(key, NA):
|
|
248
|
+
key = 0
|
|
249
|
+
|
|
250
|
+
if isinstance(key, float):
|
|
251
|
+
# A native nan offset is an na offset -> current bar (int(nan) raises)
|
|
252
|
+
key = 0 if key != key else int(key)
|
|
253
|
+
|
|
254
|
+
if isinstance(key, int):
|
|
255
|
+
# Pine: out-of-range subscript -> na (covers both negative and
|
|
256
|
+
# positive past-end indices).
|
|
257
|
+
if key < 0 or key >= self._size:
|
|
258
|
+
return self._na
|
|
259
|
+
pos = self._write_pos - 1 - key
|
|
260
|
+
if pos < 0:
|
|
261
|
+
pos += self._capacity
|
|
262
|
+
return self._buffer[pos]
|
|
263
|
+
|
|
264
|
+
elif isinstance(key, slice):
|
|
265
|
+
# Handle slice notation
|
|
266
|
+
start = 0 if key.start is None else key.start
|
|
267
|
+
stop = self._size if key.stop is None else key.stop
|
|
268
|
+
|
|
269
|
+
if start < 0 or stop < 0:
|
|
270
|
+
raise IndexError("Negative indices not supported in slice!")
|
|
271
|
+
|
|
272
|
+
if key.step is not None and key.step != 1:
|
|
273
|
+
raise ValueError("Step value not supported in slice!")
|
|
274
|
+
|
|
275
|
+
if stop > self._size:
|
|
276
|
+
raise IndexError("Slice stop index out of range!")
|
|
277
|
+
|
|
278
|
+
# Ensure start <= stop
|
|
279
|
+
if start > stop:
|
|
280
|
+
start = stop
|
|
281
|
+
|
|
282
|
+
return ReadOnlySeriesView[T](
|
|
283
|
+
self._buffer,
|
|
284
|
+
self._capacity,
|
|
285
|
+
self._write_pos,
|
|
286
|
+
self._size,
|
|
287
|
+
start,
|
|
288
|
+
stop
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
raise TypeError("Series indices must be integers or slices") # noqa
|
|
292
|
+
|
|
293
|
+
def __len__(self) -> int:
|
|
294
|
+
"""
|
|
295
|
+
Returns the number of valid data points in the buffer (<= capacity).
|
|
296
|
+
"""
|
|
297
|
+
return self._size
|
|
298
|
+
|
|
299
|
+
def __iter__(self) -> Iterator[T | NA[T]]:
|
|
300
|
+
"""
|
|
301
|
+
Iterate valid items from newest to oldest (Pine index order).
|
|
302
|
+
|
|
303
|
+
Without an explicit __iter__, ``iter(series)`` would fall back to the
|
|
304
|
+
sequence protocol, and since ``__getitem__`` returns na for every
|
|
305
|
+
out-of-range index (never IndexError), that iteration never terminates.
|
|
306
|
+
"""
|
|
307
|
+
for i in range(self._size):
|
|
308
|
+
pos = self._write_pos - 1 - i
|
|
309
|
+
if pos < 0:
|
|
310
|
+
pos += self._capacity
|
|
311
|
+
yield self._buffer[pos]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class ReadOnlySeriesView(Generic[T]):
|
|
315
|
+
"""
|
|
316
|
+
A read-only view for a circular buffer slice that follows Pine-like indexing.
|
|
317
|
+
Supports positive slice notation [start:stop] where start and stop refer to
|
|
318
|
+
bar indices from the most recent bar.
|
|
319
|
+
"""
|
|
320
|
+
__slots__ = ('_buffer', '_capacity', '_write_pos', '_size', '_start', '_stop')
|
|
321
|
+
|
|
322
|
+
def __init__(self, buffer: list[T | NA[T]], capacity: int, write_pos: int, size: int,
|
|
323
|
+
start: int, stop: int) -> None:
|
|
324
|
+
"""
|
|
325
|
+
Initialize the view.
|
|
326
|
+
|
|
327
|
+
:param buffer: The source buffer
|
|
328
|
+
:param capacity: The capacity of the buffer
|
|
329
|
+
:param write_pos: The current write position
|
|
330
|
+
:param size: The number of valid items
|
|
331
|
+
:param start: Start index (inclusive)
|
|
332
|
+
:param stop: Stop index (exclusive)
|
|
333
|
+
"""
|
|
334
|
+
self._buffer = buffer
|
|
335
|
+
self._capacity = capacity
|
|
336
|
+
self._write_pos = write_pos
|
|
337
|
+
self._size = size
|
|
338
|
+
self._start = start
|
|
339
|
+
self._stop = min(stop, size)
|
|
340
|
+
|
|
341
|
+
def __getitem__(self, idx: int) -> T | NA[T]:
|
|
342
|
+
"""Get item using Pine indexing"""
|
|
343
|
+
if not isinstance(idx, int):
|
|
344
|
+
raise TypeError("Only integer indexing is supported") # noqa
|
|
345
|
+
if idx < 0:
|
|
346
|
+
raise IndexError("Negative indices not supported")
|
|
347
|
+
if idx >= len(self):
|
|
348
|
+
raise IndexError("Index out of range")
|
|
349
|
+
|
|
350
|
+
actual_idx = idx + self._start
|
|
351
|
+
pos = self._write_pos - 1 - actual_idx
|
|
352
|
+
if pos < 0:
|
|
353
|
+
pos += self._capacity
|
|
354
|
+
return self._buffer[pos]
|
|
355
|
+
|
|
356
|
+
def __len__(self) -> int:
|
|
357
|
+
"""Get length of the view"""
|
|
358
|
+
return self._stop - self._start
|
|
359
|
+
|
|
360
|
+
def __iter__(self) -> Iterator[T | NA[T]]:
|
|
361
|
+
"""Iterate through items from newest to oldest"""
|
|
362
|
+
for i in range(self._start, self._stop):
|
|
363
|
+
pos = self._write_pos - 1 - i
|
|
364
|
+
if pos < 0:
|
|
365
|
+
pos += self._capacity
|
|
366
|
+
yield self._buffer[pos]
|
|
367
|
+
|
|
368
|
+
def __repr__(self) -> str:
|
|
369
|
+
"""Get string representation"""
|
|
370
|
+
return f"ReadOnlySeriesView({list(self)})"
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _inline_series_instance() -> Callable[..., SeriesImpl]:
|
|
374
|
+
"""One ``inline_series`` instance: a closure over its own series buffer.
|
|
375
|
+
|
|
376
|
+
The slot transform routes ``inline_series`` call sites on the uniform
|
|
377
|
+
path, so ``__bind_any__`` anchors one of these per call site (and one per
|
|
378
|
+
iteration at loop sites) through the ``__pyne_bind__`` factory — that is
|
|
379
|
+
what keeps two ``expr[n]`` rewrites in one scope independent (issue #61).
|
|
380
|
+
|
|
381
|
+
:return: The bound instance with the public ``inline_series`` signature.
|
|
382
|
+
"""
|
|
383
|
+
series: SeriesImpl = SeriesImpl()
|
|
384
|
+
|
|
385
|
+
def bound(value: T, idx: int) -> SeriesImpl[T]:
|
|
386
|
+
if isinstance(idx, float):
|
|
387
|
+
idx = int(idx)
|
|
388
|
+
series.add(value)
|
|
389
|
+
return cast(SeriesImpl[T], series[idx])
|
|
390
|
+
|
|
391
|
+
return bound
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
_inline_series_shared: SeriesImpl = SeriesImpl()
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def inline_series(value: T, idx: int) -> SeriesImpl[T]:
|
|
398
|
+
"""
|
|
399
|
+
Inline series creation
|
|
400
|
+
It is mainly for compiled codes
|
|
401
|
+
|
|
402
|
+
Calling it directly (no anchor — non-transformed code) uses one shared
|
|
403
|
+
module-lifetime buffer, the same semantics the legacy module-global
|
|
404
|
+
series gave such calls; anchored call sites get independent buffers from
|
|
405
|
+
``__pyne_bind__``.
|
|
406
|
+
|
|
407
|
+
:param value: The value to store
|
|
408
|
+
:param idx: The index, 0 is the last, 1 is the second last and so on
|
|
409
|
+
:return:
|
|
410
|
+
"""
|
|
411
|
+
if isinstance(idx, float):
|
|
412
|
+
idx = int(idx)
|
|
413
|
+
_inline_series_shared.add(value)
|
|
414
|
+
return cast(SeriesImpl[T], _inline_series_shared[idx])
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
setattr(inline_series, '__pyne_bind__', _inline_series_instance)
|