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/__init__.py +93 -0
- oakscriptpy/_metadata.py +118 -0
- oakscriptpy/_types.py +185 -0
- oakscriptpy/_utils.py +145 -0
- oakscriptpy/adapters/__init__.py +5 -0
- oakscriptpy/adapters/simple_input.py +63 -0
- oakscriptpy/array.py +342 -0
- oakscriptpy/box.py +151 -0
- oakscriptpy/chartpoint.py +21 -0
- oakscriptpy/color.py +134 -0
- oakscriptpy/indicator.py +82 -0
- oakscriptpy/input_.py +38 -0
- oakscriptpy/inputs.py +170 -0
- oakscriptpy/label.py +110 -0
- oakscriptpy/lib/__init__.py +5 -0
- oakscriptpy/lib/zigzag.py +158 -0
- oakscriptpy/line.py +120 -0
- oakscriptpy/linefill.py +26 -0
- oakscriptpy/math_.py +184 -0
- oakscriptpy/matrix.py +1136 -0
- oakscriptpy/plot_.py +49 -0
- oakscriptpy/polyline.py +60 -0
- oakscriptpy/runtime.py +150 -0
- oakscriptpy/runtime_types.py +52 -0
- oakscriptpy/series.py +292 -0
- oakscriptpy/str_.py +166 -0
- oakscriptpy/ta.py +1795 -0
- oakscriptpy/ta_series.py +353 -0
- oakscriptpy/time_.py +22 -0
- oakscriptpy-0.1.0.dist-info/METADATA +120 -0
- oakscriptpy-0.1.0.dist-info/RECORD +32 -0
- oakscriptpy-0.1.0.dist-info/WHEEL +4 -0
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)
|