def update(self, data): recordIndex = '' if len(data) == 4: # data comming from archive file (mode simulation) recordIndex, timestampMilliSec, priceFloat, volumeFloat = data else: # data comming from exchange (mode real time) timestampMilliSec, priceFloat, volumeFloat = data secondaryData = False while not secondaryData: secondaryData = self.aggregateSecondaryData( timestampMilliSec, priceFloat, volumeFloat) #calling the criterion to check if it should raise an alarm criterionData = self.criterion.check(secondaryData) # sending the secondary data to the archiver so that the sd are written in the # sd file to enable viewing them in a price/volume chart. Note that the archiver # takes care of implementing the secondary data record index. self.archiver.update(secondaryData + criterionData) if self.isVerbose: print("SecondaryDataAggregator: {} {} {} {}".format( recordIndex, timestampMilliSec, priceFloat, volumeFloat)) SeqDiagBuilder.recordFlow( ) # called to build the sequence diagram. Can be commented out later ...
def testStartModeRealtimeWithDurationInSecondsBuildSeqDiag(self): controller = Controller() duration = 2 print("running c2 in real time mode for {} seconds".format(duration)) SeqDiagBuilder.activate(parentdir, 'Controller', 'start') # activate sequence diagram building #IMPORTANT: when forcing execution parms, no space separate parm name and parm value ! try: controller.start(['-mr', '-d{}'.format(duration)]) time.sleep(duration) except SystemExit: pass csvPrimaryDataFileName = controller.primaryDataFileName csvSecondaryDataFileName = controller.buildSecondaryFileNameFromPrimaryFileName( csvPrimaryDataFileName, "secondary") os.remove(csvPrimaryDataFileName) os.remove(csvSecondaryDataFileName) commands = SeqDiagBuilder.createSeqDiaqCommands('USER') with open("c:\\temp\\ess.txt", "w") as f: f.write(commands) SeqDiagBuilder.deactivate() # deactivate sequence diagram building
def check(self, data): ''' Check if the criterion is reached. :seqdiag_note method to be implemented by Philippe ''' SeqDiagBuilder.recordFlow( ) # called to build the sequence diagram. Can be commented out later ... return data
def update(self, data): self.recordIndex += 1 timestampMilliSecCriterion = -1 priceFloatCriterion = -1 volumeFloatCriterion = -1 if len(data) == 6: # data comming from archive file (mode simulation) timestampMilliSec, priceFloat, volumeFloat, timestampMilliSecCriterion, priceFloatCriterion, volumeFloatCriterion = data else: # data comming from exchange (mode real time) timestampMilliSec, priceFloat, volumeFloat = data self.writer.writerow([self.recordIndex, timestampMilliSec, priceFloat, volumeFloat, timestampMilliSecCriterion, priceFloatCriterion, volumeFloatCriterion]) if self.isVerbose: print("{} {} {} {}".format(self.recordIndex, timestampMilliSec, priceFloat, volumeFloat, timestampMilliSecCriterion, priceFloatCriterion, volumeFloatCriterion)) SeqDiagBuilder.recordFlow() # called to build the sequence diagram. Can be commented out later ...
def update(self, data): SeqDiagBuilder.recordFlow( ) # called to build the sequence diagram. Can be commented out later ... self.recordIndex += 1 try: if len(data) == 4: # data comming from archive file (mode simulation) timestampMilliSec, numberOfTrades, volumeFloat, priceFloat = data self.writer.writerow([ self.recordIndex, numberOfTrades, timestampMilliSec, "{:.6f}".format(volumeFloat), "{:.2f}".format(priceFloat) ]) if self.isVerbose: print("{} {} {} {}".format(self.recordIndex, timestampMilliSec, priceFloat, volumeFloat)) else: # data comming from exchange (mode real time) timestampMilliSec, volumeFloat, priceFloat = data self.writer.writerow([ self.recordIndex, timestampMilliSec, "{:.6f}".format(volumeFloat), "{:.2f}".format(priceFloat) ]) if self.isVerbose: print("{} {} {} {}".format(self.recordIndex, timestampMilliSec, priceFloat, volumeFloat)) except ValueError as e: ''' This happens sometimes when C2 is started in mode RT for a specified duration. When the duration is reached, all the Observers, namely the Archivers, are closed preamptively, which sometimes causes this exception due to a tentative to write the last received data into an already closed file. ''' if str(e) == 'I/O operation on closed file.': print( 'Last real time data received after closing {}. Consequence: {} not saved/processed !' .format(self.filename, data)) else: raise e
def aggregateSecondaryData(self, timestampMilliSec, priceFloat, volumeFloat): ''' This method is called each time primary data are received. It aggregates the passed primary data and returns None if the aggregation interval (typically 1 second9 is not reached or the tuple timestampMilliSec, priceFloat, volumeFloat once data for the aggregation interval was reached. :seqdiag_note method to be implemented by Philippe :param timestampMilliSec: :param priceFloat: :param volumeFloat: :return: timestampMilliSec, priceFloat, volumeFloat ''' SeqDiagBuilder.recordFlow( ) # called to build the sequence diagram. Can be commented out later ... return timestampMilliSec, priceFloat, volumeFloat # temporally returning silly value !
def update(self, data): recordIndex = '' if len(data) == 4: # data comming from archive file (mode simulation) recordIndexStr, timestampMilliSecStr, volumeFloatStr, priceFloatStr = data else: # data comming from exchange (mode real time) timestampMilliSecStr, volumeFloatStr, priceFloatStr = data timestampMilliSec = int(timestampMilliSecStr) if self.lastSecBeginTimestamp == 0: startTimestamp = self.calculateStartTimestamp(timestampMilliSec) self.lastSecBeginTimestamp = startTimestamp self.lastSecEndTimestamp = startTimestamp + 1000 self.lastSecTradeNumber = 0 self.lastSecVolume = 0 self.lastSecPriceVolumeTotal = 0 else: priceFloat = float(priceFloatStr) volumeFloat = float(volumeFloatStr) lastNotifiedPriceFloat = priceFloat lastNotifiedVolumeFloat = volumeFloat secondary_data = self.aggregateSecondaryData( timestampMilliSec, priceFloat, volumeFloat) if not secondary_data: return catchUpSdTimestamp, catchUpSdTradesNumber, catchUpSdVolumeFloat, sdPricefloat = secondary_data # calling the criterion to check if it should raise an alarm criterionData = self.criterion.check(data) if self.isOneSecondIntervalReached: # sending the secondary data to the archiver so that the sd are written in the # sd file to enable viewing them in a price/volume chart. Note that the archiver # takes care of implementing the secondary data record index. # self.archiver.update((sdTimestamp, sdTradesNumber, sdVolumeFloat, sdPricefloat) + criterionData) if sdPricefloat and sdPricefloat > 0: self.storeAndPrintSecondaryData(sdPricefloat, catchUpSdTimestamp, catchUpSdTradesNumber, catchUpSdVolumeFloat) wasTimeCaughtUp = False catchUpSdTradesNumber = 0 catchUpSdVolumeFloat = 0 catchUpSdPricefloat = 0 while int(timestampMilliSec / 1000) * 1000 > self.lastSecEndTimestamp: wasTimeCaughtUp = True catchUpSdTimestamp = self.lastSecEndTimestamp if self.lastSecVolume > 0: # calculating the price to display for the 0 volume catchup secondary data catchUpSdPricefloat = self.lastSecPriceVolumeTotal / self.lastSecVolume self.storeAndPrintSecondaryData(catchUpSdPricefloat, catchUpSdTimestamp, catchUpSdTradesNumber, catchUpSdVolumeFloat) else: # happens if no transaction were yet processed catchUpSdPricefloat = 0 self.lastSecBeginTimestamp += 1000 self.lastSecEndTimestamp += 1000 if wasTimeCaughtUp: self.lastSecVolume = lastNotifiedVolumeFloat self.lastSecPriceVolumeTotal = lastNotifiedVolumeFloat * lastNotifiedPriceFloat self.lastSecTradeNumber = 1 self.lastSecBeginTimestamp += 1000 self.lastSecEndTimestamp += 1000 self.isOneSecondIntervalReached = False SeqDiagBuilder.recordFlow( ) # called to build the sequence diagram. Can be commented out later ...
def aggregateSecondaryData(self, timestampMilliSec, priceFloat, volumeFloat): ''' This method is called each time primary data are received. It aggregates the passed primary data and returns None if the aggregation interval (typically 1 second9 is not reached or the tuple timestampMilliSec, priceFloat, volumeFloat once data for the aggregation interval was reached. :seqdiag_note method to be implemented by Philippe :param timestampMilliSec: :param priceFloat: :param volumeFloat: :return: timestampMilliSec, priceFloat, volumeFloat ''' SeqDiagBuilder.recordFlow( ) # called to build the sequence diagram. Can be commented out later ... if timestampMilliSec >= self.lastSecBeginTimestamp and timestampMilliSec < self.lastSecEndTimestamp: # here, the current primary data ts is within the current second frame self.lastSecTradeNumber += 1 self.lastSecVolume += volumeFloat self.lastSecPriceVolumeTotal += priceFloat * volumeFloat return None, None, None, None # since we are still within the current second time frame ! elif timestampMilliSec >= self.lastSecEndTimestamp: if timestampMilliSec < self.lastSecEndTimestamp + 1000: # here, the current primary data ts is within the next second frame lastSecBeginTimestamp = self.lastSecBeginTimestamp lastSecTradeNumber = self.lastSecTradeNumber lastSecVolume = self.lastSecVolume if self.lastSecVolume > 0: lastSecAvgPrice = self.lastSecPriceVolumeTotal / self.lastSecVolume else: # happens if no transaction were yet processed, at start of RT or simulation lastSecAvgPrice = 0 self.lastSecTradeNumber = 1 self.lastSecVolume = volumeFloat self.lastSecPriceVolumeTotal = priceFloat * volumeFloat self.lastSecBeginTimestamp += 1000 self.lastSecEndTimestamp += 1000 self.isOneSecondIntervalReached = True return lastSecBeginTimestamp, lastSecTradeNumber, lastSecVolume, lastSecAvgPrice else: # here, the current primary data ts is after the next second frame lastSecBeginTimestamp = self.lastSecBeginTimestamp lastSecTradeNumber = self.lastSecTradeNumber lastSecVolume = self.lastSecVolume if self.lastSecVolume > 0: lastSecAvgPrice = self.lastSecPriceVolumeTotal / self.lastSecVolume else: # happens if no transaction were yet processed, at start of RT or simulation lastSecAvgPrice = 0 self.isOneSecondIntervalReached = True return lastSecBeginTimestamp, lastSecTradeNumber, lastSecVolume, lastSecAvgPrice else: # here, the current primary data ts is before the current second frame. This # situation occurs at the very start of receiving the RT data or when processing # the first lines of the primary data input file in simulation mode when the ts # of those lines is before the ts calculated by the calculateStartTimestamp() method. # In this case, the line must simply be ignored. return None
def testStartModeRTBuildSeqDiag(self): controller = Controller() classCtorArgsDic = { 'ArchivedDatasource': ['primary-two.csv'], 'SecondaryDataAggregator': ['secondary.csv', False], 'Archiver': ['secondary.csv', 'csv dummy col titles', False] } # SeqDiagBuilder.activate(parentdir, 'Controller', 'start', # classCtorArgsDic) # activate sequence diagram building duration = 3 # IMPORTANT: when forcing execution parms, no space separate parm name and parm value ! try: controller.start(['-mr', '-d{}'.format(duration)]) except SystemExit: pass while not controller.wasStopped(): print('controller still working') csvPrimaryDataFileName = controller.primaryDataFileName csvSecondaryDataFileName = controller.buildSecondaryFileNameFromPrimaryFileName( csvPrimaryDataFileName, "secondary") commands = SeqDiagBuilder.createSeqDiaqCommands( actorName='USER', title='C2 simulation mode sequence diagram', maxSigArgNum=None, maxSigCharLen=15, maxNoteCharLen=23) with open("c:\\temp\\C2 RT mode sequence diagram.txt", "w") as f: f.write(commands) SeqDiagBuilder.deactivate() # deactivate sequence diagram building os.remove(csvPrimaryDataFileName) os.remove(csvSecondaryDataFileName) self.assertEqual( '''@startuml title C2 simulation mode sequence diagram actor USER participant Controller /note over of Controller Entry point of the C2 application. When started at the commandline, accepts parameters like RT or simulation mode. end note participant ArchivedDatasource /note over of ArchivedDatasource In simulation mode, reads data line by line from a primary data file. For every data line read, calls the notifyObservers(data) method of its parent class, Observable. end note participant Observable /note over of Observable Pivot class in the Observable design pattern. Each time its notifyObservers(data) method is called, Observable notifies its subscribed Observers of the received data calling update(data) on each registered Observer. end note participant SecondaryDataAggregator /note over of SecondaryDataAggregator Implements the Observer part in the Observable design pattern. Each tima its update(data) method is called, it adds this data to the current secondary aggreagated data and sends the secondary data when appropriate to the Criterion calling its check() method. end note participant PriceVolumeCriterion /note over of PriceVolumeCriterion Inherits from Criterion. Is responsible for computing if an alarm must be raised. Performs its calculation each time its check() method is called. end note participant Archiver /note over of Archiver In simulation mode, this Observer (Archiver, like SecondaryData Aggregator, inherits from Observer) writes the secondary data on disk. In real time mode, saves on disk the primary data. end note USER -> Controller: start(...) activate Controller Controller -> ArchivedDatasource: processArchivedData() activate ArchivedDatasource ArchivedDatasource -> Observable: notifyObservers(data) activate Observable Observable -> SecondaryDataAggregator: update(data) activate SecondaryDataAggregator SecondaryDataAggregator -> SecondaryDataAggregator: aggregateSecondaryData(...) activate SecondaryDataAggregator note right method to be implemented by Philippe end note SecondaryDataAggregator <-- SecondaryDataAggregator: deactivate SecondaryDataAggregator SecondaryDataAggregator -> PriceVolumeCriterion: check(data) activate PriceVolumeCriterion note right method to be implemented by Philippe end note SecondaryDataAggregator <-- PriceVolumeCriterion: deactivate PriceVolumeCriterion SecondaryDataAggregator -> Archiver: update(data) activate Archiver SecondaryDataAggregator <-- Archiver: deactivate Archiver Observable <-- SecondaryDataAggregator: deactivate SecondaryDataAggregator ArchivedDatasource <-- Observable: deactivate Observable Controller <-- ArchivedDatasource: deactivate ArchivedDatasource USER <-- Controller: deactivate Controller @enduml''', commands)