dukascopy-python 4.0.0__py3-none-any.whl → 5.0.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.
- dukascopy_python/__init__.py +156 -386
- dukascopy_python/compat/__init__.py +428 -0
- dukascopy_python/{instruments.py → compat/instruments.py} +1510 -0
- dukascopy_python/core/__init__.py +22 -0
- dukascopy_python/core/http.py +34 -0
- dukascopy_python/core/interfaces.py +72 -0
- dukascopy_python/core/logger.py +54 -0
- dukascopy_python/core/utils.py +175 -0
- dukascopy_python/impl/v1/__init__.py +332 -0
- dukascopy_python/impl/v2/__init__.py +545 -0
- dukascopy_python/instruments/__init__.py +6 -0
- dukascopy_python/instruments/generator.py +98 -0
- dukascopy_python/instruments/instruments.py +21074 -0
- dukascopy_python-5.0.0.dist-info/METADATA +301 -0
- dukascopy_python-5.0.0.dist-info/RECORD +18 -0
- {dukascopy_python-4.0.0.dist-info → dukascopy_python-5.0.0.dist-info}/WHEEL +1 -1
- dukascopy_python-4.0.0.dist-info/METADATA +0 -238
- dukascopy_python-4.0.0.dist-info/RECORD +0 -7
- {dukascopy_python-4.0.0.dist-info → dukascopy_python-5.0.0.dist-info}/licenses/LICENSE +0 -0
- {dukascopy_python-4.0.0.dist-info → dukascopy_python-5.0.0.dist-info}/top_level.txt +0 -0
dukascopy_python/__init__.py
CHANGED
|
@@ -1,412 +1,182 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
TIME_UNIT_DAY = "DAY"
|
|
13
|
-
TIME_UNIT_HOUR = "HOUR"
|
|
14
|
-
TIME_UNIT_MIN = "MIN"
|
|
15
|
-
TIME_UNIT_SEC = "SEC"
|
|
16
|
-
TIME_UNIT_TICK = "TICK"
|
|
17
|
-
|
|
18
|
-
INTERVAL_MONTH_1 = f"1{TIME_UNIT_MONTH}"
|
|
19
|
-
INTERVAL_WEEK_1 = f"1{TIME_UNIT_WEEK}"
|
|
20
|
-
INTERVAL_DAY_1 = f"1{TIME_UNIT_DAY}"
|
|
21
|
-
INTERVAL_HOUR_4 = f"4{TIME_UNIT_HOUR}"
|
|
22
|
-
INTERVAL_HOUR_1 = f"1{TIME_UNIT_HOUR}"
|
|
23
|
-
INTERVAL_MIN_30 = f"30{TIME_UNIT_MIN}"
|
|
24
|
-
INTERVAL_MIN_15 = f"15{TIME_UNIT_MIN}"
|
|
25
|
-
INTERVAL_MIN_10 = f"10{TIME_UNIT_MIN}"
|
|
26
|
-
INTERVAL_MIN_5 = f"5{TIME_UNIT_MIN}"
|
|
27
|
-
INTERVAL_MIN_1 = f"1{TIME_UNIT_MIN}"
|
|
28
|
-
INTERVAL_SEC_30 = f"30{TIME_UNIT_SEC}"
|
|
29
|
-
INTERVAL_SEC_10 = f"10{TIME_UNIT_SEC}"
|
|
30
|
-
INTERVAL_SEC_1 = f"1{TIME_UNIT_SEC}"
|
|
31
|
-
INTERVAL_TICK = TIME_UNIT_TICK
|
|
32
|
-
|
|
33
|
-
_interval_units = {
|
|
34
|
-
INTERVAL_MONTH_1: TIME_UNIT_MONTH,
|
|
35
|
-
INTERVAL_WEEK_1: TIME_UNIT_WEEK,
|
|
36
|
-
INTERVAL_DAY_1: TIME_UNIT_DAY,
|
|
37
|
-
INTERVAL_HOUR_4: TIME_UNIT_HOUR,
|
|
38
|
-
INTERVAL_HOUR_1: TIME_UNIT_HOUR,
|
|
39
|
-
INTERVAL_MIN_30: TIME_UNIT_MIN,
|
|
40
|
-
INTERVAL_MIN_15: TIME_UNIT_MIN,
|
|
41
|
-
INTERVAL_MIN_10: TIME_UNIT_MIN,
|
|
42
|
-
INTERVAL_MIN_5: TIME_UNIT_MIN,
|
|
43
|
-
INTERVAL_MIN_1: TIME_UNIT_MIN,
|
|
44
|
-
INTERVAL_SEC_30: TIME_UNIT_SEC,
|
|
45
|
-
INTERVAL_SEC_10: TIME_UNIT_SEC,
|
|
46
|
-
INTERVAL_SEC_1: TIME_UNIT_SEC,
|
|
47
|
-
INTERVAL_TICK: TIME_UNIT_TICK,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
OFFER_SIDE_BID = "B"
|
|
51
|
-
OFFER_SIDE_ASK = "A"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _get_custom_logger(debug=False):
|
|
55
|
-
logger = logging.getLogger("DUKASCRIPT")
|
|
56
|
-
logger.setLevel(logging.DEBUG if debug else logging.INFO)
|
|
57
|
-
|
|
58
|
-
return logger
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _resample_to_nearest(
|
|
62
|
-
timestamp: datetime,
|
|
63
|
-
time_unit: str,
|
|
64
|
-
interval_value: int,
|
|
65
|
-
) -> datetime:
|
|
66
|
-
# Round to the nearest time unit based on the interval value
|
|
67
|
-
if time_unit == TIME_UNIT_SEC:
|
|
68
|
-
subtraction = timestamp.second % interval_value
|
|
69
|
-
return timestamp - timedelta(
|
|
70
|
-
seconds=subtraction,
|
|
71
|
-
microseconds=timestamp.microsecond,
|
|
72
|
-
)
|
|
73
|
-
elif time_unit == TIME_UNIT_MIN:
|
|
74
|
-
subtraction = timestamp.minute % interval_value
|
|
75
|
-
return timestamp - timedelta(
|
|
76
|
-
minutes=subtraction,
|
|
77
|
-
seconds=timestamp.second,
|
|
78
|
-
microseconds=timestamp.microsecond,
|
|
79
|
-
)
|
|
80
|
-
elif time_unit == TIME_UNIT_HOUR:
|
|
81
|
-
subtraction = timestamp.hour % interval_value
|
|
82
|
-
return timestamp - timedelta(
|
|
83
|
-
hours=subtraction,
|
|
84
|
-
minutes=timestamp.minute,
|
|
85
|
-
seconds=timestamp.second,
|
|
86
|
-
microseconds=timestamp.microsecond,
|
|
87
|
-
)
|
|
88
|
-
elif time_unit == TIME_UNIT_DAY:
|
|
89
|
-
subtraction = timestamp.day % interval_value
|
|
90
|
-
return timestamp - timedelta(
|
|
91
|
-
days=subtraction,
|
|
92
|
-
hours=timestamp.hour,
|
|
93
|
-
minutes=timestamp.minute,
|
|
94
|
-
seconds=timestamp.second,
|
|
95
|
-
microseconds=timestamp.microsecond,
|
|
96
|
-
)
|
|
97
|
-
elif time_unit == TIME_UNIT_WEEK:
|
|
98
|
-
subtraction = (timestamp.weekday() + 1) % (interval_value * 7)
|
|
99
|
-
return timestamp - timedelta(
|
|
100
|
-
days=subtraction,
|
|
101
|
-
hours=timestamp.hour,
|
|
102
|
-
minutes=timestamp.minute,
|
|
103
|
-
seconds=timestamp.second,
|
|
104
|
-
microseconds=timestamp.microsecond,
|
|
105
|
-
)
|
|
106
|
-
elif time_unit == TIME_UNIT_MONTH:
|
|
107
|
-
month = (timestamp.month // interval_value) + 1
|
|
108
|
-
return datetime(timestamp.year, month, 1, 0, 0, 0, 0, timestamp.tzinfo)
|
|
109
|
-
elif time_unit == TIME_UNIT_TICK:
|
|
110
|
-
return timestamp
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Iterable, TYPE_CHECKING
|
|
3
|
+
from .core import (
|
|
4
|
+
TimeUnit,
|
|
5
|
+
OfferSide,
|
|
6
|
+
Candle,
|
|
7
|
+
Tick,
|
|
8
|
+
to_dataframe,
|
|
9
|
+
create_logger,
|
|
10
|
+
create_session,
|
|
11
|
+
)
|
|
111
12
|
|
|
112
|
-
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import requests
|
|
15
|
+
from .core import DataFeed
|
|
113
16
|
|
|
114
17
|
|
|
115
|
-
|
|
18
|
+
__all__ = [
|
|
19
|
+
"fetch_candles",
|
|
20
|
+
"fetch_ticks",
|
|
21
|
+
"set_default_version",
|
|
22
|
+
"to_dataframe",
|
|
23
|
+
"create_logger",
|
|
24
|
+
"create_session",
|
|
25
|
+
"TimeUnit",
|
|
26
|
+
"OfferSide",
|
|
27
|
+
"Candle",
|
|
28
|
+
"Tick",
|
|
29
|
+
]
|
|
116
30
|
|
|
117
|
-
ohlc_df = ["timestamp", "open", "high", "low", "close", "volume"]
|
|
118
|
-
tick_df = ["timestamp", "bidPrice", "askPrice", "bidVolume", "askVolume"]
|
|
119
31
|
|
|
120
|
-
|
|
121
|
-
TIME_UNIT_DAY: ohlc_df,
|
|
122
|
-
TIME_UNIT_HOUR: ohlc_df,
|
|
123
|
-
TIME_UNIT_MIN: ohlc_df,
|
|
124
|
-
TIME_UNIT_MONTH: ohlc_df,
|
|
125
|
-
TIME_UNIT_SEC: ohlc_df,
|
|
126
|
-
TIME_UNIT_TICK: tick_df,
|
|
127
|
-
TIME_UNIT_WEEK: ohlc_df,
|
|
128
|
-
}[time_unit]
|
|
32
|
+
_default_version = 1
|
|
129
33
|
|
|
130
|
-
return df
|
|
131
34
|
|
|
35
|
+
def set_default_version(version: int):
|
|
36
|
+
global _default_version
|
|
37
|
+
if version not in (1, 2):
|
|
38
|
+
raise ValueError(f"Unsupported data feed version: {version}")
|
|
39
|
+
_default_version = version
|
|
132
40
|
|
|
133
|
-
def _fetch(
|
|
134
|
-
instrument: str,
|
|
135
|
-
interval: str,
|
|
136
|
-
offer_side: str,
|
|
137
|
-
last_update: int,
|
|
138
|
-
logger: logging.Logger = logging.getLogger(),
|
|
139
|
-
limit: int = None,
|
|
140
|
-
):
|
|
141
|
-
characters = string.ascii_letters + string.digits
|
|
142
|
-
jsonp = f"_callbacks____{''.join(random.choices(characters, k=9))}"
|
|
143
41
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
"jsonp": jsonp,
|
|
150
|
-
"last_update": f"{int(last_update)}",
|
|
151
|
-
"offer_side": f"{offer_side}",
|
|
152
|
-
"instrument": f"{instrument}",
|
|
153
|
-
"interval": f"{interval}",
|
|
154
|
-
}
|
|
42
|
+
def _create_data_feed(
|
|
43
|
+
version: int,
|
|
44
|
+
session: requests.Session,
|
|
45
|
+
**kw,
|
|
46
|
+
) -> DataFeed:
|
|
155
47
|
|
|
156
|
-
if
|
|
157
|
-
|
|
158
|
-
query_params["limit"] = f"{int(limit)}"
|
|
48
|
+
if version == 1:
|
|
49
|
+
from dukascopy_python.impl.v1 import DataFeedV1
|
|
159
50
|
|
|
160
|
-
|
|
51
|
+
return DataFeedV1(session=session)
|
|
161
52
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"Host": "freeserv.dukascopy.com",
|
|
165
|
-
"Referer": "https://freeserv.dukascopy.com/2.0/?path=chart/index&showUI=true&showTabs=true&showParameterToolbar=true&showOfferSide=true&allowInstrumentChange=true&allowPeriodChange=true&allowOfferSideChange=true&showAdditionalToolbar=true&showExportImportWorkspace=true&allowSocialSharing=true&showUndoRedoButtons=true&showDetachButton=true&presentationType=candle&axisX=true&axisY=true&legend=true&timeline=true&showDateSeparators=true&showZoom=true&showScrollButtons=true&showAutoShiftButton=true&crosshair=true&borders=false&freeMode=false&theme=Pastelle&uiColor=%23000&availableInstruments=l%3A&instrument=EUR/USD&period=5&offerSide=BID&timezone=0&live=true&allowPan=true&width=100%25&height=700&adv=popup&lang=en",
|
|
166
|
-
}
|
|
53
|
+
if version == 2:
|
|
54
|
+
from dukascopy_python.impl.v2 import DataFeedV2
|
|
167
55
|
|
|
168
|
-
|
|
56
|
+
return DataFeedV2(session=session)
|
|
169
57
|
|
|
170
|
-
|
|
58
|
+
raise ValueError(f"Unsupported data feed version: {version}")
|
|
171
59
|
|
|
172
|
-
jsonText = response.text.removeprefix(f"{jsonp}(").removesuffix(");")
|
|
173
60
|
|
|
174
|
-
|
|
61
|
+
def _validate_date_range(start: datetime, end: datetime) -> None:
|
|
62
|
+
if not isinstance(start, datetime) or not isinstance(end, datetime):
|
|
63
|
+
raise TypeError("start and end must be datetime objects")
|
|
175
64
|
|
|
65
|
+
start_aware = start.tzinfo is not None and start.utcoffset() is not None
|
|
66
|
+
end_aware = end.tzinfo is not None and end.utcoffset() is not None
|
|
176
67
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
try:
|
|
200
|
-
|
|
201
|
-
lastUpdates = _fetch(
|
|
202
|
-
instrument=instrument,
|
|
203
|
-
interval=interval,
|
|
204
|
-
offer_side=offer_side,
|
|
205
|
-
last_update=cursor,
|
|
206
|
-
limit=limit,
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
if not is_first_iteration and lastUpdates[0][0] == cursor:
|
|
210
|
-
lastUpdates = lastUpdates[1:]
|
|
211
|
-
|
|
212
|
-
if len(lastUpdates) < 1:
|
|
213
|
-
if end is not None:
|
|
214
|
-
break
|
|
215
|
-
else:
|
|
216
|
-
continue
|
|
217
|
-
|
|
218
|
-
for row in lastUpdates:
|
|
219
|
-
if end_timestamp is not None and row[0] > end_timestamp:
|
|
220
|
-
return
|
|
221
|
-
if interval == INTERVAL_TICK:
|
|
222
|
-
row[-1] = row[-1] / 1_000_000
|
|
223
|
-
row[-2] = row[-2] / 1_000_000
|
|
224
|
-
yield row
|
|
225
|
-
cursor = row[0]
|
|
226
|
-
|
|
227
|
-
logger.info(
|
|
228
|
-
f"current timestamp :{datetime.fromtimestamp(cursor/1000).isoformat()}"
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
no_of_retries = 0
|
|
232
|
-
is_first_iteration = False
|
|
233
|
-
|
|
234
|
-
except Exception as e:
|
|
235
|
-
import traceback
|
|
236
|
-
|
|
237
|
-
stacktrace = traceback.format_exc()
|
|
238
|
-
no_of_retries += 1
|
|
239
|
-
if max_retries is not None and (no_of_retries - 1) > max_retries:
|
|
240
|
-
logger.debug("error fetching")
|
|
241
|
-
logger.debug(e, stacktrace)
|
|
242
|
-
raise e
|
|
243
|
-
else:
|
|
244
|
-
logger.debug("an error occured", e)
|
|
245
|
-
logger.debug(e, stacktrace)
|
|
246
|
-
logger.debug("retrying")
|
|
247
|
-
sleep(1)
|
|
248
|
-
continue
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def fetch(
|
|
68
|
+
if start_aware != end_aware:
|
|
69
|
+
raise ValueError("start and end must both be timezone-aware or timezone-naive")
|
|
70
|
+
|
|
71
|
+
if start >= end:
|
|
72
|
+
raise ValueError(f"start must be earlier than end: start={start}, end={end}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _format_instrument_code(code: str, version: int):
|
|
76
|
+
from .instruments import INSTRUMENT_CODE_TO_DATA
|
|
77
|
+
|
|
78
|
+
meta_data = INSTRUMENT_CODE_TO_DATA.get(code)
|
|
79
|
+
|
|
80
|
+
if version == 1 and meta_data is not None:
|
|
81
|
+
return meta_data["name"], meta_data
|
|
82
|
+
|
|
83
|
+
if version == 2 and meta_data is not None:
|
|
84
|
+
return meta_data["code"], meta_data
|
|
85
|
+
|
|
86
|
+
raise ValueError(f"Unsupported data feed version: {version}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def fetch_candles(
|
|
252
90
|
instrument: str,
|
|
253
|
-
interval:
|
|
254
|
-
|
|
91
|
+
interval: int,
|
|
92
|
+
time_unit: TimeUnit,
|
|
93
|
+
offer_side: OfferSide,
|
|
255
94
|
start: datetime,
|
|
256
95
|
end: datetime,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
):
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
96
|
+
*,
|
|
97
|
+
version: int | None = None,
|
|
98
|
+
**kw,
|
|
99
|
+
) -> Iterable[Candle]:
|
|
100
|
+
|
|
101
|
+
from .core import create_session, get_logger, use_logger
|
|
102
|
+
|
|
103
|
+
_validate_date_range(start, end)
|
|
104
|
+
|
|
105
|
+
if version is None:
|
|
106
|
+
version = _default_version
|
|
107
|
+
|
|
108
|
+
instrument, meta_data = instrument, {}
|
|
109
|
+
|
|
110
|
+
if kw.get("validate_instrument_name", True):
|
|
111
|
+
instrument, meta_data = _format_instrument_code(instrument, version)
|
|
112
|
+
|
|
113
|
+
create_session_fn = lambda: create_session(retries=7)
|
|
114
|
+
if kw.get("create_session") is not None:
|
|
115
|
+
create_session_fn = kw.get("create_session")
|
|
116
|
+
|
|
117
|
+
logger = get_logger()
|
|
118
|
+
|
|
119
|
+
if kw.get("logger") is not None:
|
|
120
|
+
logger = kw.get("logger")
|
|
121
|
+
|
|
122
|
+
with create_session_fn() as session, use_logger(logger):
|
|
123
|
+
|
|
124
|
+
datafeed = _create_data_feed(
|
|
125
|
+
version=version,
|
|
126
|
+
session=session,
|
|
127
|
+
**kw,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return datafeed.fetch_candles(
|
|
131
|
+
instrument=instrument,
|
|
132
|
+
interval=interval,
|
|
133
|
+
time_unit=time_unit,
|
|
134
|
+
offer_side=offer_side,
|
|
135
|
+
start=start,
|
|
136
|
+
end=end,
|
|
137
|
+
price_scale=meta_data.get("priceScale"),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def fetch_ticks(
|
|
292
142
|
instrument: str,
|
|
293
|
-
interval_value: int,
|
|
294
|
-
time_unit: str,
|
|
295
|
-
offer_side: str,
|
|
296
143
|
start: datetime,
|
|
297
144
|
end: datetime,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
):
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
df["timestamp"],
|
|
330
|
-
unit="ms",
|
|
331
|
-
utc=True,
|
|
332
|
-
)
|
|
333
|
-
df = df.set_index("timestamp")
|
|
334
|
-
|
|
335
|
-
datafeed = _stream(
|
|
336
|
-
instrument=instrument,
|
|
337
|
-
interval=INTERVAL_TICK,
|
|
338
|
-
offer_side=offer_side,
|
|
339
|
-
start=start,
|
|
340
|
-
end=end,
|
|
341
|
-
max_retries=max_retries,
|
|
342
|
-
limit=limit,
|
|
343
|
-
logger=logger,
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
yield df
|
|
347
|
-
|
|
348
|
-
last_tick_count = 0
|
|
349
|
-
|
|
350
|
-
for tick_count, row in enumerate(datafeed, 0):
|
|
351
|
-
|
|
352
|
-
timestamp = _resample_to_nearest(
|
|
353
|
-
pd.to_datetime(
|
|
354
|
-
row[0],
|
|
355
|
-
unit="ms",
|
|
356
|
-
utc=True,
|
|
357
|
-
),
|
|
358
|
-
time_unit,
|
|
359
|
-
interval_value,
|
|
145
|
+
*,
|
|
146
|
+
version: int | None = None,
|
|
147
|
+
**kw,
|
|
148
|
+
) -> Iterable[Tick]:
|
|
149
|
+
from .core import create_session, get_logger, use_logger
|
|
150
|
+
|
|
151
|
+
_validate_date_range(start, end)
|
|
152
|
+
|
|
153
|
+
if version is None:
|
|
154
|
+
version = _default_version
|
|
155
|
+
|
|
156
|
+
instrument, meta_data = instrument, {}
|
|
157
|
+
|
|
158
|
+
if kw.get("validate_instrument_name", True):
|
|
159
|
+
instrument, meta_data = _format_instrument_code(instrument, version)
|
|
160
|
+
|
|
161
|
+
create_session_fn = lambda: create_session(retries=7)
|
|
162
|
+
if kw.get("create_session") is not None:
|
|
163
|
+
create_session_fn = kw.get("create_session")
|
|
164
|
+
|
|
165
|
+
logger = get_logger()
|
|
166
|
+
|
|
167
|
+
if kw.get("logger") is not None:
|
|
168
|
+
logger = kw.get("logger")
|
|
169
|
+
|
|
170
|
+
with create_session_fn() as session, use_logger(logger):
|
|
171
|
+
|
|
172
|
+
datafeed = _create_data_feed(
|
|
173
|
+
version=version,
|
|
174
|
+
session=session,
|
|
175
|
+
**kw,
|
|
360
176
|
)
|
|
361
177
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
*row[1:],
|
|
368
|
-
]
|
|
369
|
-
yield df
|
|
370
|
-
continue
|
|
371
|
-
|
|
372
|
-
new_tick_count = tick_count // interval_value
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
time_unit != TIME_UNIT_TICK
|
|
376
|
-
and timestamp.timestamp() != last_timestamp.timestamp()
|
|
377
|
-
) or (time_unit == TIME_UNIT_TICK and last_tick_count != new_tick_count):
|
|
378
|
-
if open is not None:
|
|
379
|
-
df.loc[last_timestamp] = [
|
|
380
|
-
open,
|
|
381
|
-
high,
|
|
382
|
-
low,
|
|
383
|
-
close,
|
|
384
|
-
volume,
|
|
385
|
-
]
|
|
386
|
-
|
|
387
|
-
yield df
|
|
388
|
-
last_timestamp = timestamp
|
|
389
|
-
last_tick_count = new_tick_count
|
|
390
|
-
open = None
|
|
391
|
-
|
|
392
|
-
if open is None:
|
|
393
|
-
open = row[price_index]
|
|
394
|
-
close = open
|
|
395
|
-
low = open
|
|
396
|
-
high = open
|
|
397
|
-
volume = 0
|
|
398
|
-
|
|
399
|
-
close = row[price_index]
|
|
400
|
-
high = max(high, close)
|
|
401
|
-
low = min(low, close)
|
|
402
|
-
volume += row[volume_index]
|
|
403
|
-
|
|
404
|
-
df.loc[timestamp] = [
|
|
405
|
-
open,
|
|
406
|
-
high,
|
|
407
|
-
low,
|
|
408
|
-
close,
|
|
409
|
-
volume,
|
|
410
|
-
]
|
|
411
|
-
|
|
412
|
-
yield df
|
|
178
|
+
return datafeed.fetch_ticks(
|
|
179
|
+
instrument=instrument,
|
|
180
|
+
start=start,
|
|
181
|
+
end=end,
|
|
182
|
+
)
|