示例#1
0
    async def resolve_head_timestamp(
        self, req_id: int, contract: ib_contract.Contract,
        show: datatype.EarliestDataPoint=datatype.EarliestDataPoint.TRADES
    ) -> int:
        """Fetch the earliest available data point for a given instrument
        from IB.

        Args:
            req_id (int): Request ID (ticker ID in IB API).
            contract (:obj:`ibapi.contract.Contract`): `Contract` object with
                sufficient info to identify the instrument.
            show (:obj:`ibpy_native.utils.datatype.EarliestDataPoint`,
                optional): Type of data for head timestamp. Defaults to
                `EarliestDataPoint.TRADES`.

        Returns:
            int: Unix timestamp of the earliest available datapoint.

        Raises:
            ibpy_native.error.IBError: If
                - queue associated with `req_id` is being used by other tasks;
                - there's any error returned from IB;
        """
        try:
            f_queue = self._wrapper.get_request_queue(req_id=req_id)
        except error.IBError as err:
            raise err

        self.reqHeadTimeStamp(reqId=req_id, contract=contract,
                              whatToShow=show.value, useRTH=0, formatDate=2)

        res = await f_queue.get()

        # Cancel the head time stamp request to release the ID after the
        # request queue is finished
        self.cancelHeadTimeStamp(reqId=req_id)

        if res:
            if f_queue.status is fq.Status.ERROR:
                if isinstance(res[-1], error.IBError):
                    raise res[-1]

                raise self._unknown_error(req_id=req_id)

            if len(res) > 1:
                raise error.IBError(
                    rid=req_id, err_code=error.IBErrorCode.RES_UNEXPECTED.value,
                    err_str="[Abnormal] Multiple result received"
                )

            return int(res[0])

        raise error.IBError(
            rid=req_id, err_code=error.IBErrorCode.RES_NO_CONTENT.value,
            err_str="Failed to get the earliest available data point"
        )
示例#2
0
 def on_disconnected(self):
     if self._account_updates_queue.status is not (fq.Status.ERROR
                                                   or fq.Status.FINISHED):
         err = error.IBError(rid=-1,
                             err_code=error.IBErrorCode.NOT_CONNECTED.value,
                             err_str=_global.MSG_NOT_CONNECTED)
         self._account_updates_queue.put(element=err)
示例#3
0
    def _init_req_queue(self, req_id: int):
        """Initials a new `FinishableQueue` if there's no object at
        `self.__req_queue[req_id]`; Resets the queue status to its' initial
        status.

        Raises:
            ibpy_native.error.IBError: If a `FinishableQueue` already exists at
                `self.__req_queue[req_id]` and it's not finished.
        """
        if req_id in self._req_queue:
            if (self._req_queue[req_id].finished
                    or (req_id == _global.IDX_NEXT_ORDER_ID and
                        (self._req_queue[_global.IDX_NEXT_ORDER_ID].status is
                         fq.Status.INIT))
                    or (req_id == _global.IDX_OPEN_ORDERS and
                        (self._req_queue[_global.IDX_OPEN_ORDERS].status is
                         fq.Status.INIT))):
                self._req_queue[req_id].reset()
            else:
                raise error.IBError(
                    rid=req_id,
                    err_code=error.IBErrorCode.QUEUE_IN_USE,
                    err_str=f"Requested queue with ID {str(req_id)} is "
                    "currently in use")
        else:
            self._req_queue[req_id] = fq.FinishableQueue(queue.Queue())
示例#4
0
    def on_disconnected(self):
        for key, f_queue in self._pending_queues.items():
            if f_queue.status is not fq.Status.FINISHED or fq.Status.ERROR:
                err = error.IBError(rid=key,
                                    err_code=error.IBErrorCode.NOT_CONNECTED,
                                    err_str=_global.MSG_NOT_CONNECTED)
                f_queue.put(element=err)

        self._reset()
示例#5
0
    def error(self, reqId, errorCode, errorString):
        # override method
        err = error.IBError(rid=reqId, err_code=errorCode, err_str=errorString)

        # -1 indicates a notification and not true error condition
        if reqId is not -1:
            self._req_queue[reqId].put(element=err)
        else:
            if self._listener is not None:
                self._listener.on_notify(msg_code=errorCode, msg=errorString)
示例#6
0
    async def resolve_contract(
            self, req_id: int,
            contract: ib_contract.Contract) -> ib_contract.Contract:
        """From a partially formed contract, returns a fully fledged version.

        Args:
            req_id (int): Request ID (ticker ID in IB API).
            contract (:obj:`ibapi.contract.Contract`):
                `Contract` object with partially completed info
                    - e.g. symbol, currency, etc...

        Returns:
            ibapi.contract.Contract: Fully resolved IB contract.

        Raises:
            ibpy_native.error.IBError: If
                - queue associated with `req_id` is being used by other tasks;
                - there's any error returned from IB;
                - no item found in received result.
        """

        # Make place to store the data that will be returned
        try:
            f_queue = self._wrapper.get_request_queue(req_id=req_id)
        except error.IBError as err:
            raise err

        print("Getting full contract details from IB...")

        self.reqContractDetails(reqId=req_id, contract=contract)

        # Run until we get a valid contract(s)
        res = await f_queue.get()

        if res:
            if f_queue.status is fq._Status.ERROR:
                if isinstance(res[-1], error.IBError):
                    raise res[-1]

                raise self._unknown_error(req_id=req_id)

            if len(res) > 1:
                print("Multiple contracts found: returning 1st contract")

            resolved_contract = res[0].contract

            return resolved_contract

        raise error.IBError(
            rid=req_id,
            err_code=error.IBErrorCode.RES_NO_CONTENT,
            err_str="Failed to get additional contract details")
示例#7
0
    async def resolve_contracts(
            self, req_id: int, contract: ib_contract.Contract
    ) -> List[ib_contract.ContractDetails]:
        """Search the fully fledged contracts with details from a partially
        formed `ibapi.contract.Contract` object.

        Args:
            req_id (int): Request ID (ticker ID in IB API).
            contract (:obj:`ibapi.contract.Contract`): `Contract` object with
                partially completed info
                    - e.g. symbol, currency, etc...

        Returns:
            List[ibapi.contract.ContractDetails]: Fully fledged IB contract(s)
                with detailed info.

        Raises:
            ibpy_native.error.IBError: If
                - queue associated with `req_id` is being used by other tasks;
                - there's any error returned from IB;
                - no item found in received result.
        """
        # Prepare queue to store the data that will be returned
        try:
            f_queue = self._wrapper.get_request_queue(req_id=req_id)
        except error.IBError as err:
            raise err

        print("Searching contracts with details from IB...")

        self.reqContractDetails(reqId=req_id, contract=contract)

        res: List[Union[ib_contract.ContractDetails, error.IBError]] = \
            await f_queue.get()

        if res:
            if f_queue.status is fq._Status.ERROR:
                if isinstance(res[-1], error.IBError):
                    raise res[-1]

            return res

        raise error.IBError(
            rid=req_id,
            err_code=error.IBErrorCode.RES_NO_CONTENT,
            err_str="Failed to get additional contract details")
示例#8
0
    def error(self, reqId, errorCode, errorString):
        err = error.IBError(rid=reqId, err_code=errorCode, err_str=errorString)

        # -1 indicates a notification and not true error condition
        if reqId != -1 and self._orders_manager.is_pending_order(
                order_id=reqId):  # Is an order error
            self._orders_manager.order_error(err)
        elif reqId != -1 and errorCode == error.IBErrorCode.ORDER_REJECTED:
            self._orders_manager.on_order_rejected(order_id=reqId,
                                                   reason=errorString)
        elif reqId != -1 and reqId in self._req_queue:
            self._req_queue[reqId].put(element=err)
        elif reqId == -1 and errorCode == error.IBErrorCode.NOT_CONNECTED:
            # Connection dropped
            self._on_disconnected()
        else:
            if self._notification_listener is not None:
                self._notification_listener.on_notify(msg_code=errorCode,
                                                      msg=errorString)
示例#9
0
    def __init_req_queue(self, req_id: int):
        """Initials a new `_FinishableQueue` if there's no object at
        `self.__req_queue[req_id]`; Resets the queue status to its' initial
        status.

        Raises:
            ibpy_native.error.IBError: If a `_FinishableQueue` already exists at
                `self.__req_queue[req_id]` and it's not finished.
        """
        if req_id in self._req_queue:
            if self._req_queue[req_id].finished:
                self._req_queue[req_id].reset()
            else:
                raise error.IBError(
                    rid=req_id, err_code=error.IBErrorCode.QUEUE_IN_USE,
                    err_str=f"Requested queue with ID {str(req_id)} is "\
                        "currently in use"
                )
        else:
            self._req_queue[req_id] = fq._FinishableQueue(queue.Queue())
示例#10
0
    def _unknown_error(self, req_id: int, extra: Any = None):
        """Constructs `IBError` with error code `UNKNOWN`

        For siturations which internal `FinishableQueue` reports error status
        but not exception received.

        Args:
            req_id (int): Request ID (ticker ID in IB API).
            extra (:obj:`Any`, optional): Extra data to be passed through the
                exception. Defaults to `None`.

        Returns:
            ibpy_native.error.IBError: Preconfigured `IBError` object with
                error code `50500: UNKNOWN`
        """
        return error.IBError(
            rid=req_id, err_code=error.IBErrorCode.UNKNOWN.value,
            err_str="Unknown error: Internal queue reported error "
                    "status but no exception received",
            err_extra=extra
        )
示例#11
0
    def cancel_live_ticks_stream(self, req_id: int):
        """Stop the live tick data stream that's currently streaming.

        Args:
            req_id (int): Request ID (ticker ID in IB API).

        Raises:
            ibpy_native.error.IBError: If there's no `FinishableQueue` object
                associated with the specified `req_id` found in the internal
                `IBWrapper` object.
        """
        f_queue = self._wrapper.get_request_queue_no_throw(req_id=req_id)

        if f_queue is not None:
            self.cancelTickByTickData(reqId=req_id)
            f_queue.put(element=fq.Status.FINISHED)
        else:
            raise error.IBError(
                rid=req_id, err_code=error.IBErrorCode.RES_NOT_FOUND.value,
                err_str=f"Task associated with request ID {req_id} not found"
            )
示例#12
0
    def on_order_submission(self, order_id: int):
        """INTERNAL FUNCTION! Creates a new `FinishableQueue` with `order_id`
        as key in `_pending_queues` for order submission task completeion
        status monitoring.

        Args:
            order_id (int): The order's identifier on TWS/Gateway.

        Raises:
            ibpy_native.error.IBError: If existing `FinishableQueue` assigned
                for the `order_id` specificed is found.
        """
        if order_id not in self._pending_queues:
            self._pending_queues[order_id] = fq.FinishableQueue(
                queue_to_finish=queue.Queue())
        else:
            raise error.IBError(
                rid=order_id,
                err_code=error.IBErrorCode.DUPLICATE_ORDER_ID.value,
                err_str=f"Existing queue assigned for order ID {order_id} "
                "found. Possiblely duplicate order ID is being used.")
示例#13
0
    def _on_disconnected(self):
        """Stop all active requests."""
        if self._req_queue[_global.IDX_NEXT_ORDER_ID].status is not (
                fq.Status.INIT or fq.Status.FINISHED):
            # Send finish signal to the active next order ID request
            self._req_queue[_global.IDX_NEXT_ORDER_ID].put(
                element=fq.Status.FINISHED)

        for key, f_queue in self._req_queue.items():
            if key == _global.IDX_NEXT_ORDER_ID:
                continue
            if f_queue.status is not fq.Status.FINISHED or fq.Status.ERROR:
                err = error.IBError(rid=key,
                                    err_code=error.IBErrorCode.NOT_CONNECTED,
                                    err_str=_global.MSG_NOT_CONNECTED)
                f_queue.put(element=err)

        self._reset()
        self._orders_manager.on_disconnected()
        self._accounts_manager.on_disconnected()

        if self._connection_listener is not None:
            self._connection_listener.on_disconnected()
示例#14
0
    async def req_historical_ticks(
        self, req_id: int, contract: ib_contract.Contract,
        start_date_time: datetime.datetime,
        show: datatype.HistoricalTicks=datatype.HistoricalTicks.TRADES
    ) -> _typing.ResHistoricalTicks:
        """Request historical tick data of the given instrument from IB.

        Args:
            req_id (int): Request ID (ticker ID in IB API).
            contract (:obj:`ibapi.contract.Contract`): `Contract` object with
                sufficient info to identify the instrument.
            start_date_time (:obj:`datetime.datetime`): Time for the earliest
                tick data to be included.
            show (:obj:`ibpy_native.utils.datatype.HistoricalTicks`, optional):
                Type of data to be requested. Defaults to
                `HistoricalTicks.TRADES`.

        Returns:
            :obj:`ibpy_native._internal._typing.ResHistoricalTicks`: Tick data
                returned from IB.

        Raises:
            ValueError: If argument `start_date_time` is an aware `datetime`
                object.
            ibpy_native.error.IBError: If
                - queue associated with argument `req_id` is being used by other
                task;
                - there's any error returned from IB;
                - Data received from IB is indicated as incomplete.

        Notes:
            Around 1000 ticks will be returned from IB. Ticks returned will
            always cover a full second as describled in IB API document.
        """
        # Error checking
        if start_date_time.tzinfo is not None:
            raise ValueError("Value of argument `start_date_time` must not "
                             "be an aware `datetime` object.")
        # Pre-processing
        try:
            f_queue = self._wrapper.get_request_queue(req_id)
        except error.IBError as err:
            raise err

        converted_start_time = _global.TZ.localize(start_date_time)

        self.reqHistoricalTicks(
            reqId=req_id, contract=contract,
            startDateTime=converted_start_time.strftime(_global.TIME_FMT),
            endDateTime="", numberOfTicks=1000, whatToShow=show.value,
            useRth=0, ignoreSize=False, miscOptions=[]
        )

        result: _typing.WrapperResHistoricalTicks = await f_queue.get()

        if result:
            if f_queue.status is fq.Status.ERROR:
                # Handle error returned from IB
                if isinstance(result[-1], error.IBError):
                    raise result[-1]

            if not result[1]:
                raise error.IBError(
                    rid=req_id, err_code=error.IBErrorCode.RES_UNEXPECTED.value,
                    err_str="Not all historical tick data has been received "
                            "for this request. Please retry."
                )

            return result[0]
示例#15
0
    async def fetch_historical_ticks(
            self, req_id: int, contract: ib_contract.Contract,
            start: datetime.datetime,
            end: Optional[datetime.datetime] = datetime.datetime.now()\
                .astimezone(TZ),
            show: Optional[dt.HistoricalTicks] = dt.HistoricalTicks.TRADES
        ) -> dt.HistoricalTicksResult:
        """Fetch the historical ticks data for a given instrument from IB.

        Args:
            req_id (int): Request ID (ticker ID in IB API).
            contract (:obj:`ibapi.contract.Contract`): `Contract` object with
                sufficient info to identify the instrument.
            start (:obj:`datetime.datetime`): The time for the earliest tick
                data to be included.
            end (:obj:`datetime.datetime`, optional): The time for the latest
                tick data to be included. Defaults to now.
            show (Literal['MIDPOINT', 'BID_ASK', 'TRADES'], optional):
                Type of data requested. Defaults to 'TRADES'.

        Returns:
            Ticks data (fetched recursively to get around IB 1000 ticks limit)

        Raises:
            ValueError: If
                - `tzinfo` of `start` & `end` do not align;
                - Value of start` > `end`.
            ibpy_native.error.IBError: If
                - queue associated with `req_id` is being used by other tasks;
                - there's any error returned from IB before any tick data is
                fetched successfully;
                - no result received from IB with no tick fetched in pervious
                request(s);
                - incorrect number of items (!= 2) found in the result received
                from IB with no tick fetched in pervious request(s).
        """
        # Pre-process & error checking
        if type(start.tzinfo) is not type(end.tzinfo):
            raise ValueError(
                "Timezone of the start time and end time must be the same")

        if start.timestamp() > end.timestamp():
            raise ValueError(
                "Specificed start time cannot be later than end time")

        # Time to fetch the ticks
        try:
            f_queue = self._wrapper.get_request_queue(req_id=req_id)
        except error.IBError as err:
            raise err

        all_ticks: list = []

        real_start_time = _IBClient.TZ.localize(start) if start.tzinfo is None \
            else start

        next_end_time = _IBClient.TZ.localize(end) if end.tzinfo is None \
            else end

        finished = False

        print(f"Getting historical ticks data [{show}] for the given"
              " instrument from IB...")

        while not finished:
            self.reqHistoricalTicks(reqId=req_id,
                                    contract=contract,
                                    startDateTime="",
                                    endDateTime=next_end_time.strftime(
                                        const._IB.TIME_FMT),
                                    numberOfTicks=1000,
                                    whatToShow=show.value,
                                    useRth=0,
                                    ignoreSize=False,
                                    miscOptions=[])

            res: List[List[Union[ib_wrapper.HistoricalTick,
                                 ib_wrapper.HistoricalTickBidAsk,
                                 ib_wrapper.HistoricalTickLast]],
                      bool] = await f_queue.get()

            if res and f_queue.status is fq._Status.ERROR:
                # Response received and internal queue reports error
                if isinstance(res[-1], error.IBError):
                    if all_ticks:
                        if res[-1].err_code == error.IBErrorCode\
                            .INVALID_CONTRACT:
                            # Continue if IB returns error `No security
                            # definition has been found for the request` as
                            # it's not possible that ticks can be fetched
                            # on pervious attempts for an invalid contract.
                            f_queue.reset()
                            continue

                        # Encounters error. Returns ticks fetched in
                        # pervious loop(s).
                        break

                    res[-1].err_extra = next_end_time
                    raise res[-1]

                raise self._unknown_error(req_id=req_id, extra=next_end_time)

            if res:
                if len(res) != 2:
                    # The result should be a list that contains 2 items:
                    # [ticks: ListOfHistoricalTick(BidAsk/Last), done: bool]
                    if all_ticks:
                        print("Abnormal result received while fetching the "
                              f"remaining ticks: returning {len(all_ticks)} "
                              "ticks fetched")
                        break

                    raise error.IBError(
                        rid=req_id,
                        err_code=error.IBErrorCode.RES_UNEXPECTED,
                        err_str="[Abnormal] Incorrect number of items "
                        f"received: {len(res)}")

                # Process the data
                processed_result = self._process_historical_ticks(
                    ticks=res[0],
                    start_time=real_start_time,
                    end_time=next_end_time)
                all_ticks.extend(processed_result['ticks'])
                next_end_time = processed_result['next_end_time']

                print(f"{len(all_ticks)} ticks fetched ("
                      f"{len(processed_result['ticks'])} new ticks); Next end "
                      f"time - {next_end_time.strftime(const._IB.TIME_FMT)}")

                if next_end_time.timestamp() <= real_start_time.timestamp():
                    # All tick data within the specificed range has been
                    # fetched from IB. Finishes the while loop.
                    finished = True

                    break

                # Resets the queue for next historical ticks request
                f_queue.reset()

            else:
                if all_ticks:
                    print("Request failed while fetching the remaining ticks: "
                          f"returning {len(all_ticks)} ticks fetched")

                    break

                raise error.IBError(
                    rid=req_id,
                    err_code=error.IBErrorCode.RES_NO_CONTENT,
                    err_str="Failed to get historical ticks data")

        all_ticks.reverse()

        # return (all_ticks, finished)
        return {'ticks': all_ticks, 'completed': finished}