oakscriptpy 0.1.0__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.
oakscriptpy/array.py ADDED
@@ -0,0 +1,342 @@
1
+ """Array namespace — mirrors PineScript array.* functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from typing import Any, Callable, TypeVar
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ def new_array(size: int = 0, initial_value: Any = None) -> list:
12
+ return [initial_value] * size
13
+
14
+
15
+ def size(id: list) -> int:
16
+ return len(id)
17
+
18
+
19
+ def get(id: list, index: int) -> Any:
20
+ return id[index]
21
+
22
+
23
+ def set(id: list, index: int, value: Any) -> None:
24
+ id[index] = value
25
+
26
+
27
+ def push(id: list, value: Any) -> None:
28
+ id.append(value)
29
+
30
+
31
+ def pop(id: list) -> Any:
32
+ return id.pop()
33
+
34
+
35
+ def unshift(id: list, value: Any) -> None:
36
+ id.insert(0, value)
37
+
38
+
39
+ def shift(id: list) -> Any:
40
+ return id.pop(0)
41
+
42
+
43
+ def clear(id: list) -> None:
44
+ id.clear()
45
+
46
+
47
+ def insert(id: list, index: int, value: Any) -> None:
48
+ id.insert(index, value)
49
+
50
+
51
+ def remove(id: list, index: int) -> Any:
52
+ return id.pop(index)
53
+
54
+
55
+ def includes(id: list, value: Any) -> bool:
56
+ return value in id
57
+
58
+
59
+ def indexof(id: list, value: Any) -> int:
60
+ try:
61
+ return id.index(value)
62
+ except ValueError:
63
+ return -1
64
+
65
+
66
+ def lastindexof(id: list, value: Any) -> int:
67
+ for i in range(len(id) - 1, -1, -1):
68
+ if id[i] == value:
69
+ return i
70
+ return -1
71
+
72
+
73
+ def copy(id: list) -> list:
74
+ return id[:]
75
+
76
+
77
+ def concat(id1: list, id2: list) -> list:
78
+ return id1 + id2
79
+
80
+
81
+ def join(id: list, separator: str = ",") -> str:
82
+ return separator.join(str(x) for x in id)
83
+
84
+
85
+ def reverse(id: list) -> None:
86
+ id.reverse()
87
+
88
+
89
+ def slice(id: list, index_from: int, index_to: int | None = None) -> list:
90
+ return id[index_from:index_to]
91
+
92
+
93
+ def sort(id: list, order: str = "asc") -> None:
94
+ id.sort(reverse=(order == "desc"))
95
+
96
+
97
+ def sum(id: list[float]) -> float:
98
+ return builtins_sum(id)
99
+
100
+
101
+ builtins_sum = __builtins__["sum"] if isinstance(__builtins__, dict) else getattr(__builtins__, "sum")
102
+
103
+
104
+ def avg(id: list[float]) -> float:
105
+ return sum(id) / len(id)
106
+
107
+
108
+ def min(id: list[float]) -> float:
109
+ return builtins_min(id)
110
+
111
+
112
+ builtins_min = __builtins__["min"] if isinstance(__builtins__, dict) else getattr(__builtins__, "min")
113
+
114
+
115
+ def max(id: list[float]) -> float:
116
+ return builtins_max(id)
117
+
118
+
119
+ builtins_max = __builtins__["max"] if isinstance(__builtins__, dict) else getattr(__builtins__, "max")
120
+
121
+
122
+ def median(id: list[float]) -> float:
123
+ sorted_arr = sorted(id)
124
+ mid = len(sorted_arr) // 2
125
+ if len(sorted_arr) % 2 == 0:
126
+ return (sorted_arr[mid - 1] + sorted_arr[mid]) / 2
127
+ return sorted_arr[mid]
128
+
129
+
130
+ def mode(id: list[float]) -> float:
131
+ frequency: dict[float, int] = {}
132
+ max_freq = 0
133
+ result = id[0]
134
+ for val in id:
135
+ freq = frequency.get(val, 0) + 1
136
+ frequency[val] = freq
137
+ if freq > max_freq:
138
+ max_freq = freq
139
+ result = val
140
+ return result
141
+
142
+
143
+ def stdev(id: list[float]) -> float:
144
+ mean = avg(id)
145
+ square_diffs = [(v - mean) ** 2 for v in id]
146
+ return math.sqrt(builtins_sum(square_diffs) / len(id))
147
+
148
+
149
+ def variance(id: list[float]) -> float:
150
+ mean = avg(id)
151
+ square_diffs = [(v - mean) ** 2 for v in id]
152
+ return builtins_sum(square_diffs) / len(id)
153
+
154
+
155
+ def fill(id: list, value: Any, index_from: int = 0, index_to: int | None = None) -> None:
156
+ end = index_to if index_to is not None else len(id)
157
+ for i in range(index_from, end):
158
+ id[i] = value
159
+
160
+
161
+ def from_array(id: list) -> list:
162
+ return copy(id)
163
+
164
+
165
+ def first(id: list) -> Any:
166
+ return id[0]
167
+
168
+
169
+ def last(id: list) -> Any:
170
+ return id[-1]
171
+
172
+
173
+ def some(id: list, predicate: Callable) -> bool:
174
+ return any(predicate(x) for x in id)
175
+
176
+
177
+ def every(id: list, predicate: Callable) -> bool:
178
+ return all(predicate(x) for x in id)
179
+
180
+
181
+ def new_bool(size: int = 0, initial_value: bool = False) -> list[bool]:
182
+ return new_array(size, initial_value)
183
+
184
+
185
+ def new_float(size: int = 0, initial_value: float = float("nan")) -> list[float]:
186
+ return new_array(size, initial_value)
187
+
188
+
189
+ def new_int(size: int = 0, initial_value: int | float = float("nan")) -> list:
190
+ return new_array(size, initial_value)
191
+
192
+
193
+ def new_string(size: int = 0, initial_value: str | None = None) -> list:
194
+ return new_array(size, initial_value)
195
+
196
+
197
+ def new_color(size: int = 0, initial_value: Any = None) -> list:
198
+ return new_array(size, initial_value)
199
+
200
+
201
+ def new_line(size: int = 0, initial_value: Any = None) -> list:
202
+ return new_array(size, initial_value)
203
+
204
+
205
+ def new_box(size: int = 0, initial_value: Any = None) -> list:
206
+ return new_array(size, initial_value)
207
+
208
+
209
+ def new_label(size: int = 0, initial_value: Any = None) -> list:
210
+ return new_array(size, initial_value)
211
+
212
+
213
+ def new_linefill(size: int = 0, initial_value: Any = None) -> list:
214
+ return new_array(size, initial_value)
215
+
216
+
217
+ def abs(id: list[float]) -> list[float]:
218
+ return [builtins_abs(x) for x in id]
219
+
220
+
221
+ builtins_abs = __builtins__["abs"] if isinstance(__builtins__, dict) else getattr(__builtins__, "abs")
222
+
223
+
224
+ def range(id: list[float]) -> float:
225
+ if len(id) == 0:
226
+ return float("nan")
227
+ return max(id) - min(id)
228
+
229
+
230
+ def binary_search(id: list[float], val: float) -> int:
231
+ left, right = 0, len(id) - 1
232
+ while left <= right:
233
+ mid = (left + right) // 2
234
+ if id[mid] == val:
235
+ return mid
236
+ if id[mid] < val:
237
+ left = mid + 1
238
+ else:
239
+ right = mid - 1
240
+ return -1
241
+
242
+
243
+ def binary_search_leftmost(id: list[float], val: float) -> int:
244
+ left, right = 0, len(id) - 1
245
+ result = -1
246
+ found = False
247
+ while left <= right:
248
+ mid = (left + right) // 2
249
+ if id[mid] < val:
250
+ if not found:
251
+ result = mid
252
+ left = mid + 1
253
+ elif id[mid] == val:
254
+ result = mid
255
+ found = True
256
+ right = mid - 1
257
+ else:
258
+ right = mid - 1
259
+ return result
260
+
261
+
262
+ def binary_search_rightmost(id: list[float], val: float) -> int:
263
+ left, right = 0, len(id) - 1
264
+ result = -1
265
+ found = False
266
+ while left <= right:
267
+ mid = (left + right) // 2
268
+ if id[mid] <= val:
269
+ if id[mid] == val:
270
+ result = mid
271
+ found = True
272
+ left = mid + 1
273
+ else:
274
+ if not found:
275
+ result = mid
276
+ right = mid - 1
277
+ return result
278
+
279
+
280
+ def covariance(id1: list[float], id2: list[float], biased: bool = True) -> float:
281
+ if not id1 or not id2 or len(id1) != len(id2):
282
+ return float("nan")
283
+ n = len(id1)
284
+ mean1 = avg(id1)
285
+ mean2 = avg(id2)
286
+ total = builtins_sum((id1[i] - mean1) * (id2[i] - mean2) for i in builtins_range(n))
287
+ divisor = n if biased else n - 1
288
+ return total / divisor
289
+
290
+
291
+ builtins_range = __builtins__["range"] if isinstance(__builtins__, dict) else getattr(__builtins__, "range")
292
+
293
+
294
+ def percentile_linear_interpolation(id: list[float], percentage: float) -> float:
295
+ if not id:
296
+ return float("nan")
297
+ sorted_arr = sorted(id)
298
+ n = len(sorted_arr)
299
+ rank = (percentage / 100) * (n - 1)
300
+ lower_idx = int(rank)
301
+ upper_idx = builtins_min(lower_idx + 1, n - 1)
302
+ frac = rank - lower_idx
303
+ return sorted_arr[lower_idx] + frac * (sorted_arr[upper_idx] - sorted_arr[lower_idx])
304
+
305
+
306
+ def percentile_nearest_rank(id: list[float], percentage: float) -> float:
307
+ if not id:
308
+ return float("nan")
309
+ sorted_arr = sorted(id)
310
+ n = len(sorted_arr)
311
+ rank = int(math.ceil((percentage / 100) * n)) - 1
312
+ rank = builtins_max(0, builtins_min(rank, n - 1))
313
+ return sorted_arr[rank]
314
+
315
+
316
+ builtins_max = __builtins__["max"] if isinstance(__builtins__, dict) else getattr(__builtins__, "max")
317
+
318
+
319
+ def percentrank(id: list[float], index: int) -> float:
320
+ if not id or index < 0 or index >= len(id):
321
+ return float("nan")
322
+ value = id[index]
323
+ if math.isnan(value):
324
+ return float("nan")
325
+ count = builtins_sum(1 for v in id if not math.isnan(v) and v <= value)
326
+ return (count / len(id)) * 100
327
+
328
+
329
+ def sort_indices(id: list[float], order: str = "asc") -> list[int]:
330
+ indices = list(builtins_range(len(id)))
331
+ indices.sort(key=lambda i: id[i], reverse=(order == "desc"))
332
+ return indices
333
+
334
+
335
+ def standardize(id: list[float]) -> list[float]:
336
+ if not id:
337
+ return []
338
+ mean = avg(id)
339
+ std_dev = stdev(id)
340
+ if std_dev == 0:
341
+ return [float("nan")] * len(id)
342
+ return [(v - mean) / std_dev for v in id]
oakscriptpy/box.py ADDED
@@ -0,0 +1,151 @@
1
+ """Box namespace — mirrors PineScript box.* functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy as _copy
6
+
7
+ from ._types import Box
8
+
9
+
10
+ def new(
11
+ left: float, top: float, right: float, bottom: float,
12
+ xloc: str = "bar_index", extend: str = "none",
13
+ border_color: str | int | None = None, border_width: int | None = None,
14
+ border_style: str | None = None, bgcolor: str | int | None = None,
15
+ text: str | None = None, text_size: str | int | None = None,
16
+ text_color: str | int | None = None, text_halign: str | None = None,
17
+ text_valign: str | None = None, text_wrap: str | None = None,
18
+ text_font_family: str | None = None,
19
+ ) -> Box:
20
+ return Box(
21
+ left=left, top=top, right=right, bottom=bottom,
22
+ xloc=xloc, extend=extend,
23
+ border_color=border_color, border_width=border_width or 1,
24
+ border_style=border_style or "solid", bgcolor=bgcolor,
25
+ text=text, text_size=text_size, text_color=text_color,
26
+ text_halign=text_halign, text_valign=text_valign,
27
+ text_wrap=text_wrap, text_font_family=text_font_family,
28
+ )
29
+
30
+
31
+ def get_left(id: Box) -> float:
32
+ return id.left
33
+
34
+
35
+ def get_right(id: Box) -> float:
36
+ return id.right
37
+
38
+
39
+ def get_top(id: Box) -> float:
40
+ return id.top
41
+
42
+
43
+ def get_bottom(id: Box) -> float:
44
+ return id.bottom
45
+
46
+
47
+ def copy(id: Box) -> Box:
48
+ return _copy.copy(id)
49
+
50
+
51
+ def set_left(id: Box, left: float) -> Box:
52
+ id.left = left
53
+ return id
54
+
55
+
56
+ def set_right(id: Box, right: float) -> Box:
57
+ id.right = right
58
+ return id
59
+
60
+
61
+ def set_top(id: Box, top: float) -> Box:
62
+ id.top = top
63
+ return id
64
+
65
+
66
+ def set_bottom(id: Box, bottom: float) -> Box:
67
+ id.bottom = bottom
68
+ return id
69
+
70
+
71
+ def set_lefttop(id: Box, left: float, top: float) -> Box:
72
+ id.left = left
73
+ id.top = top
74
+ return id
75
+
76
+
77
+ def set_rightbottom(id: Box, right: float, bottom: float) -> Box:
78
+ id.right = right
79
+ id.bottom = bottom
80
+ return id
81
+
82
+
83
+ def set_xloc(id: Box, left: float, right: float, xloc: str) -> Box:
84
+ id.left = left
85
+ id.right = right
86
+ id.xloc = xloc
87
+ return id
88
+
89
+
90
+ def set_extend(id: Box, extend: str) -> Box:
91
+ id.extend = extend
92
+ return id
93
+
94
+
95
+ def set_border_color(id: Box, color: str | int) -> Box:
96
+ id.border_color = color
97
+ return id
98
+
99
+
100
+ def set_border_width(id: Box, width: int) -> Box:
101
+ id.border_width = width
102
+ return id
103
+
104
+
105
+ def set_border_style(id: Box, style: str) -> Box:
106
+ id.border_style = style
107
+ return id
108
+
109
+
110
+ def set_bgcolor(id: Box, color: str | int) -> Box:
111
+ id.bgcolor = color
112
+ return id
113
+
114
+
115
+ def set_text(id: Box, text: str) -> Box:
116
+ id.text = text
117
+ return id
118
+
119
+
120
+ def set_text_color(id: Box, color: str | int) -> Box:
121
+ id.text_color = color
122
+ return id
123
+
124
+
125
+ def set_text_size(id: Box, size: str | int) -> Box:
126
+ id.text_size = size
127
+ return id
128
+
129
+
130
+ def set_text_halign(id: Box, align: str) -> Box:
131
+ id.text_halign = align
132
+ return id
133
+
134
+
135
+ def set_text_valign(id: Box, align: str) -> Box:
136
+ id.text_valign = align
137
+ return id
138
+
139
+
140
+ def set_text_wrap(id: Box, wrap: str) -> Box:
141
+ id.text_wrap = wrap
142
+ return id
143
+
144
+
145
+ def set_text_font_family(id: Box, font: str) -> Box:
146
+ id.text_font_family = font
147
+ return id
148
+
149
+
150
+ def delete(id: Box) -> None:
151
+ pass
@@ -0,0 +1,21 @@
1
+ """ChartPoint namespace — mirrors PineScript chart.point.* functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._types import ChartPoint
6
+
7
+
8
+ def new(time: float | None, index: int | None, price: float) -> ChartPoint:
9
+ return ChartPoint(time=time, index=index, price=price)
10
+
11
+
12
+ def from_time(time: float, price: float) -> ChartPoint:
13
+ return ChartPoint(time=time, index=None, price=price)
14
+
15
+
16
+ def from_index(index: int, price: float) -> ChartPoint:
17
+ return ChartPoint(time=None, index=index, price=price)
18
+
19
+
20
+ def copy(point: ChartPoint) -> ChartPoint:
21
+ return ChartPoint(time=point.time, index=point.index, price=point.price)
oakscriptpy/color.py ADDED
@@ -0,0 +1,134 @@
1
+ """Color namespace — mirrors PineScript color.* functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ # Named color constants
8
+ aqua = "#00FFFF"
9
+ black = "#000000"
10
+ blue = "#0000FF"
11
+ fuchsia = "#FF00FF"
12
+ gray = "#808080"
13
+ green = "#00FF00"
14
+ lime = "#00FF00"
15
+ maroon = "#800000"
16
+ navy = "#000080"
17
+ olive = "#808000"
18
+ orange = "#FFA500"
19
+ purple = "#800080"
20
+ red = "#FF0000"
21
+ silver = "#C0C0C0"
22
+ teal = "#008080"
23
+ white = "#FFFFFF"
24
+ yellow = "#FFFF00"
25
+
26
+
27
+ def rgb(red: int, green: int, blue: int, transp: float | None = None) -> str:
28
+ r = max(0, min(255, red))
29
+ g = max(0, min(255, green))
30
+ b = max(0, min(255, blue))
31
+ a = 1 - (transp / 100) if transp is not None else 1.0
32
+ if a == 1:
33
+ return f"rgb({r}, {g}, {b})"
34
+ return f"rgba({r}, {g}, {b}, {a})"
35
+
36
+
37
+ def from_hex(hex_str: str, transp: float | None = None) -> str:
38
+ hex_str = hex_str.lstrip("#")
39
+ r = int(hex_str[0:2], 16)
40
+ g = int(hex_str[2:4], 16)
41
+ b = int(hex_str[4:6], 16)
42
+ return rgb(r, g, b, transp)
43
+
44
+
45
+ def new_color(base_color: str, transp: float) -> str:
46
+ rgba = _parse_color(base_color)
47
+ return rgb(rgba["r"], rgba["g"], rgba["b"], transp)
48
+
49
+
50
+ # Alias for PineScript color.new()
51
+ new = new_color
52
+
53
+
54
+ def r(clr: str) -> int:
55
+ return _parse_color(clr)["r"]
56
+
57
+
58
+ def g(clr: str) -> int:
59
+ return _parse_color(clr)["g"]
60
+
61
+
62
+ def b(clr: str) -> int:
63
+ return _parse_color(clr)["b"]
64
+
65
+
66
+ def t(clr: str) -> float:
67
+ return _parse_color(clr)["t"]
68
+
69
+
70
+ def from_gradient(
71
+ value: float,
72
+ bottom_value: float,
73
+ top_value: float,
74
+ bottom_color: str,
75
+ top_color: str,
76
+ ) -> str:
77
+ c1 = _parse_color_rgba(bottom_color)
78
+ c2 = _parse_color_rgba(top_color)
79
+
80
+ if value <= bottom_value:
81
+ f = 0.0
82
+ elif value >= top_value:
83
+ f = 1.0
84
+ else:
85
+ f = (value - bottom_value) / (top_value - bottom_value)
86
+
87
+ r = builtins_round(c1[0] + (c2[0] - c1[0]) * f)
88
+ g = builtins_round(c1[1] + (c2[1] - c1[1]) * f)
89
+ b = builtins_round(c1[2] + (c2[2] - c1[2]) * f)
90
+ a = c1[3] + (c2[3] - c1[3]) * f
91
+
92
+ if a == 1:
93
+ return f"rgb({r}, {g}, {b})"
94
+ return f"rgba({r}, {g}, {b}, {a})"
95
+
96
+
97
+ builtins_round = __builtins__["round"] if isinstance(__builtins__, dict) else getattr(__builtins__, "round")
98
+
99
+
100
+ def _parse_color(clr: str) -> dict:
101
+ if isinstance(clr, str):
102
+ m = re.match(r"rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)", clr)
103
+ if m:
104
+ return {
105
+ "r": int(m.group(1)),
106
+ "g": int(m.group(2)),
107
+ "b": int(m.group(3)),
108
+ "t": (1 - float(m.group(4))) * 100 if m.group(4) else 0.0,
109
+ }
110
+ if clr.startswith("#"):
111
+ h = clr.lstrip("#")
112
+ return {
113
+ "r": int(h[0:2], 16),
114
+ "g": int(h[2:4], 16),
115
+ "b": int(h[4:6], 16),
116
+ "t": 0.0,
117
+ }
118
+ return {"r": 0, "g": 0, "b": 0, "t": 0.0}
119
+
120
+
121
+ def _parse_color_rgba(clr: str) -> tuple[int, int, int, float]:
122
+ if isinstance(clr, str):
123
+ if clr.startswith("#"):
124
+ h = clr.lstrip("#")
125
+ return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16), 1.0)
126
+ m = re.match(r"rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)", clr)
127
+ if m:
128
+ return (
129
+ int(m.group(1)),
130
+ int(m.group(2)),
131
+ int(m.group(3)),
132
+ float(m.group(4)) if m.group(4) else 1.0,
133
+ )
134
+ return (0, 0, 0, 1.0)