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.
@@ -0,0 +1,93 @@
1
+ """OakScriptPy — PineScript-like API for technical analysis in Python."""
2
+ from __future__ import annotations
3
+
4
+ # Core namespaces (array-based functions)
5
+ from . import ta as ta_core
6
+ from . import math_ as math
7
+ from . import array
8
+ from . import str_ as str
9
+ from . import color
10
+ from . import time_ as time
11
+ from . import matrix
12
+
13
+ # Drawing object namespaces
14
+ from . import line
15
+ from . import box
16
+ from . import label
17
+ from . import linefill
18
+ from . import chartpoint as chart_point
19
+ from . import polyline
20
+
21
+ # Series class (self-contained, no context)
22
+ from .series import Series, BarData
23
+
24
+ # TA-Series namespace (Series-based wrappers)
25
+ from . import ta_series as ta
26
+
27
+ # Types
28
+ from ._types import (
29
+ Bar, Line as LineType, Box as BoxType, Label as LabelType,
30
+ Linefill as LinefillType, ChartPoint, Polyline as PolylineType,
31
+ Table, TableCell, Plot, HLine, OHLC, PineMatrix,
32
+ series_float, series_int, series_bool, series_string,
33
+ Source, color as color_type, na, xloc,
34
+ )
35
+
36
+ # Metadata types
37
+ from ._metadata import (
38
+ PlotOptions, HLineOptions, FillOptions, InputMetadata,
39
+ PlotMetadata, IndicatorMetadata, TimeValue, PlotData,
40
+ HLineData, FillData, IndicatorResult,
41
+ )
42
+
43
+ # Utilities
44
+ from ._utils import is_na, nz, ohlc_from_bars, get_close, get_high, get_low, get_open, get_source
45
+
46
+ # Runtime
47
+ from .runtime import (
48
+ set_context, clear_context, get_context,
49
+ register_calculate, recalculate,
50
+ plot, hline, clear_plots, get_active_plots,
51
+ )
52
+ from .runtime_types import OakScriptContext, OhlcvData, InputConfig
53
+
54
+ # Inputs
55
+ from .inputs import (
56
+ input_int, input_float, input_bool, input_string, input_source,
57
+ enable_auto_recalculate, disable_auto_recalculate, reset_inputs,
58
+ )
59
+
60
+ # Indicator infrastructure
61
+ from .indicator import indicator, IndicatorMetadataConfig, IndicatorContext, IndicatorInstance
62
+
63
+ # Input helper
64
+ from . import input_ as input
65
+
66
+ # Plot helper
67
+ from .plot_ import plot as plot_helper, create_plot, TimeValuePair, PlotResult
68
+
69
+ # Adapters
70
+ from .adapters import SimpleInputAdapter
71
+
72
+ # Libraries
73
+ from .lib import ZigZag, calculate_zigzag
74
+
75
+
76
+ def alertcondition(_condition: object = None, _title: str | None = None, _message: str | None = None) -> None:
77
+ """No-op stub for PineScript alertcondition() compatibility."""
78
+ pass
79
+
80
+
81
+ VERSION = "0.1.0"
82
+
83
+ info = {
84
+ "name": "OakScriptPy",
85
+ "version": VERSION,
86
+ "description": "PineScript-like API for technical analysis in Python",
87
+ "namespaces": {
88
+ "core": ["ta", "math", "array", "str", "color", "time", "matrix"],
89
+ "drawing": ["line", "box", "label", "linefill", "chart_point", "polyline"],
90
+ "runtime": ["set_context", "plot", "hline", "input_*"],
91
+ "indicator": ["indicator", "input", "plot_helper", "create_plot"],
92
+ },
93
+ }
@@ -0,0 +1,118 @@
1
+ """Indicator metadata types for oakscriptPy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+
9
+ PlotStyle = str # 'line' | 'stepline' | 'histogram' | 'area' | 'circles' | 'columns' | 'cross' | 'areabr' | 'steplinebr' | 'linebr'
10
+ LineStyle = str # 'solid' | 'dashed' | 'dotted'
11
+ PlotDisplay = str # 'all' | 'none' | 'data_window' | 'status_line' | 'pane'
12
+ InputType = str # 'int' | 'float' | 'bool' | 'string' | 'source' | 'color' | 'timeframe' | 'session'
13
+
14
+
15
+ @dataclass
16
+ class PlotOptions:
17
+ title: str | None = None
18
+ color: str | None = None
19
+ linewidth: int | None = None
20
+ style: str | None = None
21
+ trackprice: bool | None = None
22
+ histbase: float | None = None
23
+ offset: int | None = None
24
+ join: bool | None = None
25
+ editable: bool | None = None
26
+ display: str | None = None
27
+ transp: int | None = None
28
+
29
+
30
+ @dataclass
31
+ class HLineOptions:
32
+ title: str | None = None
33
+ color: str | None = None
34
+ linestyle: str | None = None
35
+ linewidth: int | None = None
36
+ editable: bool | None = None
37
+
38
+
39
+ @dataclass
40
+ class FillOptions:
41
+ color: str | None = None
42
+ transp: int | None = None
43
+ title: str | None = None
44
+ editable: bool | None = None
45
+ fillgaps: bool | None = None
46
+
47
+
48
+ @dataclass
49
+ class InputMetadata:
50
+ type: str
51
+ name: str
52
+ title: str
53
+ defval: Any
54
+ minval: float | None = None
55
+ maxval: float | None = None
56
+ step: float | None = None
57
+ tooltip: str | None = None
58
+ inline: str | None = None
59
+ group: str | None = None
60
+ options: list[Any] | None = None
61
+ confirm: bool | None = None
62
+
63
+
64
+ @dataclass
65
+ class PlotMetadata:
66
+ var_name: str
67
+ title: str
68
+ color: str
69
+ linewidth: int
70
+ style: str
71
+
72
+
73
+ @dataclass
74
+ class IndicatorMetadata:
75
+ title: str
76
+ shorttitle: str | None = None
77
+ overlay: bool = False
78
+ precision: int | None = None
79
+ format: str | None = None
80
+ timeframe: str | None = None
81
+ timeframe_gaps: bool | None = None
82
+ inputs: list[InputMetadata] | None = None
83
+ plots: list[PlotMetadata] | None = None
84
+
85
+
86
+ @dataclass
87
+ class TimeValue:
88
+ time: Any
89
+ value: float
90
+ color: str | None = None
91
+
92
+
93
+ @dataclass
94
+ class PlotData:
95
+ data: list[TimeValue]
96
+ options: PlotOptions | None = None
97
+
98
+
99
+ @dataclass
100
+ class HLineData:
101
+ value: float
102
+ options: HLineOptions | None = None
103
+
104
+
105
+ @dataclass
106
+ class FillData:
107
+ plot1: int | str
108
+ plot2: int | str
109
+ options: FillOptions | None = None
110
+ colors: list[str] | None = None
111
+
112
+
113
+ @dataclass
114
+ class IndicatorResult:
115
+ metadata: IndicatorMetadata
116
+ plots: dict[str, list[TimeValue]]
117
+ hlines: list[HLineData] | None = None
118
+ fills: list[FillData] | None = None
oakscriptpy/_types.py ADDED
@@ -0,0 +1,185 @@
1
+ """Core types for oakscriptPy — mirrors PineScript type system."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ # Basic type aliases
9
+ series_float = list[float]
10
+ series_int = list[int]
11
+ series_bool = list[bool]
12
+ series_string = list[str]
13
+ series_color = list[str | int]
14
+ Source = list[float]
15
+ color = str | int
16
+
17
+ # Constants
18
+ na = None
19
+
20
+
21
+ class _Xloc:
22
+ bar_index: str = "bar_index"
23
+ bar_time: str = "bar_time"
24
+
25
+
26
+ xloc = _Xloc()
27
+
28
+
29
+ # Drawing objects
30
+
31
+ @dataclass
32
+ class Line:
33
+ x1: float
34
+ y1: float
35
+ x2: float
36
+ y2: float
37
+ xloc: str = "bar_index"
38
+ extend: str = "none"
39
+ color: str | int | None = None
40
+ style: str | None = None
41
+ width: int | None = None
42
+
43
+
44
+ @dataclass
45
+ class Box:
46
+ left: float
47
+ top: float
48
+ right: float
49
+ bottom: float
50
+ xloc: str = "bar_index"
51
+ extend: str = "none"
52
+ border_color: str | int | None = None
53
+ border_width: int | None = None
54
+ border_style: str | None = None
55
+ bgcolor: str | int | None = None
56
+ text: str | None = None
57
+ text_size: str | int | None = None
58
+ text_color: str | int | None = None
59
+ text_halign: str | None = None
60
+ text_valign: str | None = None
61
+ text_wrap: str | None = None
62
+ text_font_family: str | None = None
63
+
64
+
65
+ @dataclass
66
+ class Label:
67
+ x: float
68
+ y: float
69
+ xloc: str = "bar_index"
70
+ yloc: str = "price"
71
+ text: str | None = None
72
+ tooltip: str | None = None
73
+ color: str | int | None = None
74
+ style: str | None = None
75
+ textcolor: str | int | None = None
76
+ size: str | int | None = None
77
+ textalign: str | None = None
78
+ text_font_family: str | None = None
79
+
80
+
81
+ @dataclass
82
+ class Linefill:
83
+ line1: Line
84
+ line2: Line
85
+ color: str | int | None = None
86
+
87
+
88
+ @dataclass
89
+ class Table:
90
+ position: str
91
+ columns: int
92
+ rows: int
93
+ frame_color: str | int | None = None
94
+ frame_width: int | None = None
95
+ border_color: str | int | None = None
96
+ border_width: int | None = None
97
+ bgcolor: str | int | None = None
98
+ cells: dict[str, TableCell] | None = None
99
+
100
+
101
+ @dataclass
102
+ class TableCell:
103
+ text: str | None = None
104
+ width: float | None = None
105
+ height: float | None = None
106
+ text_color: str | int | None = None
107
+ text_halign: str | None = None
108
+ text_valign: str | None = None
109
+ text_size: str | int | None = None
110
+ bgcolor: str | int | None = None
111
+ tooltip: str | None = None
112
+ text_font_family: str | None = None
113
+
114
+
115
+ @dataclass
116
+ class Plot:
117
+ id: str
118
+ series: Any = None
119
+ title: str | None = None
120
+ color: Any = None
121
+ linewidth: int | None = None
122
+ style: str | None = None
123
+ trackprice: bool | None = None
124
+ histbase: float | None = None
125
+ offset: int | None = None
126
+ join: bool | None = None
127
+ editable: bool | None = None
128
+ display: str | None = None
129
+
130
+
131
+ @dataclass
132
+ class HLine:
133
+ id: str
134
+ price: float
135
+ title: str | None = None
136
+ color: Any = None
137
+ linestyle: str | None = None
138
+ linewidth: int | None = None
139
+ editable: bool | None = None
140
+
141
+
142
+ @dataclass
143
+ class Bar:
144
+ time: int
145
+ open: float
146
+ high: float
147
+ low: float
148
+ close: float
149
+ volume: float = 0.0
150
+
151
+
152
+ @dataclass
153
+ class OHLC:
154
+ open: list[float]
155
+ high: list[float]
156
+ low: list[float]
157
+ close: list[float]
158
+
159
+
160
+ @dataclass
161
+ class ChartPoint:
162
+ time: float | None
163
+ index: int | None
164
+ price: float
165
+
166
+
167
+ @dataclass
168
+ class Polyline:
169
+ id: str
170
+ points: list[ChartPoint]
171
+ curved: bool = False
172
+ closed: bool = False
173
+ xloc: str = "bar_index"
174
+ line_color: str | int = "#2962FF"
175
+ fill_color: str | int | None = None
176
+ line_style: str = "solid"
177
+ line_width: int = 1
178
+ force_overlay: bool = False
179
+
180
+
181
+ @dataclass
182
+ class PineMatrix:
183
+ rows: int
184
+ columns: int
185
+ data: list[list[Any]] = field(default_factory=list)
oakscriptpy/_utils.py ADDED
@@ -0,0 +1,145 @@
1
+ """Internal utility functions for oakscriptPy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import warnings
7
+ from typing import TYPE_CHECKING
8
+
9
+ from ._types import Bar, OHLC, series_float
10
+
11
+ if TYPE_CHECKING:
12
+ from .series import Series
13
+
14
+
15
+ def validate_series_length(source: list[float], length: int, function_name: str) -> None:
16
+ if len(source) < length:
17
+ warnings.warn(
18
+ f"{function_name}: Source length ({len(source)}) is less than required length ({length})"
19
+ )
20
+
21
+
22
+ def create_nan_series(length: int) -> series_float:
23
+ return [float("nan")] * length
24
+
25
+
26
+ def is_na(value: object) -> bool:
27
+ if value is None:
28
+ return True
29
+ if isinstance(value, float) and math.isnan(value):
30
+ return True
31
+ return False
32
+
33
+
34
+ def nz(value: object, default: object = 0) -> object:
35
+ return default if is_na(value) else value
36
+
37
+
38
+ def clamp(value: float, min_val: float, max_val: float) -> float:
39
+ return max(min_val, min(max_val, value))
40
+
41
+
42
+ def lerp(start: float, end: float, t: float) -> float:
43
+ return start + (end - start) * t
44
+
45
+
46
+ def normalize(
47
+ value: float, min_val: float, max_val: float, new_min: float = 0.0, new_max: float = 1.0
48
+ ) -> float:
49
+ return new_min + ((value - min_val) * (new_max - new_min)) / (max_val - min_val)
50
+
51
+
52
+ # Chart data utilities
53
+
54
+ def ohlc_from_bars(bars: list[Bar]) -> OHLC:
55
+ return OHLC(
56
+ open=[b.open for b in bars],
57
+ high=[b.high for b in bars],
58
+ low=[b.low for b in bars],
59
+ close=[b.close for b in bars],
60
+ )
61
+
62
+
63
+ def get_close(ohlc: OHLC) -> series_float:
64
+ return ohlc.close
65
+
66
+
67
+ def get_high(ohlc: OHLC) -> series_float:
68
+ return ohlc.high
69
+
70
+
71
+ def get_low(ohlc: OHLC) -> series_float:
72
+ return ohlc.low
73
+
74
+
75
+ def get_open(ohlc: OHLC) -> series_float:
76
+ return ohlc.open
77
+
78
+
79
+ def get_source(
80
+ data: list[Bar] | OHLC,
81
+ source: str = "close",
82
+ ) -> series_float:
83
+ if isinstance(data, OHLC):
84
+ ohlc = data
85
+ if source == "close":
86
+ return ohlc.close
87
+ if source == "open":
88
+ return ohlc.open
89
+ if source == "high":
90
+ return ohlc.high
91
+ if source == "low":
92
+ return ohlc.low
93
+ if source == "hl2":
94
+ return [(h + l) / 2 for h, l in zip(ohlc.high, ohlc.low)]
95
+ if source == "hlc3":
96
+ return [(h + l + c) / 3 for h, l, c in zip(ohlc.high, ohlc.low, ohlc.close)]
97
+ if source == "ohlc4":
98
+ return [
99
+ (o + h + l + c) / 4
100
+ for o, h, l, c in zip(ohlc.open, ohlc.high, ohlc.low, ohlc.close)
101
+ ]
102
+ if source == "hlcc4":
103
+ return [
104
+ (h + l + c + c) / 4 for h, l, c in zip(ohlc.high, ohlc.low, ohlc.close)
105
+ ]
106
+ else:
107
+ bars = data
108
+ if source == "close":
109
+ return [b.close for b in bars]
110
+ if source == "open":
111
+ return [b.open for b in bars]
112
+ if source == "high":
113
+ return [b.high for b in bars]
114
+ if source == "low":
115
+ return [b.low for b in bars]
116
+ if source == "hl2":
117
+ return [(b.high + b.low) / 2 for b in bars]
118
+ if source == "hlc3":
119
+ return [(b.high + b.low + b.close) / 3 for b in bars]
120
+ if source == "ohlc4":
121
+ return [(b.open + b.high + b.low + b.close) / 4 for b in bars]
122
+ if source == "hlcc4":
123
+ return [(b.high + b.low + b.close + b.close) / 4 for b in bars]
124
+ return []
125
+
126
+
127
+ def shift(series: series_float, offset: int) -> series_float:
128
+ if offset == 0:
129
+ return series
130
+ if offset > 0:
131
+ return [float("nan")] * offset + series[: -offset]
132
+ else:
133
+ return series[-offset:] + [float("nan")] * (-offset)
134
+
135
+
136
+ def format_output(
137
+ values: series_float, timestamps: list[float] | None = None
138
+ ) -> list[dict]:
139
+ result = []
140
+ for i, value in enumerate(values):
141
+ result.append({
142
+ "time": timestamps[i] if timestamps else i,
143
+ "value": None if math.isnan(value) else value,
144
+ })
145
+ return result
@@ -0,0 +1,5 @@
1
+ """Adapter implementations."""
2
+
3
+ from .simple_input import SimpleInputAdapter
4
+
5
+ __all__ = ["SimpleInputAdapter"]
@@ -0,0 +1,63 @@
1
+ """Simple in-memory input adapter for demos and testing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from typing import Any, Callable
7
+
8
+ from ..runtime_types import InputConfig
9
+
10
+
11
+ class SimpleInputAdapter:
12
+ """Stores input configurations and values in memory."""
13
+
14
+ def __init__(self) -> None:
15
+ self._inputs: dict[str, dict[str, Any]] = {}
16
+ self._change_callbacks: list[Callable[[str, Any], None]] = []
17
+
18
+ def register_input(self, config: InputConfig) -> Any:
19
+ if config.id not in self._inputs:
20
+ self._inputs[config.id] = {"config": config, "value": config.defval}
21
+ return self._inputs[config.id]["value"]
22
+
23
+ def get_value(self, id: str) -> Any:
24
+ entry = self._inputs.get(id)
25
+ return entry["value"] if entry is not None else None
26
+
27
+ def set_value(self, id: str, value: Any) -> None:
28
+ entry = self._inputs.get(id)
29
+ if entry is None:
30
+ return
31
+
32
+ config: InputConfig = entry["config"]
33
+ validated = value
34
+
35
+ if config.type in ("int", "float") and isinstance(value, (int, float)):
36
+ if config.min is not None and value < config.min:
37
+ validated = config.min
38
+ if config.max is not None and value > config.max:
39
+ validated = config.max
40
+ if config.type == "int":
41
+ validated = int(validated)
42
+
43
+ if config.type == "string" and config.options and isinstance(value, str):
44
+ if value not in config.options:
45
+ return
46
+
47
+ if config.type == "source" and config.options and isinstance(value, str):
48
+ if value not in config.options:
49
+ return
50
+
51
+ entry["value"] = validated
52
+ for cb in self._change_callbacks:
53
+ cb(id, validated)
54
+
55
+ def on_input_change(self, callback: Callable[[str, Any], None]) -> None:
56
+ self._change_callbacks.append(callback)
57
+
58
+ def get_all_inputs(self) -> dict[str, dict[str, Any]]:
59
+ return dict(self._inputs)
60
+
61
+ def clear(self) -> None:
62
+ self._inputs.clear()
63
+ self._change_callbacks.clear()