Make `ib` failed history requests more debug-able
Been hitting wayy too many cases like this so, finally put my foot down
and stuck in a buncha helper code to figure why (especially for gappy
ass pennies) this can/is happening XD
inside the `.ib.api.Client()`:
- in `.bars()` pack all `.reqHistoricalDataAsync()` kwargs into a dict such that
  wen/if we rx a blank frame we can enter pdb and make sync calls using
  a little `get_hist()` closure from the REPL.
  - tidy up type annots a bit too.
- add a new `.maybe_get_head_time()` meth which will return `None` when
  the dt can't be retrieved for the contract.
inside `.feed.open_history_client()`:
- use new `Client.maybe_get_head_time()` and only do `DataUnavailable`
  raises when the request `end_dt` is actually earlier.
- when `get_bars()` returns a `None` and the `head_dt` is not earlier
  then the `end_dt` submitted, raise a `NoData` with more `.info: dict`.
- deliver a new `frame_types: dict[int, pendulum.Duration]` as part
  of the yielded `config: dict`.
- in `.get_bars()` always assume a `tuple` returned from
  `Client.bars()`.
  - return a `None` on empty frames instead of raising `NoData` at this
    call frame.
- do more explicit imports from `pendulum` for brevity.
inside `.brokers._util`:
- make `NoData` take an `info: dict` as input to allow backends to pack
  in empty frame meta-data for (eventual) use in the tsp back-filling
  layer.
			
			
				distribute_dis
			
			
		
							parent
							
								
									c82ca812a8
								
							
						
					
					
						commit
						9be29a707d
					
				| 
						 | 
					@ -50,6 +50,7 @@ class SymbolNotFound(BrokerError):
 | 
				
			||||||
    "Symbol not found by broker search"
 | 
					    "Symbol not found by broker search"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO: these should probably be moved to `.tsp/.data`?
 | 
				
			||||||
class NoData(BrokerError):
 | 
					class NoData(BrokerError):
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    Symbol data not permitted or no data
 | 
					    Symbol data not permitted or no data
 | 
				
			||||||
| 
						 | 
					@ -59,14 +60,15 @@ class NoData(BrokerError):
 | 
				
			||||||
    def __init__(
 | 
					    def __init__(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        *args,
 | 
					        *args,
 | 
				
			||||||
        frame_size: int = 1000,
 | 
					        info: dict,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        super().__init__(*args)
 | 
					        super().__init__(*args)
 | 
				
			||||||
 | 
					        self.info: dict = info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # when raised, machinery can check if the backend
 | 
					        # when raised, machinery can check if the backend
 | 
				
			||||||
        # set a "frame size" for doing datetime calcs.
 | 
					        # set a "frame size" for doing datetime calcs.
 | 
				
			||||||
        self.frame_size: int = 1000
 | 
					        # self.frame_size: int = 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DataUnavailable(BrokerError):
 | 
					class DataUnavailable(BrokerError):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,7 +41,6 @@ import time
 | 
				
			||||||
from typing import (
 | 
					from typing import (
 | 
				
			||||||
    Any,
 | 
					    Any,
 | 
				
			||||||
    Callable,
 | 
					    Callable,
 | 
				
			||||||
    Union,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from types import SimpleNamespace
 | 
					from types import SimpleNamespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -312,8 +311,8 @@ class Client:
 | 
				
			||||||
        fqme: str,
 | 
					        fqme: str,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # EST in ISO 8601 format is required... below is EPOCH
 | 
					        # EST in ISO 8601 format is required... below is EPOCH
 | 
				
			||||||
        start_dt: Union[datetime, str] = "1970-01-01T00:00:00.000000-05:00",
 | 
					        start_dt: datetime | str = "1970-01-01T00:00:00.000000-05:00",
 | 
				
			||||||
        end_dt: Union[datetime, str] = "",
 | 
					        end_dt: datetime | str = "",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # ohlc sample period in seconds
 | 
					        # ohlc sample period in seconds
 | 
				
			||||||
        sample_period_s: int = 1,
 | 
					        sample_period_s: int = 1,
 | 
				
			||||||
| 
						 | 
					@ -339,17 +338,13 @@ class Client:
 | 
				
			||||||
            default_dt_duration,
 | 
					            default_dt_duration,
 | 
				
			||||||
        ) = _samplings[sample_period_s]
 | 
					        ) = _samplings[sample_period_s]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dt_duration: DateTime = (
 | 
					        dt_duration: Duration = (
 | 
				
			||||||
            duration
 | 
					            duration
 | 
				
			||||||
            or default_dt_duration
 | 
					            or default_dt_duration
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # TODO: maybe remove all this?
 | 
				
			||||||
        global _enters
 | 
					        global _enters
 | 
				
			||||||
        log.info(
 | 
					 | 
				
			||||||
            f"REQUESTING {ib_duration_str}'s worth {bar_size} BARS\n"
 | 
					 | 
				
			||||||
            f'{_enters} @ end={end_dt}"'
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not end_dt:
 | 
					        if not end_dt:
 | 
				
			||||||
            end_dt = ''
 | 
					            end_dt = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -358,8 +353,8 @@ class Client:
 | 
				
			||||||
        contract: Contract = (await self.find_contracts(fqme))[0]
 | 
					        contract: Contract = (await self.find_contracts(fqme))[0]
 | 
				
			||||||
        bars_kwargs.update(getattr(contract, 'bars_kwargs', {}))
 | 
					        bars_kwargs.update(getattr(contract, 'bars_kwargs', {}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bars = await self.ib.reqHistoricalDataAsync(
 | 
					        kwargs: dict[str, Any] = dict(
 | 
				
			||||||
            contract,
 | 
					            contract=contract,
 | 
				
			||||||
            endDateTime=end_dt,
 | 
					            endDateTime=end_dt,
 | 
				
			||||||
            formatDate=2,
 | 
					            formatDate=2,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -381,17 +376,38 @@ class Client:
 | 
				
			||||||
            # whatToShow='MIDPOINT',
 | 
					            # whatToShow='MIDPOINT',
 | 
				
			||||||
            # whatToShow='TRADES',
 | 
					            # whatToShow='TRADES',
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        log.info(
 | 
				
			||||||
 | 
					            f'REQUESTING {ib_duration_str} worth {bar_size} BARS\n'
 | 
				
			||||||
 | 
					            f'fqme: {fqme}\n'
 | 
				
			||||||
 | 
					            f'global _enters: {_enters}\n'
 | 
				
			||||||
 | 
					            f'kwargs: {pformat(kwargs)}\n'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bars = await self.ib.reqHistoricalDataAsync(
 | 
				
			||||||
 | 
					            **kwargs,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # tail case if no history for range or none prior.
 | 
					        # tail case if no history for range or none prior.
 | 
				
			||||||
        if not bars:
 | 
					        if not bars:
 | 
				
			||||||
            # NOTE: there's 2 cases here to handle (and this should be
 | 
					            # NOTE: there's actually 3 cases here to handle (and
 | 
				
			||||||
            # read alongside the implementation of
 | 
					            # this should be read alongside the implementation of
 | 
				
			||||||
            # ``.reqHistoricalDataAsync()``):
 | 
					            # `.reqHistoricalDataAsync()`):
 | 
				
			||||||
            # - no data is returned for the period likely due to
 | 
					 | 
				
			||||||
            # a weekend, holiday or other non-trading period prior to
 | 
					 | 
				
			||||||
            # ``end_dt`` which exceeds the ``duration``,
 | 
					 | 
				
			||||||
            # - a timeout occurred in which case insync internals return
 | 
					            # - a timeout occurred in which case insync internals return
 | 
				
			||||||
            #   an empty list thing with bars.clear()...
 | 
					            #   an empty list thing with bars.clear()...
 | 
				
			||||||
 | 
					            # - no data exists for the period likely due to
 | 
				
			||||||
 | 
					            #   a weekend, holiday or other non-trading period prior to
 | 
				
			||||||
 | 
					            #   ``end_dt`` which exceeds the ``duration``,
 | 
				
			||||||
 | 
					            # - LITERALLY this is the start of the mkt's history!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # sync requester for debugging empty frame cases
 | 
				
			||||||
 | 
					            def get_hist():
 | 
				
			||||||
 | 
					                return self.ib.reqHistoricalData(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            assert get_hist
 | 
				
			||||||
 | 
					            import pdbp
 | 
				
			||||||
 | 
					            pdbp.set_trace()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return [], np.empty(0), dt_duration
 | 
					            return [], np.empty(0), dt_duration
 | 
				
			||||||
            # TODO: we could maybe raise ``NoData`` instead if we
 | 
					            # TODO: we could maybe raise ``NoData`` instead if we
 | 
				
			||||||
            # rewrite the method in the first case? right now there's no
 | 
					            # rewrite the method in the first case? right now there's no
 | 
				
			||||||
| 
						 | 
					@ -444,7 +460,7 @@ class Client:
 | 
				
			||||||
                r_bars.extend(bars)
 | 
					                r_bars.extend(bars)
 | 
				
			||||||
                bars = r_bars
 | 
					                bars = r_bars
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        nparr = bars_to_np(bars)
 | 
					        nparr: np.ndarray = bars_to_np(bars)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # timestep should always be at least as large as the
 | 
					        # timestep should always be at least as large as the
 | 
				
			||||||
        # period step.
 | 
					        # period step.
 | 
				
			||||||
| 
						 | 
					@ -457,9 +473,17 @@ class Client:
 | 
				
			||||||
                'time steps which are shorter then expected?!"'
 | 
					                'time steps which are shorter then expected?!"'
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            # OOF: this will break teardown?
 | 
					            # OOF: this will break teardown?
 | 
				
			||||||
 | 
					            # -[ ] check if it's greenback
 | 
				
			||||||
 | 
					            # -[ ] why tf are we leaking shm entries..
 | 
				
			||||||
 | 
					            # -[ ] make a test on the debugging asyncio testing
 | 
				
			||||||
 | 
					            #    branch..
 | 
				
			||||||
            # breakpoint()
 | 
					            # breakpoint()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return bars, nparr, dt_duration
 | 
					        return (
 | 
				
			||||||
 | 
					            bars,
 | 
				
			||||||
 | 
					            nparr,
 | 
				
			||||||
 | 
					            dt_duration,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def con_deats(
 | 
					    async def con_deats(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
| 
						 | 
					@ -803,6 +827,23 @@ class Client:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return contracts
 | 
					        return contracts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def maybe_get_head_time(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        fqme: str,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ) -> datetime | None:
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Return the first datetime stamp for `fqme` or `None`
 | 
				
			||||||
 | 
					        on request failure.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            head_dt: datetime = await self.get_head_time(fqme=fqme)
 | 
				
			||||||
 | 
					            return head_dt
 | 
				
			||||||
 | 
					        except RequestError:
 | 
				
			||||||
 | 
					            log.warning(f'Unable to get head time: {fqme} ?')
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def get_head_time(
 | 
					    async def get_head_time(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        fqme: str,
 | 
					        fqme: str,
 | 
				
			||||||
| 
						 | 
					@ -1391,7 +1432,7 @@ class MethodProxy:
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        pattern: str,
 | 
					        pattern: str,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> Union[dict[str, Any], trio.Event]:
 | 
					    ) -> dict[str, Any] | trio.Event:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ev = self.event_table.get(pattern)
 | 
					        ev = self.event_table.get(pattern)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,13 @@ from typing import (
 | 
				
			||||||
from async_generator import aclosing
 | 
					from async_generator import aclosing
 | 
				
			||||||
import ib_insync as ibis
 | 
					import ib_insync as ibis
 | 
				
			||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
import pendulum
 | 
					from pendulum import (
 | 
				
			||||||
 | 
					    now,
 | 
				
			||||||
 | 
					    from_timestamp,
 | 
				
			||||||
 | 
					    # DateTime,
 | 
				
			||||||
 | 
					    Duration,
 | 
				
			||||||
 | 
					    duration as mk_duration,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
import tractor
 | 
					import tractor
 | 
				
			||||||
import trio
 | 
					import trio
 | 
				
			||||||
from trio_typing import TaskStatus
 | 
					from trio_typing import TaskStatus
 | 
				
			||||||
| 
						 | 
					@ -46,10 +52,9 @@ from piker.accounting import (
 | 
				
			||||||
    MktPair,
 | 
					    MktPair,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from piker.data.validate import FeedInit
 | 
					from piker.data.validate import FeedInit
 | 
				
			||||||
from .._util import (
 | 
					from piker.brokers._util import (
 | 
				
			||||||
    NoData,
 | 
					    NoData,
 | 
				
			||||||
    DataUnavailable,
 | 
					    DataUnavailable,
 | 
				
			||||||
    SymbolNotFound,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .api import (
 | 
					from .api import (
 | 
				
			||||||
    # _adhoc_futes_set,
 | 
					    # _adhoc_futes_set,
 | 
				
			||||||
| 
						 | 
					@ -160,13 +165,13 @@ async def open_history_client(
 | 
				
			||||||
        head_dt: None | datetime = None
 | 
					        head_dt: None | datetime = None
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            # fx cons seem to not provide this endpoint?
 | 
					            # fx cons seem to not provide this endpoint?
 | 
				
			||||||
 | 
					            # TODO: guard against all contract types which don't
 | 
				
			||||||
 | 
					            # support it?
 | 
				
			||||||
            'idealpro' not in fqme
 | 
					            'idealpro' not in fqme
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            try:
 | 
					            head_dt: datetime | None = await proxy.maybe_get_head_time(
 | 
				
			||||||
                head_dt = await proxy.get_head_time(fqme=fqme)
 | 
					                fqme=fqme
 | 
				
			||||||
            except RequestError:
 | 
					            )
 | 
				
			||||||
                log.warning(f'Unable to get head time: {fqme} ?')
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        async def get_hist(
 | 
					        async def get_hist(
 | 
				
			||||||
            timeframe: float,
 | 
					            timeframe: float,
 | 
				
			||||||
| 
						 | 
					@ -206,17 +211,26 @@ async def open_history_client(
 | 
				
			||||||
            # could be trying to retreive bars over weekend
 | 
					            # could be trying to retreive bars over weekend
 | 
				
			||||||
            if out is None:
 | 
					            if out is None:
 | 
				
			||||||
                log.error(f"Can't grab bars starting at {end_dt}!?!?")
 | 
					                log.error(f"Can't grab bars starting at {end_dt}!?!?")
 | 
				
			||||||
                raise NoData(
 | 
					 | 
				
			||||||
                    f'{end_dt}',
 | 
					 | 
				
			||||||
                    # frame_size=2000,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (
 | 
					                if (
 | 
				
			||||||
                    end_dt
 | 
					                    end_dt
 | 
				
			||||||
                    and head_dt
 | 
					                    and head_dt
 | 
				
			||||||
                    and end_dt <= head_dt
 | 
					                    and end_dt <= head_dt
 | 
				
			||||||
                ):
 | 
					                ):
 | 
				
			||||||
                raise DataUnavailable(f'First timestamp is {head_dt}')
 | 
					                    raise DataUnavailable(
 | 
				
			||||||
 | 
					                        f'First timestamp is {head_dt}\n'
 | 
				
			||||||
 | 
					                        f'But {end_dt} was requested..'
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    raise NoData(
 | 
				
			||||||
 | 
					                        info={
 | 
				
			||||||
 | 
					                            'fqme': fqme,
 | 
				
			||||||
 | 
					                            'head_dt': head_dt,
 | 
				
			||||||
 | 
					                            'start_dt': start_dt,
 | 
				
			||||||
 | 
					                            'end_dt': end_dt,
 | 
				
			||||||
 | 
					                            'timedout': timedout,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # also see return type for `get_bars()`
 | 
					            # also see return type for `get_bars()`
 | 
				
			||||||
            bars: ibis.objects.BarDataList
 | 
					            bars: ibis.objects.BarDataList
 | 
				
			||||||
| 
						 | 
					@ -249,7 +263,18 @@ async def open_history_client(
 | 
				
			||||||
        # quite sure why.. needs some tinkering and probably
 | 
					        # quite sure why.. needs some tinkering and probably
 | 
				
			||||||
        # a lookthrough of the ``ib_insync`` machinery, for eg. maybe
 | 
					        # a lookthrough of the ``ib_insync`` machinery, for eg. maybe
 | 
				
			||||||
        # we have to do the batch queries on the `asyncio` side?
 | 
					        # we have to do the batch queries on the `asyncio` side?
 | 
				
			||||||
        yield get_hist, {'erlangs': 1, 'rate': 3}
 | 
					        yield (
 | 
				
			||||||
 | 
					            get_hist,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                'erlangs': 1,  # max conc reqs
 | 
				
			||||||
 | 
					                'rate': 3,  # max req rate
 | 
				
			||||||
 | 
					                'frame_types': {  # expected frame sizes
 | 
				
			||||||
 | 
					                    1: mk_duration(seconds=2e3),
 | 
				
			||||||
 | 
					                    60: mk_duration(days=2),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_pacing: str = (
 | 
					_pacing: str = (
 | 
				
			||||||
| 
						 | 
					@ -394,7 +419,11 @@ async def get_bars(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while _failed_resets < max_failed_resets:
 | 
					        while _failed_resets < max_failed_resets:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                out = await proxy.bars(
 | 
					                (
 | 
				
			||||||
 | 
					                    bars,
 | 
				
			||||||
 | 
					                    bars_array,
 | 
				
			||||||
 | 
					                    dt_duration,
 | 
				
			||||||
 | 
					                ) = await proxy.bars(
 | 
				
			||||||
                    fqme=fqme,
 | 
					                    fqme=fqme,
 | 
				
			||||||
                    end_dt=end_dt,
 | 
					                    end_dt=end_dt,
 | 
				
			||||||
                    sample_period_s=timeframe,
 | 
					                    sample_period_s=timeframe,
 | 
				
			||||||
| 
						 | 
					@ -405,13 +434,6 @@ async def get_bars(
 | 
				
			||||||
                    # current impl) to detect a cancel case.
 | 
					                    # current impl) to detect a cancel case.
 | 
				
			||||||
                    # timeout=timeout,
 | 
					                    # timeout=timeout,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                if out is None:
 | 
					 | 
				
			||||||
                    raise NoData(f'{end_dt}')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                bars, bars_array, dt_duration = out
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if bars_array is None:
 | 
					 | 
				
			||||||
                    raise SymbolNotFound(fqme)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # not enough bars signal, likely due to venue
 | 
					                # not enough bars signal, likely due to venue
 | 
				
			||||||
                # operational gaps.
 | 
					                # operational gaps.
 | 
				
			||||||
| 
						 | 
					@ -425,11 +447,16 @@ async def get_bars(
 | 
				
			||||||
                            f'end_dt: {end_dt}\n'
 | 
					                            f'end_dt: {end_dt}\n'
 | 
				
			||||||
                            f'duration: {dt_duration}\n'
 | 
					                            f'duration: {dt_duration}\n'
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        raise NoData(f'{end_dt}')
 | 
					                        result = None
 | 
				
			||||||
 | 
					                        return None
 | 
				
			||||||
 | 
					                        # raise NoData(
 | 
				
			||||||
 | 
					                        #     f'{fqme}\n'
 | 
				
			||||||
 | 
					                        #     f'end_dt:{end_dt}\n'
 | 
				
			||||||
 | 
					                        # )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        dur_s: float = len(bars) * timeframe
 | 
					                        dur_s: float = len(bars) * timeframe
 | 
				
			||||||
                        bars_dur = pendulum.Duration(seconds=dur_s)
 | 
					                        bars_dur = Duration(seconds=dur_s)
 | 
				
			||||||
                        dt_dur_s: float = dt_duration.in_seconds()
 | 
					                        dt_dur_s: float = dt_duration.in_seconds()
 | 
				
			||||||
                        if dur_s < dt_dur_s:
 | 
					                        if dur_s < dt_dur_s:
 | 
				
			||||||
                            log.warning(
 | 
					                            log.warning(
 | 
				
			||||||
| 
						 | 
					@ -459,10 +486,10 @@ async def get_bars(
 | 
				
			||||||
                            # continue
 | 
					                            # continue
 | 
				
			||||||
                            # await tractor.pause()
 | 
					                            # await tractor.pause()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                first_dt = pendulum.from_timestamp(
 | 
					                first_dt = from_timestamp(
 | 
				
			||||||
                    bars[0].date.timestamp())
 | 
					                    bars[0].date.timestamp())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                last_dt = pendulum.from_timestamp(
 | 
					                last_dt = from_timestamp(
 | 
				
			||||||
                    bars[-1].date.timestamp())
 | 
					                    bars[-1].date.timestamp())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                time = bars_array['time']
 | 
					                time = bars_array['time']
 | 
				
			||||||
| 
						 | 
					@ -475,6 +502,7 @@ async def get_bars(
 | 
				
			||||||
                if data_cs:
 | 
					                if data_cs:
 | 
				
			||||||
                    data_cs.cancel()
 | 
					                    data_cs.cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # NOTE: setting this is critical!
 | 
				
			||||||
                result = (
 | 
					                result = (
 | 
				
			||||||
                    bars,  # ib native
 | 
					                    bars,  # ib native
 | 
				
			||||||
                    bars_array,  # numpy
 | 
					                    bars_array,  # numpy
 | 
				
			||||||
| 
						 | 
					@ -485,6 +513,7 @@ async def get_bars(
 | 
				
			||||||
                # signal data reset loop parent task
 | 
					                # signal data reset loop parent task
 | 
				
			||||||
                result_ready.set()
 | 
					                result_ready.set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # NOTE: this isn't getting collected anywhere!
 | 
				
			||||||
                return result
 | 
					                return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            except RequestError as err:
 | 
					            except RequestError as err:
 | 
				
			||||||
| 
						 | 
					@ -510,7 +539,7 @@ async def get_bars(
 | 
				
			||||||
                    if end_dt is not None:
 | 
					                    if end_dt is not None:
 | 
				
			||||||
                        end_dt = end_dt.subtract(days=1)
 | 
					                        end_dt = end_dt.subtract(days=1)
 | 
				
			||||||
                    elif end_dt is None:
 | 
					                    elif end_dt is None:
 | 
				
			||||||
                        end_dt = pendulum.now().subtract(days=1)
 | 
					                        end_dt = now().subtract(days=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    log.warning(
 | 
					                    log.warning(
 | 
				
			||||||
                        f'NO DATA found ending @ {end_dt}\n'
 | 
					                        f'NO DATA found ending @ {end_dt}\n'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue