def __init__(self, fix, state_listeners=None): print "Protocol %s %s init" % (self.__class__, fix.version) self.fix = fix self.heartbeat_checks = {} self.pending_test_requests = {} self.heart_beat_interval = None self.factory = None self.session = None self.handler = None # State Objects self.awaiting_logon = self.AwaitingLogonKlazz(self) self.awaiting_logout = AwaitingLogout(self) self.normal_message_processing = NormalMessageProcessing(self) self.logged_out_state = LoggedOut(self) # Keep track of the callables I have scheduled at any given time # if the protocol dies want to cancel these so we don't have 'ghost' # calls after we''re done self.send_heartbeat_call = None self.test_request_checks = {} self.heartbeat_checks = {} self.state = None self.set_state(self.awaiting_logon) self.parser = FIXParser(self.fix, self.on_msg)
def connectionMade(self): print "Connection Made" self.parser = FIXParser(self.fix, self.on_msg)
class FIXProtocol(Protocol): def __init__(self, fix, state_listeners=None): print "Protocol %s %s init" % (self.__class__, fix.version) self.fix = fix self.heartbeat_checks = {} self.pending_test_requests = {} self.heart_beat_interval = None self.factory = None self.session = None self.handler = None # State Objects self.awaiting_logon = self.AwaitingLogonKlazz(self) self.awaiting_logout = AwaitingLogout(self) self.normal_message_processing = NormalMessageProcessing(self) self.logged_out_state = LoggedOut(self) # Keep track of the callables I have scheduled at any given time # if the protocol dies want to cancel these so we don't have 'ghost' # calls after we''re done self.send_heartbeat_call = None self.test_request_checks = {} self.heartbeat_checks = {} self.state = None self.set_state(self.awaiting_logon) self.parser = FIXParser(self.fix, self.on_msg) def set_state(self, s): print "State change %s->%s" % (self.state.__class__.__name__, s.__class__.__name__) old_state = self.state self.state = s if self.session: self.session.set_state(old_state, self.state) @staticmethod def get_seq(msg): return msg.getHeader def send_heartbeat(self): if self.state == self.normal_message_processing: msg = self.fix.Heartbeat() message_string = self.session.compile_message(msg) #print "Sending heartbeat ... %s" % strMsg msg_seq_num = msg.get_header_field_value(self.fix.MsgSeqNum) log(">>> %s %s %s" % (msg_seq_num, msg, message_string)) self.transport.write(message_string) #print "Scheduling heartbeat in %s " % self.heartbeatInterval else: print "NOT SENDING HEARTBEAT - dodgy state %s" % self.state # Depending on what you want to do this may or may not be useful # from observation when a large number of sessions connect at roughly # the same time the heartbeats can cause a 'wave' of messages every heartbeat # interval ( or multiple thereof ). We'll help the reactor out a bit by # adding a small randomization ( 1 second standard deviation ) to the interval # this should be small enough to prevent any test request cycles but large enough # to smooth out heartbeats between multiple sessions delay = random.normalvariate(self.heartbeat_interval, 1) # Or if you're OCD or into Wagner/Kraftwerk/Techno you're probably # not a fan of randomness and like regularly spaced metronomic 'doof's # - uncomment the line below delay = self.heartbeatInterval # delay = self.heartbeatInterval self.send_heartbeat_call = reactor.callLater(delay, self.send_heartbeat) # We'll rely on our heartbeat but myObj = datetime.now() self.heartbeat_checks[myObj] = reactor.callLater(self.heartbeat_interval * 1.5, self.check_heartbeat, myObj) def on_logout(self, msg, in_msg_seq_num, poss_dup_flag): print "onLogout %s" % msg self.session.want_to_be_logged_on = False msg = self.fix.Logout(fields=[self.fix.Text("Accepted logoff request")]) message_string = self.session.compile_message(msg, persist=False) print ">>> %s" % message_string self.transport.write(message_string) self.transport.loseConnection() reactor.callLater(0, self.clean_up_and_die) def check_heartbeat(self, token): assert self.heartbeat_checks.has_key(token) del self.heartbeat_checks[token] print "Check Heartbeat %s" % token if self.state == self.normal_message_processing: print "No heartbeat recieved for %s (%s seconds)" % (token, datetime.now() - token) challenge = "TEST_%s" % str(str(random.random())[2:15]) test_request_id = self.fix.TestReqID(challenge) msg = self.fix.TestRequest(fields=[test_request_id]) message_string = self.session.compile_message(msg) #print "Sending heartbeat ... %s" % strMsg msg_seq_num = msg.get_header_field_value(self.fix.MsgSeqNum) print ">>> %s %s %s" % (msg_seq_num, msg, message_string) self.transport.write(message_string) self.test_request_checks[challenge] = reactor.callLater(10, self.check_test_request_acknowledged, challenge) else: print "Skipping heartbeat checks - not in normal message processing phase" def check_test_request_acknowledged(self, challenge): assert self.test_request_checks.has_key(challenge) print "He's out of there" msg = self.fix.Logout(fields=[self.fix.Text("No response to test request %s" % challenge)]) message_string = self.session.compile_message(msg) msg_seq_num = msg.get_header_field_value(self.fix.MsgSeqNum) print ">>> %s %s %s" % (msg_seq_num, msg, message_string) self.transport.write(message_string) # Bye bye self.transport.loseConnection() reactor.callLater(0, self.clean_up_and_die) def _logoff(self, reason=None, extreme_prejudice=False): """Don't call me - call the session objects logoff instead""" if not self.state == self.normal_message_processing: self.transport.loseConnection() self.clean_up_and_die() if reason: fields = [self.fix.Text(reason)] else: fields = [] msg = self.fix.Logout(fields) message_string = self.session.compile_message(msg) self.transport.write(message_string) if extreme_prejudice: reactor.callLater(0, self.clean_up_and_die) else: self.set_state(self.awaiting_logout) def clean_up_and_die(self): # Get rid of the balls we've got already up in the air! # Note to self # I assume as long as code is logically correct I don't have to worry about if self.state == self.logged_out_state: print "Already cleaned up - nothing to do" else: self.set_state(self.logged_out_state) calls = [self.send_heartbeat_call] + self.test_request_checks.values() + self.heartbeat_checks.values() for call in calls: if call: try: call.cancel() except AlreadyCancelled: pass # TODO - pycharm complaining these havent been used. # Doesnt look like they're required. Delete when happy # self.loggedIn = False # self.loggedOut = True # self.parser = None # quick sanity checks on disconnect if self.session: assert self.session.protocol is self self.session.release_protocol() print "Cleaned up" def on_heartbeat(self, msg, seq, dup): test_request_id = msg.get_field(self.fix.TestReqID) if test_request_id: challenge = test_request_id.value if self.test_request_checks.has_key(challenge): self.test_request_checks[challenge].cancel() del self.test_request_checks[challenge] else: pass #print "Got testRequestID I didn't send!!! %s" % testRequestID else: for posted, cb in self.heartbeat_checks.items()[:]: #print "Clearing check %s %s after %s seconds" % ( callable, posted, (now-posted).seconds) cb.cancel() del self.heartbeat_checks[posted] def on_test_request(self, msg, seq, dup): #print "onTestRequest %s" % msg test_request_id = msg.get_field(self.fix.TestReqID) #assert self.loggedIn msg = self.fix.Heartbeat(fields=[test_request_id]) message_string = self.session.compile_message(msg) #print "Test Request %s .. replying with %s" % (testRequestId, strMsg) self.transport.write(message_string) def connectionMade(self): print "Connection Made" self.parser = FIXParser(self.fix, self.on_msg) def dataReceived(self, data): #print "============================================" #print "Got some data!!! %s" % data self.parser.feed(data) def on_resend_request(self, msg, seq, dup): begin = msg.get_field_value(self.fix.BeginSeqNo) tmp_end = msg.get_field_value(self.fix.EndSeqNo) resend_details = [] gap = None db = self.session.out_db if tmp_end == 0: end_seq_no = self.session.out_msg_seq_num - 1 else: end_seq_no = tmp_end print "onResendRequest %s-%s Playing back %s-%s" % ( begin, tmp_end, begin, end_seq_no) parser = SynchronousParser(self.fix) for i in range(begin, end_seq_no + 1): #print i , "... " , haveMsg = False if db.has_key(i): message_string = db[i] msg, _, _ = parser.feed(message_string) #print "Recovered %s" % msg if msg.Section != 'Session': # We have a message to resend if gap: resend_details.append(gap) gap = None resend_details.append(msg) haveMsg = True if not haveMsg: if gap: gap[1] = i else: gap = [i, i] if gap: resend_details.append(gap) print "Resend Details %s" % len(resend_details) if 1: for obj in resend_details: if type(obj) == ListType: # Gap Fill [send_sequence_number, last_sequence_number] = obj #print "GapFill %s-%s" % (sendSequenceNumber, lastSequenceNumber) else: orig_seq = obj.get_header_field_value(self.fix.MsgSeqNum) #obj.dump( "GapFill>>>") #print "gf2 =%s %s %s" % (obj, msg.getHeaderField( self.pyfix.MsgSeqNum ).value, origSeq ) for obj in resend_details: i = 0 if type(obj) == ListType: # Gap Fill [send_sequence_number, last_sequence_number] = obj gapFill = self.fix.SequenceReset(fields=[self.fix.NewSeqNo(last_sequence_number + 1), self.fix.GapFillFlag('Y')]) message_string = self.session.compile_message(gapFill, poss_dup=True, force_sequence_number=send_sequence_number) print "Sending sequence reset %s->%s %s" % ( send_sequence_number, last_sequence_number + 1, message_string) self.transport.write(message_string) else: # Hmm wonder if this will work. Header of old msg is going to be blatted orig_seq = obj.get_header_field_value(self.fix.MsgSeqNum) print "%10s %s" % (obj, orig_seq) orig_sending_time = obj.get_header_field_value(self.fix.SendingTime) message_string = self.session.compile_message(obj, poss_dup=True, force_sequence_number=orig_seq, orig_sending_time=orig_sending_time) #obj.dump("Recovered: " ) #print "REC>>> %s %s %s" % (origSeq, obj, fixMsg) self.transport.write(message_string) i += 1 if i % 100 == 0: print "Recovering %s ..." def on_sequence_reset(self, msg, seq, dup): #print "onSequenceReset %s %s" % ( msg, msg.toFix() ) is_gap_fill = msg.get_optional_field_values(self.fix.GapFillFlag) if is_gap_fill: self.onGapFill(msg) else: assert False, "Pure sequence reset not handled yet" def connectionLost(self, reason): if self.state != self.logged_out_state: print "PROTOCOL: Connection lost reason = %s" % reason self.clean_up_and_die() else: print "Connection Closed" def on_msg(self, msg, data): message_class = msg.__class__ message_seq_num = msg.get_header_field_value(self.fix.MsgSeqNum) poss_dup_flag = msg.get_header_field_value(self.fix.PossDupFlag) if self.session is not None: sender = self.session.sender else: sender = "UNKN" log("<<< %s %s %s->%s %s" % (sender, message_seq_num, msg, self.state, data)) try: msg.validate() except MessageIntegrityException, e: print "Integrity Exception " + e.message e.msg.dump() self.session.last_integrity_exception = msg, e # No further processing required on a message that fails integrity checks if self.session.onIntegrityException: self.session.onIntegrityException(e) return except BusinessReject, br: self.last_business_reject = msg, br fields = [self.fix.RefSeqNum(message_seq_num), self.fix.Text(br.message)] if br.field is not None: fields.append(br.field) reject = self.fix.Reject(fields=fields) # br.field = SessionRejectReason print "Creating reject - fields are %s" % str(fields) reject_string = self.session.compile_message(reject) self.transport.write(reject_string) # Spec says messages which fail business validation sholud be logged + sequence numbers # incremented self.session.persist_and_advance(msg.to_fix(), checkInSequence=True) return
class FIXProtocol(Protocol): def __init__(self, fix, state_listeners=None): print "Protocol %s %s init" % (self.__class__, fix.version) self.fix = fix self.heartbeat_checks = {} self.pending_test_requests = {} self.heart_beat_interval = None self.factory = None self.session = None self.handler = None # State Objects self.awaiting_logon = self.AwaitingLogonKlazz(self) self.awaiting_logout = AwaitingLogout(self) self.normal_message_processing = NormalMessageProcessing(self) self.logged_out_state = LoggedOut(self) # Keep track of the callables I have scheduled at any given time # if the protocol dies want to cancel these so we don't have 'ghost' # calls after we''re done self.send_heartbeat_call = None self.test_request_checks = {} self.heartbeat_checks = {} self.state = None self.set_state(self.awaiting_logon) self.parser = FIXParser(self.fix, self.on_msg) def set_state(self, s): print "State change %s->%s" % (self.state.__class__.__name__, s.__class__.__name__) old_state = self.state self.state = s if self.session: self.session.set_state(old_state, self.state) @staticmethod def get_seq(msg): return msg.getHeader def send_heartbeat(self): if self.state == self.normal_message_processing: msg = self.fix.Heartbeat() message_string = self.session.compile_message(msg) #print "Sending heartbeat ... %s" % strMsg msg_seq_num = msg.get_header_field_value(self.fix.MsgSeqNum) log(">>> %s %s %s" % (msg_seq_num, msg, message_string)) self.transport.write(message_string) #print "Scheduling heartbeat in %s " % self.heartbeatInterval else: print "NOT SENDING HEARTBEAT - dodgy state %s" % self.state # Depending on what you want to do this may or may not be useful # from observation when a large number of sessions connect at roughly # the same time the heartbeats can cause a 'wave' of messages every heartbeat # interval ( or multiple thereof ). We'll help the reactor out a bit by # adding a small randomization ( 1 second standard deviation ) to the interval # this should be small enough to prevent any test request cycles but large enough # to smooth out heartbeats between multiple sessions delay = random.normalvariate(self.heartbeat_interval, 1) # Or if you're OCD or into Wagner/Kraftwerk/Techno you're probably # not a fan of randomness and like regularly spaced metronomic 'doof's # - uncomment the line below delay = self.heartbeatInterval # delay = self.heartbeatInterval self.send_heartbeat_call = reactor.callLater(delay, self.send_heartbeat) # We'll rely on our heartbeat but myObj = datetime.now() self.heartbeat_checks[myObj] = reactor.callLater( self.heartbeat_interval * 1.5, self.check_heartbeat, myObj) def on_logout(self, msg, in_msg_seq_num, poss_dup_flag): print "onLogout %s" % msg self.session.want_to_be_logged_on = False msg = self.fix.Logout( fields=[self.fix.Text("Accepted logoff request")]) message_string = self.session.compile_message(msg, persist=False) print ">>> %s" % message_string self.transport.write(message_string) self.transport.loseConnection() reactor.callLater(0, self.clean_up_and_die) def check_heartbeat(self, token): assert self.heartbeat_checks.has_key(token) del self.heartbeat_checks[token] print "Check Heartbeat %s" % token if self.state == self.normal_message_processing: print "No heartbeat recieved for %s (%s seconds)" % ( token, datetime.now() - token) challenge = "TEST_%s" % str(str(random.random())[2:15]) test_request_id = self.fix.TestReqID(challenge) msg = self.fix.TestRequest(fields=[test_request_id]) message_string = self.session.compile_message(msg) #print "Sending heartbeat ... %s" % strMsg msg_seq_num = msg.get_header_field_value(self.fix.MsgSeqNum) print ">>> %s %s %s" % (msg_seq_num, msg, message_string) self.transport.write(message_string) self.test_request_checks[challenge] = reactor.callLater( 10, self.check_test_request_acknowledged, challenge) else: print "Skipping heartbeat checks - not in normal message processing phase" def check_test_request_acknowledged(self, challenge): assert self.test_request_checks.has_key(challenge) print "He's out of there" msg = self.fix.Logout(fields=[ self.fix.Text("No response to test request %s" % challenge) ]) message_string = self.session.compile_message(msg) msg_seq_num = msg.get_header_field_value(self.fix.MsgSeqNum) print ">>> %s %s %s" % (msg_seq_num, msg, message_string) self.transport.write(message_string) # Bye bye self.transport.loseConnection() reactor.callLater(0, self.clean_up_and_die) def _logoff(self, reason=None, extreme_prejudice=False): """Don't call me - call the session objects logoff instead""" if not self.state == self.normal_message_processing: self.transport.loseConnection() self.clean_up_and_die() if reason: fields = [self.fix.Text(reason)] else: fields = [] msg = self.fix.Logout(fields) message_string = self.session.compile_message(msg) self.transport.write(message_string) if extreme_prejudice: reactor.callLater(0, self.clean_up_and_die) else: self.set_state(self.awaiting_logout) def clean_up_and_die(self): # Get rid of the balls we've got already up in the air! # Note to self # I assume as long as code is logically correct I don't have to worry about if self.state == self.logged_out_state: print "Already cleaned up - nothing to do" else: self.set_state(self.logged_out_state) calls = [self.send_heartbeat_call ] + self.test_request_checks.values( ) + self.heartbeat_checks.values() for call in calls: if call: try: call.cancel() except AlreadyCancelled: pass # TODO - pycharm complaining these havent been used. # Doesnt look like they're required. Delete when happy # self.loggedIn = False # self.loggedOut = True # self.parser = None # quick sanity checks on disconnect if self.session: assert self.session.protocol is self self.session.release_protocol() print "Cleaned up" def on_heartbeat(self, msg, seq, dup): test_request_id = msg.get_field(self.fix.TestReqID) if test_request_id: challenge = test_request_id.value if self.test_request_checks.has_key(challenge): self.test_request_checks[challenge].cancel() del self.test_request_checks[challenge] else: pass #print "Got testRequestID I didn't send!!! %s" % testRequestID else: for posted, cb in self.heartbeat_checks.items()[:]: #print "Clearing check %s %s after %s seconds" % ( callable, posted, (now-posted).seconds) cb.cancel() del self.heartbeat_checks[posted] def on_test_request(self, msg, seq, dup): #print "onTestRequest %s" % msg test_request_id = msg.get_field(self.fix.TestReqID) #assert self.loggedIn msg = self.fix.Heartbeat(fields=[test_request_id]) message_string = self.session.compile_message(msg) #print "Test Request %s .. replying with %s" % (testRequestId, strMsg) self.transport.write(message_string) def connectionMade(self): print "Connection Made" self.parser = FIXParser(self.fix, self.on_msg) def dataReceived(self, data): #print "============================================" #print "Got some data!!! %s" % data self.parser.feed(data) def on_resend_request(self, msg, seq, dup): begin = msg.get_field_value(self.fix.BeginSeqNo) tmp_end = msg.get_field_value(self.fix.EndSeqNo) resend_details = [] gap = None db = self.session.out_db if tmp_end == 0: end_seq_no = self.session.out_msg_seq_num - 1 else: end_seq_no = tmp_end print "onResendRequest %s-%s Playing back %s-%s" % (begin, tmp_end, begin, end_seq_no) parser = SynchronousParser(self.fix) for i in range(begin, end_seq_no + 1): #print i , "... " , haveMsg = False if db.has_key(i): message_string = db[i] msg, _, _ = parser.feed(message_string) #print "Recovered %s" % msg if msg.Section != 'Session': # We have a message to resend if gap: resend_details.append(gap) gap = None resend_details.append(msg) haveMsg = True if not haveMsg: if gap: gap[1] = i else: gap = [i, i] if gap: resend_details.append(gap) print "Resend Details %s" % len(resend_details) if 1: for obj in resend_details: if type(obj) == ListType: # Gap Fill [send_sequence_number, last_sequence_number] = obj #print "GapFill %s-%s" % (sendSequenceNumber, lastSequenceNumber) else: orig_seq = obj.get_header_field_value(self.fix.MsgSeqNum) #obj.dump( "GapFill>>>") #print "gf2 =%s %s %s" % (obj, msg.getHeaderField( self.pyfix.MsgSeqNum ).value, origSeq ) for obj in resend_details: i = 0 if type(obj) == ListType: # Gap Fill [send_sequence_number, last_sequence_number] = obj gapFill = self.fix.SequenceReset(fields=[ self.fix.NewSeqNo(last_sequence_number + 1), self.fix.GapFillFlag('Y') ]) message_string = self.session.compile_message( gapFill, poss_dup=True, force_sequence_number=send_sequence_number) print "Sending sequence reset %s->%s %s" % ( send_sequence_number, last_sequence_number + 1, message_string) self.transport.write(message_string) else: # Hmm wonder if this will work. Header of old msg is going to be blatted orig_seq = obj.get_header_field_value(self.fix.MsgSeqNum) print "%10s %s" % (obj, orig_seq) orig_sending_time = obj.get_header_field_value( self.fix.SendingTime) message_string = self.session.compile_message( obj, poss_dup=True, force_sequence_number=orig_seq, orig_sending_time=orig_sending_time) #obj.dump("Recovered: " ) #print "REC>>> %s %s %s" % (origSeq, obj, fixMsg) self.transport.write(message_string) i += 1 if i % 100 == 0: print "Recovering %s ..." def on_sequence_reset(self, msg, seq, dup): #print "onSequenceReset %s %s" % ( msg, msg.toFix() ) is_gap_fill = msg.get_optional_field_values(self.fix.GapFillFlag) if is_gap_fill: self.onGapFill(msg) else: assert False, "Pure sequence reset not handled yet" def connectionLost(self, reason): if self.state != self.logged_out_state: print "PROTOCOL: Connection lost reason = %s" % reason self.clean_up_and_die() else: print "Connection Closed" def on_msg(self, msg, data): message_class = msg.__class__ message_seq_num = msg.get_header_field_value(self.fix.MsgSeqNum) poss_dup_flag = msg.get_header_field_value(self.fix.PossDupFlag) if self.session is not None: sender = self.session.sender else: sender = "UNKN" log("<<< %s %s %s->%s %s" % (sender, message_seq_num, msg, self.state, data)) try: msg.validate() except MessageIntegrityException, e: print "Integrity Exception " + e.message e.msg.dump() self.session.last_integrity_exception = msg, e # No further processing required on a message that fails integrity checks if self.session.onIntegrityException: self.session.onIntegrityException(e) return except BusinessReject, br: self.last_business_reject = msg, br fields = [ self.fix.RefSeqNum(message_seq_num), self.fix.Text(br.message) ] if br.field is not None: fields.append(br.field) reject = self.fix.Reject( fields=fields) # br.field = SessionRejectReason print "Creating reject - fields are %s" % str(fields) reject_string = self.session.compile_message(reject) self.transport.write(reject_string) # Spec says messages which fail business validation sholud be logged + sequence numbers # incremented self.session.persist_and_advance(msg.to_fix(), checkInSequence=True) return