|
1 | | -from .BaseAPI import BaseAPI |
| 1 | +#APIs/TechnicalIndicatorAPI.py |
2 | 2 |
|
| 3 | +from .BaseAPI import BaseAPI |
3 | 4 |
|
4 | 5 |
|
5 | 6 | class TechnicalIndicatorAPI(BaseAPI): |
| 7 | + """ |
| 8 | + Docs: https://eodhd.com/financial-apis/technical-indicators-api :contentReference[oaicite:4]{index=4} |
| 9 | + Endpoint: |
| 10 | + GET /api/technical/{ticker}?function=...&api_token=...&fmt=json|csv |
| 11 | + """ |
| 12 | + |
| 13 | + # Full set from the documentation, including BETA and dx alias :contentReference[oaicite:5]{index=5} |
| 14 | + possible_functions = [ |
| 15 | + "splitadjusted", |
| 16 | + "avgvol", |
| 17 | + "avgvolccy", |
| 18 | + "sma", |
| 19 | + "ema", |
| 20 | + "wma", |
| 21 | + "volatility", |
| 22 | + "stochastic", |
| 23 | + "rsi", |
| 24 | + "stddev", |
| 25 | + "stochrsi", |
| 26 | + "slope", |
| 27 | + "dmi", # docs also mention dx alias |
| 28 | + "dx", # alias -> will be normalized to dmi |
| 29 | + "adx", |
| 30 | + "macd", |
| 31 | + "atr", |
| 32 | + "cci", |
| 33 | + "sar", |
| 34 | + "beta", |
| 35 | + "bbands", |
| 36 | + "format_amibroker", |
| 37 | + ] |
| 38 | + |
| 39 | + _splitadjusted_only_supported = { |
| 40 | + "sma", "ema", "wma", "volatility", "rsi", "slope", "macd" |
| 41 | + } # :contentReference[oaicite:6]{index=6} |
| 42 | + |
| 43 | + def get_technical_indicator_data( |
| 44 | + self, |
| 45 | + api_token: str, |
| 46 | + ticker: str, |
| 47 | + function: str, |
| 48 | + period: int = 50, |
| 49 | + date_from: str = None, |
| 50 | + date_to: str = None, |
| 51 | + order: str = "a", |
| 52 | + fmt: str = "json", |
| 53 | + filter_field: str = None, # e.g. "last_ema", "last_volume" :contentReference[oaicite:7]{index=7} |
| 54 | + |
| 55 | + # splitadjusted |
| 56 | + agg_period: str = None, # d|w|m |
| 57 | + |
| 58 | + # stochastic |
| 59 | + fast_kperiod: int = None, |
| 60 | + slow_kperiod: int = None, |
| 61 | + slow_dperiod: int = None, |
| 62 | + |
| 63 | + # stochrsi |
| 64 | + fast_dperiod: int = None, |
6 | 65 |
|
7 | | - possible_functions = ['avgvol', 'avgvolccy', 'sma', 'ema', 'wma', 'volatility', 'stochastic', |
8 | | - 'rsi', 'stddev', 'stochrsi', 'slope', 'dmi', 'adx', 'macd', 'atr', |
9 | | - 'cci', 'sar', 'bbands', 'format_amibroker', 'splitadjusted'] |
| 66 | + # macd |
| 67 | + fast_period: int = None, |
| 68 | + slow_period: int = None, |
| 69 | + signal_period: int = None, |
10 | 70 |
|
11 | | - def get_technical_indicator_data(self, api_token: str, ticker: str, function: str, period: int = 50, |
12 | | - date_from: str = None, date_to: str = None, order: str = 'a', |
13 | | - splitadjusted_only: str = '0', agg_period = None, |
14 | | - fast_kperiod = None, slow_kperiod = None, slow_dperiod = None, |
15 | | - fast_dperiod = None, fast_period = None, slow_period = None, |
16 | | - signal_period = None, acceleration = None, maximum = None): |
17 | | - endpoint = 'technical/' |
| 71 | + # sar |
| 72 | + acceleration: float = None, |
| 73 | + maximum: float = None, |
18 | 74 |
|
19 | | - if ticker.strip() == "" or ticker is None: |
| 75 | + # beta |
| 76 | + code2: str = None, # default is GSPC.INDX :contentReference[oaicite:8]{index=8} |
| 77 | + |
| 78 | + # splitadjusted_only |
| 79 | + splitadjusted_only: str = "0", # accepts "0"/"1" or bool-like |
| 80 | + ): |
| 81 | + endpoint = "technical/" |
| 82 | + |
| 83 | + # --- basic validation --- |
| 84 | + if ticker is None or str(ticker).strip() == "": |
20 | 85 | raise ValueError("Ticker is empty. You need to add ticker to args") |
21 | | - |
22 | | - if function.strip() == "" or function is None: |
| 86 | + |
| 87 | + if function is None or str(function).strip() == "": |
23 | 88 | raise ValueError("Function is empty. You need to add function to args") |
24 | | - |
25 | | - if function not in self.possible_functions: |
26 | | - raise ValueError("Incorrect value for fanction parameter") |
27 | | - |
28 | | - query_string = f'&order={order}&splitadjusted_only={splitadjusted_only}&period={period}&function={function}' |
29 | | - |
30 | | - if date_to is not None: |
31 | | - query_string += "&to=" + str(date_to) |
| 89 | + |
| 90 | + fn = str(function).strip().lower() |
| 91 | + |
| 92 | + # normalize dx -> dmi (docs list required function as dmi; heading mentions dx alias) :contentReference[oaicite:9]{index=9} |
| 93 | + if fn == "dx": |
| 94 | + fn = "dmi" |
| 95 | + |
| 96 | + if fn not in self.possible_functions and fn != "dmi": |
| 97 | + raise ValueError("Incorrect value for function parameter") |
| 98 | + |
| 99 | + if order is not None: |
| 100 | + order = str(order).lower() |
| 101 | + if order not in ("a", "d"): |
| 102 | + raise ValueError("order must be 'a' (asc) or 'd' (desc)") |
| 103 | + |
| 104 | + if fmt is not None: |
| 105 | + fmt = str(fmt).lower() |
| 106 | + if fmt not in ("json", "csv"): |
| 107 | + raise ValueError("fmt must be 'json' or 'csv'") |
| 108 | + |
| 109 | + # normalize splitadjusted_only |
| 110 | + if isinstance(splitadjusted_only, bool): |
| 111 | + splitadjusted_only = "1" if splitadjusted_only else "0" |
| 112 | + else: |
| 113 | + splitadjusted_only = str(splitadjusted_only) |
| 114 | + |
| 115 | + # validate period when used (docs: 2..100000, default 50) :contentReference[oaicite:10]{index=10} |
| 116 | + if period is not None: |
| 117 | + try: |
| 118 | + period_int = int(period) |
| 119 | + except Exception: |
| 120 | + raise ValueError("period must be an integer") |
| 121 | + if period_int < 2 or period_int > 100000: |
| 122 | + raise ValueError("period must be in range [2..100000]") |
| 123 | + period = period_int |
| 124 | + |
| 125 | + # --- build query --- |
| 126 | + query_string = f"&function={fn}" |
| 127 | + |
| 128 | + if order is not None: |
| 129 | + query_string += f"&order={order}" |
| 130 | + |
32 | 131 | if date_from is not None: |
33 | | - query_string += "&from=" + str(date_from) |
| 132 | + query_string += f"&from={date_from}" |
| 133 | + if date_to is not None: |
| 134 | + query_string += f"&to={date_to}" |
| 135 | + |
| 136 | + if fmt is not None: |
| 137 | + query_string += f"&fmt={fmt}" |
34 | 138 |
|
35 | | - if function == 'splitadjusted': |
36 | | - possible_agg_period = ['d', 'w', 'm'] |
| 139 | + # filter fields (works with fmt=json per docs; we don't hard-enforce, just pass through) :contentReference[oaicite:11]{index=11} |
| 140 | + if filter_field is not None and str(filter_field).strip() != "": |
| 141 | + query_string += f"&filter={str(filter_field).strip()}" |
| 142 | + |
| 143 | + # period is applicable to most functions; keep it unless function is format_amibroker or splitadjusted |
| 144 | + if fn not in ("format_amibroker", "splitadjusted") and period is not None: |
| 145 | + query_string += f"&period={period}" |
| 146 | + |
| 147 | + # splitadjusted_only only for supported functions :contentReference[oaicite:12]{index=12} |
| 148 | + if fn in self._splitadjusted_only_supported: |
| 149 | + query_string += f"&splitadjusted_only={splitadjusted_only}" |
| 150 | + |
| 151 | + # splitadjusted function options :contentReference[oaicite:13]{index=13} |
| 152 | + if fn == "splitadjusted": |
37 | 153 | if agg_period is not None: |
38 | | - if agg_period not in possible_agg_period: |
| 154 | + agg_period = str(agg_period).lower() |
| 155 | + if agg_period not in ("d", "w", "m"): |
39 | 156 | raise ValueError("agg_period must be in ['d', 'w', 'm']") |
40 | | - query_string += "&agg_period=" + str(agg_period) |
| 157 | + query_string += f"&agg_period={agg_period}" |
41 | 158 |
|
42 | | - if function == 'stochastic': |
| 159 | + # stochastic :contentReference[oaicite:14]{index=14} |
| 160 | + if fn == "stochastic": |
43 | 161 | if fast_kperiod is not None: |
44 | | - query_string += "&fast_kperiod=" + str(fast_kperiod) |
| 162 | + query_string += f"&fast_kperiod={int(fast_kperiod)}" |
45 | 163 | if slow_kperiod is not None: |
46 | | - query_string += "&slow_kperiod=" + str(slow_kperiod) |
| 164 | + query_string += f"&slow_kperiod={int(slow_kperiod)}" |
47 | 165 | if slow_dperiod is not None: |
48 | | - query_string += "&slow_dperiod=" + str(slow_dperiod) |
| 166 | + query_string += f"&slow_dperiod={int(slow_dperiod)}" |
49 | 167 |
|
50 | | - if function == 'stochrsi': |
| 168 | + # stochrsi :contentReference[oaicite:15]{index=15} |
| 169 | + if fn == "stochrsi": |
51 | 170 | if fast_kperiod is not None: |
52 | | - query_string += "&fast_kperiod=" + str(fast_kperiod) |
| 171 | + query_string += f"&fast_kperiod={int(fast_kperiod)}" |
53 | 172 | if fast_dperiod is not None: |
54 | | - query_string += "&fast_dperiod=" + str(fast_dperiod) |
55 | | - |
56 | | - if function == 'macd': |
| 173 | + query_string += f"&fast_dperiod={int(fast_dperiod)}" |
| 174 | + |
| 175 | + # macd :contentReference[oaicite:16]{index=16} |
| 176 | + if fn == "macd": |
57 | 177 | if fast_period is not None: |
58 | | - query_string += "&fast_period=" + str(fast_period) |
| 178 | + query_string += f"&fast_period={int(fast_period)}" |
59 | 179 | if slow_period is not None: |
60 | | - query_string += "&slow_period=" + str(slow_period) |
| 180 | + query_string += f"&slow_period={int(slow_period)}" |
61 | 181 | if signal_period is not None: |
62 | | - query_string += "&signal_period=" + str(signal_period) |
| 182 | + query_string += f"&signal_period={int(signal_period)}" |
63 | 183 |
|
64 | | - if function == 'sar': |
| 184 | + # sar :contentReference[oaicite:17]{index=17} |
| 185 | + if fn == "sar": |
65 | 186 | if acceleration is not None: |
66 | | - query_string += "&acceleration=" + str(acceleration) |
| 187 | + query_string += f"&acceleration={acceleration}" |
67 | 188 | if maximum is not None: |
68 | | - query_string += "&maximum=" + str(maximum) |
69 | | - |
70 | | - return self._rest_get_method(api_key = api_token, endpoint = endpoint, uri = ticker, querystring = query_string) |
71 | | - |
72 | | - |
| 189 | + query_string += f"&maximum={maximum}" |
73 | 190 |
|
| 191 | + # beta :contentReference[oaicite:18]{index=18} |
| 192 | + if fn == "beta": |
| 193 | + if code2 is not None and str(code2).strip() != "": |
| 194 | + query_string += f"&code2={str(code2).strip()}" |
| 195 | + else: |
| 196 | + # docs default is GSPC.INDX; omit to let server default, or set explicitly |
| 197 | + # query_string += "&code2=GSPC.INDX" |
| 198 | + pass |
| 199 | + if period is not None: |
| 200 | + query_string += f"&period={period}" |
74 | 201 |
|
| 202 | + return self._rest_get_method( |
| 203 | + api_key=api_token, |
| 204 | + endpoint=endpoint, |
| 205 | + uri=str(ticker).strip(), |
| 206 | + querystring=query_string, |
| 207 | + ) |
0 commit comments