"""The sample demonstrates how to request options chain by commodity."""

import logging
import threading
from datetime import datetime

from dateutil.relativedelta import relativedelta
from tabulate import tabulate
from win32com.client import Dispatch, constants

import CELEnvironment
from CELEnvironment import AssertMessage

# region Settings

# Replace it with enabled symbol
COMMODITY = "EP"

# Possible filters: only calls (itOptionCall), only puts (itOptionPut) or both (itAllOptions).
TYPE: int = constants.itOptionCall

# First option month
RANGE_START = datetime.today()
# Last option month
RANGE_END = RANGE_START + relativedelta(months=3)

# Specify to True to track changes in group, updates will be fired via OnInstrumentsGroupChanged event handler.
TRACE_GROUP_CHANGES = False

# Logging settings: specify log level and log format here.
# Set log level to DEBUG to see more internal messages.
# See more information about logging lib usage: https://docs.python.org/3/howto/logging-cookbook.html
CELEnvironment.LOG_LEVEL = logging.INFO
# CELEnvironment.LOG_FORMAT = ...

# CELEnvironment.SinkInternal uses own LOG_LEVEL (which is inherited from the global one) and own LOG_FORMAT.
# You can redefine them as follows:
# CELEnvironment.SinkInternal.LOG_LEVEL = logging.DEBUG
# CELEnvironment.SinkInternal.LOG_FORMAT = ...

LOG = CELEnvironment.GetLogger()

# endregion


class InstrumentsGroupRequest(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.eventDone = threading.Event()

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        request = Dispatch(self.celEnvironment.cqgCEL.CreateInstrumentsGroupRequest())
        request.Commodity = COMMODITY
        # next 3 lines are optional, see default values on CQGInstrumentsGroupRequest doc page
        request.InstrumentTypes = TYPE
        request.RangeStart = RANGE_START
        request.RangeEnd = RANGE_END
        request.PropertyLevel = constants.gpsGreeks | constants.gpsVolatility | constants.gpsTheoreticalValue

        LOG.info("Request options chain for %s", COMMODITY)
        self.celEnvironment.cqgCEL.RequestInstrumentsGroup(request, TRACE_GROUP_CHANGES)
        AssertMessage(self.eventDone.wait(30), "Timeout on instruments group resolution")
        if self.celEnvironment.cqgCEL.AllInstrumentsGroups.Count == 0:
            # resolution failed, error is reported in OnInstrumentsGroupResolved
            return

        input("\nInstrument group is resolved, wait for changes in group or press Enter to exit...")

        LOG.info("Remove instrument group.")
        allGroups = Dispatch(self.celEnvironment.cqgCEL.AllInstrumentsGroups)
        self.celEnvironment.cqgCEL.RemoveInstrumentsGroup(allGroups.Item(0))

    def OnDataError(self, cqgError, errorDescription):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnDataError: Code: %d Description: %s", error.Code, error.Description)

        self.eventDone.set()

    def OnInstrumentsGroupResolved(self, cqgInstrumentsGroup, cqgError):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnInstrumentsGroupResolved error: Error code: %d Description: %s", error.Code, error.Description)
            self.eventDone.set()
            return

        instrumentGroup = Dispatch(cqgInstrumentsGroup)
        LOG.info("Resolved %d contracts.", instrumentGroup.Instruments.Count)

        instruments = list(instrumentGroup.Instruments)
        if not instruments:
            self.eventDone.set()
            return

        table = []
        for instrument in instruments:
            props = Dispatch(instrument.Properties)
            table.append(
                [
                    instrument.DetailedName,
                    instrument.Strike,
                    instrument.ExpirationDate,
                    props(constants.ipDelta).Value,
                    props(constants.ipTheta).Value,
                    props(constants.ipGamma).Value,
                    props(constants.ipVega).Value,
                    props(constants.ipRho).Value,
                    props(constants.ipImpliedVolatility).Value,
                    props(constants.ipImpliedVolatilityNetChange).Value,
                    props(constants.ipTheoreticalValue).Value,
                ]
            )

        # let's limit output to 30 lines due to limitations of cmd output
        headers = [
            "Name",
            "Strike",
            "Expiration",
            "Delta",
            "Theta",
            "Gamma",
            "Vega",
            "Rho",
            "ImplVol",
            "ImplVol NC",
            "TheoV",
        ]
        LOG.info("First 30 resolved instruments:\n%s\n", tabulate(table[:30], headers=headers))

        # Sessions description is the same for all instruments in the group, so any can be used
        sessions = Dispatch(instruments[0].Sessions)
        data = [[s.Number, s.Name, s.StartTime.strftime("%H:%M:%S"), s.EndTime.strftime("%H:%M:%S")] for s in sessions]
        LOG.info(
            "Current commodity sessions (description is valid from %s till %s):\n%s\n",
            sessions.DescriptionStart.strftime("%m/%d/%Y %H:%M"),
            sessions.DescriptionEnd.strftime("%m/%d/%Y %H:%M"),
            tabulate(data, headers=["#", "Name", "StartTime", "EndTime"]),
        )

        self.eventDone.set()

    def OnInstrumentsGroupChanged(self, changeType, cqgInstrumentsGroup, instrumentsNames):
        LOG.info(
            "OnInstrumentsGroupChanged: changeType=%d. Instruments that changed: %s", changeType, list(instrumentsNames)
        )


if __name__ == "__main__":
    CELEnvironment.Start(InstrumentsGroupRequest)
