Skip to content

Commit 4c7759b

Browse files
authored
Merge pull request #60 from EodHistoricalData/v1.3.2
New functions of Technical API have been added
2 parents 8f65ea9 + 22db022 commit 4c7759b

1 file changed

Lines changed: 179 additions & 46 deletions

File tree

Lines changed: 179 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,207 @@
1-
from .BaseAPI import BaseAPI
1+
#APIs/TechnicalIndicatorAPI.py
22

3+
from .BaseAPI import BaseAPI
34

45

56
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,
665

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,
1070

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,
1874

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() == "":
2085
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() == "":
2388
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+
32131
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}"
34138

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":
37153
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"):
39156
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}"
41158

42-
if function == 'stochastic':
159+
# stochastic :contentReference[oaicite:14]{index=14}
160+
if fn == "stochastic":
43161
if fast_kperiod is not None:
44-
query_string += "&fast_kperiod=" + str(fast_kperiod)
162+
query_string += f"&fast_kperiod={int(fast_kperiod)}"
45163
if slow_kperiod is not None:
46-
query_string += "&slow_kperiod=" + str(slow_kperiod)
164+
query_string += f"&slow_kperiod={int(slow_kperiod)}"
47165
if slow_dperiod is not None:
48-
query_string += "&slow_dperiod=" + str(slow_dperiod)
166+
query_string += f"&slow_dperiod={int(slow_dperiod)}"
49167

50-
if function == 'stochrsi':
168+
# stochrsi :contentReference[oaicite:15]{index=15}
169+
if fn == "stochrsi":
51170
if fast_kperiod is not None:
52-
query_string += "&fast_kperiod=" + str(fast_kperiod)
171+
query_string += f"&fast_kperiod={int(fast_kperiod)}"
53172
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":
57177
if fast_period is not None:
58-
query_string += "&fast_period=" + str(fast_period)
178+
query_string += f"&fast_period={int(fast_period)}"
59179
if slow_period is not None:
60-
query_string += "&slow_period=" + str(slow_period)
180+
query_string += f"&slow_period={int(slow_period)}"
61181
if signal_period is not None:
62-
query_string += "&signal_period=" + str(signal_period)
182+
query_string += f"&signal_period={int(signal_period)}"
63183

64-
if function == 'sar':
184+
# sar :contentReference[oaicite:17]{index=17}
185+
if fn == "sar":
65186
if acceleration is not None:
66-
query_string += "&acceleration=" + str(acceleration)
187+
query_string += f"&acceleration={acceleration}"
67188
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}"
73190

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}"
74201

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

Comments
 (0)