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,244 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pine method dispatch on the slot-based instance-state scheme.
|
|
3
|
+
|
|
4
|
+
``method_call`` is on the transformer's NON_TRANSFORMABLE list, so its call
|
|
5
|
+
sites stay raw and there is no caller anchor — the method is selected at
|
|
6
|
+
runtime and bound through a module-lifetime per-method cache instead: ONE
|
|
7
|
+
shared instance per method function, cleared between runs. This formalizes
|
|
8
|
+
what the legacy runtime did implicitly (its ``__scope_id__`` here was forever
|
|
9
|
+
``''``, so every isolation it requested landed on the same empty-scope cache
|
|
10
|
+
key, shared by all call sites of a method and dropped on ``reset()``).
|
|
11
|
+
"""
|
|
12
|
+
import sys
|
|
13
|
+
from types import ModuleType
|
|
14
|
+
from typing import Any, Callable
|
|
15
|
+
from functools import partial
|
|
16
|
+
|
|
17
|
+
from ..lib import array, matrix, box, line, label, table, linefill, polyline
|
|
18
|
+
from ..lib import map as map_lib
|
|
19
|
+
from ..utils.sequence_view import SequenceView
|
|
20
|
+
from ..types import matrix as matrix_types
|
|
21
|
+
from ..types import line as line_types
|
|
22
|
+
from ..types import box as box_types
|
|
23
|
+
from ..types import label as label_types
|
|
24
|
+
from ..types import table as table_types
|
|
25
|
+
from ..types import linefill as linefill_types
|
|
26
|
+
from ..types import polyline as polyline_types
|
|
27
|
+
from ..types.na import NA
|
|
28
|
+
|
|
29
|
+
from .instance_state import _make_state, register_shared_cache
|
|
30
|
+
from .pine_export import Exported
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def method(func: Callable) -> Callable:
|
|
34
|
+
"""
|
|
35
|
+
Decorator to mark a function as a Pine method.
|
|
36
|
+
This is used to indicate that the function should be treated as a method in Pine Script.
|
|
37
|
+
"""
|
|
38
|
+
setattr(func, '__pine_method__', True)
|
|
39
|
+
return func
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_builtin_method(method_name: str, var: Any) -> Callable | None:
|
|
43
|
+
"""
|
|
44
|
+
Get the built-in method for a Pine Script object.
|
|
45
|
+
:param method_name: The name of the method
|
|
46
|
+
:param var: The object on which the method is being called
|
|
47
|
+
:return: The built-in method, or None if not found
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
var_type = type(var)
|
|
51
|
+
if var_type is NA:
|
|
52
|
+
# An na value carries its DECLARED type. A builtin method invoked on
|
|
53
|
+
# an na builtin object (e.g. ``naLine.delete()`` on a line that was
|
|
54
|
+
# never drawn) must dispatch to that builtin namespace, which treats
|
|
55
|
+
# na as a no-op the way Pine does. An na of a UDT (or untyped) type
|
|
56
|
+
# matches nothing here and falls through to user-method dispatch.
|
|
57
|
+
declared = var.type
|
|
58
|
+
if declared is None:
|
|
59
|
+
return None
|
|
60
|
+
var_type = declared if isinstance(declared, type) else type(declared)
|
|
61
|
+
match var_type:
|
|
62
|
+
case _ if var_type is list:
|
|
63
|
+
return getattr(array, method_name)
|
|
64
|
+
case _ if var_type is SequenceView:
|
|
65
|
+
# array.slice() returns a view; method calls on it (e.g.
|
|
66
|
+
# slice(...).max()) dispatch to the array namespace too.
|
|
67
|
+
return getattr(array, method_name)
|
|
68
|
+
case _ if var_type is dict:
|
|
69
|
+
return getattr(map_lib, method_name)
|
|
70
|
+
case _ if var_type is matrix_types.Matrix:
|
|
71
|
+
return getattr(matrix, method_name)
|
|
72
|
+
case _ if var_type is line_types.Line:
|
|
73
|
+
return getattr(line, method_name)
|
|
74
|
+
case _ if var_type is box_types.Box:
|
|
75
|
+
return getattr(box, method_name)
|
|
76
|
+
case _ if var_type is label_types.Label:
|
|
77
|
+
return getattr(label, method_name)
|
|
78
|
+
case _ if var_type is table_types.Table:
|
|
79
|
+
return getattr(table, method_name)
|
|
80
|
+
case _ if var_type is linefill_types.LineFill:
|
|
81
|
+
return getattr(linefill, method_name)
|
|
82
|
+
case _ if var_type is polyline_types.Polyline:
|
|
83
|
+
return getattr(polyline, method_name)
|
|
84
|
+
case _:
|
|
85
|
+
return None
|
|
86
|
+
except AttributeError:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Method key -> (function identity, state vector or None, bound callable).
|
|
93
|
+
# A per-function key is essential: a constant key would make every method
|
|
94
|
+
# share one cache entry, running one method with another method's state.
|
|
95
|
+
# Registered so instance_state.reset() clears it between runs.
|
|
96
|
+
_method_anchors: dict[str, tuple[Any, list | None, Callable]] = register_shared_cache({})
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# noinspection PyShadowingNames
|
|
100
|
+
def _bound_method(method: Any) -> Callable:
|
|
101
|
+
"""Module-lifetime bound instance of a runtime-dispatched method.
|
|
102
|
+
|
|
103
|
+
The cache key is the method's qualified name and the entry survives
|
|
104
|
+
function-object re-creation: a method defined inside ``main()`` is a
|
|
105
|
+
fresh object every invocation, but its persistent state must live on —
|
|
106
|
+
on an identity miss the entry's state vector is rebound to the new
|
|
107
|
+
object (whose closure cells are the live ones). State from the cache,
|
|
108
|
+
closure from the passed object: the exact legacy split.
|
|
109
|
+
|
|
110
|
+
:param method: The method callable, or an ``Exported`` proxy of one.
|
|
111
|
+
:return: The callable to invoke with the visible arguments.
|
|
112
|
+
:raises ValueError: If an ``Exported`` proxy is not initialized yet.
|
|
113
|
+
"""
|
|
114
|
+
target = method
|
|
115
|
+
if isinstance(target, Exported):
|
|
116
|
+
target = target.__fn__
|
|
117
|
+
if target is None:
|
|
118
|
+
raise ValueError("Exported proxy has not been initialized with a function yet")
|
|
119
|
+
key = f"{getattr(target, '__module__', '?')}.{getattr(target, '__qualname__', '?')}"
|
|
120
|
+
entry = _method_anchors.get(key)
|
|
121
|
+
if entry is not None and entry[0] is target:
|
|
122
|
+
return entry[2]
|
|
123
|
+
if isinstance(target, type) or (
|
|
124
|
+
hasattr(target, '__self__') and isinstance(target.__self__, type)):
|
|
125
|
+
return target # types and classmethods are called as-is (legacy guard)
|
|
126
|
+
state: list | None = None
|
|
127
|
+
bind = getattr(target, '__pyne_bind__', None)
|
|
128
|
+
if bind is not None:
|
|
129
|
+
bound: Callable = bind() # overload dispatcher: one shared anchored entry
|
|
130
|
+
else:
|
|
131
|
+
layout = getattr(target, '__pyne_layout__', None)
|
|
132
|
+
if layout is not None:
|
|
133
|
+
state = entry[1] if entry is not None and entry[1] is not None \
|
|
134
|
+
else _make_state(layout)
|
|
135
|
+
bound = partial(target, state)
|
|
136
|
+
else:
|
|
137
|
+
bound = target # stateless — called raw
|
|
138
|
+
_method_anchors[key] = (target, state, bound)
|
|
139
|
+
return bound
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _adapt_exported_kwargs(exported: Exported, kwargs: dict) -> dict:
|
|
143
|
+
"""
|
|
144
|
+
Map keyword arguments onto a compiled library's canonically renamed
|
|
145
|
+
parameter names when needed.
|
|
146
|
+
|
|
147
|
+
A compiler cannot always know at emission time that a string-dispatched
|
|
148
|
+
method call targets a library export (the receiver may be untyped), so a
|
|
149
|
+
keyword argument can arrive under its original Pine spelling while the
|
|
150
|
+
library ``def`` declares ``name + '__ren__'``. The rename is applied only
|
|
151
|
+
when the raw name is absent from the target signature and the suffixed
|
|
152
|
+
one is present — a correct call is never altered.
|
|
153
|
+
|
|
154
|
+
:param exported: The resolved Exported proxy
|
|
155
|
+
:param kwargs: Keyword arguments as emitted at the call site
|
|
156
|
+
:return: Keyword arguments matching the target's parameter names
|
|
157
|
+
"""
|
|
158
|
+
if not kwargs:
|
|
159
|
+
return kwargs
|
|
160
|
+
code: Any = getattr(exported.__fn__, '__code__', None)
|
|
161
|
+
if code is None:
|
|
162
|
+
return kwargs
|
|
163
|
+
params = code.co_varnames[:code.co_argcount + code.co_kwonlyargcount]
|
|
164
|
+
if all(k in params for k in kwargs):
|
|
165
|
+
return kwargs
|
|
166
|
+
return {(k + '__ren__' if k not in params and k + '__ren__' in params else k): v
|
|
167
|
+
for k, v in kwargs.items()}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# noinspection PyShadowingNames
|
|
171
|
+
def method_call(method: str | Callable, var: Any, *args, **kwargs) -> Any:
|
|
172
|
+
"""
|
|
173
|
+
Dispatch a method call on a Pine Script variable to the appropriate handler.
|
|
174
|
+
|
|
175
|
+
This function serves as the central dispatcher for Pine Script method calls, handling both
|
|
176
|
+
built-in type methods (like array and matrix operations) and user-defined local methods.
|
|
177
|
+
It provides the Pine Script-like method calling syntax by routing calls to the correct
|
|
178
|
+
implementation based on the variable type and method name.
|
|
179
|
+
|
|
180
|
+
Closure-converted methods need no special handling: the closure transform
|
|
181
|
+
prepends the closure parameters to the method's signature and inserts the
|
|
182
|
+
matching arguments BEFORE the receiver at the call site, so the plain
|
|
183
|
+
positional order already lines up.
|
|
184
|
+
|
|
185
|
+
:param method: The method to call, either as a string name (for built-in methods) or a callable (for local methods)
|
|
186
|
+
:param var: The object/variable on which the method is being called (e.g., array, matrix, or custom object)
|
|
187
|
+
:param args: Positional arguments to pass to the method
|
|
188
|
+
:param kwargs: Keyword arguments to pass to the method
|
|
189
|
+
:return: The result of the method call, or None if the method cannot be dispatched
|
|
190
|
+
:raises AssertionError: If a string method name is provided but no matching method is found for the variable type
|
|
191
|
+
"""
|
|
192
|
+
# If method is a string
|
|
193
|
+
if isinstance(method, str):
|
|
194
|
+
# Support for builtin methods
|
|
195
|
+
_method = _get_builtin_method(method, var)
|
|
196
|
+
if _method is not None:
|
|
197
|
+
return _method(var, *args, **kwargs)
|
|
198
|
+
|
|
199
|
+
# Modules
|
|
200
|
+
try:
|
|
201
|
+
return getattr(var, method)(*args, **kwargs)
|
|
202
|
+
except AttributeError:
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
# Methods exported by a compiled library are module-level Exported proxies.
|
|
206
|
+
# Try the module that defines the receiver's UDT class first, then the
|
|
207
|
+
# caller's own module, then every library module the caller imports -- a
|
|
208
|
+
# library can export methods on another library's UDT, so the defining
|
|
209
|
+
# module is not always the UDT's own.
|
|
210
|
+
# If the raw name misses everywhere, retry once with PyneComp's canonical
|
|
211
|
+
# rename suffix: a compiled library emits a method whose Pine name cannot
|
|
212
|
+
# live verbatim in Python (``lambda``, ``method_call``, ...) under
|
|
213
|
+
# ``name + '__ren__'``. A stub with that shape can only be the image of
|
|
214
|
+
# this Pine name (a Pine ``x__ren__`` compiles to ``x__ren____ren__``),
|
|
215
|
+
# so the retry can never hit a wrong export.
|
|
216
|
+
caller_globals = sys._getframe(1).f_globals
|
|
217
|
+
mod = sys.modules.get(type(var).__module__)
|
|
218
|
+
for lookup_name in (method, method + '__ren__'):
|
|
219
|
+
_method = getattr(mod, lookup_name, None) if mod is not None else None
|
|
220
|
+
if not isinstance(_method, Exported):
|
|
221
|
+
_method = caller_globals.get(lookup_name)
|
|
222
|
+
if not isinstance(_method, Exported):
|
|
223
|
+
for _gval in caller_globals.values():
|
|
224
|
+
if isinstance(_gval, ModuleType) and _gval.__name__.startswith('lib.'):
|
|
225
|
+
_method = getattr(_gval, lookup_name, None)
|
|
226
|
+
if isinstance(_method, Exported):
|
|
227
|
+
break
|
|
228
|
+
if isinstance(_method, Exported):
|
|
229
|
+
return _bound_method(_method)(var, *args,
|
|
230
|
+
**_adapt_exported_kwargs(_method, kwargs))
|
|
231
|
+
|
|
232
|
+
assert False, f'No such method: {var}->{method}'
|
|
233
|
+
|
|
234
|
+
# It is a local method, it should be a local function
|
|
235
|
+
elif callable(method):
|
|
236
|
+
# It may not detected well the type and there may be a user with the same method name.
|
|
237
|
+
# So we 1st trt if it is a built-in object and has that method, because it has priority
|
|
238
|
+
_method = _get_builtin_method(method.__name__, var)
|
|
239
|
+
if _method:
|
|
240
|
+
return _method(var, *args, **kwargs)
|
|
241
|
+
|
|
242
|
+
return _bound_method(method)(var, *args, **kwargs)
|
|
243
|
+
|
|
244
|
+
return None
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Generator, overload
|
|
2
|
+
|
|
3
|
+
from pynecore.types.pine_types import PyneFloat, PyneInt
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@overload
|
|
7
|
+
def pine_range(from_num: int, to_num: int, step_num: int | None = None) -> range: ...
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@overload
|
|
11
|
+
def pine_range(from_num: PyneInt | PyneFloat, to_num: PyneInt | PyneFloat,
|
|
12
|
+
step_num: PyneInt | PyneFloat | None = None) -> Generator[float, None, None]: ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def pine_range(from_num: PyneInt | PyneFloat, to_num: PyneInt | PyneFloat, step_num: PyneInt | PyneFloat | None = None):
|
|
16
|
+
"""
|
|
17
|
+
Emulates Pine Script's for loop range behavior.
|
|
18
|
+
|
|
19
|
+
:param from_num: Start value (inclusive)
|
|
20
|
+
:param to_num: End value (inclusive)
|
|
21
|
+
:param step_num: Step value (optional, defaults to +1/-1 based on direction)
|
|
22
|
+
:return: A native ``range`` for integer bounds, otherwise a generator that yields
|
|
23
|
+
values from from_num to to_num (inclusive)
|
|
24
|
+
:raises ValueError: If step_num is zero
|
|
25
|
+
"""
|
|
26
|
+
# Fast path: pure-integer bounds map exactly onto a native range, which iterates at
|
|
27
|
+
# C speed instead of resuming a Python generator on every step. The vast majority of
|
|
28
|
+
# Pine for loops are integer index ranges, so this is the common case.
|
|
29
|
+
if isinstance(from_num, int) and isinstance(to_num, int) and (step_num is None or isinstance(step_num, int)):
|
|
30
|
+
if from_num <= to_num:
|
|
31
|
+
step = 1 if step_num is None else abs(step_num)
|
|
32
|
+
if step == 0:
|
|
33
|
+
raise ValueError("Step cannot be zero in pine_range")
|
|
34
|
+
# +1 makes the upper bound inclusive, matching Pine's `to`
|
|
35
|
+
return range(from_num, to_num + 1, step)
|
|
36
|
+
step = -1 if step_num is None else -abs(step_num)
|
|
37
|
+
if step == 0:
|
|
38
|
+
raise ValueError("Step cannot be zero in pine_range")
|
|
39
|
+
# -1 makes the lower bound inclusive for the descending direction
|
|
40
|
+
return range(from_num, to_num - 1, step)
|
|
41
|
+
|
|
42
|
+
return _pine_range_float(from_num, to_num, step_num)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _pine_range_float(from_num: PyneFloat, to_num: PyneFloat, step_num: PyneFloat | None = None):
|
|
46
|
+
"""
|
|
47
|
+
Generator fallback for Pine for-loop ranges with non-integer bounds.
|
|
48
|
+
|
|
49
|
+
:param from_num: Start value (inclusive)
|
|
50
|
+
:param to_num: End value (inclusive)
|
|
51
|
+
:param step_num: Step value (optional, defaults to +1/-1 based on direction)
|
|
52
|
+
:return: A generator that yields values from from_num to to_num (inclusive)
|
|
53
|
+
:raises ValueError: If step_num is zero
|
|
54
|
+
"""
|
|
55
|
+
# Determine direction based on from_num and to_num
|
|
56
|
+
direction = 1 if from_num <= to_num else -1
|
|
57
|
+
|
|
58
|
+
# Use default step if none provided
|
|
59
|
+
step_val = step_num if step_num is not None else direction
|
|
60
|
+
|
|
61
|
+
# Prevent infinite loops
|
|
62
|
+
if step_val == 0:
|
|
63
|
+
raise ValueError("Step cannot be zero in pine_range")
|
|
64
|
+
|
|
65
|
+
# Ensure step direction matches the from->to direction
|
|
66
|
+
if (direction > 0 > step_val) or (direction < 0 < step_val):
|
|
67
|
+
step_val = -step_val
|
|
68
|
+
|
|
69
|
+
# Generate values
|
|
70
|
+
current = from_num
|
|
71
|
+
if direction > 0:
|
|
72
|
+
# Ascending loop
|
|
73
|
+
while current <= to_num:
|
|
74
|
+
yield current
|
|
75
|
+
current += step_val
|
|
76
|
+
# Safety check to prevent infinite loops due to floating point precision
|
|
77
|
+
if step_val > 0 and current > to_num + abs(step_val):
|
|
78
|
+
break
|
|
79
|
+
else:
|
|
80
|
+
# Descending loop
|
|
81
|
+
while current >= to_num:
|
|
82
|
+
yield current
|
|
83
|
+
current += step_val
|
|
84
|
+
# Safety check to prevent infinite loops due to floating point precision
|
|
85
|
+
if step_val < 0 and current < to_num - abs(step_val):
|
|
86
|
+
break
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import copy as _copy
|
|
2
|
+
from dataclasses import dataclass, replace as _dataclass_replace, is_dataclass
|
|
3
|
+
from typing import TypeVar, Type, Any, dataclass_transform
|
|
4
|
+
|
|
5
|
+
__all__ = ['udt', 'udt_copy']
|
|
6
|
+
|
|
7
|
+
T = TypeVar('T')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def udt_copy(obj: T, **changes: Any) -> T:
|
|
11
|
+
"""
|
|
12
|
+
Copy a UDT or object value (used by the transpiler for Pine `.copy()`).
|
|
13
|
+
|
|
14
|
+
A dataclass UDT is copied with ``dataclasses.replace`` (honouring optional
|
|
15
|
+
field overrides). Non-dataclass objects — the drawing types (Label, Box,
|
|
16
|
+
Line, …) are plain classes, not dataclasses — are deep-copied instead, so
|
|
17
|
+
`.copy()` on e.g. a `map<string, label>` value works rather than raising
|
|
18
|
+
"replace() should be called on dataclass instances".
|
|
19
|
+
"""
|
|
20
|
+
if is_dataclass(obj):
|
|
21
|
+
return _dataclass_replace(obj, **changes)
|
|
22
|
+
return _copy.deepcopy(obj)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass_transform()
|
|
26
|
+
def udt(cls: Type[T]) -> Type[T]:
|
|
27
|
+
"""
|
|
28
|
+
Custom dataclass decorator that adds a `copy` method to the class.
|
|
29
|
+
|
|
30
|
+
This decorator applies the standard dataclass decorator and then adds
|
|
31
|
+
a `copy` method that creates a copy of the instance using dataclass.replace().
|
|
32
|
+
|
|
33
|
+
:param cls: The class to decorate
|
|
34
|
+
:return: The decorated class with added copy method
|
|
35
|
+
"""
|
|
36
|
+
# Apply the standard dataclass decorator with slots=True for better performance
|
|
37
|
+
decorated_cls = dataclass(cls, slots=True) # type: ignore
|
|
38
|
+
|
|
39
|
+
def copy(self: T, **changes: Any) -> T:
|
|
40
|
+
"""
|
|
41
|
+
Create a copy of this instance with optional field modifications.
|
|
42
|
+
|
|
43
|
+
:param self: The instance to copy
|
|
44
|
+
:param changes: Optional keyword arguments to override field values
|
|
45
|
+
:return: A new instance with the specified changes
|
|
46
|
+
"""
|
|
47
|
+
return udt_copy(self, **changes)
|
|
48
|
+
|
|
49
|
+
# noinspection PyShadowingNames
|
|
50
|
+
@classmethod # noqa
|
|
51
|
+
def new(cls, *args, **kwargs) -> T:
|
|
52
|
+
"""
|
|
53
|
+
Pine Script-style constructor method.
|
|
54
|
+
|
|
55
|
+
Creates a new instance of the class using the same arguments as __init__.
|
|
56
|
+
This provides Pine Script compatibility where UDTs are created with .new().
|
|
57
|
+
|
|
58
|
+
:param cls: The class to construct
|
|
59
|
+
:param args: Positional arguments for the constructor
|
|
60
|
+
:param kwargs: Keyword arguments for the constructor
|
|
61
|
+
:return: A new instance of the class
|
|
62
|
+
"""
|
|
63
|
+
return cls(*args, **kwargs)
|
|
64
|
+
|
|
65
|
+
# Add the methods to the class
|
|
66
|
+
decorated_cls.copy = copy # type: ignore
|
|
67
|
+
decorated_cls.new = new
|
|
68
|
+
|
|
69
|
+
return decorated_cls
|