"""The sample demonstrates how to define a strategy and place a strategy order"""

import logging
import threading

from win32com.client import constants, Dispatch

import CELEnvironment

# region Settings

# Replace it with real login before run
LOGIN = "your_gw_login"
PASSWORD = "your_gw_password"

# Replace it with enabled symbol
STRATEGY = "SPREAD(F.US.EPM24-S.US.AAPL, L1, , 1:1, , 0)"

# Events waiting time
TIMEOUT = 10

# 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 MultiAccountStrategyPlacingSample(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.celEnvironment = None
        self.eventGatewayIsUp = threading.Event()
        self.eventGatewayIsDown = threading.Event()
        self.commonEvent = threading.Event()
        self.account = None
        self.executionPatternRequestId = None
        self.executionPattern = None

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        LOG.info("Connecting to GW")
        self.celEnvironment.cqgCEL.GWLogon(LOGIN, PASSWORD)

        LOG.info("Waiting for GW connection...")
        assert self.eventGatewayIsUp.wait(TIMEOUT), "GW connection timeout!"

        self.celEnvironment.cqgCEL.AccountSubscriptionLevel = constants.aslAccountUpdatesAndOrders
        LOG.info("Waiting for accounts to load...")
        assert self.commonEvent.wait(TIMEOUT), "Accounts loading timeout!"
        self.commonEvent.clear()

        LOG.info("Select the first account")
        accounts = Dispatch(self.celEnvironment.cqgCEL.Accounts)
        self.account = Dispatch(accounts.ItemByIndex(0))
        LOG.info("Selected account: %s", self.account.GWAccountName)

        LOG.info("Define strategy...")
        definedStrategy = self.celEnvironment.cqgCEL.DefineStrategyEx(STRATEGY, self.account)

        LOG.info("Waiting for strategy resolution...")
        assert self.commonEvent.wait(TIMEOUT), "Strategy resolution timeout!"
        self.dispatchedStrategy = Dispatch(definedStrategy)
        self.commonEvent.clear()

        LOG.info("Request execution pattern...")
        request = self.celEnvironment.cqgCEL.CreateExecutionPatternRequest()
        self.executionPatternRequestId = request.GUID
        request.Account = self.account
        request.StrategyDefinition = definedStrategy
        request.OrderType = constants.otLimit

        self.celEnvironment.cqgCEL.RequestExecutionPattern(request)
        assert self.commonEvent.wait(TIMEOUT), "Execution pattern timeout!"
        LOG.info("Execution pattern:\n%s", self.executionPattern)
        self.commonEvent.clear()

        LOG.info("Subscribe to %s for a best ask value receiving. It will be used for an order placing...", STRATEGY)
        request = self.celEnvironment.cqgCEL.CreateInstrumentRequest()
        request.Symbol = STRATEGY
        request.QuoteLevel = constants.qsBBA
        self.celEnvironment.cqgCEL.SubscribeNewInstrument(request)
        LOG.info("%s subscription waiting...", STRATEGY)
        assert self.commonEvent.wait(TIMEOUT), "Instrument resolution timeout!"
        self.commonEvent.clear()

        instrument = Dispatch(self.instrument)
        bestAsk = instrument.Ask
        assert bestAsk is not None and bestAsk.IsValid, "Error! Can't set an order price due to invalid BBA"

        LOG.info("Best ask value is %f", bestAsk.Price)
        LOG.info("Calculating a price that above the market by 10 tick sizes...")
        sellOrderPrice = bestAsk.Price + 10 * instrument.TickSize
        LOG.info("It is %f", sellOrderPrice)

        LOG.info("Creating a strategy order...")
        quantity = -1
        order = Dispatch(
            self.celEnvironment.cqgCEL.CreateStrategyOrder(
                constants.otLimit,
                self.dispatchedStrategy,
                self.account,
                None,
                quantity,
                constants.osdUndefined,
                sellOrderPrice,
            )
        )
        LOG.info("Set an execution pattern order property...")
        Dispatch(order.Properties)(constants.opExecutionPattern).Value = self.executionPattern

        LOG.info("Place the order...")
        order.Place()

        LOG.info("Waiting for order placing...")
        assert self.commonEvent.wait(TIMEOUT), "Order placing timeout!"

        LOG.info("Logoff from GW")
        self.eventGatewayIsDown.clear()
        self.celEnvironment.cqgCEL.GWLogoff()
        assert self.eventGatewayIsDown.wait(TIMEOUT), "GW disconnection timeout!"

        LOG.info("Done.")

    def OnGWConnectionStatusChanged(self, newStatus):
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventGatewayIsUp.set()
        if newStatus == constants.csConnectionDown:
            LOG.info("GW connection is DOWN!")
            self.eventGatewayIsDown.set()

    def OnAccountChanged(self, changeType, cqgAccount, cqgPosition):
        if changeType != constants.actAccountsReloaded:
            return

        LOG.info("Accounts are ready!")
        self.commonEvent.set()

    def OnStrategyDefinitionProgress(self, cqgDefinition, cqgError):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnStrategyDefinitionProgress error: Code: %d Description: %s", error.Code, error.Description)
            return

        dispatchedDefinition = Dispatch(cqgDefinition)
        LOG.info(
            "OnStrategyDefinitionProgress: requested symbol: %s GW symbol: %s status: %d",
            dispatchedDefinition.RequestString,
            dispatchedDefinition.Symbol,
            dispatchedDefinition.Status,
        )
        if dispatchedDefinition.Status == constants.srsSuccess:
            self.commonEvent.set()

    def OnExecutionPatternReady(self, requestId, executionPatternString, cqgError):
        assert requestId == self.executionPatternRequestId
        if cqgError is None:
            self.executionPattern = executionPatternString
            self.commonEvent.set()
        else:
            error = Dispatch(cqgError)
            LOG.error("OnInstrumentResolved error: Error code: %d Description: %s", error.Code, error.Description)

    def OnInstrumentResolved(self, symbol, cqgInstrument, cqgError):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnInstrumentResolved error: Error code: %d Description: %s", error.Code, error.Description)
            return

        self.instrument = cqgInstrument
        LOG.info("Instrument %s is resolved!", symbol)
        self.commonEvent.set()

    def OnOrderChanged(self, changeType, cqgOrder, oldProperties, cqgFill, cqgError):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnOrderChanged error: Code: %d Description: %s", error.Code, error.Description)
            return

        dispatchedOrder = Dispatch(cqgOrder)
        properties = Dispatch(dispatchedOrder.Properties)
        gwStatus = properties(constants.opGWStatus).Value
        quantity = properties(constants.opQuantity).Value
        instrument = properties(constants.opInstrumentName).Value
        limitPrice = properties(constants.opLimitPrice).Value

        LOG.info(
            "OnOrderChanged: change type: %d; GW status: %d; Quantity: %d; Instrument: %s; Price: %f;",
            changeType,
            gwStatus,
            quantity,
            instrument,
            limitPrice,
        )

        if changeType != constants.ctChanged:
            return

        if gwStatus == constants.osInOrderBook:
            LOG.info("Strategy order is placed!")
            self.commonEvent.set()


def CustomAPIConfiguration(APIConfiguration):
    # If CQGCEL.APIConfiguration must be customized, then do it through this function
    APIConfiguration.NewInstrumentMode = True


if __name__ == "__main__":
    CELEnvironment.Start(MultiAccountStrategyPlacingSample, CustomAPIConfiguration)
