# AUTOGENERATED! DO NOT EDIT! File to edit: 00_core.ipynb (unless otherwise specified).

__all__ = ['DAX', 'STOXX', 'VSTOXX', 'SetupLogger', 'printWhenExecuting', 'printinstance', 'Activity', 'RequestMgr',
           'Client', 'Wrapper', 'write_contract_to_mongo', 'App', 'start_stream']

# Cell
import datetime
import collections
import inspect

import logging
import time
import os.path

import pymongo
from pymongo import MongoClient

from ibapi import wrapper
from ibapi import utils
from ibapi.client import EClient
from ibapi.utils import iswrapper


from ibapi.common import * # @UnusedWildImport
from ibapi.contract import * # @UnusedWildImport
from ibapi.ticktype import * # @UnusedWildImport


from fastscript import *

# Cell
def DAX():
    #! [futcontract_multiplier]
    contract = Contract()
    contract.symbol = "DAX"
    contract.secType = "FUT"
    contract.exchange = "DTB"
    contract.currency = "EUR"
    contract.lastTradeDateOrContractMonth = "202006"
    contract.multiplier = "5"
    return contract


def STOXX():
    #! [futcontract_multiplier]
    contract = Contract()
    contract.symbol = "ESTX50"
    contract.secType = "FUT"
    contract.exchange = "DTB"
    contract.currency = "EUR"
    contract.lastTradeDateOrContractMonth = "202006"
    return contract


def VSTOXX():
    #! [futcontract_multiplier]
    contract = Contract()
    contract.symbol = "V2TX"
    contract.secType = "FUT"
    contract.exchange = "DTB"
    contract.currency = "EUR"
    contract.lastTradeDateOrContractMonth = "202005"
    return contract


# Cell
def SetupLogger():
    if not os.path.exists("log"):
        os.makedirs("log")

    time.strftime("pyibapi.%Y%m%d_%H%M%S.log")

    recfmt = '(%(threadName)s) %(asctime)s.%(msecs)03d %(levelname)s %(filename)s:%(lineno)d %(message)s'

    timefmt = '%y%m%d_%H:%M:%S'

    # logging.basicConfig( level=logging.DEBUG,
    #                    format=recfmt, datefmt=timefmt)
    logging.basicConfig(filename=time.strftime("log/pyibapi.%y%m%d_%H%M%S.log"),
                        filemode="w",
                        level=logging.INFO,
                        format=recfmt, datefmt=timefmt)
    logger = logging.getLogger()
    console = logging.StreamHandler()
    console.setLevel(logging.ERROR)
    logger.addHandler(console)


# Cell
def printWhenExecuting(fn):
    def fn2(self):
        print("   doing", fn.__name__)
        fn(self)
        print("   done w/", fn.__name__)

    return fn2

def printinstance(inst:Object):
    attrs = vars(inst)
    print(', '.join("%s: %s" % item for item in attrs.items()))

class Activity(Object):
    def __init__(self, reqMsgId, ansMsgId, ansEndMsgId, reqId):
        self.reqMsdId = reqMsgId
        self.ansMsgId = ansMsgId
        self.ansEndMsgId = ansEndMsgId
        self.reqId = reqId


class RequestMgr(Object):
    def __init__(self):
        # I will keep this simple even if slower for now: only one list of
        # requests finding will be done by linear search
        self.requests = []

    def addReq(self, req):
        self.requests.append(req)

    def receivedMsg(self, msg):
        pass


# Cell
class Client(EClient):
    def __init__(self, wrapper):
        EClient.__init__(self, wrapper)

        # how many times a method is called to see test coverage
        self.clntMeth2callCount = collections.defaultdict(int)
        self.clntMeth2reqIdIdx = collections.defaultdict(lambda: -1)
        self.reqId2nReq = collections.defaultdict(int)
        self.setupDetectReqId()

    def countReqId(self, methName, fn):
        def countReqId_(*args, **kwargs):
            self.clntMeth2callCount[methName] += 1
            idx = self.clntMeth2reqIdIdx[methName]
            if idx >= 0:
                sign = -1 if 'cancel' in methName else 1
                self.reqId2nReq[sign * args[idx]] += 1
            return fn(*args, **kwargs)

        return countReqId_

    def setupDetectReqId(self):
        methods = inspect.getmembers(EClient, inspect.isfunction)
        for (methName, meth) in methods:
            if methName != "send_msg":
                # don't screw up the nice automated logging in the send_msg()
                self.clntMeth2callCount[methName] = 0
                # logging.debug("meth %s", name)
                sig = inspect.signature(meth)
                for (idx, pnameNparam) in enumerate(sig.parameters.items()):
                    (paramName, param) = pnameNparam # @UnusedVariable
                    if paramName == "reqId":
                        self.clntMeth2reqIdIdx[methName] = idx

                setattr(Client, methName, self.countReqId(methName, meth))

# Cell
class Wrapper(wrapper.EWrapper):

    def __init__(self):
        wrapper.EWrapper.__init__(self)

        self.wrapMeth2callCount = collections.defaultdict(int)
        self.wrapMeth2reqIdIdx = collections.defaultdict(lambda: -1)
        self.reqId2nAns = collections.defaultdict(int)
        self.setupDetectWrapperReqId()

    def countWrapReqId(self, methName, fn):
        def countWrapReqId_(*args, **kwargs):
            self.wrapMeth2callCount[methName] += 1
            idx = self.wrapMeth2reqIdIdx[methName]
            if idx >= 0:
                self.reqId2nAns[args[idx]] += 1
            return fn(*args, **kwargs)

        return countWrapReqId_

    def setupDetectWrapperReqId(self):

        methods = inspect.getmembers(wrapper.EWrapper, inspect.isfunction)
        for (methName, meth) in methods:
            self.wrapMeth2callCount[methName] = 0
            # logging.debug("meth %s", name)
            sig = inspect.signature(meth)
            for (idx, pnameNparam) in enumerate(sig.parameters.items()):
                (paramName, param) = pnameNparam # @UnusedVariable
                # we want to count the errors as 'error' not 'answer'
                if 'error' not in methName and paramName == "reqId":
                    self.wrapMeth2reqIdIdx[methName] = idx

            setattr(Wrapper, methName, self.countWrapReqId(methName, meth))

# Cell
def write_contract_to_mongo(reqId, contract:Contract, symbols:pymongo.collection.Collection):
    """write a contract to symbols database with valid from attribute"""
    contract = vars(contract)
    contract["reqId"] = reqId
    contract["validFrom"] = datetime.datetime.now()
    symbols.insert_one(contract)

# Cell
class App(Wrapper, Client):
    def __init__(self):
        Wrapper.__init__(self)
        Client.__init__(self, wrapper=self)
        self.nKeybInt = 0
        self.started = False
        self.nextValidOrderId = None
        self.permId2ord = {}
        self.reqId2nErr = collections.defaultdict(int)
        self.globalCancelOnly = False
        self.simplePlaceOid = None
        self.mongo = MongoClient()["tws_data"]
        self.depthSubs = {}

    def dumpTestCoverageSituation(self):
        for clntMeth in sorted(self.clntMeth2callCount.keys()):
            logging.debug("ClntMeth: %-30s %6d" % (clntMeth,
                                                   self.clntMeth2callCount[clntMeth]))

        for wrapMeth in sorted(self.wrapMeth2callCount.keys()):
            logging.debug("WrapMeth: %-30s %6d" % (wrapMeth,
                                                   self.wrapMeth2callCount[wrapMeth]))

    def dumpReqAnsErrSituation(self):
        logging.debug("%s\t%s\t%s\t%s" % ("ReqId", "#Req", "#Ans", "#Err"))
        for reqId in sorted(self.reqId2nReq.keys()):
            nReq = self.reqId2nReq.get(reqId, 0)
            nAns = self.reqId2nAns.get(reqId, 0)
            nErr = self.reqId2nErr.get(reqId, 0)
            logging.debug("%d\t%d\t%s\t%d" % (reqId, nReq, nAns, nErr))

    @iswrapper
    def connectAck(self):
        if self.asynchronous:
            self.startApi()

    @iswrapper
    # ! [nextvalidid]
    def nextValidId(self, orderId: int):
        super().nextValidId(orderId)
        logging.debug("setting nextValidOrderId: %d", orderId)
        self.nextValidOrderId = orderId
        print("NextValidId:", orderId)
        self.start()

    def start(self):
        if self.started:
            return
        self.started = True
        print("Executing requests")
        self.tickByTickOperations_req()
        self.marketDepthOperations_req()
        print("Executing requests ... finished")

    def keyboardInterrupt(self):
        self.nKeybInt += 1
        if self.nKeybInt == 1:
            self.stop()
        else:
            print("Finishing test")
            self.done = True

    def stop(self):
        print("Executing cancels")
        self.tickByTickOperations_cancel()
        self.marketDepthOperations_cancel()
        print("Executing cancels ... finished")

    def nextOrderId(self):
        oid = self.nextValidOrderId
        self.nextValidOrderId += 1
        return oid

    @iswrapper
    # ! [error]
    def error(self, reqId: TickerId, errorCode: int, errorString: str):
        super().error(reqId, errorCode, errorString)
        print("Error. Id:", reqId, "Code:", errorCode, "Msg:", errorString)

    @iswrapper
    def winError(self, text: str, lastError: int):
        super().winError(text, lastError)

    @printWhenExecuting
    def marketDepthOperations_req(self):
        self.reqMktDepth(2001, STOXX(), 5, False, [])
        write_contract_to_mongo(2001, STOXX(), self.mongo["reference_data"])
        self.reqMktDepth(2002, DAX(), 5, False, [])
        write_contract_to_mongo(2002, DAX(), self.mongo["reference_data"])
        self.reqMktDepth(2003, VSTOXX(), 5, False, [])
        write_contract_to_mongo(2003, VSTOXX(), self.mongo["reference_data"])

    @printWhenExecuting
    def marketDepthOperations_cancel(self):
        self.cancelMktDepth(2001, False)
        self.cancelMktDepth(2002, False)
        self.cancelMktDepth(2003, False)

    @printWhenExecuting
    def tickByTickOperations_req(self):
        self.reqTickByTickData(19001, STOXX(), "Last", 0, True)
        write_contract_to_mongo(19001, STOXX(), self.mongo["reference_data"])
        self.reqTickByTickData(19002, DAX(), "Last", 0, True)
        write_contract_to_mongo(19002, DAX(), self.mongo["reference_data"])
        self.reqTickByTickData(19003, VSTOXX(), "Last", 0, True)
        write_contract_to_mongo(19003, VSTOXX(), self.mongo["reference_data"])

    @printWhenExecuting
    def tickByTickOperations_cancel(self):
        self.cancelTickByTickData(19001)
        self.cancelTickByTickData(19002)
        self.cancelTickByTickData(19003)

    @iswrapper
    def updateMktDepth(self, reqId: TickerId, position: int, operation: int,
                       side: int, price: float, size: int):
        super().updateMktDepth(reqId, position, operation, side, price, size)
        data = {
            "time": datetime.datetime.now(),
            "reqId": reqId,
            "position": position,
            "operation": operation,
            "side": side,
            "price": price,
            "size": size
        }
        self.mongo["orderbook"].insert_one(data)


    @iswrapper
    def tickByTickAllLast(self, reqId: int, tickType: int, time: int, price: float,
                          size: int, tickAtrribLast: TickAttribLast, exchange: str,
                          specialConditions: str):
        super().tickByTickAllLast(reqId, tickType, time, price, size, tickAtrribLast,
                                  exchange, specialConditions)

        data = {
            "reqId": reqId,
            "tickType": tickType,
            "time": time,
            "price": price,
            "size": size
        }
        self.mongo["tick_data"].insert_one(data)


# Cell
@call_parse
def start_stream():
    """start stream"""
    SetupLogger()
    logging.debug("now is %s", datetime.datetime.now())
    logging.getLogger().setLevel(logging.ERROR)

    from ibapi import utils
    Contract.__setattr__ = utils.setattr_log
    try:
        app = App()
        app.connect("127.0.0.1", 7497, clientId=0)
        app.run()
    except:
        raise
    finally:
        app.dumpTestCoverageSituation()
        app.dumpReqAnsErrSituation()

# AUTOGENERATED! DO NOT EDIT! File to edit: 00_core.ipynb (unless otherwise specified).

__all__ = ['DAX', 'STOXX', 'VSTOXX', 'SetupLogger', 'printWhenExecuting', 'printinstance', 'Activity', 'RequestMgr',
           'Client', 'Wrapper', 'write_contract_to_mongo', 'App', 'start_stream']

# Cell
import datetime
import collections
import inspect

import logging
import time
import os.path

import pymongo
from pymongo import MongoClient

from ibapi import wrapper
from ibapi import utils
from ibapi.client import EClient
from ibapi.utils import iswrapper


from ibapi.common import * # @UnusedWildImport
from ibapi.contract import * # @UnusedWildImport
from ibapi.ticktype import * # @UnusedWildImport


from fastscript import *

# Cell
def DAX():
    #! [futcontract_multiplier]
    contract = Contract()
    contract.symbol = "DAX"
    contract.secType = "FUT"
    contract.exchange = "DTB"
    contract.currency = "EUR"
    contract.lastTradeDateOrContractMonth = "202006"
    contract.multiplier = "5"
    return contract


def STOXX():
    #! [futcontract_multiplier]
    contract = Contract()
    contract.symbol = "ESTX50"
    contract.secType = "FUT"
    contract.exchange = "DTB"
    contract.currency = "EUR"
    contract.lastTradeDateOrContractMonth = "202006"
    return contract


def VSTOXX():
    #! [futcontract_multiplier]
    contract = Contract()
    contract.symbol = "V2TX"
    contract.secType = "FUT"
    contract.exchange = "DTB"
    contract.currency = "EUR"
    contract.lastTradeDateOrContractMonth = "202005"
    return contract


# Cell
def SetupLogger():
    if not os.path.exists("log"):
        os.makedirs("log")

    time.strftime("pyibapi.%Y%m%d_%H%M%S.log")

    recfmt = '(%(threadName)s) %(asctime)s.%(msecs)03d %(levelname)s %(filename)s:%(lineno)d %(message)s'

    timefmt = '%y%m%d_%H:%M:%S'

    # logging.basicConfig( level=logging.DEBUG,
    #                    format=recfmt, datefmt=timefmt)
    logging.basicConfig(filename=time.strftime("log/pyibapi.%y%m%d_%H%M%S.log"),
                        filemode="w",
                        level=logging.INFO,
                        format=recfmt, datefmt=timefmt)
    logger = logging.getLogger()
    console = logging.StreamHandler()
    console.setLevel(logging.ERROR)
    logger.addHandler(console)


# Cell
def printWhenExecuting(fn):
    def fn2(self):
        print("   doing", fn.__name__)
        fn(self)
        print("   done w/", fn.__name__)

    return fn2

def printinstance(inst:Object):
    attrs = vars(inst)
    print(', '.join("%s: %s" % item for item in attrs.items()))

class Activity(Object):
    def __init__(self, reqMsgId, ansMsgId, ansEndMsgId, reqId):
        self.reqMsdId = reqMsgId
        self.ansMsgId = ansMsgId
        self.ansEndMsgId = ansEndMsgId
        self.reqId = reqId


class RequestMgr(Object):
    def __init__(self):
        # I will keep this simple even if slower for now: only one list of
        # requests finding will be done by linear search
        self.requests = []

    def addReq(self, req):
        self.requests.append(req)

    def receivedMsg(self, msg):
        pass


# Cell
class Client(EClient):
    def __init__(self, wrapper):
        EClient.__init__(self, wrapper)

        # how many times a method is called to see test coverage
        self.clntMeth2callCount = collections.defaultdict(int)
        self.clntMeth2reqIdIdx = collections.defaultdict(lambda: -1)
        self.reqId2nReq = collections.defaultdict(int)
        self.setupDetectReqId()

    def countReqId(self, methName, fn):
        def countReqId_(*args, **kwargs):
            self.clntMeth2callCount[methName] += 1
            idx = self.clntMeth2reqIdIdx[methName]
            if idx >= 0:
                sign = -1 if 'cancel' in methName else 1
                self.reqId2nReq[sign * args[idx]] += 1
            return fn(*args, **kwargs)

        return countReqId_

    def setupDetectReqId(self):
        methods = inspect.getmembers(EClient, inspect.isfunction)
        for (methName, meth) in methods:
            if methName != "send_msg":
                # don't screw up the nice automated logging in the send_msg()
                self.clntMeth2callCount[methName] = 0
                # logging.debug("meth %s", name)
                sig = inspect.signature(meth)
                for (idx, pnameNparam) in enumerate(sig.parameters.items()):
                    (paramName, param) = pnameNparam # @UnusedVariable
                    if paramName == "reqId":
                        self.clntMeth2reqIdIdx[methName] = idx

                setattr(Client, methName, self.countReqId(methName, meth))

# Cell
class Wrapper(wrapper.EWrapper):

    def __init__(self):
        wrapper.EWrapper.__init__(self)

        self.wrapMeth2callCount = collections.defaultdict(int)
        self.wrapMeth2reqIdIdx = collections.defaultdict(lambda: -1)
        self.reqId2nAns = collections.defaultdict(int)
        self.setupDetectWrapperReqId()

    def countWrapReqId(self, methName, fn):
        def countWrapReqId_(*args, **kwargs):
            self.wrapMeth2callCount[methName] += 1
            idx = self.wrapMeth2reqIdIdx[methName]
            if idx >= 0:
                self.reqId2nAns[args[idx]] += 1
            return fn(*args, **kwargs)

        return countWrapReqId_

    def setupDetectWrapperReqId(self):

        methods = inspect.getmembers(wrapper.EWrapper, inspect.isfunction)
        for (methName, meth) in methods:
            self.wrapMeth2callCount[methName] = 0
            # logging.debug("meth %s", name)
            sig = inspect.signature(meth)
            for (idx, pnameNparam) in enumerate(sig.parameters.items()):
                (paramName, param) = pnameNparam # @UnusedVariable
                # we want to count the errors as 'error' not 'answer'
                if 'error' not in methName and paramName == "reqId":
                    self.wrapMeth2reqIdIdx[methName] = idx

            setattr(Wrapper, methName, self.countWrapReqId(methName, meth))

# Cell
def write_contract_to_mongo(reqId, contract:Contract, symbols:pymongo.collection.Collection):
    """write a contract to symbols database with valid from attribute"""
    contract = vars(contract)
    contract["reqId"] = reqId
    contract["validFrom"] = datetime.datetime.now()
    symbols.insert_one(contract)

# Cell
class App(Wrapper, Client):
    def __init__(self):
        Wrapper.__init__(self)
        Client.__init__(self, wrapper=self)
        self.nKeybInt = 0
        self.started = False
        self.nextValidOrderId = None
        self.permId2ord = {}
        self.reqId2nErr = collections.defaultdict(int)
        self.globalCancelOnly = False
        self.simplePlaceOid = None
        self.mongo = MongoClient()["tws_data"]
        self.depthSubs = {}

    def dumpTestCoverageSituation(self):
        for clntMeth in sorted(self.clntMeth2callCount.keys()):
            logging.debug("ClntMeth: %-30s %6d" % (clntMeth,
                                                   self.clntMeth2callCount[clntMeth]))

        for wrapMeth in sorted(self.wrapMeth2callCount.keys()):
            logging.debug("WrapMeth: %-30s %6d" % (wrapMeth,
                                                   self.wrapMeth2callCount[wrapMeth]))

    def dumpReqAnsErrSituation(self):
        logging.debug("%s\t%s\t%s\t%s" % ("ReqId", "#Req", "#Ans", "#Err"))
        for reqId in sorted(self.reqId2nReq.keys()):
            nReq = self.reqId2nReq.get(reqId, 0)
            nAns = self.reqId2nAns.get(reqId, 0)
            nErr = self.reqId2nErr.get(reqId, 0)
            logging.debug("%d\t%d\t%s\t%d" % (reqId, nReq, nAns, nErr))

    @iswrapper
    def connectAck(self):
        if self.asynchronous:
            self.startApi()

    @iswrapper
    # ! [nextvalidid]
    def nextValidId(self, orderId: int):
        super().nextValidId(orderId)
        logging.debug("setting nextValidOrderId: %d", orderId)
        self.nextValidOrderId = orderId
        print("NextValidId:", orderId)
        self.start()

    def start(self):
        if self.started:
            return
        self.started = True
        print("Executing requests")
        self.tickByTickOperations_req()
        self.marketDepthOperations_req()
        print("Executing requests ... finished")

    def keyboardInterrupt(self):
        self.nKeybInt += 1
        if self.nKeybInt == 1:
            self.stop()
        else:
            print("Finishing test")
            self.done = True

    def stop(self):
        print("Executing cancels")
        self.tickByTickOperations_cancel()
        self.marketDepthOperations_cancel()
        print("Executing cancels ... finished")

    def nextOrderId(self):
        oid = self.nextValidOrderId
        self.nextValidOrderId += 1
        return oid

    @iswrapper
    # ! [error]
    def error(self, reqId: TickerId, errorCode: int, errorString: str):
        super().error(reqId, errorCode, errorString)
        print("Error. Id:", reqId, "Code:", errorCode, "Msg:", errorString)

    @iswrapper
    def winError(self, text: str, lastError: int):
        super().winError(text, lastError)

    @printWhenExecuting
    def marketDepthOperations_req(self):
        self.reqMktDepth(2001, STOXX(), 5, False, [])
        write_contract_to_mongo(2001, STOXX(), self.mongo["reference_data"])
        self.reqMktDepth(2002, DAX(), 5, False, [])
        write_contract_to_mongo(2002, DAX(), self.mongo["reference_data"])
        self.reqMktDepth(2003, VSTOXX(), 5, False, [])
        write_contract_to_mongo(2003, VSTOXX(), self.mongo["reference_data"])

    @printWhenExecuting
    def marketDepthOperations_cancel(self):
        self.cancelMktDepth(2001, False)
        self.cancelMktDepth(2002, False)
        self.cancelMktDepth(2003, False)

    @printWhenExecuting
    def tickByTickOperations_req(self):
        self.reqTickByTickData(19001, STOXX(), "Last", 0, True)
        write_contract_to_mongo(19001, STOXX(), self.mongo["reference_data"])
        self.reqTickByTickData(19002, DAX(), "Last", 0, True)
        write_contract_to_mongo(19002, DAX(), self.mongo["reference_data"])
        self.reqTickByTickData(19003, VSTOXX(), "Last", 0, True)
        write_contract_to_mongo(19003, VSTOXX(), self.mongo["reference_data"])

    @printWhenExecuting
    def tickByTickOperations_cancel(self):
        self.cancelTickByTickData(19001)
        self.cancelTickByTickData(19002)
        self.cancelTickByTickData(19003)

    @iswrapper
    def updateMktDepth(self, reqId: TickerId, position: int, operation: int,
                       side: int, price: float, size: int):
        super().updateMktDepth(reqId, position, operation, side, price, size)
        data = {
            "time": datetime.datetime.now(),
            "reqId": reqId,
            "position": position,
            "operation": operation,
            "side": side,
            "price": price,
            "size": size
        }
        self.mongo["orderbook"].insert_one(data)


    @iswrapper
    def tickByTickAllLast(self, reqId: int, tickType: int, time: int, price: float,
                          size: int, tickAtrribLast: TickAttribLast, exchange: str,
                          specialConditions: str):
        super().tickByTickAllLast(reqId, tickType, time, price, size, tickAtrribLast,
                                  exchange, specialConditions)

        data = {
            "reqId": reqId,
            "tickType": tickType,
            "time": time,
            "price": price,
            "size": size
        }
        self.mongo["tick_data"].insert_one(data)


# Cell
@call_parse
def start_stream():
    """start stream"""
    SetupLogger()
    logging.debug("now is %s", datetime.datetime.now())
    logging.getLogger().setLevel(logging.ERROR)

    from ibapi import utils
    Contract.__setattr__ = utils.setattr_log
    try:
        app = App()
        app.connect("127.0.0.1", 7497, clientId=0)
        app.run()
    except:
        raise
    finally:
        app.dumpTestCoverageSituation()
        app.dumpReqAnsErrSituation()

# AUTOGENERATED! DO NOT EDIT! File to edit: 00_core.ipynb (unless otherwise specified). # Cell # @UnusedWildImport # @UnusedWildImport # @UnusedWildImport # Cell #! [futcontract_multiplier] #! [futcontract_multiplier] #! [futcontract_multiplier] # Cell # logging.basicConfig( level=logging.DEBUG, #                    format=recfmt, datefmt=timefmt) # Cell # I will keep this simple even if slower for now: only one list of # requests finding will be done by linear search # Cell # how many times a method is called to see test coverage # don't screw up the nice automated logging in the send_msg() # logging.debug("meth %s", name) # @UnusedVariable # Cell # logging.debug("meth %s", name) # @UnusedVariable # we want to count the errors as 'error' not 'answer' # Cell write a contract to symbols database with valid from attribute # Cell # ! [nextvalidid] # ! [error] # Cell start stream