Skip to content

Commit ea80a36

Browse files
committed
lots of changes. See diff
1 parent d9fbcef commit ea80a36

File tree

1 file changed

+74
-15
lines changed

1 file changed

+74
-15
lines changed

pymt5adapter/calendar.py

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import enum
22
import functools
3+
import re
34
from datetime import datetime
45
from datetime import timedelta
56
from typing import Callable
67
from typing import Iterable
78
from typing import List
89
from typing import Type
910
from typing import Union
11+
import math
1012

1113
import requests
1214

15+
BASE_URL = "https://www.mql5.com"
16+
OMIT_RESULT_KEYS = ['FullDate']
17+
1318

1419
class Importance(enum.IntFlag):
1520
HOLIDAY = enum.auto()
@@ -43,8 +48,10 @@ def _get_calendar_events(datetime_from: datetime,
4348
datetime_to: datetime,
4449
importance: int,
4550
currencies: int,
51+
language: str = None,
4652
) -> List[dict]:
47-
url = "https://www.mql5.com/en/economic-calendar/content"
53+
lang = 'en' if language is None else language
54+
url = BASE_URL + f"/{lang}/economic-calendar/content"
4855
headers = {"x-requested-with": "XMLHttpRequest"}
4956
time_format = "%Y-%m-%dT%H:%M:%S"
5057
data = {
@@ -54,28 +61,53 @@ def _get_calendar_events(datetime_from: datetime,
5461
"importance": importance,
5562
"currencies": currencies,
5663
}
57-
events = requests.post(url=url, headers=headers, data=data).json()
64+
response = requests.post(url=url, headers=headers, data=data)
65+
events = response.json()
5866
filtered_events = []
5967
for e in events:
60-
e['ReleaseDate'] = time = e['ReleaseDate'] / 1000
61-
t = datetime.fromtimestamp(time)
62-
if datetime_from <= t <= datetime_to:
68+
time = datetime.fromtimestamp(e['ReleaseDate'] / 1000)
69+
if datetime_from <= time <= datetime_to:
70+
e['Url'] = BASE_URL + e['Url']
71+
e['ReleaseDate'] = time
72+
e['request'] = data
6373
filtered_events.append(e)
64-
return events
74+
filtered_events = [
75+
{_camel_to_snake(k): v for k, v in x.items() if k not in OMIT_RESULT_KEYS}
76+
for x in filtered_events
77+
]
78+
return filtered_events
6579

6680

67-
def _round_time_to_mins(time: datetime, round_minutes: int):
68-
secs = round_minutes * 60.0
69-
res = round(time.timestamp() / secs, 0) * secs
81+
def _normalize_time(f, time: datetime, minutes: int):
82+
secs = minutes * 60
83+
res = f(time.timestamp() / secs) * secs
7084
new_time = datetime.fromtimestamp(res)
7185
return new_time
7286

7387

88+
def _time_ceil(time: datetime, minutes: int):
89+
return _normalize_time(math.ceil, time, minutes)
90+
91+
92+
def _time_floor(time: datetime, minutes: int):
93+
return _normalize_time(math.floor, time, minutes)
94+
95+
96+
7497
def _split_pairs(p: Iterable[str]):
7598
for s in p:
7699
yield from (s,) if len(s) < 6 else (s[:3], s[3:6])
77100

78101

102+
def _camel_to_snake(w):
103+
if (c := _camel_to_snake.pattern.findall(w)):
104+
return '_'.join(map(str.lower, c))
105+
return w
106+
107+
108+
_camel_to_snake.pattern = re.compile(r'[A-Z][a-z]+')
109+
110+
79111
@functools.lru_cache
80112
def _make_flag(enum_cls: Union[Type[Importance], Type[Currency]],
81113
flags: Union[Iterable[str], int, str] = None
@@ -101,24 +133,51 @@ def calendar_events(time_to: Union[datetime, timedelta] = None,
101133
function: Callable = None,
102134
round_minutes: int = 15,
103135
cache_clear: bool = False,
136+
language: str = None,
104137
**kwargs,
105138
) -> List[dict]:
139+
"""Get economic events from mql5.com/calendar. A call with empty args will results in all events for the next week.
140+
Since the function is memoized, the time is rounded to ``round_minutes`` and cached. This avoids repeat requests
141+
to the mql5.com server. In order to refresh the results on subsequest function calls you'll need to set the
142+
``cache_clear`` param to True.
143+
144+
:param time_to: Can be a timedelta or datetime object. If timedelta then the to time is calculated as
145+
datetime.now() + time_to. If the the time_from param is specified the the time_to will be calculated as
146+
time_from + time_to. Can also do a history look-back by passing in a negative timedelta to this param.
147+
:param time_from: Can be a timedelta or datetime object. If a timedelta object is passed then the time_from is
148+
calculated as time_from + now(), thus it needs to be a negative delta.
149+
:param importance: Can be int flags from the Importance enum class, and iterable of strings or a (space and/or
150+
comma separated string)
151+
:param currencies: Can be int flags from the Importance enum class, and iterable of strings or a (space and/or
152+
comma separated string) Pairs are automatically separated to the respective currency codes.
153+
:param function: A callback function that receives an event dict and returns bool for filtering.
154+
:param round_minutes: Round the number of minutes to this factor. Rounding aides the memoization of parameters.
155+
:param cache_clear: Clear the memo cache to refresh events from the server.
156+
:param language: The language code for the calendar. Default is 'en'
157+
:param kwargs:
158+
:return:
159+
"""
106160
if cache_clear:
107161
_get_calendar_events.cache_clear()
108162
now = datetime.now()
109163
if time_to is None and time_from is None:
110164
time_to = now + timedelta(weeks=1)
111-
time_from, time_to = time_from or now, time_to or now
112-
_f = lambda x: now + x if isinstance(x, timedelta) else x
113-
time_from, time_to = _f(time_from), _f(time_to)
165+
time_from, time_to = (time_from or now, time_to or now)
166+
_f = lambda x: (now + x) if isinstance(x, timedelta) else x
167+
time_from = _f(time_from) # time_from must go first in order to mutate it then add time_to
168+
time_to = _f(time_to)
114169
if time_from > time_to:
115170
time_from, time_to = time_to, time_from
116-
time_from = _round_time_to_mins(time_from, round_minutes)
117-
time_to = _round_time_to_mins(time_to, round_minutes)
171+
time_from = _time_floor(time_from, round_minutes)
172+
time_to = _time_ceil(time_to, round_minutes)
118173
_f = lambda x: tuple(x) if isinstance(x, Iterable) and not isinstance(x, str) else x
119174
importance, currencies = _f(importance), _f(currencies)
120175
i_flag, c_flag = _make_flag(Importance, importance), _make_flag(Currency, currencies)
121-
events = _get_calendar_events(datetime_from=time_from, datetime_to=time_to, importance=i_flag, currencies=c_flag)
176+
events = _get_calendar_events(datetime_from=time_from,
177+
datetime_to=time_to,
178+
importance=i_flag,
179+
currencies=c_flag,
180+
language=language)
122181
if function:
123182
events = list(filter(function, events))
124183
return events

0 commit comments

Comments
 (0)