|
103 | 103 | import requests |
104 | 104 | from dateutil.tz import tzlocal |
105 | 105 | from mako.template import Template # Mako Templates for Python (https://www.makotemplates.org/). Mako is a template library provides simple syntax and maximum performance. |
106 | | -from concurrent.futures import ThreadPoolExecutor |
| 106 | +from concurrent.futures import ThreadPoolExecutor, as_completed |
107 | 107 |
|
108 | 108 | # Add the current dir for the local run: |
109 | 109 | packageDir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) |
@@ -3037,6 +3037,26 @@ def _InfoStr(data1: dict, data2: dict, data3: dict, data4: dict, cur: str = "") |
3037 | 3037 |
|
3038 | 3038 | return ops, customStat |
3039 | 3039 |
|
| 3040 | + def _DownloadHistoryBlock(self, blockStart, blockEnd, interval): |
| 3041 | + """ |
| 3042 | + Internal method for downloading a single history block (used in threads). |
| 3043 | + """ |
| 3044 | + # REST API for request: https://tinkoff.github.io/investAPI/swagger-ui/#/MarketDataService/MarketDataService_GetCandles |
| 3045 | + historyURL = self.server + r"/tinkoff.public.invest.api.contract.v1.MarketDataService/GetCandles" |
| 3046 | + |
| 3047 | + self.body = str({ |
| 3048 | + "figi": self._figi, |
| 3049 | + "from": blockStart.strftime(TKS_DATE_TIME_FORMAT), |
| 3050 | + "to": blockEnd.strftime(TKS_DATE_TIME_FORMAT), |
| 3051 | + "interval": TKS_CANDLE_INTERVALS[interval][0] |
| 3052 | + }) |
| 3053 | + |
| 3054 | + if self.moreDebug: |
| 3055 | + threadName = threading.current_thread().name |
| 3056 | + uLogger.debug(f"Started downloading block [{blockStart} — {blockEnd}] in thread [{threadName}]") |
| 3057 | + |
| 3058 | + return blockStart, blockEnd, self.SendAPIRequest(historyURL, reqType="POST", methodName="GetCandles") |
| 3059 | + |
3040 | 3060 | def History(self, start: str = None, end: str = None, interval: str = "hour", onlyMissing: bool = False, csvSep: str = ",", show: bool = True) -> pd.DataFrame: |
3041 | 3061 | """ |
3042 | 3062 | This method returns last history candles of the current instrument defined by `ticker` or `figi` (FIGI id). |
@@ -3133,48 +3153,41 @@ def History(self, start: str = None, end: str = None, interval: str = "hour", on |
3133 | 3153 | uLogger.error("📝 An issue occurred while loading file [{}] — possibly due to incorrect format. It will be rewritten. Message: {}".format(os.path.abspath(self.historyFile), e)) |
3134 | 3154 |
|
3135 | 3155 | responseJSONs = [] # raw history blocks of data |
3136 | | - |
| 3156 | + blockRanges = [] |
3137 | 3157 | blockEnd = dtEnd |
| 3158 | + |
3138 | 3159 | for item in range(blocks): |
3139 | 3160 | tail = length % TKS_CANDLE_INTERVALS[interval][2] if item + 1 == blocks else TKS_CANDLE_INTERVALS[interval][2] |
3140 | 3161 | blockStart = blockEnd - timedelta(minutes=TKS_CANDLE_INTERVALS[interval][1] * tail) |
3141 | 3162 |
|
3142 | | - if show and self.moreDebug: |
3143 | | - uLogger.debug("[Block #{}/{}] time period: [{}] UTC - [{}] UTC".format( |
3144 | | - item + 1, blocks, blockStart.strftime(TKS_DATE_TIME_FORMAT), blockEnd.strftime(TKS_DATE_TIME_FORMAT), |
3145 | | - )) |
| 3163 | + if blockStart != blockEnd: |
| 3164 | + blockRanges.append((blockStart, blockEnd)) |
3146 | 3165 |
|
3147 | | - if blockStart == blockEnd: |
3148 | | - if show and self.moreDebug: |
3149 | | - uLogger.debug("Skipped this zero-length block...") |
| 3166 | + blockEnd = blockStart |
3150 | 3167 |
|
3151 | | - else: |
3152 | | - # REST API for request: https://tinkoff.github.io/investAPI/swagger-ui/#/MarketDataService/MarketDataService_GetCandles |
3153 | | - historyURL = self.server + r"/tinkoff.public.invest.api.contract.v1.MarketDataService/GetCandles" |
3154 | | - self.body = str({ |
3155 | | - "figi": self._figi, |
3156 | | - "from": blockStart.strftime(TKS_DATE_TIME_FORMAT), |
3157 | | - "to": blockEnd.strftime(TKS_DATE_TIME_FORMAT), |
3158 | | - "interval": TKS_CANDLE_INTERVALS[interval][0] |
3159 | | - }) |
3160 | | - responseJSON = self.SendAPIRequest(historyURL, reqType="POST") |
| 3168 | + with ThreadPoolExecutor(max_workers=min(CPU_USAGES, len(blockRanges))) as executor: |
| 3169 | + futures = [executor.submit(self._DownloadHistoryBlock, bStart, bEnd, interval) for bStart, bEnd in blockRanges] |
| 3170 | + |
| 3171 | + for future in as_completed(futures): |
| 3172 | + try: |
| 3173 | + bStart, bEnd, responseJSON = future.result() |
3161 | 3174 |
|
3162 | | - if "code" in responseJSON.keys(): |
3163 | | - if show and self.moreDebug: |
3164 | | - uLogger.debug("An issue occurred and block #{}/{} is empty".format(item + 1, blocks)) |
| 3175 | + if "code" in responseJSON: |
| 3176 | + if show and self.moreDebug: |
| 3177 | + uLogger.debug(f"Block [{bStart} — {bEnd}] returned an error or empty result") |
3165 | 3178 |
|
3166 | | - else: |
3167 | | - if "candles" in responseJSON.keys(): |
| 3179 | + elif "candles" in responseJSON: |
3168 | 3180 | if start is not None and (start.lower() == "yesterday" or start == end) and interval == "day" and len(responseJSON["candles"]) > 1: |
3169 | 3181 | responseJSON["candles"] = responseJSON["candles"][:-1] # removes last candle for "yesterday" request |
3170 | 3182 |
|
3171 | | - responseJSONs = responseJSON["candles"] + responseJSONs # add more old history behind newest dates |
| 3183 | + responseJSONs.extend(responseJSON["candles"]) |
3172 | 3184 |
|
3173 | 3185 | else: |
3174 | 3186 | if show and self.moreDebug: |
3175 | | - uLogger.debug("`candles` key not in responseJSON keys! Block #{}/{} is empty".format(item + 1, blocks)) |
| 3187 | + uLogger.debug(f"`candles` key missing in block [{bStart} — {bEnd}]") |
3176 | 3188 |
|
3177 | | - blockEnd = blockStart |
| 3189 | + except Exception as e: |
| 3190 | + uLogger.debug(f"❌ Error while downloading history block: {e}") |
3178 | 3191 |
|
3179 | 3192 | if responseJSONs: |
3180 | 3193 | tempHistory = pd.DataFrame( |
@@ -3212,6 +3225,12 @@ def History(self, start: str = None, end: str = None, interval: str = "hour", on |
3212 | 3225 | else: |
3213 | 3226 | history = tempHistory # if no `--only-missing` key then load full data from server |
3214 | 3227 |
|
| 3228 | + # Sorting blocks by date and time: |
| 3229 | + history["__dt"] = pd.to_datetime(history["date"] + " " + history["time"], format="%Y.%m.%d %H:%M") |
| 3230 | + history.sort_values(by="__dt", inplace=True) |
| 3231 | + history.drop(columns=["__dt"], inplace=True) |
| 3232 | + history.reset_index(drop=True, inplace=True) |
| 3233 | + |
3215 | 3234 | if show and self.moreDebug: |
3216 | 3235 | uLogger.debug("Last 3 rows of received history:\n{}".format(pd.DataFrame.to_string(history[["date", "time", "open", "high", "low", "close", "volume"]][-3:], max_cols=20, index=False))) |
3217 | 3236 |
|
|
0 commit comments