From c46c8d3ff2dc13cd1a7c35fc6cf21caba598b3b8 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 00:34:23 +0000 Subject: [PATCH 01/33] Enter order. --- rnps/asxouch.py | 141 +++++++++++++++++++++--------------------------- 1 file changed, 62 insertions(+), 79 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index edd4621..5e4e70e 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -20,119 +20,102 @@ # Implements ASX OUCH as at 2015/02/25. # -# OUCH4 is a simple order protocol. It uses binary numbers, in contrast -# to its precursor, OUCH3. Messages are relatively compact, and require -# a client-side state machine to maintain an accurate view of the active -# order book. +# ASX OUCH is implemented by NASDAQ OMX's Genium INET platform, as +# deployed for ASX. It's based on NASDAQ's OUCH4, but has a bunch of +# extra fields. # -# OUCH4 is typically encapsulated in the SoupBinTCP 3.0 or UFO framing -# protocols. +# Source documents: +# - ASX Trade OUCH v1.0 (document #036435). +# - ASX OUCH Message Specification, v2.0, 2 September 2013. +# - ASX Trade Q2 2015 Release (SR8) Appendices to ASX Notice. ######################################################################## import struct import errors - -######################################################################## - -def get_message(soup_type, buf): - if len(buf) < 1: - return None - - if soup_type == 'S': - constructor = SEQUENCED_MESSAGES.get(buf[0]) - elif soup_type == 'U': - constructor = UNSEQUENCED_MESSAGES.get(buf[0]) - else: - return None - - if not constructor: - return None - - msg = constructor() - msg.decode(buf) - return msg +from ouch4 import get_message, OuchMessage ######################################################################## -class OuchMessage(object): - _ouch_type = None - - def get_type(self): - return self._ouch_type - - def set_field(self, field_name, value): - if field_name[0:1] == "_" or not hasattr(self, field_name): - raise errors.BadFieldNameError(field_name) - - setattr(self, field_name, value) - return - - def has_field(self, field_name): - return hasattr(self, field_name) - - def get_field(self, field_name): - if field_name[0:1] == "_" or not hasattr(self, field_name): - raise errors.BadFieldNameError(field_name) - - return getattr(self, field_name) - - class EnterOrder(OuchMessage): + #_format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q' _format = '!c14scL8sLL4scccLcc' _ouch_type = 'O' def __init__(self): self.order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - self.stock = '' + self.order_book_id = 0 + self.side = '' + self.quantity = 0 self.price = 0 self.time_in_force = 0 - self.firm = '' - self.display = '' + self.open_close = 0 + self.client_account = '' + self.customer_info = '' + self.exchange_info = '' + self.clearing_participant = '' + self.crossing_key = 0 self.capacity = '' - self.intermarket_sweep_eligibility = '' - self.minimum_quantity = 0 - self.cross_type = '' - self.customer_type = '' + self.directed_wholesale = '' + self.execution_venue = '' + self.intermediary_id = '' + self.order_origin = '' + self.filler = ' ' + self.order_type = '' + self.short_sell_quantity = 0 return def encode(self): return struct.pack(self._format, self._ouch_type, self.order_token.ljust(14), - self.buy_sell_indicator, - self.shares, - self.stock.ljust(8), + self.order_book_id, + self.side, + self.quantity, self.price, self.time_in_force, - self.firm.ljust(4), - self.display, + self.open_close, + self.client_account, + self.customer_info, + self.exchange_info, + self.clearing_participant, + self.crossing_key, self.capacity, - self.intermarket_sweep_eligibility, - self.minimum_quantity, - self.cross_type, - self.customer_type) + self.directed_wholesale, + self.execution_venue, + self.intermediary_id, + self.order_origin, + self.filler, + self.order_type, + self.short_sell_quantity) + +) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.order_token = fields[1].strip() - self.buy_sell_indicator = fields[2] - self.shares = fields[3] - self.stock = fields[4].strip() - self.price = fields[5] - self.time_in_force = fields[6] - self.firm = fields[7].strip() - self.display = fields[8] - self.capacity = fields[9] - self.intermarket_sweep_eligibility = fields[10] - self.minimum_quantity = fields[11] - self.cross_type = fields[12] - self.customer_type = fields[13] + self.order_book_id = int(fields[2]) + self.side = fields[3] + self.quantity = int(fields[4]) + self.price = int(fields[5]) + self.time_in_force = int(fields[6]) + self.open_close = int(fields[7]) + self.client_account = fields[8].strip() + self.customer_info = fields[9].strip() + self.exchange_info = fields[10].strip() + self.clearing_participant = fields[11] + self.crossing_key = int(fields[12]) + self.capacity = fields[13] + self.directed_wholesale = fields[14] + self.execution_venue = fields[15] + self.intermediary_id = fields[16].strip() + self.order_origin = fields[17].strip() + # filler (8 spaces) + self.order_type = fields[19] + self.short_sell_quantity = int(fields[20]) return From a6aa4729d90f7283ce0adda3e882d119784efc26 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:02:46 +0000 Subject: [PATCH 02/33] Replace order. --- rnps/asxouch.py | 60 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 5e4e70e..fec9cb0 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -40,8 +40,7 @@ ######################################################################## class EnterOrder(OuchMessage): - #_format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q' - _format = '!c14scL8sLL4scccLcc' + _format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q' _ouch_type = 'O' def __init__(self): @@ -90,8 +89,6 @@ def encode(self): self.filler, self.order_type, self.short_sell_quantity) - -) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -120,18 +117,25 @@ def decode(self, buf): class ReplaceOrder(OuchMessage): - _format = '!c14s14sLLLccL' + _format = '!c 14s 14s Q L B 10s 15s 32s c c 4s 10s 20s 8s Q' _ouch_type = 'U' def __init__(self): self.existing_order_token = '' self.replacement_order_token = '' - self.shares = 0 + self.quantity = 0 self.price = 0 - self.time_in_force = 0 - self.display = '' - self.intermarket_sweep_eligibility = '' - self.minimum_quantity = 0 + self.open_close = 0 + self.client_account = '' + self.customer_info = '' + self.exchange_info = '' + self.capacity = '' + self.directed_wholesale = '' + self.execution_venue = '' + self.intermediary_id = '' + self.order_origin = '' + self.filler = ' ' + self.short_sell_quantity = 0 return def encode(self): @@ -139,24 +143,38 @@ def encode(self): self._ouch_type, self.existing_order_token.ljust(14), self.replacement_order_token.ljust(14), - self.shares, + self.quantity, self.price, - self.time_in_force, - self.display, - self.intermarket_sweep_eligibility, - self.minimum_quantity) + self.open_close, + self.client_account, + self.customer_info, + self.exchange_info, + self.capacity, + self.directed_wholesale, + self.execution_venue, + self.intermediary_id, + self.order_origin, + self.filler, + self.short_sell_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.existing_order_token = fields[1].strip() self.replacement_order_token = fields[2].strip() - self.shares = fields[3] - self.price = fields[4] - self.time_in_force = fields[5] - self.display = fields[6] - self.intermarket_sweep_eligibility = fields[7] - self.minimum_quantity = fields[8] + self.quantity = int(fields[3]) + self.price = int(fields[4]) + self.open_close = int(fields[5]) + self.client_account = fields[6] + self.customer_info = fields[7] + self.exchange_info = fields[8] + self.capacity = fields[9] + self.directed_wholesale = fields[10] + self.execution_venue = fields[11] + self.intermediary_id = fields[12] + self.order_origin = fields[13] + # filler + self.short_sell_quantity = int(fields[15]) return From e71967b329e2fda7f6084de923e70a8e02774225 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:07:44 +0000 Subject: [PATCH 03/33] Cancel and CancelByOrderId. --- rnps/asxouch.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index fec9cb0..e90a65b 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -179,25 +179,48 @@ def decode(self, buf): class CancelOrder(OuchMessage): - _format = '!c14sL' + _format = '!c 14s' _ouch_type = 'X' def __init__(self): self.order_token = '' - self.shares = 0 return def encode(self): return struct.pack(self._format, self._ouch_type, - self.order_token.ljust(14), - self.shares) + self.order_token.ljust(14)) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.order_token = fields[1].strip() - self.shares = fields[2] + return + + +class CancelByOrderId(OuchMessage): + _format = '!c L c Q' + _ouch_type = 'Y' + + def __init__(self): + self.order_book_id = 0 + self.side = '' + self.order_id = 0 + return + + def encode(self): + return struct.pack(self._format, + self._ouch_type, + self.order_book_id, + self.side, + self.order_id) + + def decode(self, buf): + fields = struct.unpack(self._format, buf) + assert fields[0] == self._ouch_type + self.order_book_id = int(fields[1]) + self.side = fields[2] + self.order_id = int(fields[3]) return @@ -687,6 +710,7 @@ def decode(self, buf): "O": EnterOrder, "U": ReplaceOrder, "X": CancelOrder, + "Y": CancelByOrderId, } SEQUENCED_MESSAGES = { From dd2255a64d15c86da31d9c20c13983c1a2cc7875 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:10:58 +0000 Subject: [PATCH 04/33] Remove ModifyOrder and SystemEvent (neither supported). --- rnps/asxouch.py | 54 ------------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index e90a65b..0f550ce 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -224,58 +224,6 @@ def decode(self, buf): return -class ModifyOrder(OuchMessage): - _format = '!c14scL' - _ouch_type = 'M' - - def __init__(self): - self.order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.order_token.ljust(14), - self.buy_sell_indicator, - self.shares) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.order_token = fields[1].strip() - self.buy_sell_indicator = fields[2] - self.shares = fields[3] - return - - -class SystemEvent(OuchMessage): - _format = '!cQc' - _ouch_type = 'S' - - START_OF_DAY = 'S' - END_OF_DAY = 'E' - - def __init__(self): - self.timestamp = 0 - self.event_code = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.event_code) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.event_code = fields[2] - return - - class Accepted(OuchMessage): _format = '!cQ14scL8sLL4scQccLccc' _ouch_type = 'A' @@ -706,7 +654,6 @@ def decode(self, buf): UNSEQUENCED_MESSAGES = { - "M": ModifyOrder, "O": EnterOrder, "U": ReplaceOrder, "X": CancelOrder, @@ -724,7 +671,6 @@ def decode(self, buf): "K": PriceCorrection, "M": OrderModified, "P": CancelPending, - "S": SystemEvent, "T": OrderPriorityUpdate, "U": Replaced, } From 01bd7137e291b1a1734c2a101e8d465ef0002e63 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:25:01 +0000 Subject: [PATCH 05/33] Accepted. --- rnps/asxouch.py | 95 ++++++++++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 0f550ce..87b1ba7 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -225,26 +225,33 @@ def decode(self, buf): class Accepted(OuchMessage): - _format = '!cQ14scL8sLL4scQccLccc' + _format = '!c Q 14s L c Q Q L B B 10s B 15s 32s c L c c 4s 10s 20s 8s c Q' _ouch_type = 'A' def __init__(self): self.timestamp = 0 self.order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - self.stock = '' + self.order_book_id = 0 + self.side = '' + self.order_id = 0 + self.quantity = 0 self.price = 0 self.time_in_force = 0 - self.firm = '' - self.display = '' - self.order_reference_number = 0 + self.open_close = 0 + self.client_account = '' + self.order_slate = 0 + self.customer_info = '' + self.exchange_info = '' + self.clearing_participant = '' + self.crossing_key = 0 self.capacity = '' - self.intermarket_sweep_eligibility = '' - self.minimum_quantity = 0 - self.cross_type = '' - self.order_state = '' - self.bbo_weight_indicator = '' + self.directed_wholesale = '' + self.execution_venue = '' + self.intermediary_id = '' + self.order_origin = '' + self.filler = ' ' + self.order_type = '' + self.short_sell_quantity = 0 return def encode(self): @@ -252,40 +259,54 @@ def encode(self): self._ouch_type, self.timestamp, self.order_token.ljust(14), - self.buy_sell_indicator, - self.shares, - self.stock.ljust(8), + self.order_book_id, + self.side, + self.order_id, + self.quantity, self.price, self.time_in_force, - self.firm.ljust(4), - self.display, - self.order_reference_number, + self.open_close, + self.client_account, + self.order_slate, + self.customer_info, + self.exchange_info, + self.clearing_participant, + self.crossing_key, self.capacity, - self.intermarket_sweep_eligibility, - self.minimum_quantity, - self.cross_type, - self.order_state, - self.bbo_weight_indicator) + self.directed_wholesale, + self.execution_venue, + self.intermediary_id, + self.order_origin, + self.filler, + self.order_type, + self.short_sell_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.timestamp = fields[1] self.order_token = fields[2].strip() - self.buy_sell_indicator = fields[3] - self.shares = fields[4] - self.stock = fields[5].strip() - self.price = fields[6] - self.time_in_force = fields[7] - self.firm = fields[8].strip() - self.display = fields[9] - self.order_reference_number = fields[10] - self.capacity = fields[11] - self.intermarket_sweep_eligibility = fields[12] - self.minimum_quantity = fields[13] - self.cross_type = fields[14] - self.order_state = fields[15] - self.bbo_weight_indicator = fields[16] + self.order_book_id = fields[3] + self.side = fields[4] + self.order_id = fields[5] + self.quantity = fields[6] + self.price = fields[7] + self.time_in_force = fields[8] + self.open_close = fields[9] + self.client_account = fields[10] + self.order_state = fields[11] + self.customer_info = fields[12] + self.exchange_info = fields[13] + self.clearing_participant = fields[14] + self.crossing_key = fields[15] + self.capacity = fields[16] + self.directed_wholesale = fields[17] + self.execution_venue = fields[18] + self.intermediary_id = fields[19].strip() + self.order_origin = fields[20].strip() + # filler (8 spaces) + self.order_type = fields[22] + self.short_sell_quantity = fields[23] return From 15b06e0b8c8653b31e3fa6d01a765220ee543f6e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:26:45 +0000 Subject: [PATCH 06/33] Rejected. --- rnps/asxouch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 87b1ba7..d31544a 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -542,13 +542,13 @@ def decode(self, buf): class Rejected(OuchMessage): - _format = '!cQ14sc' + _format = '!c Q 14s L' _ouch_type = 'J' def __init__(self): self.timestamp = 0 self.order_token = '' - self.reason = '' + self.reject_code = 0 return def encode(self): @@ -556,14 +556,14 @@ def encode(self): self._ouch_type, self.timestamp, self.order_token.ljust(14), - self.reason) + self.reject_code) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.timestamp = fields[1] self.order_token = fields[2].strip() - self.reason = fields[3] + self.reject_code = fields[3] return From 9ca278d462e52e78abd8fa5697c9e3a74d23390d Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:30:19 +0000 Subject: [PATCH 07/33] Remove unsupported outgoing messages. --- rnps/asxouch.py | 213 ------------------------------------------------ 1 file changed, 213 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index d31544a..88dfa10 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -407,44 +407,6 @@ def decode(self, buf): return -class AIQCanceled(OuchMessage): - _format = '!cQ14sLcLLc' - _ouch_type = 'D' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.decrement_shares = 0 - self.reason = '' - self.quantity_prevented_from_trading = 0 - self.execution_price = 0 - self.liquidity_flag = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.decrement_shares, - self.reason, - self.quantity_prevented_from_trading, - self.execution_price, - self.liquidity_flag) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.decrement_shares = fields[3] - self.reason = fields[4] - self.quantity_prevented_from_trading = fields[5] - self.execution_price = fields[6] - self.liquidity_flag = fields[7] - return - - class Executed(OuchMessage): _format = '!cQ14sLLcQ' _ouch_type = 'E' @@ -480,67 +442,6 @@ def decode(self, buf): return -class BrokenTrade(OuchMessage): - _format = '!cQ14sQc' - _ouch_type = 'B' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.match_number = 0 - self.reason = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.match_number, - self.reason) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.match_number = fields[3] - self.reason = fields[4] - return - - -class PriceCorrection(OuchMessage): - _format = '!cQ14sQLc' - _ouch_type = 'K' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.match_number = 0 - self.new_execution_price = 0 - self.reason = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.match_number, - self.new_execution_price, - self.reason) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.match_number = fields[3] - self.new_execution_price = fields[4] - self.reason = fields[5] - return - - class Rejected(OuchMessage): _format = '!c Q 14s L' _ouch_type = 'J' @@ -567,113 +468,6 @@ def decode(self, buf): return -class CancelPending(OuchMessage): - _format = '!cQ14s' - _ouch_type = 'P' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14)) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - return - - -class CancelReject(OuchMessage): - _format = '!cQ14s' - _ouch_type = 'I' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14)) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - return - - -class OrderPriorityUpdate(OuchMessage): - _format = 'cQ14sLcQ' - _ouch_type = 'T' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.price = 0 - self.display = '' - self.order_reference_number = 0 - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.price, - self.display, - self.order_reference_number) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.price = fields[3] - self.display = fields[4] - self.order_reference_number = fields[5] - return - - -class OrderModified(OuchMessage): - _format = '!cQ14scL' - _ouch_type = 'M' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.buy_sell_indicator, - self.shares) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.buy_sell_indicator = fields[3] - self.shares = fields[4] - return - - UNSEQUENCED_MESSAGES = { "O": EnterOrder, "U": ReplaceOrder, @@ -683,16 +477,9 @@ def decode(self, buf): SEQUENCED_MESSAGES = { "A": Accepted, - "B": BrokenTrade, "C": Canceled, - "D": AIQCanceled, "E": Executed, - "I": CancelReject, "J": Rejected, - "K": PriceCorrection, - "M": OrderModified, - "P": CancelPending, - "T": OrderPriorityUpdate, "U": Replaced, } From fd6dd3377d990ee8eca0c556f71b6173436da5d0 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 02:08:49 +0000 Subject: [PATCH 08/33] Replaced. --- rnps/asxouch.py | 121 ++++++++++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 88dfa10..3b359a9 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -239,7 +239,7 @@ def __init__(self): self.time_in_force = 0 self.open_close = 0 self.client_account = '' - self.order_slate = 0 + self.order_state = 0 self.customer_info = '' self.exchange_info = '' self.clearing_participant = '' @@ -267,7 +267,7 @@ def encode(self): self.time_in_force, self.open_close, self.client_account, - self.order_slate, + self.order_state, self.customer_info, self.exchange_info, self.clearing_participant, @@ -311,27 +311,35 @@ def decode(self, buf): class Replaced(OuchMessage): - _format = '!cQ14scL8sLL4scQccLcc14sc' + _format = '!c Q 14s 14s L c Q Q L B B 10s B 15s 32s c L ' + \ + 'c c 4s 10s 20s 8s c Q' _ouch_type = 'U' def __init__(self): self.timestamp = 0 self.replacement_order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - self.stock = '' + self.previous_order_token = '' + self.order_book_id = 0 + self.side = '' + self.order_id = 0 + self.quantity = 0 self.price = 0 self.time_in_force = 0 - self.firm = '' - self.display = '' - self.order_reference_number = 0 + self.open_close = 0 + self.client_account = '' + self.order_state = 0 + self.customer_info = '' + self.exchange_info = '' + self.clearing_participant = '' + self.crossing_key = 0 self.capacity = '' - self.intermarket_sweep_eligibility = '' - self.minimum_quantity = 0 - self.cross_type = '' - self.order_state = '' - self.previous_order_token = '' - self.bbo_weight_indicator = '' + self.directed_wholesale = '' + self.execution_venue = '' + self.intermediary_id = '' + self.order_origin = '' + self.filler = ' ' * 8 + self.order_type = '' + self.short_sell_quantity = 0 return def encode(self): @@ -339,42 +347,54 @@ def encode(self): self._ouch_type, self.timestamp, self.replacement_order_token.ljust(14), - self.buy_sell_indicator, - self.shares, - self.stock.ljust(8), + self.previous_order_token.ljust(14), + self.order_book_id, + self.side, + self.order_id, + self.quantity, self.price, self.time_in_force, - self.firm.ljust(4), - self.display, - self.order_reference_number, + self.open_close, + self.customer_info.ljust(15), + self.exchange_info.ljust(32), + self.clearing_participant, + self.crossing_key, self.capacity, - self.intermarket_sweep_eligibility, - self.minimum_quantity, - self.cross_type, - self.order_state, - self.previous_order_token.ljust(14), - self.bbo_weight_indicator) + self.directory_wholesale, + self.execution_venue.ljust(4), + self.intermediary_id.ljust(10), + self.order_origin.ljust(20), + self.filler.ljust(8), + self.order_type, + self.short_sell_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.timestamp = fields[1] self.replacement_order_token = fields[2].strip() - self.buy_sell_indicator = fields[3] - self.shares = fields[4] - self.stock = fields[5].strip() - self.price = fields[6] - self.time_in_force = fields[7] - self.firm = fields[8].strip() - self.display = fields[9] - self.order_reference_number = fields[10] - self.capacity = fields[11] - self.intermarket_sweep_eligibility = fields[12] - self.minimum_quantity = fields[13] - self.cross_type = fields[14] - self.order_state = fields[15] - self.previous_order_token = fields[16].strip() - self.bbo_weight_indicator = fields[17] + self.previous_order_token = fields[3].strip() + self.order_book_id = fields[4] + self.side = fields[5] + self.order_id = fields[6] + self.quantity = fields[7] + self.price = fields[8] + self.time_in_force = fields[9] + self.open_close = fields[10] + self.client_account = fields[11].strip() + self.order_state = fields[12] + self.customer_info = fields[13].strip() + self.exchange_info = fields[14].strip() + self.clearing_participant = fields[15].strip() + self.crossing_key = fields[16] + self.capacity = fields[17] + self.directed_wholesale = fields[18] + self.execution_venue = fields[19].strip() + self.intermediary_id = fields[20].strip() + self.order_origin = fields[21].strip() + # filler + self.order_type = fields[23] + self.short_sell_quantity = fields[24] return @@ -484,16 +504,15 @@ def decode(self, buf): } INTEGER_FIELDS = [ - "decrement_shares", - "executed_shares", - "execution_price", - "match_number", - "minimum_quantity", - "new_execution_price", - "order_reference_number", + "crossing_key", + "open_close", + "order_book_id", + "order_id", + "order_state" "price", - "quantity_prevented_from_trading", - "shares", + "quantity", + "reject_code", + "short_sell_quantity", "time_in_force", "timestamp", ] From a1e27835da8fb5df2970f6b8852001f64d98a065 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 02:11:36 +0000 Subject: [PATCH 09/33] Canceled. --- rnps/asxouch.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 3b359a9..7547fa1 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -399,14 +399,16 @@ def decode(self, buf): class Canceled(OuchMessage): - _format = '!cQ14sLc' + _format = '!c Q 14s L c Q B' _ouch_type = 'C' def __init__(self): self.timestamp = 0 self.order_token = '' - self.decrement_shares = 0 - self.reason = '' + self.order_book_id = 0 + self.side = '' + self.order_id = 0 + self.reason = 0 return def encode(self): @@ -414,7 +416,9 @@ def encode(self): self._ouch_type, self.timestamp, self.order_token.ljust(14), - self.decrement_shares, + self.order_book_id, + self.side, + self.order_id, self.reason) def decode(self, buf): @@ -422,8 +426,10 @@ def decode(self, buf): assert fields[0] == self._ouch_type self.timestamp = fields[1] self.order_token = fields[2].strip() - self.decrement_shares = fields[3] - self.reason = fields[4] + self.order_book_id = fields[3] + self.side = fields[4] + self.order_id = fields[5] + self.reason = fields[6] return From f49cd0687d762017e8b43bfd8ee91c92e70887ad Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 03:37:31 +0000 Subject: [PATCH 10/33] Executed. --- rnps/asxouch.py | 74 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 7547fa1..c98b8e3 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -434,37 +434,80 @@ def decode(self, buf): class Executed(OuchMessage): - _format = '!cQ14sLLcQ' + _format = '!c Q 14s L Q L 12B H' _ouch_type = 'E' def __init__(self): self.timestamp = 0 self.order_token = '' - self.executed_shares = 0 - self.execution_price = 0 - self.liquidity_flag = '' - self.match_number = 0 + self.order_book_id = 0 + self.traded_quantity = 0 + self.trade_price = 0 + self.match_id = 0 + self.deal_source = 0 return def encode(self): + m = chr((self.match_id >> 88) & 0xff) + m += chr((self.match_id >> 80) & 0xff) + m += chr((self.match_id >> 72) & 0xff) + m += chr((self.match_id >> 64) & 0xff) + + m += chr((self.match_id >> 56) & 0xff) + m += chr((self.match_id >> 48) & 0xff) + m += chr((self.match_id >> 40) & 0xff) + m += chr((self.match_id >> 32) & 0xff) + + m += chr((self.match_id >> 24) & 0xff) + m += chr((self.match_id >> 16) & 0xff) + m += chr((self.match_id >> 8) & 0xff) + m += chr((self.match_id >> 0) & 0xff) + return struct.pack(self._format, self._ouch_type, self.timestamp, self.order_token.ljust(14), - self.executed_shares, - self.execution_price, - self.liquidity_flag, - self.match_number) + self.order_book_id, + self.traded_quantity, + self.trade_price, + m, + self.deal_source) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.timestamp = fields[1] self.order_token = fields[2].strip() - self.executed_shares = fields[3] - self.execution_price = fields[4] - self.liquidity_flag = fields[5] - self.match_number = fields[6] + self.order_book_id = fields[3] + self.traded_quantity = fields[4] + self.trade_price = fields[5] + m = fields[6] + self.deal_source = fields[7] + + match_id = 0 + if True: + for i in range(12): + match_id |= ord(m[i]) + if i < 11: + match_id = match_id << 8 + else: + match_id = 0 + match_id |= ord(m[0]) << 88 + match_id |= ord(m[1]) << 80 + match_id |= ord(m[2]) << 72 + match_id |= ord(m[3]) << 64 + + match_id |= ord(m[4]) << 56 + match_id |= ord(m[5]) << 48 + match_id |= ord(m[6]) << 40 + match_id |= ord(m[7]) << 32 + + match_id |= ord(m[8]) << 24 + match_id |= ord(m[9]) << 16 + match_id |= ord(m[10]) << 8 + match_id |= ord(m[11]) + + self.match_id = match_id return @@ -511,16 +554,21 @@ def decode(self, buf): INTEGER_FIELDS = [ "crossing_key", + "deal_source", + "match_id", "open_close", "order_book_id", "order_id", "order_state" "price", "quantity", + "reason", "reject_code", "short_sell_quantity", "time_in_force", "timestamp", + "traded_quantity", + "trade_price", ] From 9a220cf6c7f3b0002d254710ca247fe53de5d9f5 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sun, 12 Apr 2015 23:03:50 +0000 Subject: [PATCH 11/33] Cloned script for ASX OUCH simulator. --- rnps-asxouch | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 rnps-asxouch diff --git a/rnps-asxouch b/rnps-asxouch new file mode 100644 index 0000000..386a050 --- /dev/null +++ b/rnps-asxouch @@ -0,0 +1,103 @@ +#! /usr/bin/env python +######################################################################## +# robot-nps, Network Protocol Simulator for Robot Framework +# +# Copyright (C) 2014 David Arnold +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################## + +import argparse +import logging +import rnps + +from twistedremoteserver import TwistedRemoteServer +from twisted.internet import reactor +from twisted.web.resource import Resource +from twisted.web.server import Site + + +######################################################################## + +if __name__ == "__main__": + # Logging + root = logging.getLogger() + root.setLevel(logging.DEBUG) + + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + + fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch.setFormatter(fmt) + root.addHandler(ch) + + logging.getLogger().debug("Logging initialised.") + + + parser = argparse.ArgumentParser(description="A Robot Framework remote " + "server that provides both " + "client and server-side " + "simulation of the OUCH4 " + "protocol.") + parser.add_argument("-p", "--port", + nargs=1, + type=int, + help="TCP port number for remote Robot Framework " + "access.", + default=0) + parser.add_argument("-i", "--interface", + nargs=1, + type=str, + help="IP address of listening interface. Default is " + "%(default)s.", + default="0.0.0.0") + parser.add_argument("-f", "--file", + nargs=1, + type=str, + help="Name of file to write listening port to.", + default=None) + args = parser.parse_args() + interface = args.interface + + if type(args.port) == type([]): + port = args.port[0] + else: + port = args.port + + if type(args.file) == type([]): + port_file = args.file[0] + else: + port_file = args.file + + ouch_robot = rnps.OuchRobot() + robot_api = TwistedRemoteServer(ouch_robot, interface, port) + + root = Resource() + root.putChild("RPC2", robot_api) + factory = Site(root) + listener = reactor.listenTCP(port, factory, interface=interface) + if port_file: + f = open(port_file, "w") + f.write("%u\n" % listener.getHost().port) + f.close() + + logging.getLogger().info("Listening on %s:%u", + interface, + listener.getHost().port) + reactor.run() + + + +######################################################################## From b9203fb63818aa54fd2ea4e39878a6b9f86e65d6 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 29 Apr 2015 05:15:22 +0000 Subject: [PATCH 12/33] Update for SR8 release. Add MAQ and match attributes, basically. --- rnps/asxouch.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index c98b8e3..2a3ad51 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -1,7 +1,7 @@ ######################################################################## # robot-nps, Network Protocol Simulator for Robot Framework # -# Copyright (C) 2014 David Arnold +# Copyright (C) 2015 David Arnold # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,7 +18,7 @@ # ######################################################################## -# Implements ASX OUCH as at 2015/02/25. +# Implements ASX OUCH SR8 as at 2015/04/25. # # ASX OUCH is implemented by NASDAQ OMX's Genium INET platform, as # deployed for ASX. It's based on NASDAQ's OUCH4, but has a bunch of @@ -27,6 +27,7 @@ # Source documents: # - ASX Trade OUCH v1.0 (document #036435). # - ASX OUCH Message Specification, v2.0, 2 September 2013. +# - ASX Trade OUCH Specification, Q2 2015 Release - SR8, 18 March 2015. # - ASX Trade Q2 2015 Release (SR8) Appendices to ASX Notice. ######################################################################## @@ -40,7 +41,7 @@ ######################################################################## class EnterOrder(OuchMessage): - _format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q' + _format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q Q' _ouch_type = 'O' def __init__(self): @@ -64,6 +65,7 @@ def __init__(self): self.filler = ' ' self.order_type = '' self.short_sell_quantity = 0 + self.minimum_acceptable_quantity = 0 return def encode(self): @@ -88,7 +90,8 @@ def encode(self): self.order_origin, self.filler, self.order_type, - self.short_sell_quantity) + self.short_sell_quantity, + self.minimum_acceptable_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -113,11 +116,12 @@ def decode(self, buf): # filler (8 spaces) self.order_type = fields[19] self.short_sell_quantity = int(fields[20]) + self.minimum_acceptable_quantity = int(fields[21]) return class ReplaceOrder(OuchMessage): - _format = '!c 14s 14s Q L B 10s 15s 32s c c 4s 10s 20s 8s Q' + _format = '!c 14s 14s Q L B 10s 15s 32s c c 4s 10s 20s 8s Q Q' _ouch_type = 'U' def __init__(self): @@ -136,6 +140,7 @@ def __init__(self): self.order_origin = '' self.filler = ' ' self.short_sell_quantity = 0 + self.minimum_acceptable_quantity = 0 return def encode(self): @@ -155,7 +160,8 @@ def encode(self): self.intermediary_id, self.order_origin, self.filler, - self.short_sell_quantity) + self.short_sell_quantity, + self.minimum_acceptable_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -175,6 +181,7 @@ def decode(self, buf): self.order_origin = fields[13] # filler self.short_sell_quantity = int(fields[15]) + self.minimum_acceptable_quantity = int(fields[16]) return @@ -252,6 +259,7 @@ def __init__(self): self.filler = ' ' self.order_type = '' self.short_sell_quantity = 0 + self.minimum_acceptable_quantity = 0 return def encode(self): @@ -279,7 +287,8 @@ def encode(self): self.order_origin, self.filler, self.order_type, - self.short_sell_quantity) + self.short_sell_quantity, + self.minimum_acceptable_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -307,12 +316,13 @@ def decode(self, buf): # filler (8 spaces) self.order_type = fields[22] self.short_sell_quantity = fields[23] + self.minimum_acceptable_quantity = fields[24] return class Replaced(OuchMessage): _format = '!c Q 14s 14s L c Q Q L B B 10s B 15s 32s c L ' + \ - 'c c 4s 10s 20s 8s c Q' + 'c c 4s 10s 20s 8s c Q Q' _ouch_type = 'U' def __init__(self): @@ -340,6 +350,7 @@ def __init__(self): self.filler = ' ' * 8 self.order_type = '' self.short_sell_quantity = 0 + self.minimum_acceptable_quantity = 0 return def encode(self): @@ -360,13 +371,14 @@ def encode(self): self.clearing_participant, self.crossing_key, self.capacity, - self.directory_wholesale, + self.directed_wholesale, self.execution_venue.ljust(4), self.intermediary_id.ljust(10), self.order_origin.ljust(20), self.filler.ljust(8), self.order_type, - self.short_sell_quantity) + self.short_sell_quantity, + self.minimum_acceptable_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -395,6 +407,7 @@ def decode(self, buf): # filler self.order_type = fields[23] self.short_sell_quantity = fields[24] + self.minimum_acceptable_quantity = fields[25] return @@ -434,7 +447,7 @@ def decode(self, buf): class Executed(OuchMessage): - _format = '!c Q 14s L Q L 12B H' + _format = '!c Q 14s L Q L 12B H B' _ouch_type = 'E' def __init__(self): @@ -445,6 +458,7 @@ def __init__(self): self.trade_price = 0 self.match_id = 0 self.deal_source = 0 + self.match_attributes = 0 return def encode(self): @@ -471,7 +485,8 @@ def encode(self): self.traded_quantity, self.trade_price, m, - self.deal_source) + self.deal_source, + self.match_attributes) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -483,6 +498,7 @@ def decode(self, buf): self.trade_price = fields[5] m = fields[6] self.deal_source = fields[7] + self.match_attributes = fields[8] match_id = 0 if True: @@ -555,7 +571,9 @@ def decode(self, buf): INTEGER_FIELDS = [ "crossing_key", "deal_source", + "match_attributes", "match_id", + "minimum_acceptable_quantity", "open_close", "order_book_id", "order_id", From 184c7497da60e486df4bbcf92b96399f7bb8caf2 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 00:34:23 +0000 Subject: [PATCH 13/33] Enter order. --- rnps/asxouch.py | 141 +++++++++++++++++++++--------------------------- 1 file changed, 62 insertions(+), 79 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index edd4621..5e4e70e 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -20,119 +20,102 @@ # Implements ASX OUCH as at 2015/02/25. # -# OUCH4 is a simple order protocol. It uses binary numbers, in contrast -# to its precursor, OUCH3. Messages are relatively compact, and require -# a client-side state machine to maintain an accurate view of the active -# order book. +# ASX OUCH is implemented by NASDAQ OMX's Genium INET platform, as +# deployed for ASX. It's based on NASDAQ's OUCH4, but has a bunch of +# extra fields. # -# OUCH4 is typically encapsulated in the SoupBinTCP 3.0 or UFO framing -# protocols. +# Source documents: +# - ASX Trade OUCH v1.0 (document #036435). +# - ASX OUCH Message Specification, v2.0, 2 September 2013. +# - ASX Trade Q2 2015 Release (SR8) Appendices to ASX Notice. ######################################################################## import struct import errors - -######################################################################## - -def get_message(soup_type, buf): - if len(buf) < 1: - return None - - if soup_type == 'S': - constructor = SEQUENCED_MESSAGES.get(buf[0]) - elif soup_type == 'U': - constructor = UNSEQUENCED_MESSAGES.get(buf[0]) - else: - return None - - if not constructor: - return None - - msg = constructor() - msg.decode(buf) - return msg +from ouch4 import get_message, OuchMessage ######################################################################## -class OuchMessage(object): - _ouch_type = None - - def get_type(self): - return self._ouch_type - - def set_field(self, field_name, value): - if field_name[0:1] == "_" or not hasattr(self, field_name): - raise errors.BadFieldNameError(field_name) - - setattr(self, field_name, value) - return - - def has_field(self, field_name): - return hasattr(self, field_name) - - def get_field(self, field_name): - if field_name[0:1] == "_" or not hasattr(self, field_name): - raise errors.BadFieldNameError(field_name) - - return getattr(self, field_name) - - class EnterOrder(OuchMessage): + #_format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q' _format = '!c14scL8sLL4scccLcc' _ouch_type = 'O' def __init__(self): self.order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - self.stock = '' + self.order_book_id = 0 + self.side = '' + self.quantity = 0 self.price = 0 self.time_in_force = 0 - self.firm = '' - self.display = '' + self.open_close = 0 + self.client_account = '' + self.customer_info = '' + self.exchange_info = '' + self.clearing_participant = '' + self.crossing_key = 0 self.capacity = '' - self.intermarket_sweep_eligibility = '' - self.minimum_quantity = 0 - self.cross_type = '' - self.customer_type = '' + self.directed_wholesale = '' + self.execution_venue = '' + self.intermediary_id = '' + self.order_origin = '' + self.filler = ' ' + self.order_type = '' + self.short_sell_quantity = 0 return def encode(self): return struct.pack(self._format, self._ouch_type, self.order_token.ljust(14), - self.buy_sell_indicator, - self.shares, - self.stock.ljust(8), + self.order_book_id, + self.side, + self.quantity, self.price, self.time_in_force, - self.firm.ljust(4), - self.display, + self.open_close, + self.client_account, + self.customer_info, + self.exchange_info, + self.clearing_participant, + self.crossing_key, self.capacity, - self.intermarket_sweep_eligibility, - self.minimum_quantity, - self.cross_type, - self.customer_type) + self.directed_wholesale, + self.execution_venue, + self.intermediary_id, + self.order_origin, + self.filler, + self.order_type, + self.short_sell_quantity) + +) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.order_token = fields[1].strip() - self.buy_sell_indicator = fields[2] - self.shares = fields[3] - self.stock = fields[4].strip() - self.price = fields[5] - self.time_in_force = fields[6] - self.firm = fields[7].strip() - self.display = fields[8] - self.capacity = fields[9] - self.intermarket_sweep_eligibility = fields[10] - self.minimum_quantity = fields[11] - self.cross_type = fields[12] - self.customer_type = fields[13] + self.order_book_id = int(fields[2]) + self.side = fields[3] + self.quantity = int(fields[4]) + self.price = int(fields[5]) + self.time_in_force = int(fields[6]) + self.open_close = int(fields[7]) + self.client_account = fields[8].strip() + self.customer_info = fields[9].strip() + self.exchange_info = fields[10].strip() + self.clearing_participant = fields[11] + self.crossing_key = int(fields[12]) + self.capacity = fields[13] + self.directed_wholesale = fields[14] + self.execution_venue = fields[15] + self.intermediary_id = fields[16].strip() + self.order_origin = fields[17].strip() + # filler (8 spaces) + self.order_type = fields[19] + self.short_sell_quantity = int(fields[20]) return From bf6f76b576c0e697d8c1c5f14537f5863cab8a77 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:02:46 +0000 Subject: [PATCH 14/33] Replace order. --- rnps/asxouch.py | 60 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 5e4e70e..fec9cb0 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -40,8 +40,7 @@ ######################################################################## class EnterOrder(OuchMessage): - #_format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q' - _format = '!c14scL8sLL4scccLcc' + _format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q' _ouch_type = 'O' def __init__(self): @@ -90,8 +89,6 @@ def encode(self): self.filler, self.order_type, self.short_sell_quantity) - -) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -120,18 +117,25 @@ def decode(self, buf): class ReplaceOrder(OuchMessage): - _format = '!c14s14sLLLccL' + _format = '!c 14s 14s Q L B 10s 15s 32s c c 4s 10s 20s 8s Q' _ouch_type = 'U' def __init__(self): self.existing_order_token = '' self.replacement_order_token = '' - self.shares = 0 + self.quantity = 0 self.price = 0 - self.time_in_force = 0 - self.display = '' - self.intermarket_sweep_eligibility = '' - self.minimum_quantity = 0 + self.open_close = 0 + self.client_account = '' + self.customer_info = '' + self.exchange_info = '' + self.capacity = '' + self.directed_wholesale = '' + self.execution_venue = '' + self.intermediary_id = '' + self.order_origin = '' + self.filler = ' ' + self.short_sell_quantity = 0 return def encode(self): @@ -139,24 +143,38 @@ def encode(self): self._ouch_type, self.existing_order_token.ljust(14), self.replacement_order_token.ljust(14), - self.shares, + self.quantity, self.price, - self.time_in_force, - self.display, - self.intermarket_sweep_eligibility, - self.minimum_quantity) + self.open_close, + self.client_account, + self.customer_info, + self.exchange_info, + self.capacity, + self.directed_wholesale, + self.execution_venue, + self.intermediary_id, + self.order_origin, + self.filler, + self.short_sell_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.existing_order_token = fields[1].strip() self.replacement_order_token = fields[2].strip() - self.shares = fields[3] - self.price = fields[4] - self.time_in_force = fields[5] - self.display = fields[6] - self.intermarket_sweep_eligibility = fields[7] - self.minimum_quantity = fields[8] + self.quantity = int(fields[3]) + self.price = int(fields[4]) + self.open_close = int(fields[5]) + self.client_account = fields[6] + self.customer_info = fields[7] + self.exchange_info = fields[8] + self.capacity = fields[9] + self.directed_wholesale = fields[10] + self.execution_venue = fields[11] + self.intermediary_id = fields[12] + self.order_origin = fields[13] + # filler + self.short_sell_quantity = int(fields[15]) return From 8296c13542939ef82a84d9fdccedcdfb1f5ed2fa Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:07:44 +0000 Subject: [PATCH 15/33] Cancel and CancelByOrderId. --- rnps/asxouch.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index fec9cb0..e90a65b 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -179,25 +179,48 @@ def decode(self, buf): class CancelOrder(OuchMessage): - _format = '!c14sL' + _format = '!c 14s' _ouch_type = 'X' def __init__(self): self.order_token = '' - self.shares = 0 return def encode(self): return struct.pack(self._format, self._ouch_type, - self.order_token.ljust(14), - self.shares) + self.order_token.ljust(14)) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.order_token = fields[1].strip() - self.shares = fields[2] + return + + +class CancelByOrderId(OuchMessage): + _format = '!c L c Q' + _ouch_type = 'Y' + + def __init__(self): + self.order_book_id = 0 + self.side = '' + self.order_id = 0 + return + + def encode(self): + return struct.pack(self._format, + self._ouch_type, + self.order_book_id, + self.side, + self.order_id) + + def decode(self, buf): + fields = struct.unpack(self._format, buf) + assert fields[0] == self._ouch_type + self.order_book_id = int(fields[1]) + self.side = fields[2] + self.order_id = int(fields[3]) return @@ -687,6 +710,7 @@ def decode(self, buf): "O": EnterOrder, "U": ReplaceOrder, "X": CancelOrder, + "Y": CancelByOrderId, } SEQUENCED_MESSAGES = { From cfb227d1c3b7e91514ae1aadf6c7ccc95fabac39 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:10:58 +0000 Subject: [PATCH 16/33] Remove ModifyOrder and SystemEvent (neither supported). --- rnps/asxouch.py | 54 ------------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index e90a65b..0f550ce 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -224,58 +224,6 @@ def decode(self, buf): return -class ModifyOrder(OuchMessage): - _format = '!c14scL' - _ouch_type = 'M' - - def __init__(self): - self.order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.order_token.ljust(14), - self.buy_sell_indicator, - self.shares) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.order_token = fields[1].strip() - self.buy_sell_indicator = fields[2] - self.shares = fields[3] - return - - -class SystemEvent(OuchMessage): - _format = '!cQc' - _ouch_type = 'S' - - START_OF_DAY = 'S' - END_OF_DAY = 'E' - - def __init__(self): - self.timestamp = 0 - self.event_code = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.event_code) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.event_code = fields[2] - return - - class Accepted(OuchMessage): _format = '!cQ14scL8sLL4scQccLccc' _ouch_type = 'A' @@ -706,7 +654,6 @@ def decode(self, buf): UNSEQUENCED_MESSAGES = { - "M": ModifyOrder, "O": EnterOrder, "U": ReplaceOrder, "X": CancelOrder, @@ -724,7 +671,6 @@ def decode(self, buf): "K": PriceCorrection, "M": OrderModified, "P": CancelPending, - "S": SystemEvent, "T": OrderPriorityUpdate, "U": Replaced, } From 6f25d68c8ca8073544cd67bd03c5739d8840210d Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:25:01 +0000 Subject: [PATCH 17/33] Accepted. --- rnps/asxouch.py | 95 ++++++++++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 0f550ce..87b1ba7 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -225,26 +225,33 @@ def decode(self, buf): class Accepted(OuchMessage): - _format = '!cQ14scL8sLL4scQccLccc' + _format = '!c Q 14s L c Q Q L B B 10s B 15s 32s c L c c 4s 10s 20s 8s c Q' _ouch_type = 'A' def __init__(self): self.timestamp = 0 self.order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - self.stock = '' + self.order_book_id = 0 + self.side = '' + self.order_id = 0 + self.quantity = 0 self.price = 0 self.time_in_force = 0 - self.firm = '' - self.display = '' - self.order_reference_number = 0 + self.open_close = 0 + self.client_account = '' + self.order_slate = 0 + self.customer_info = '' + self.exchange_info = '' + self.clearing_participant = '' + self.crossing_key = 0 self.capacity = '' - self.intermarket_sweep_eligibility = '' - self.minimum_quantity = 0 - self.cross_type = '' - self.order_state = '' - self.bbo_weight_indicator = '' + self.directed_wholesale = '' + self.execution_venue = '' + self.intermediary_id = '' + self.order_origin = '' + self.filler = ' ' + self.order_type = '' + self.short_sell_quantity = 0 return def encode(self): @@ -252,40 +259,54 @@ def encode(self): self._ouch_type, self.timestamp, self.order_token.ljust(14), - self.buy_sell_indicator, - self.shares, - self.stock.ljust(8), + self.order_book_id, + self.side, + self.order_id, + self.quantity, self.price, self.time_in_force, - self.firm.ljust(4), - self.display, - self.order_reference_number, + self.open_close, + self.client_account, + self.order_slate, + self.customer_info, + self.exchange_info, + self.clearing_participant, + self.crossing_key, self.capacity, - self.intermarket_sweep_eligibility, - self.minimum_quantity, - self.cross_type, - self.order_state, - self.bbo_weight_indicator) + self.directed_wholesale, + self.execution_venue, + self.intermediary_id, + self.order_origin, + self.filler, + self.order_type, + self.short_sell_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.timestamp = fields[1] self.order_token = fields[2].strip() - self.buy_sell_indicator = fields[3] - self.shares = fields[4] - self.stock = fields[5].strip() - self.price = fields[6] - self.time_in_force = fields[7] - self.firm = fields[8].strip() - self.display = fields[9] - self.order_reference_number = fields[10] - self.capacity = fields[11] - self.intermarket_sweep_eligibility = fields[12] - self.minimum_quantity = fields[13] - self.cross_type = fields[14] - self.order_state = fields[15] - self.bbo_weight_indicator = fields[16] + self.order_book_id = fields[3] + self.side = fields[4] + self.order_id = fields[5] + self.quantity = fields[6] + self.price = fields[7] + self.time_in_force = fields[8] + self.open_close = fields[9] + self.client_account = fields[10] + self.order_state = fields[11] + self.customer_info = fields[12] + self.exchange_info = fields[13] + self.clearing_participant = fields[14] + self.crossing_key = fields[15] + self.capacity = fields[16] + self.directed_wholesale = fields[17] + self.execution_venue = fields[18] + self.intermediary_id = fields[19].strip() + self.order_origin = fields[20].strip() + # filler (8 spaces) + self.order_type = fields[22] + self.short_sell_quantity = fields[23] return From 8571798f579f04bb0e0083061b8515239a242f7a Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:26:45 +0000 Subject: [PATCH 18/33] Rejected. --- rnps/asxouch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 87b1ba7..d31544a 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -542,13 +542,13 @@ def decode(self, buf): class Rejected(OuchMessage): - _format = '!cQ14sc' + _format = '!c Q 14s L' _ouch_type = 'J' def __init__(self): self.timestamp = 0 self.order_token = '' - self.reason = '' + self.reject_code = 0 return def encode(self): @@ -556,14 +556,14 @@ def encode(self): self._ouch_type, self.timestamp, self.order_token.ljust(14), - self.reason) + self.reject_code) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.timestamp = fields[1] self.order_token = fields[2].strip() - self.reason = fields[3] + self.reject_code = fields[3] return From d4c3ccbeb598368f1aeba85bdab2ceeb67a55d4e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 01:30:19 +0000 Subject: [PATCH 19/33] Remove unsupported outgoing messages. --- rnps/asxouch.py | 213 ------------------------------------------------ 1 file changed, 213 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index d31544a..88dfa10 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -407,44 +407,6 @@ def decode(self, buf): return -class AIQCanceled(OuchMessage): - _format = '!cQ14sLcLLc' - _ouch_type = 'D' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.decrement_shares = 0 - self.reason = '' - self.quantity_prevented_from_trading = 0 - self.execution_price = 0 - self.liquidity_flag = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.decrement_shares, - self.reason, - self.quantity_prevented_from_trading, - self.execution_price, - self.liquidity_flag) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.decrement_shares = fields[3] - self.reason = fields[4] - self.quantity_prevented_from_trading = fields[5] - self.execution_price = fields[6] - self.liquidity_flag = fields[7] - return - - class Executed(OuchMessage): _format = '!cQ14sLLcQ' _ouch_type = 'E' @@ -480,67 +442,6 @@ def decode(self, buf): return -class BrokenTrade(OuchMessage): - _format = '!cQ14sQc' - _ouch_type = 'B' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.match_number = 0 - self.reason = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.match_number, - self.reason) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.match_number = fields[3] - self.reason = fields[4] - return - - -class PriceCorrection(OuchMessage): - _format = '!cQ14sQLc' - _ouch_type = 'K' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.match_number = 0 - self.new_execution_price = 0 - self.reason = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.match_number, - self.new_execution_price, - self.reason) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.match_number = fields[3] - self.new_execution_price = fields[4] - self.reason = fields[5] - return - - class Rejected(OuchMessage): _format = '!c Q 14s L' _ouch_type = 'J' @@ -567,113 +468,6 @@ def decode(self, buf): return -class CancelPending(OuchMessage): - _format = '!cQ14s' - _ouch_type = 'P' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14)) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - return - - -class CancelReject(OuchMessage): - _format = '!cQ14s' - _ouch_type = 'I' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14)) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - return - - -class OrderPriorityUpdate(OuchMessage): - _format = 'cQ14sLcQ' - _ouch_type = 'T' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.price = 0 - self.display = '' - self.order_reference_number = 0 - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.price, - self.display, - self.order_reference_number) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.price = fields[3] - self.display = fields[4] - self.order_reference_number = fields[5] - return - - -class OrderModified(OuchMessage): - _format = '!cQ14scL' - _ouch_type = 'M' - - def __init__(self): - self.timestamp = 0 - self.order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - return - - def encode(self): - return struct.pack(self._format, - self._ouch_type, - self.timestamp, - self.order_token.ljust(14), - self.buy_sell_indicator, - self.shares) - - def decode(self, buf): - fields = struct.unpack(self._format, buf) - assert fields[0] == self._ouch_type - self.timestamp = fields[1] - self.order_token = fields[2].strip() - self.buy_sell_indicator = fields[3] - self.shares = fields[4] - return - - UNSEQUENCED_MESSAGES = { "O": EnterOrder, "U": ReplaceOrder, @@ -683,16 +477,9 @@ def decode(self, buf): SEQUENCED_MESSAGES = { "A": Accepted, - "B": BrokenTrade, "C": Canceled, - "D": AIQCanceled, "E": Executed, - "I": CancelReject, "J": Rejected, - "K": PriceCorrection, - "M": OrderModified, - "P": CancelPending, - "T": OrderPriorityUpdate, "U": Replaced, } From 10566d582f1785931d936f414b481ceb93859649 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 02:08:49 +0000 Subject: [PATCH 20/33] Replaced. --- rnps/asxouch.py | 121 ++++++++++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 88dfa10..3b359a9 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -239,7 +239,7 @@ def __init__(self): self.time_in_force = 0 self.open_close = 0 self.client_account = '' - self.order_slate = 0 + self.order_state = 0 self.customer_info = '' self.exchange_info = '' self.clearing_participant = '' @@ -267,7 +267,7 @@ def encode(self): self.time_in_force, self.open_close, self.client_account, - self.order_slate, + self.order_state, self.customer_info, self.exchange_info, self.clearing_participant, @@ -311,27 +311,35 @@ def decode(self, buf): class Replaced(OuchMessage): - _format = '!cQ14scL8sLL4scQccLcc14sc' + _format = '!c Q 14s 14s L c Q Q L B B 10s B 15s 32s c L ' + \ + 'c c 4s 10s 20s 8s c Q' _ouch_type = 'U' def __init__(self): self.timestamp = 0 self.replacement_order_token = '' - self.buy_sell_indicator = '' - self.shares = 0 - self.stock = '' + self.previous_order_token = '' + self.order_book_id = 0 + self.side = '' + self.order_id = 0 + self.quantity = 0 self.price = 0 self.time_in_force = 0 - self.firm = '' - self.display = '' - self.order_reference_number = 0 + self.open_close = 0 + self.client_account = '' + self.order_state = 0 + self.customer_info = '' + self.exchange_info = '' + self.clearing_participant = '' + self.crossing_key = 0 self.capacity = '' - self.intermarket_sweep_eligibility = '' - self.minimum_quantity = 0 - self.cross_type = '' - self.order_state = '' - self.previous_order_token = '' - self.bbo_weight_indicator = '' + self.directed_wholesale = '' + self.execution_venue = '' + self.intermediary_id = '' + self.order_origin = '' + self.filler = ' ' * 8 + self.order_type = '' + self.short_sell_quantity = 0 return def encode(self): @@ -339,42 +347,54 @@ def encode(self): self._ouch_type, self.timestamp, self.replacement_order_token.ljust(14), - self.buy_sell_indicator, - self.shares, - self.stock.ljust(8), + self.previous_order_token.ljust(14), + self.order_book_id, + self.side, + self.order_id, + self.quantity, self.price, self.time_in_force, - self.firm.ljust(4), - self.display, - self.order_reference_number, + self.open_close, + self.customer_info.ljust(15), + self.exchange_info.ljust(32), + self.clearing_participant, + self.crossing_key, self.capacity, - self.intermarket_sweep_eligibility, - self.minimum_quantity, - self.cross_type, - self.order_state, - self.previous_order_token.ljust(14), - self.bbo_weight_indicator) + self.directory_wholesale, + self.execution_venue.ljust(4), + self.intermediary_id.ljust(10), + self.order_origin.ljust(20), + self.filler.ljust(8), + self.order_type, + self.short_sell_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.timestamp = fields[1] self.replacement_order_token = fields[2].strip() - self.buy_sell_indicator = fields[3] - self.shares = fields[4] - self.stock = fields[5].strip() - self.price = fields[6] - self.time_in_force = fields[7] - self.firm = fields[8].strip() - self.display = fields[9] - self.order_reference_number = fields[10] - self.capacity = fields[11] - self.intermarket_sweep_eligibility = fields[12] - self.minimum_quantity = fields[13] - self.cross_type = fields[14] - self.order_state = fields[15] - self.previous_order_token = fields[16].strip() - self.bbo_weight_indicator = fields[17] + self.previous_order_token = fields[3].strip() + self.order_book_id = fields[4] + self.side = fields[5] + self.order_id = fields[6] + self.quantity = fields[7] + self.price = fields[8] + self.time_in_force = fields[9] + self.open_close = fields[10] + self.client_account = fields[11].strip() + self.order_state = fields[12] + self.customer_info = fields[13].strip() + self.exchange_info = fields[14].strip() + self.clearing_participant = fields[15].strip() + self.crossing_key = fields[16] + self.capacity = fields[17] + self.directed_wholesale = fields[18] + self.execution_venue = fields[19].strip() + self.intermediary_id = fields[20].strip() + self.order_origin = fields[21].strip() + # filler + self.order_type = fields[23] + self.short_sell_quantity = fields[24] return @@ -484,16 +504,15 @@ def decode(self, buf): } INTEGER_FIELDS = [ - "decrement_shares", - "executed_shares", - "execution_price", - "match_number", - "minimum_quantity", - "new_execution_price", - "order_reference_number", + "crossing_key", + "open_close", + "order_book_id", + "order_id", + "order_state" "price", - "quantity_prevented_from_trading", - "shares", + "quantity", + "reject_code", + "short_sell_quantity", "time_in_force", "timestamp", ] From 59cfa8b7e7cbf8d2d0574c770befa693a171d455 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 02:11:36 +0000 Subject: [PATCH 21/33] Canceled. --- rnps/asxouch.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 3b359a9..7547fa1 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -399,14 +399,16 @@ def decode(self, buf): class Canceled(OuchMessage): - _format = '!cQ14sLc' + _format = '!c Q 14s L c Q B' _ouch_type = 'C' def __init__(self): self.timestamp = 0 self.order_token = '' - self.decrement_shares = 0 - self.reason = '' + self.order_book_id = 0 + self.side = '' + self.order_id = 0 + self.reason = 0 return def encode(self): @@ -414,7 +416,9 @@ def encode(self): self._ouch_type, self.timestamp, self.order_token.ljust(14), - self.decrement_shares, + self.order_book_id, + self.side, + self.order_id, self.reason) def decode(self, buf): @@ -422,8 +426,10 @@ def decode(self, buf): assert fields[0] == self._ouch_type self.timestamp = fields[1] self.order_token = fields[2].strip() - self.decrement_shares = fields[3] - self.reason = fields[4] + self.order_book_id = fields[3] + self.side = fields[4] + self.order_id = fields[5] + self.reason = fields[6] return From 372de6fc90a8fd933ea51737b39e55d5ecb5b755 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 8 Apr 2015 03:37:31 +0000 Subject: [PATCH 22/33] Executed. --- rnps/asxouch.py | 74 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 7547fa1..c98b8e3 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -434,37 +434,80 @@ def decode(self, buf): class Executed(OuchMessage): - _format = '!cQ14sLLcQ' + _format = '!c Q 14s L Q L 12B H' _ouch_type = 'E' def __init__(self): self.timestamp = 0 self.order_token = '' - self.executed_shares = 0 - self.execution_price = 0 - self.liquidity_flag = '' - self.match_number = 0 + self.order_book_id = 0 + self.traded_quantity = 0 + self.trade_price = 0 + self.match_id = 0 + self.deal_source = 0 return def encode(self): + m = chr((self.match_id >> 88) & 0xff) + m += chr((self.match_id >> 80) & 0xff) + m += chr((self.match_id >> 72) & 0xff) + m += chr((self.match_id >> 64) & 0xff) + + m += chr((self.match_id >> 56) & 0xff) + m += chr((self.match_id >> 48) & 0xff) + m += chr((self.match_id >> 40) & 0xff) + m += chr((self.match_id >> 32) & 0xff) + + m += chr((self.match_id >> 24) & 0xff) + m += chr((self.match_id >> 16) & 0xff) + m += chr((self.match_id >> 8) & 0xff) + m += chr((self.match_id >> 0) & 0xff) + return struct.pack(self._format, self._ouch_type, self.timestamp, self.order_token.ljust(14), - self.executed_shares, - self.execution_price, - self.liquidity_flag, - self.match_number) + self.order_book_id, + self.traded_quantity, + self.trade_price, + m, + self.deal_source) def decode(self, buf): fields = struct.unpack(self._format, buf) assert fields[0] == self._ouch_type self.timestamp = fields[1] self.order_token = fields[2].strip() - self.executed_shares = fields[3] - self.execution_price = fields[4] - self.liquidity_flag = fields[5] - self.match_number = fields[6] + self.order_book_id = fields[3] + self.traded_quantity = fields[4] + self.trade_price = fields[5] + m = fields[6] + self.deal_source = fields[7] + + match_id = 0 + if True: + for i in range(12): + match_id |= ord(m[i]) + if i < 11: + match_id = match_id << 8 + else: + match_id = 0 + match_id |= ord(m[0]) << 88 + match_id |= ord(m[1]) << 80 + match_id |= ord(m[2]) << 72 + match_id |= ord(m[3]) << 64 + + match_id |= ord(m[4]) << 56 + match_id |= ord(m[5]) << 48 + match_id |= ord(m[6]) << 40 + match_id |= ord(m[7]) << 32 + + match_id |= ord(m[8]) << 24 + match_id |= ord(m[9]) << 16 + match_id |= ord(m[10]) << 8 + match_id |= ord(m[11]) + + self.match_id = match_id return @@ -511,16 +554,21 @@ def decode(self, buf): INTEGER_FIELDS = [ "crossing_key", + "deal_source", + "match_id", "open_close", "order_book_id", "order_id", "order_state" "price", "quantity", + "reason", "reject_code", "short_sell_quantity", "time_in_force", "timestamp", + "traded_quantity", + "trade_price", ] From ee5504fe03672aa96effe496c86793493e0bea06 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sun, 12 Apr 2015 23:03:50 +0000 Subject: [PATCH 23/33] Cloned script for ASX OUCH simulator. --- rnps-asxouch | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 rnps-asxouch diff --git a/rnps-asxouch b/rnps-asxouch new file mode 100644 index 0000000..386a050 --- /dev/null +++ b/rnps-asxouch @@ -0,0 +1,103 @@ +#! /usr/bin/env python +######################################################################## +# robot-nps, Network Protocol Simulator for Robot Framework +# +# Copyright (C) 2014 David Arnold +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################## + +import argparse +import logging +import rnps + +from twistedremoteserver import TwistedRemoteServer +from twisted.internet import reactor +from twisted.web.resource import Resource +from twisted.web.server import Site + + +######################################################################## + +if __name__ == "__main__": + # Logging + root = logging.getLogger() + root.setLevel(logging.DEBUG) + + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + + fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch.setFormatter(fmt) + root.addHandler(ch) + + logging.getLogger().debug("Logging initialised.") + + + parser = argparse.ArgumentParser(description="A Robot Framework remote " + "server that provides both " + "client and server-side " + "simulation of the OUCH4 " + "protocol.") + parser.add_argument("-p", "--port", + nargs=1, + type=int, + help="TCP port number for remote Robot Framework " + "access.", + default=0) + parser.add_argument("-i", "--interface", + nargs=1, + type=str, + help="IP address of listening interface. Default is " + "%(default)s.", + default="0.0.0.0") + parser.add_argument("-f", "--file", + nargs=1, + type=str, + help="Name of file to write listening port to.", + default=None) + args = parser.parse_args() + interface = args.interface + + if type(args.port) == type([]): + port = args.port[0] + else: + port = args.port + + if type(args.file) == type([]): + port_file = args.file[0] + else: + port_file = args.file + + ouch_robot = rnps.OuchRobot() + robot_api = TwistedRemoteServer(ouch_robot, interface, port) + + root = Resource() + root.putChild("RPC2", robot_api) + factory = Site(root) + listener = reactor.listenTCP(port, factory, interface=interface) + if port_file: + f = open(port_file, "w") + f.write("%u\n" % listener.getHost().port) + f.close() + + logging.getLogger().info("Listening on %s:%u", + interface, + listener.getHost().port) + reactor.run() + + + +######################################################################## From c078e305ebf66e390c15720665f8232fb553feca Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 29 Apr 2015 05:15:22 +0000 Subject: [PATCH 24/33] Update for SR8 release. Add MAQ and match attributes, basically. --- rnps/asxouch.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index c98b8e3..2a3ad51 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -1,7 +1,7 @@ ######################################################################## # robot-nps, Network Protocol Simulator for Robot Framework # -# Copyright (C) 2014 David Arnold +# Copyright (C) 2015 David Arnold # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,7 +18,7 @@ # ######################################################################## -# Implements ASX OUCH as at 2015/02/25. +# Implements ASX OUCH SR8 as at 2015/04/25. # # ASX OUCH is implemented by NASDAQ OMX's Genium INET platform, as # deployed for ASX. It's based on NASDAQ's OUCH4, but has a bunch of @@ -27,6 +27,7 @@ # Source documents: # - ASX Trade OUCH v1.0 (document #036435). # - ASX OUCH Message Specification, v2.0, 2 September 2013. +# - ASX Trade OUCH Specification, Q2 2015 Release - SR8, 18 March 2015. # - ASX Trade Q2 2015 Release (SR8) Appendices to ASX Notice. ######################################################################## @@ -40,7 +41,7 @@ ######################################################################## class EnterOrder(OuchMessage): - _format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q' + _format = '!c 14s L c Q L B B 10s 15s 32s c L c c 4s 10s 20s 8s c Q Q' _ouch_type = 'O' def __init__(self): @@ -64,6 +65,7 @@ def __init__(self): self.filler = ' ' self.order_type = '' self.short_sell_quantity = 0 + self.minimum_acceptable_quantity = 0 return def encode(self): @@ -88,7 +90,8 @@ def encode(self): self.order_origin, self.filler, self.order_type, - self.short_sell_quantity) + self.short_sell_quantity, + self.minimum_acceptable_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -113,11 +116,12 @@ def decode(self, buf): # filler (8 spaces) self.order_type = fields[19] self.short_sell_quantity = int(fields[20]) + self.minimum_acceptable_quantity = int(fields[21]) return class ReplaceOrder(OuchMessage): - _format = '!c 14s 14s Q L B 10s 15s 32s c c 4s 10s 20s 8s Q' + _format = '!c 14s 14s Q L B 10s 15s 32s c c 4s 10s 20s 8s Q Q' _ouch_type = 'U' def __init__(self): @@ -136,6 +140,7 @@ def __init__(self): self.order_origin = '' self.filler = ' ' self.short_sell_quantity = 0 + self.minimum_acceptable_quantity = 0 return def encode(self): @@ -155,7 +160,8 @@ def encode(self): self.intermediary_id, self.order_origin, self.filler, - self.short_sell_quantity) + self.short_sell_quantity, + self.minimum_acceptable_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -175,6 +181,7 @@ def decode(self, buf): self.order_origin = fields[13] # filler self.short_sell_quantity = int(fields[15]) + self.minimum_acceptable_quantity = int(fields[16]) return @@ -252,6 +259,7 @@ def __init__(self): self.filler = ' ' self.order_type = '' self.short_sell_quantity = 0 + self.minimum_acceptable_quantity = 0 return def encode(self): @@ -279,7 +287,8 @@ def encode(self): self.order_origin, self.filler, self.order_type, - self.short_sell_quantity) + self.short_sell_quantity, + self.minimum_acceptable_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -307,12 +316,13 @@ def decode(self, buf): # filler (8 spaces) self.order_type = fields[22] self.short_sell_quantity = fields[23] + self.minimum_acceptable_quantity = fields[24] return class Replaced(OuchMessage): _format = '!c Q 14s 14s L c Q Q L B B 10s B 15s 32s c L ' + \ - 'c c 4s 10s 20s 8s c Q' + 'c c 4s 10s 20s 8s c Q Q' _ouch_type = 'U' def __init__(self): @@ -340,6 +350,7 @@ def __init__(self): self.filler = ' ' * 8 self.order_type = '' self.short_sell_quantity = 0 + self.minimum_acceptable_quantity = 0 return def encode(self): @@ -360,13 +371,14 @@ def encode(self): self.clearing_participant, self.crossing_key, self.capacity, - self.directory_wholesale, + self.directed_wholesale, self.execution_venue.ljust(4), self.intermediary_id.ljust(10), self.order_origin.ljust(20), self.filler.ljust(8), self.order_type, - self.short_sell_quantity) + self.short_sell_quantity, + self.minimum_acceptable_quantity) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -395,6 +407,7 @@ def decode(self, buf): # filler self.order_type = fields[23] self.short_sell_quantity = fields[24] + self.minimum_acceptable_quantity = fields[25] return @@ -434,7 +447,7 @@ def decode(self, buf): class Executed(OuchMessage): - _format = '!c Q 14s L Q L 12B H' + _format = '!c Q 14s L Q L 12B H B' _ouch_type = 'E' def __init__(self): @@ -445,6 +458,7 @@ def __init__(self): self.trade_price = 0 self.match_id = 0 self.deal_source = 0 + self.match_attributes = 0 return def encode(self): @@ -471,7 +485,8 @@ def encode(self): self.traded_quantity, self.trade_price, m, - self.deal_source) + self.deal_source, + self.match_attributes) def decode(self, buf): fields = struct.unpack(self._format, buf) @@ -483,6 +498,7 @@ def decode(self, buf): self.trade_price = fields[5] m = fields[6] self.deal_source = fields[7] + self.match_attributes = fields[8] match_id = 0 if True: @@ -555,7 +571,9 @@ def decode(self, buf): INTEGER_FIELDS = [ "crossing_key", "deal_source", + "match_attributes", "match_id", + "minimum_acceptable_quantity", "open_close", "order_book_id", "order_id", From 5593008dbfeaee7b7d80f8ae43be1451354dc47e Mon Sep 17 00:00:00 2001 From: David Arnold Date: Fri, 15 Jul 2016 13:34:54 +1000 Subject: [PATCH 25/33] Make ASX server executable. --- rnps-asxouch | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 rnps-asxouch diff --git a/rnps-asxouch b/rnps-asxouch old mode 100644 new mode 100755 From d2c860ebe0ced32e7e307712eafbe582aaa51c30 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Fri, 15 Jul 2016 13:39:38 +1000 Subject: [PATCH 26/33] Update copyright claims. --- rnps-asxouch | 6 +++--- rnps/asxouch.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rnps-asxouch b/rnps-asxouch index 386a050..b0fe7a1 100755 --- a/rnps-asxouch +++ b/rnps-asxouch @@ -2,7 +2,7 @@ ######################################################################## # robot-nps, Network Protocol Simulator for Robot Framework # -# Copyright (C) 2014 David Arnold +# Copyright (C) 2015-2016 David Arnold # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -49,8 +49,8 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description="A Robot Framework remote " "server that provides both " "client and server-side " - "simulation of the OUCH4 " - "protocol.") + "simulation of the ASX " + "OUCH SR8 protocol.") parser.add_argument("-p", "--port", nargs=1, type=int, diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 2a3ad51..78f0424 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -1,7 +1,7 @@ ######################################################################## # robot-nps, Network Protocol Simulator for Robot Framework # -# Copyright (C) 2015 David Arnold +# Copyright (C) 2015-2016 David Arnold # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -318,7 +318,7 @@ def decode(self, buf): self.short_sell_quantity = fields[23] self.minimum_acceptable_quantity = fields[24] return - + class Replaced(OuchMessage): _format = '!c Q 14s 14s L c Q Q L B B 10s B 15s 32s c L ' + \ @@ -525,7 +525,7 @@ def decode(self, buf): self.match_id = match_id return - + class Rejected(OuchMessage): _format = '!c Q 14s L' From ce736956ff00a38738e778580050f07ec5cc8833 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Mon, 18 Jul 2016 17:48:37 +1000 Subject: [PATCH 27/33] Running ASX OUCH SR8 simulator. --- rnps-asxouch | 7 +- rnps/__init__.py | 2 + rnps/asxouch_robot.py | 297 ++++++++++++++++++ ...ipt.robot => 004_nasdaq_cert_script.robot} | 0 robot_tests/005_asx_cert_script.robot | 83 +++++ 5 files changed, 386 insertions(+), 3 deletions(-) create mode 100644 rnps/asxouch_robot.py rename robot_tests/{004_full_cert_script.robot => 004_nasdaq_cert_script.robot} (100%) create mode 100644 robot_tests/005_asx_cert_script.robot diff --git a/rnps-asxouch b/rnps-asxouch index b0fe7a1..ff5049c 100755 --- a/rnps-asxouch +++ b/rnps-asxouch @@ -21,7 +21,8 @@ import argparse import logging -import rnps + +from rnps import AsxOuchRobot from twistedremoteserver import TwistedRemoteServer from twisted.internet import reactor @@ -81,8 +82,8 @@ if __name__ == "__main__": else: port_file = args.file - ouch_robot = rnps.OuchRobot() - robot_api = TwistedRemoteServer(ouch_robot, interface, port) + robot = AsxOuchRobot() + robot_api = TwistedRemoteServer(robot, interface, port) root = Resource() root.putChild("RPC2", robot_api) diff --git a/rnps/__init__.py b/rnps/__init__.py index be5e3b9..427719b 100644 --- a/rnps/__init__.py +++ b/rnps/__init__.py @@ -1,4 +1,6 @@ # Main RNPS package. import errors + +from asxouch_robot import * from ouch_robot import * diff --git a/rnps/asxouch_robot.py b/rnps/asxouch_robot.py new file mode 100644 index 0000000..fa98ad4 --- /dev/null +++ b/rnps/asxouch_robot.py @@ -0,0 +1,297 @@ +######################################################################## +# robot-nps, Network Protocol Simulator for Robot Framework +# +# Copyright (C) 2014-2016 David Arnold +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################## + +from base import * +import soupbin +import asxouch + +# Module-local logger. +logger = logging.getLogger(__name__) + + +######################################################################## + +class AsxOuchServerSession(BaseServerSession): + def __init__(self, factory, address): + BaseServerSession.__init__(self, factory, address) + self.set_protocol("ASXOUCH") + return + + def dataReceived(self, data): + """Handle a received packet. + + Override of Twisted method from base class.""" + + self.receive_buffer += data + + while len(self.receive_buffer) > 0: + if not soupbin.has_complete_message(self.receive_buffer): + return + + msg, self.receive_buffer = soupbin.get_message(self.receive_buffer) + soup_type = msg.get_type() + + if soup_type == 'R' and self.factory.auto_receive_heartbeats: + logger.info("OUCH: Server session [%s]: consumed heartbeat", + self.factory.name) + return + + if soup_type == 'S' or soup_type == 'U': + logger.debug("server session got soup_type [%s], " + "length %u, ouch_type [%s]", + soup_type, len(msg.message), msg.message[0:1]) + + msg._payload = asxouch.get_message(soup_type, msg.message) + + self.received_messages.append(msg) + logger.info("%s: server queuing %s", self.factory.name, soup_type) + + return + + +class AsxOuchServerFactory(BaseServerFactory): + def __init__(self, robot, name, port, version): + BaseServerFactory.__init__(self, robot, name, port, version) + self.set_protocol("ASXOUCH", AsxOuchServerSession) + return + + +class AsxOuchClient(BaseClient): + def __init__(self, factory, address): + self.set_protocol("ASXOUCH") + BaseClient.__init__(self, factory, address) + return + + def dataReceived(self, data): + """Handle a received packet. + + Override of Twisted method from base class.""" + + self.receive_buffer += data + + while len(self.receive_buffer) > 0: + if not soupbin.has_complete_message(self.receive_buffer): + return + + msg, self.receive_buffer = soupbin.get_message(self.receive_buffer) + if not msg: + return + soup_type = msg.get_type() + + if soup_type == 'H' and self.factory.auto_receive_heartbeats: + logger.info("OUCH: Session [%s]: consumed heartbeat", + self.factory.name) + return + + if soup_type == 'S' or soup_type == 'U': + logger.debug("client got soup_type [%s], " + "length %u, ouch_type [%s]", + soup_type, len(msg.message), msg.message[0:1]) + + msg._payload = asxouch.get_message(soup_type, msg.message) + + self.received_messages.append(msg) + logger.info("%s: client queuing %s", self.factory.name, soup_type) + + return + + +class AsxOuchClientFactory(BaseClientFactory): + def __init__(self, robot, name, + server_host, server_port, + protocol_version, client_port): + BaseClientFactory.__init__(self, robot, name, + server_host, server_port, + protocol_version, client_port) + self.set_protocol("ASXOUCH", AsxOuchClient) + return + + +class OuchRobot(BaseRobot): + def __init__(self): + self.protocol_name = "OUCH" + self.client_factory = AsxOuchClientFactory + self.server_factory = AsxOuchServerFactory + self.framing_protocol = soupbin + self.message_protocol = asxouch + + BaseRobot.__init__(self) + return + + def create_client(self, client_name, + server_host, server_port, + protocol_version, client_port="0"): + """Create a client session.""" + + if client_name in self.clients: + raise errors.DuplicateClientError(client_name) + + client = self.client_factory(self, client_name, + server_host, server_port, + protocol_version, client_port) + self.clients[client_name] = client + logger.info("Created %s client: %s %s -> %s:%s v%s", + self.protocol_name, + client_name, client_port, server_host, server_port, + protocol_version) + return + + def create_server(self, server_name, port, version): + """Create a server.""" + + if server_name in self.servers: + raise errors.DuplicateServerError(server_name) + + server = self.server_factory(self, server_name, port, version) + self.servers[server_name] = server + logger.info("Created %s server: %s @ port %s v%s", + self.protocol_name, server_name, port, version) + return + + def create_soup_message(self, name, soup_type): + if name in self.messages: + raise errors.DuplicateMessageError(name) + + constructor = self.framing_protocol.Messages.get(soup_type) + if not constructor: + raise errors.BadMessageTypeError(soup_type) + + self.messages[name] = constructor() + return + + def destroy_soup_message(self, name): + if name not in self.messages: + raise errors.NoSuchMessageError(name) + + del self.messages[name] + return + + def get_soup_type(self, message_name): + """Get the SOUP type of the specified message.""" + + msg = self.messages.get(message_name) + if not msg: + raise errors.NoSuchMessageError(message_name) + + return msg.get_type() + + def set_soup_field(self, name, field, value): + """Set a field in the specified SOUP message.""" + + msg = self.messages.get(name) + if not msg: + raise errors.NoSuchMessageError(name) + + if not hasattr(msg, field) or field[0:1] == "_": + raise errors.BadFieldNameError(field) + + setattr(msg, field, value) + logger.debug("set_soup_field(%s, %s, %s)", name, field, value) + return + + def get_soup_field(self, message_name, field_name): + """Get the value of a field in a SOUP message.""" + + msg = self.messages.get(message_name) + if not msg: + raise errors.NoSuchMessageError(message_name) + + if field_name[0:1] == "_" or not hasattr(msg, field_name): + raise errors.BadFieldNameError(field_name) + + return getattr(msg, field_name) + + def set_ouch_type(self, message_name, ouch_type): + """Set the OUCH message type for this message.""" + + msg = self.messages.get(message_name) + if not msg: + raise errors.NoSuchMessageError(message_name) + + if msg.get_type() == 'U': + constructor = self.message_protocol.UNSEQUENCED_MESSAGES.get(ouch_type) + elif msg.get_type() == 'S': + constructor = self.message_protocol.SEQUENCED_MESSAGES.get(ouch_type) + else: + raise errors.BadMessageTypeError("SOUP message must be U or S, " + "not %c" % msg.get_type()) + if not constructor: + raise errors.BadMessageTypeError(ouch_type) + + msg._payload = constructor() + return + + def get_ouch_type(self, message_name): + """Get the OUCH message type for this message.""" + + msg = self.messages.get(message_name) + if not msg: + raise errors.NoSuchMessageError(message_name) + + if not msg.has_payload(): + raise errors.NotAnOuchMessageError(message_name) + + return msg.get_payload().get_type() + + def get_ouch_field(self, message_name, field_name): + """Get an OUCH message field from this message.""" + + msg = self.messages.get(message_name) + if not msg: + raise errors.NoSuchMessageError(message_name) + + if not msg.has_payload(): + raise errors.NotAnOuchMessageError(message_name) + + return msg.get_payload().get_field(field_name) + + def set_ouch_field(self, message_name, field_name, value): + """Set an OUCH message field value in this message.""" + + msg = self.messages.get(message_name) + if not msg: + raise errors.NoSuchMessageError(message_name) + + if not msg.has_payload(): + raise errors.NotAnOuchMessageError(message_name) + + if field_name in self.message_protocol.INTEGER_FIELDS: + value = int(value) + + msg.get_payload().set_field(field_name, value) + logger.debug("set_ouch_field(%s, %s, %s)", + message_name, field_name, value) + return + + +class AsxOuchRobot(OuchRobot): + def __init__(self): + self.protocol_name = "ASX OUCH" + self.client_factory = AsxOuchClientFactory + self.server_factory = AsxOuchServerFactory + self.framing_protocol = soupbin + self.message_protocol = asxouch + + OuchRobot.__init__(self) + return + + + +######################################################################## diff --git a/robot_tests/004_full_cert_script.robot b/robot_tests/004_nasdaq_cert_script.robot similarity index 100% rename from robot_tests/004_full_cert_script.robot rename to robot_tests/004_nasdaq_cert_script.robot diff --git a/robot_tests/005_asx_cert_script.robot b/robot_tests/005_asx_cert_script.robot new file mode 100644 index 0000000..3705125 --- /dev/null +++ b/robot_tests/005_asx_cert_script.robot @@ -0,0 +1,83 @@ +*** Settings *** +Library Remote http://localhost:${OUCH_PORT} WITH NAME ASX + +*** Test Cases *** +Certification Workflow + [Setup] ASX.Reset + ASX.Create Server server_a 44001 4.2 + ASX.Create Client client_a localhost 44001 4.2 + ASX.Server Start Listening server_a + ASX.Connect Client client_a + Wait Until Keyword Succeeds 5s 1s ASX.Server Has New Session server_a + ASX.Get New Server Session server_a session_a + + + + Comment Send Login + + ASX.Create Soup Message c_login_a L + ASX.Set Soup Field c_login_a username user01 + ASX.Set Soup Field c_login_a password password + ASX.Send Client Message client_a c_login_a + + Comment Send LoginRejected + + ASX.Create Soup Message s_loginrej_a J + ASX.Set Soup Field s_loginrej_a reject_reason_code S + ASX.Send Server Message session_a s_loginrej_a + + + + Comment Send Login + + ASX.Create Soup Message c_login_b L + ASX.Set Soup Field c_login_b username user01 + ASX.Set Soup Field c_login_b password password + ASX.Send Client Message client_a c_login_b + + Comment Send LoginAccepted + + ASX.Create Soup Message s_loginack_b A + ASX.Set Soup Field s_loginack_b session SESS0001 + ASX.Set Soup Field s_loginack_b sequence_number 0 + ASX.Send Server Message session_a s_loginack_b + + + + Comment -> Enter Order + + ASX.Create Soup Message ac_enter U + ASX.Set Ouch Type ac_enter O + ASX.Set Ouch Field ac_enter order_token TEST_A1 + ASX.Set Ouch Field ac_enter order_book_id 42 + ASX.Set Ouch Field ac_enter side B + ASX.Set Ouch Field ac_enter quantity 1000 + ASX.Set Ouch Field ac_enter price 1234 + ASX.Set Ouch Field ac_enter time_in_force 0 + ASX.Set Ouch Field ac_enter open_close 0 + ASX.Set Ouch Field ac_enter client_account CLIENT_ACCT + ASX.Set Ouch Field ac_enter customer_info CUST_INFO + ASX.Set Ouch Field ac_enter exchange_info EXCG_INFO + ASX.Set Ouch Field ac_enter clearing_participant P + ASX.Set Ouch Field ac_enter crossing_key 1001 + ASX.Set Ouch Field ac_enter capacity A + ASX.Set Ouch Field ac_enter directed_wholesale N + Comment ASX.Set Ouch Field ac_enter execution_venue + ASX.Set Ouch Field ac_enter intermediary_id INTERMED_ID + ASX.Set Ouch Field ac_enter order_origin ORDER_ORIGIN + ASX.Set Ouch Field ac_enter order_type A + ASX.Set Ouch Field ac_enter short_sell_quantity 0 + ASX.Set Ouch Field ac_enter minimum_acceptable_quantity 0 + ASX.Send Client Message client_a ac_enter + + + + Comment -> Logout + + ASX.Create Soup Message tc_logout O + ASX.Send Client Message client_a tc_logout + + + ASX.Disconnect Server Session session_a + ASX.Disconnect Client client_a + ASX.Destroy Client client_a From 69bdf0cb959a5438ac70914c2d294c37a68bbb15 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Mon, 18 Jul 2016 22:13:19 +1000 Subject: [PATCH 28/33] Enter Order, Order Accepted, Order Rejected. --- rnps/asxouch.py | 57 +++++++++++++++++-- rnps/asxouch_robot.py | 4 +- robot_tests/005_asx_cert_script.robot | 82 ++++++++++++++++++++++++++- 3 files changed, 136 insertions(+), 7 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 78f0424..04e69b4 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -35,7 +35,28 @@ import struct import errors -from ouch4 import get_message, OuchMessage +from ouch4 import OuchMessage + + +######################################################################## + +def get_message(soup_type, buf): + if len(buf) < 1: + return None + + if soup_type == 'S': + constructor = SEQUENCED_MESSAGES.get(buf[0]) + elif soup_type == 'U': + constructor = UNSEQUENCED_MESSAGES.get(buf[0]) + else: + return None + + if not constructor: + return None + + msg = constructor() + msg.decode(buf) + return msg ######################################################################## @@ -69,6 +90,34 @@ def __init__(self): return def encode(self): + print "AAAAAAAAAAAAAAAAA" + l = [self._format, + self._ouch_type, + self.order_token.ljust(14), + self.order_book_id, + self.side, + self.quantity, + self.price, + self.time_in_force, + self.open_close, + self.client_account, + self.customer_info, + self.exchange_info, + self.clearing_participant, + self.crossing_key, + self.capacity, + self.directed_wholesale, + self.execution_venue, + self.intermediary_id, + self.order_origin, + self.filler, + self.order_type, + self.short_sell_quantity, + self.minimum_acceptable_quantity] + for i in l: + print type(i), i + + return struct.pack(self._format, self._ouch_type, self.order_token.ljust(14), @@ -232,7 +281,7 @@ def decode(self, buf): class Accepted(OuchMessage): - _format = '!c Q 14s L c Q Q L B B 10s B 15s 32s c L c c 4s 10s 20s 8s c Q' + _format = '!c Q 14s L c Q Q L B B 10s B 15s 32s c L c c 4s 10s 20s 8s c Q Q' _ouch_type = 'A' def __init__(self): @@ -577,7 +626,7 @@ def decode(self, buf): "open_close", "order_book_id", "order_id", - "order_state" + "order_state", "price", "quantity", "reason", @@ -585,8 +634,8 @@ def decode(self, buf): "short_sell_quantity", "time_in_force", "timestamp", - "traded_quantity", "trade_price", + "traded_quantity" ] diff --git a/rnps/asxouch_robot.py b/rnps/asxouch_robot.py index fa98ad4..5e672bb 100644 --- a/rnps/asxouch_robot.py +++ b/rnps/asxouch_robot.py @@ -276,8 +276,8 @@ def set_ouch_field(self, message_name, field_name, value): value = int(value) msg.get_payload().set_field(field_name, value) - logger.debug("set_ouch_field(%s, %s, %s)", - message_name, field_name, value) + logger.debug("set_ouch_field(%s, %s, %s, %s)", + message_name, field_name, value, str(type(value))) return diff --git a/robot_tests/005_asx_cert_script.robot b/robot_tests/005_asx_cert_script.robot index 3705125..0e731f6 100644 --- a/robot_tests/005_asx_cert_script.robot +++ b/robot_tests/005_asx_cert_script.robot @@ -62,7 +62,6 @@ Certification Workflow ASX.Set Ouch Field ac_enter crossing_key 1001 ASX.Set Ouch Field ac_enter capacity A ASX.Set Ouch Field ac_enter directed_wholesale N - Comment ASX.Set Ouch Field ac_enter execution_venue ASX.Set Ouch Field ac_enter intermediary_id INTERMED_ID ASX.Set Ouch Field ac_enter order_origin ORDER_ORIGIN ASX.Set Ouch Field ac_enter order_type A @@ -70,6 +69,87 @@ Certification Workflow ASX.Set Ouch Field ac_enter minimum_acceptable_quantity 0 ASX.Send Client Message client_a ac_enter + Comment <- Order Accepted + + ASX.Create Soup Message as_accepted S + ASX.Set Ouch Type as_accepted A + ASX.Set Ouch Field as_accepted timestamp 1001 + ASX.Set Ouch Field as_accepted order_token TEST_A1 + ASX.Set Ouch Field as_accepted order_book_id 42 + ASX.Set Ouch Field as_accepted side B + ASX.Set Ouch Field as_accepted order_id 421001 + ASX.Set Ouch Field as_accepted quantity 1000 + ASX.Set Ouch Field as_accepted price 1234 + ASX.Set Ouch Field as_accepted time_in_force 0 + ASX.Set Ouch Field as_accepted open_close 0 + ASX.Set Ouch Field as_accepted client_account CLIENT_ACCT + ASX.Set Ouch Field as_accepted order_state 1 + ASX.Set Ouch Field as_accepted customer_info CUST_INFO + ASX.Set Ouch Field as_accepted exchange_info EXCG_INFO + ASX.Set Ouch Field as_accepted clearing_participant P + ASX.Set Ouch Field as_accepted crossing_key 1001 + ASX.Set Ouch Field as_accepted capacity A + ASX.Set Ouch Field as_accepted directed_wholesale N + ASX.Set Ouch Field as_accepted intermediary_id INTERMED_ID + ASX.Set Ouch Field as_accepted order_origin ORDER_ORIGIN + ASX.Set Ouch Field as_accepted order_type A + ASX.Set Ouch Field as_accepted short_sell_quantity 0 + ASX.Set Ouch Field as_accepted minimum_acceptable_quantity 0 + ASX.Send Server Message session_a as_accepted + + Comment -> Replace Order + + Comment <- Order Replaced + + Comment <- Order Executed + + + Comment -> Enter Order + + ASX.Create Soup Message bc_enter U + ASX.Set Ouch Type bc_enter O + ASX.Set Ouch Field bc_enter order_token TEST_B1 + ASX.Set Ouch Field bc_enter order_book_id 42 + ASX.Set Ouch Field bc_enter side B + ASX.Set Ouch Field bc_enter quantity 1000 + ASX.Set Ouch Field bc_enter price 1234 + ASX.Set Ouch Field bc_enter time_in_force 0 + ASX.Set Ouch Field bc_enter open_close 0 + ASX.Set Ouch Field bc_enter client_account CLIENT_ACCT + ASX.Set Ouch Field bc_enter customer_info CUST_INFO + ASX.Set Ouch Field bc_enter exchange_info EXCG_INFO + ASX.Set Ouch Field bc_enter clearing_participant P + ASX.Set Ouch Field bc_enter crossing_key 2001 + ASX.Set Ouch Field bc_enter capacity A + ASX.Set Ouch Field bc_enter directed_wholesale N + ASX.Set Ouch Field bc_enter intermediary_id INTERMED_ID + ASX.Set Ouch Field bc_enter order_origin ORDER_ORIGIN + ASX.Set Ouch Field bc_enter order_type A + ASX.Set Ouch Field bc_enter short_sell_quantity 0 + ASX.Set Ouch Field bc_enter minimum_acceptable_quantity 0 + ASX.Send Client Message client_a bc_enter + + Comment <- Order Rejected + + ASX.Create Soup Message bs_rejected S + ASX.Set Ouch Type bs_rejected J + ASX.Set Ouch Field bs_rejected timestamp 2001 + ASX.Set Ouch Field bs_rejected order_token TEST_B1 + ASX.Set Ouch Field bs_rejected reject_code 42 + ASX.Send Server Message session_a bs_rejected + ASX.Destroy Soup Message bs_rejected + + + Comment -> Enter Order + Comment <- Order Accepted + Comment -> Cancel Order + Comment <- Order Cancelled + + Comment -> Enter Order + Comment <- Order Accepted + Comment -> Cancel By OrderID + Comment <- Order Cancelled + Comment -> Logout From 80a787037340786b2a7dbf7b954edb583f089785 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 19 Jul 2016 08:23:54 +1000 Subject: [PATCH 29/33] Added Executed, Cancel, CancelByOrderID, Canceled. --- rnps/asxouch.py | 72 ++++----- robot_tests/005_asx_cert_script.robot | 211 ++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 43 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index 04e69b4..f187e08 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -415,6 +415,8 @@ def encode(self): self.price, self.time_in_force, self.open_close, + self.client_account, + self.order_state, self.customer_info.ljust(15), self.exchange_info.ljust(32), self.clearing_participant, @@ -511,20 +513,6 @@ def __init__(self): return def encode(self): - m = chr((self.match_id >> 88) & 0xff) - m += chr((self.match_id >> 80) & 0xff) - m += chr((self.match_id >> 72) & 0xff) - m += chr((self.match_id >> 64) & 0xff) - - m += chr((self.match_id >> 56) & 0xff) - m += chr((self.match_id >> 48) & 0xff) - m += chr((self.match_id >> 40) & 0xff) - m += chr((self.match_id >> 32) & 0xff) - - m += chr((self.match_id >> 24) & 0xff) - m += chr((self.match_id >> 16) & 0xff) - m += chr((self.match_id >> 8) & 0xff) - m += chr((self.match_id >> 0) & 0xff) return struct.pack(self._format, self._ouch_type, @@ -533,7 +521,18 @@ def encode(self): self.order_book_id, self.traded_quantity, self.trade_price, - m, + ((self.match_id >> 88) & 0xff), + ((self.match_id >> 80) & 0xff), + ((self.match_id >> 72) & 0xff), + ((self.match_id >> 64) & 0xff), + ((self.match_id >> 56) & 0xff), + ((self.match_id >> 48) & 0xff), + ((self.match_id >> 40) & 0xff), + ((self.match_id >> 32) & 0xff), + ((self.match_id >> 24) & 0xff), + ((self.match_id >> 16) & 0xff), + ((self.match_id >> 8) & 0xff), + ((self.match_id >> 0) & 0xff), self.deal_source, self.match_attributes) @@ -545,34 +544,21 @@ def decode(self, buf): self.order_book_id = fields[3] self.traded_quantity = fields[4] self.trade_price = fields[5] - m = fields[6] - self.deal_source = fields[7] - self.match_attributes = fields[8] - - match_id = 0 - if True: - for i in range(12): - match_id |= ord(m[i]) - if i < 11: - match_id = match_id << 8 - else: - match_id = 0 - match_id |= ord(m[0]) << 88 - match_id |= ord(m[1]) << 80 - match_id |= ord(m[2]) << 72 - match_id |= ord(m[3]) << 64 - - match_id |= ord(m[4]) << 56 - match_id |= ord(m[5]) << 48 - match_id |= ord(m[6]) << 40 - match_id |= ord(m[7]) << 32 - - match_id |= ord(m[8]) << 24 - match_id |= ord(m[9]) << 16 - match_id |= ord(m[10]) << 8 - match_id |= ord(m[11]) - - self.match_id = match_id + self.match_id = 0 + self.match_id |= fields[6] << 88 + self.match_id |= fields[7] << 80 + self.match_id |= fields[8] << 72 + self.match_id |= fields[9] << 64 + self.match_id |= fields[10] << 56 + self.match_id |= fields[11] << 48 + self.match_id |= fields[12] << 40 + self.match_id |= fields[13] << 32 + self.match_id |= fields[14] << 24 + self.match_id |= fields[15] << 16 + self.match_id |= fields[16] << 8 + self.match_id |= fields[17] + self.deal_source = fields[18] + self.match_attributes = fields[19] return diff --git a/robot_tests/005_asx_cert_script.robot b/robot_tests/005_asx_cert_script.robot index 0e731f6..6d98b59 100644 --- a/robot_tests/005_asx_cert_script.robot +++ b/robot_tests/005_asx_cert_script.robot @@ -68,6 +68,7 @@ Certification Workflow ASX.Set Ouch Field ac_enter short_sell_quantity 0 ASX.Set Ouch Field ac_enter minimum_acceptable_quantity 0 ASX.Send Client Message client_a ac_enter + ASX.Destroy Soup Message ac_enter Comment <- Order Accepted @@ -96,13 +97,75 @@ Certification Workflow ASX.Set Ouch Field as_accepted short_sell_quantity 0 ASX.Set Ouch Field as_accepted minimum_acceptable_quantity 0 ASX.Send Server Message session_a as_accepted + ASX.Destroy Soup Message as_accepted Comment -> Replace Order + ASX.Create Soup Message ac_replace U + ASX.Set Ouch Type ac_replace U + ASX.Set Ouch Field ac_replace existing_order_token TEST_A1 + ASX.Set Ouch Field ac_replace replacement_order_token TEST_A2 + ASX.Set Ouch Field ac_replace quantity 1000 + ASX.Set Ouch Field ac_replace price 1234 + ASX.Set Ouch Field ac_replace open_close 0 + ASX.Set Ouch Field ac_replace client_account CLIENT_ACCT + ASX.Set Ouch Field ac_replace customer_info CUST_INFO + ASX.Set Ouch Field ac_replace exchange_info EXCG_INFO + ASX.Set Ouch Field ac_replace capacity A + ASX.Set Ouch Field ac_replace directed_wholesale N + ASX.Set Ouch Field ac_replace intermediary_id INTERMED_ID + ASX.Set Ouch Field ac_replace order_origin ORDER_ORIGIN + ASX.Set Ouch Field ac_replace short_sell_quantity 0 + ASX.Set Ouch Field ac_replace minimum_acceptable_quantity 0 + ASX.Send Client Message client_a ac_replace + ASX.Destroy Soup Message ac_replace + Comment <- Order Replaced + ASX.Create Soup Message as_replaced S + ASX.Set Ouch Type as_replaced U + ASX.Set Ouch Field as_replaced timestamp 1002 + ASX.Set Ouch Field as_replaced replacement_order_token TEST_A2 + ASX.Set Ouch Field as_replaced previous_order_token TEST_A1 + ASX.Set Ouch Field as_replaced order_book_id 42 + ASX.Set Ouch Field as_replaced side B + ASX.Set Ouch Field as_replaced order_id 421001 + ASX.Set Ouch Field as_replaced quantity 1000 + ASX.Set Ouch Field as_replaced price 1234 + ASX.Set Ouch Field as_replaced time_in_force 0 + ASX.Set Ouch Field as_replaced open_close 0 + ASX.Set Ouch Field as_replaced client_account CLIENT_ACCT + ASX.Set Ouch Field as_replaced order_state 1 + ASX.Set Ouch Field as_replaced customer_info CUST_INFO + ASX.Set Ouch Field as_replaced exchange_info EXCG_INFO + ASX.Set Ouch Field as_replaced clearing_participant P + ASX.Set Ouch Field as_replaced crossing_key 1001 + ASX.Set Ouch Field as_replaced capacity A + ASX.Set Ouch Field as_replaced directed_wholesale N + ASX.Set Ouch Field as_replaced intermediary_id INTERMED_ID + ASX.Set Ouch Field as_replaced order_origin ORDER_ORIGIN + ASX.Set Ouch Field as_replaced order_type A + ASX.Set Ouch Field as_replaced short_sell_quantity 0 + ASX.Set Ouch Field as_replaced minimum_acceptable_quantity 0 + ASX.Send Server Message session_a as_replaced + ASX.Destroy Soup Message as_replaced + Comment <- Order Executed + ASX.Create Soup Message as_executed S + ASX.Set Ouch Type as_executed E + ASX.Set Ouch Field as_executed timestamp 1003 + ASX.Set Ouch Field as_executed order_token TEST_A2 + ASX.Set Ouch Field as_executed order_book_id 42 + ASX.Set Ouch Field as_executed traded_quantity 1000 + ASX.Set Ouch Field as_executed trade_price 1234 + ASX.Set Ouch Field as_executed match_id 1001 + ASX.Set Ouch Field as_executed deal_source 1 + ASX.Set Ouch Field as_executed match_attributes 3 + ASX.Send Server Message session_a as_executed + ASX.Destroy Soup Message as_executed + + Comment -> Enter Order @@ -128,6 +191,7 @@ Certification Workflow ASX.Set Ouch Field bc_enter short_sell_quantity 0 ASX.Set Ouch Field bc_enter minimum_acceptable_quantity 0 ASX.Send Client Message client_a bc_enter + ASX.Destroy Soup Message bc_enter Comment <- Order Rejected @@ -140,16 +204,163 @@ Certification Workflow ASX.Destroy Soup Message bs_rejected + Comment -> Enter Order + + ASX.Create Soup Message cc_enter U + ASX.Set Ouch Type cc_enter O + ASX.Set Ouch Field cc_enter order_token TEST_C1 + ASX.Set Ouch Field cc_enter order_book_id 42 + ASX.Set Ouch Field cc_enter side B + ASX.Set Ouch Field cc_enter quantity 1000 + ASX.Set Ouch Field cc_enter price 1234 + ASX.Set Ouch Field cc_enter time_in_force 0 + ASX.Set Ouch Field cc_enter open_close 0 + ASX.Set Ouch Field cc_enter client_account CLIENT_ACCT + ASX.Set Ouch Field cc_enter customer_info CUST_INFO + ASX.Set Ouch Field cc_enter exchange_info EXCG_INFO + ASX.Set Ouch Field cc_enter clearing_participant P + ASX.Set Ouch Field cc_enter crossing_key 3001 + ASX.Set Ouch Field cc_enter capacity A + ASX.Set Ouch Field cc_enter directed_wholesale N + ASX.Set Ouch Field cc_enter intermediary_id INTERMED_ID + ASX.Set Ouch Field cc_enter order_origin ORDER_ORIGIN + ASX.Set Ouch Field cc_enter order_type A + ASX.Set Ouch Field cc_enter short_sell_quantity 0 + ASX.Set Ouch Field cc_enter minimum_acceptable_quantity 0 + ASX.Send Client Message client_a cc_enter + ASX.Destroy Soup Message cc_enter + Comment <- Order Accepted + + ASX.Create Soup Message cs_accepted S + ASX.Set Ouch Type cs_accepted A + ASX.Set Ouch Field cs_accepted timestamp 3001 + ASX.Set Ouch Field cs_accepted order_token TEST_C1 + ASX.Set Ouch Field cs_accepted order_book_id 42 + ASX.Set Ouch Field cs_accepted side B + ASX.Set Ouch Field cs_accepted order_id 423001 + ASX.Set Ouch Field cs_accepted quantity 1000 + ASX.Set Ouch Field cs_accepted price 1234 + ASX.Set Ouch Field cs_accepted time_in_force 0 + ASX.Set Ouch Field cs_accepted open_close 0 + ASX.Set Ouch Field cs_accepted client_account CLIENT_ACCT + ASX.Set Ouch Field cs_accepted order_state 1 + ASX.Set Ouch Field cs_accepted customer_info CUST_INFO + ASX.Set Ouch Field cs_accepted exchange_info EXCG_INFO + ASX.Set Ouch Field cs_accepted clearing_participant P + ASX.Set Ouch Field cs_accepted crossing_key 3001 + ASX.Set Ouch Field cs_accepted capacity A + ASX.Set Ouch Field cs_accepted directed_wholesale N + ASX.Set Ouch Field cs_accepted intermediary_id INTERMED_ID + ASX.Set Ouch Field cs_accepted order_origin ORDER_ORIGIN + ASX.Set Ouch Field cs_accepted order_type A + ASX.Set Ouch Field cs_accepted short_sell_quantity 0 + ASX.Set Ouch Field cs_accepted minimum_acceptable_quantity 0 + ASX.Send Server Message session_a cs_accepted + ASX.Destroy Soup Message cs_accepted + Comment -> Cancel Order + + ASX.Create Soup Message cc_cancel U + ASX.Set Ouch Type cc_cancel X + ASX.Set Ouch Field cc_cancel order_token TEST_C1 + ASX.Send Client Message client_a cc_cancel + ASX.Destroy Soup Message cc_cancel + Comment <- Order Cancelled + ASX.Create Soup Message cs_canceled S + ASX.Set Ouch Type cs_canceled C + ASX.Set Ouch Field cs_canceled timestamp 3002 + ASX.Set Ouch Field cs_canceled order_token TEST_C1 + ASX.Set Ouch Field cs_canceled order_book_id 42 + ASX.Set Ouch Field cs_canceled side B + ASX.Set Ouch Field cs_canceled order_id 423001 + ASX.Set Ouch Field cs_canceled reason 1 + ASX.Send Server Message session_a cs_canceled + ASX.Destroy Soup Message cs_canceled + + + Comment -> Enter Order + + ASX.Create Soup Message dc_enter U + ASX.Set Ouch Type dc_enter O + ASX.Set Ouch Field dc_enter order_token TEST_D1 + ASX.Set Ouch Field dc_enter order_book_id 42 + ASX.Set Ouch Field dc_enter side B + ASX.Set Ouch Field dc_enter quantity 1000 + ASX.Set Ouch Field dc_enter price 1234 + ASX.Set Ouch Field dc_enter time_in_force 0 + ASX.Set Ouch Field dc_enter open_close 0 + ASX.Set Ouch Field dc_enter client_account CLIENT_ACCT + ASX.Set Ouch Field dc_enter customer_info CUST_INFO + ASX.Set Ouch Field dc_enter exchange_info EXCG_INFO + ASX.Set Ouch Field dc_enter clearing_participant P + ASX.Set Ouch Field dc_enter crossing_key 4001 + ASX.Set Ouch Field dc_enter capacity A + ASX.Set Ouch Field dc_enter directed_wholesale N + ASX.Set Ouch Field dc_enter intermediary_id INTERMED_ID + ASX.Set Ouch Field dc_enter order_origin ORDER_ORIGIN + ASX.Set Ouch Field dc_enter order_type A + ASX.Set Ouch Field dc_enter short_sell_quantity 0 + ASX.Set Ouch Field dc_enter minimum_acceptable_quantity 0 + ASX.Send Client Message client_a dc_enter + ASX.Destroy Soup Message dc_enter + Comment <- Order Accepted + + ASX.Create Soup Message ds_accepted S + ASX.Set Ouch Type ds_accepted A + ASX.Set Ouch Field ds_accepted timestamp 4001 + ASX.Set Ouch Field ds_accepted order_token TEST_D1 + ASX.Set Ouch Field ds_accepted order_book_id 42 + ASX.Set Ouch Field ds_accepted side B + ASX.Set Ouch Field ds_accepted order_id 424001 + ASX.Set Ouch Field ds_accepted quantity 1000 + ASX.Set Ouch Field ds_accepted price 1234 + ASX.Set Ouch Field ds_accepted time_in_force 0 + ASX.Set Ouch Field ds_accepted open_close 0 + ASX.Set Ouch Field ds_accepted client_account CLIENT_ACCT + ASX.Set Ouch Field ds_accepted order_state 1 + ASX.Set Ouch Field ds_accepted customer_info CUST_INFO + ASX.Set Ouch Field ds_accepted exchange_info EXCG_INFO + ASX.Set Ouch Field ds_accepted clearing_participant P + ASX.Set Ouch Field ds_accepted crossing_key 4001 + ASX.Set Ouch Field ds_accepted capacity A + ASX.Set Ouch Field ds_accepted directed_wholesale N + ASX.Set Ouch Field ds_accepted intermediary_id INTERMED_ID + ASX.Set Ouch Field ds_accepted order_origin ORDER_ORIGIN + ASX.Set Ouch Field ds_accepted order_type A + ASX.Set Ouch Field ds_accepted short_sell_quantity 0 + ASX.Set Ouch Field ds_accepted minimum_acceptable_quantity 0 + ASX.Send Server Message session_a ds_accepted + ASX.Destroy Soup Message ds_accepted + Comment -> Cancel By OrderID + + ASX.Create Soup Message dc_cancelbyid U + ASX.Set Ouch Type dc_cancelbyid Y + ASX.Set Ouch Field dc_cancelbyid order_book_id 42 + ASX.Set Ouch Field dc_cancelbyid side B + ASX.Set Ouch Field dc_cancelbyid order_id 424001 + ASX.Send Client Message client_a dc_cancelbyid + ASX.Destroy Soup Message dc_cancelbyid + Comment <- Order Cancelled + ASX.Create Soup Message ds_canceled S + ASX.Set Ouch Type ds_canceled C + ASX.Set Ouch Field ds_canceled timestamp 3002 + ASX.Set Ouch Field ds_canceled order_token TEST_C1 + ASX.Set Ouch Field ds_canceled order_book_id 42 + ASX.Set Ouch Field ds_canceled side B + ASX.Set Ouch Field ds_canceled order_id 423001 + ASX.Set Ouch Field ds_canceled reason 1 + ASX.Send Server Message session_a ds_canceled + ASX.Destroy Soup Message ds_canceled + Comment -> Logout From 2248c986867e26d213a4cdcd5c9e7774f8ccb184 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 19 Jul 2016 08:42:30 +1000 Subject: [PATCH 30/33] Update TravisCI build to use ASX OUCH agent in tests. --- .travis.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7fcb5a8..7f93fad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,18 @@ script: - cd unit_tests - env PYTHONOPATH=.. python -m unittest --verbose test_all - cd .. - - env PYTHONPATH=. python ./rnps-ouch -f port & - - while test ! -f port; do sleep 1; done + - env PYTHONPATH=. python ./rnps-ouch -f nasdaq_port & + - while test ! -f nasdaq_port; do sleep 1; done - cd robot_tests - - pybot -v OUCH_PORT:`cat ../port` *.robot + - pybot -v OUCH_PORT:`cat ../nasdaq_port` {001,002,003,004}*.robot - cd .. - - rm -f port + - rm -f nasdaq_port + - env PYTHONPATH=. python ./rnps-asxouch -f asx_port & + - while test ! -f asx_port; do sleep 1; done + - cd robot_tests + - pybot -v OUCH_PORT:`cat ../asx_port` 005*.robot + - cd .. + - rm -f asx_port - cd docs - make html - cd .. From dcb89ea829734d99f4ee2ed65fcfd64bd76e4a63 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 19 Jul 2016 08:51:50 +1000 Subject: [PATCH 31/33] Remove debugging and unneeded import. --- rnps/asxouch.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/rnps/asxouch.py b/rnps/asxouch.py index f187e08..a1309c8 100644 --- a/rnps/asxouch.py +++ b/rnps/asxouch.py @@ -33,8 +33,6 @@ ######################################################################## import struct -import errors - from ouch4 import OuchMessage @@ -90,34 +88,6 @@ def __init__(self): return def encode(self): - print "AAAAAAAAAAAAAAAAA" - l = [self._format, - self._ouch_type, - self.order_token.ljust(14), - self.order_book_id, - self.side, - self.quantity, - self.price, - self.time_in_force, - self.open_close, - self.client_account, - self.customer_info, - self.exchange_info, - self.clearing_participant, - self.crossing_key, - self.capacity, - self.directed_wholesale, - self.execution_venue, - self.intermediary_id, - self.order_origin, - self.filler, - self.order_type, - self.short_sell_quantity, - self.minimum_acceptable_quantity] - for i in l: - print type(i), i - - return struct.pack(self._format, self._ouch_type, self.order_token.ljust(14), From c3f837d8895792eb71504bc907419a724e985ff5 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 19 Jul 2016 09:00:16 +1000 Subject: [PATCH 32/33] Use decoded soup message type, and avoid Landscape warning. --- rnps/soupbin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rnps/soupbin.py b/rnps/soupbin.py index 2f6d522..b3e035d 100644 --- a/rnps/soupbin.py +++ b/rnps/soupbin.py @@ -1,7 +1,7 @@ ######################################################################## # robot-nps, Network Protocol Simulator for Robot Framework # -# Copyright (C) 2014 David Arnold +# Copyright (C) 2014-2016 David Arnold # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -45,7 +45,7 @@ def has_complete_message(buf): # SoupBin header length field is count of following bytes (so + 2). return False - if buf[2] not in Messages.keys(): + if soup_type not in Messages.keys(): # Unrecognised SoupBin message type code. return False @@ -60,7 +60,7 @@ def get_message(buf): if len(buf) < soup_length + 2: return None, buf - constructor = Messages.get(buf[2]) + constructor = Messages.get(soup_type) if not constructor: return None, buf @@ -127,7 +127,7 @@ def decode(self, buf): # Bytes 0 & 1 are len, 2 is type, 3+ are payload. # Length: +1 for type, +1 for Python slicing. - self.text = buf[3:soup_len + 2] + self.text = buf[3:soup_len + 2] return From 7ba29db8f5c31ff88448f0ae068aff53bca0826b Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 19 Jul 2016 09:09:01 +1000 Subject: [PATCH 33/33] Fix merge issue. --- rnps/soupbin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rnps/soupbin.py b/rnps/soupbin.py index ae0d8c0..c5b1f67 100644 --- a/rnps/soupbin.py +++ b/rnps/soupbin.py @@ -40,8 +40,8 @@ def has_complete_message(buf): # SoupBin header is 3 bytes. return False - length = struct.unpack("!Hc", buf[:3])[0] - if len(buf) < length + 2: + soup_length, soup_type = struct.unpack("!Hc", buf[:3]) + if len(buf) < soup_length + 2: # SoupBin header length field is count of following bytes (so + 2). return False @@ -56,7 +56,7 @@ def get_message(buf): if len(buf) < 3: return None, buf - soup_length = struct.unpack("!Hc", buf[:3])[0] + soup_length, soup_type = struct.unpack("!Hc", buf[:3]) if len(buf) < soup_length + 2: return None, buf