11import enum
22import functools
3+ import re
34from datetime import datetime
45from datetime import timedelta
56from typing import Callable
67from typing import Iterable
78from typing import List
89from typing import Type
910from typing import Union
11+ import math
1012
1113import requests
1214
15+ BASE_URL = "https://www.mql5.com"
16+ OMIT_RESULT_KEYS = ['FullDate' ]
17+
1318
1419class 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+
7497def _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
80112def _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