Skip to content

Commit ea153e7

Browse files
author
Ahmad Noman Musleh
committed
Added all remaining messages
1 parent a4a8eb2 commit ea153e7

4 files changed

Lines changed: 135 additions & 60 deletions

File tree

ctrader_fix/client.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
from twisted.internet import reactor
88
from fixProtocol import FixProtocol
99
from twisted.internet import reactor
10-
import datetime
1110

1211
class Client(ClientService):
13-
def __init__(self, host, port, ssl=False, retryPolicy=None, clock=None, prepareConnection=None, numberOfMessagesToSendPerSecond=5):
12+
def __init__(self, host, port, ssl=False, delimiter = "", retryPolicy=None, clock=None, prepareConnection=None, numberOfMessagesToSendPerSecond=5):
1413
self._runningReactor = reactor
1514
self.numberOfMessagesToSendPerSecond = numberOfMessagesToSendPerSecond
15+
self.delimiter = delimiter
1616
endpoint = clientFromString(self._runningReactor, f"ssl:{host}:{port}" if ssl else f"tcp:{host}:{port}")
1717
factory = Factory.forProtocol(FixProtocol, client=self)
1818
super().__init__(endpoint, factory, retryPolicy=retryPolicy, clock=clock, prepareConnection=prepareConnection)
@@ -45,10 +45,17 @@ def _received(self, responseMessage):
4545
self._messageReceivedCallback(self, responseMessage)
4646

4747
def send(self, requestMessage):
48+
requestMessage.delimiter = self.delimiter
4849
diferred = self.whenConnected(failAfterFailures=1)
4950
diferred.addCallback(lambda protocol: protocol.send(requestMessage))
5051
return diferred
5152

53+
def changeMessageSequenceNumber(self, newMessageSequenceNumber):
54+
self.factory.messageSequenceNumber = newMessageSequenceNumber
55+
56+
def getMessageSequenceNumber(self):
57+
return self.factory.messageSequenceNumber
58+
5259
def setConnectedCallback(self, callback):
5360
self._connectedCallback = callback
5461

@@ -75,8 +82,9 @@ def onMessageReceived(client, responseMessage):
7582
if isMessageSent is True:
7683
return
7784
isMessageSent = True
78-
request = OrderStatusRequest(tradeSessionInfo)
79-
request.ClOrdID = "A"
85+
request = SecurityListRequest(tradeSessionInfo)
86+
request.SecurityReqID = "A"
87+
request.SecurityListRequestType = 0
8088
client.send(request)
8189

8290
client.setConnectedCallback(onConnected)

ctrader_fix/factory.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
from twisted.internet.protocol import ClientFactory
44

55
class Factory(ClientFactory):
6-
def __init__(self, *args, **kwargs):
7-
super().__init__()
8-
self.client = kwargs['client']
9-
self.numberOfMessagesToSendPerSecond = self.client.numberOfMessagesToSendPerSecond
10-
def connected(self):
11-
self.client._connected()
12-
def disconnected(self, reason):
13-
self.client._disconnected(reason)
14-
def received(self, message):
15-
self.client._received(message)
6+
messageSequenceNumber = 0
7+
def __init__(self, *args, **kwargs):
8+
super().__init__()
9+
self.client = kwargs['client']
10+
self.delimiter = self.client.delimiter
11+
self.numberOfMessagesToSendPerSecond = self.client.numberOfMessagesToSendPerSecond
12+
def connected(self):
13+
self.client._connected()
14+
def disconnected(self, reason):
15+
self.client._disconnected(reason)
16+
def received(self, message):
17+
self.client._received(message)

ctrader_fix/fixProtocol.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from messages import ResponseMessage
55

66
class FixProtocol(Protocol):
7-
_messageSequenceNumber = 0
7+
_currentMessage = ''
88
def connectionMade(self):
99
super().connectionMade()
1010
self.factory.connected()
@@ -14,11 +14,15 @@ def connectionLost(self, reason):
1414
self.factory.disconnected(reason)
1515

1616
def dataReceived(self, data):
17-
responseMessage = ResponseMessage(data.decode("ascii"))
18-
self.factory.received(responseMessage)
17+
dataString = data.decode("ascii")
18+
self._currentMessage += dataString
19+
if f"{self.factory.delimiter}10=" in dataString and dataString.endswith(self.factory.delimiter):
20+
responseMessage = ResponseMessage(self._currentMessage, self.factory.delimiter)
21+
self._currentMessage = ''
22+
self.factory.received(responseMessage)
1923

2024
def send(self, requestMessage):
21-
self._messageSequenceNumber += 1
22-
messageString = requestMessage.getMessage(self._messageSequenceNumber)
25+
self.factory.messageSequenceNumber += 1
26+
messageString = requestMessage.getMessage(self.factory.messageSequenceNumber)
2327
print("Sending: ", messageString)
2428
return self.transport.write(messageString.encode("ascii"))

ctrader_fix/messages.py

Lines changed: 102 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,15 @@
33
import datetime
44

55
class ResponseMessage:
6-
def __init__(self, message, delimiter = ""):
6+
def __init__(self, message, delimiter):
77
self._message = message.replace(delimiter, "|")
8-
self._fields = {int(field.split("=")[0]):field.split("=")[1] for field in message.split(delimiter) if field != ""}
8+
self._fields = [(int(field.split("=")[0]), field.split("=")[1]) for field in message.split(delimiter) if field != "" and "=" in field]
99

10-
def getFieldStr(self, fieldNumber):
11-
return self._fields[fieldNumber]
12-
13-
def getFieldInt(self, fieldNumber):
14-
return int(self._fields[fieldNumber])
15-
16-
def getFieldFloat(self, fieldNumber):
17-
return float(self._fields[fieldNumber])
10+
def getFieldValue(self, fieldNumber):
11+
for field in self._fields:
12+
if field[0] == fieldNumber:
13+
return field[1]
14+
return None
1815

1916
def getMessageType(self):
2017
return self._fields[35]
@@ -23,21 +20,20 @@ def getMessage(self):
2320
return self._message
2421

2522
class RequestMessage:
26-
def __init__(self, messageType, sessionInfo, delimiter = ""):
23+
def __init__(self, messageType, sessionInfo):
2724
self._type = messageType
2825
self._sessionInfo = sessionInfo
29-
self._delimiter = delimiter
3026

3127
def getMessage(self, sequenceNumber):
3228
body = self._getBody()
3329
if body is not None:
3430
header = self._getHeader(len(body), sequenceNumber)
35-
headerAndBody = f"{header}{self._delimiter}{body}{self._delimiter}"
31+
headerAndBody = f"{header}{self.delimiter}{body}{self.delimiter}"
3632
else:
3733
header = self._getHeader(0, sequenceNumber)
38-
headerAndBody = "{header}{self._delimiter}"
34+
headerAndBody = "{header}{self.delimiter}"
3935
trailer = self._getTrailer(headerAndBody)
40-
return f"{headerAndBody}{trailer}{self._delimiter}"
36+
return f"{headerAndBody}{trailer}{self.delimiter}"
4137

4238
def _getBody(self):
4339
return None
@@ -51,8 +47,8 @@ def _getHeader(self, lenBody, sequenceNumber):
5147
fields.append(f"50={self._sessionInfo['SenderSubID']}")
5248
fields.append(f"34={sequenceNumber}")
5349
fields.append(f"52={datetime.datetime.utcnow().strftime('%Y%m%d-%H:%M:%S')}")
54-
fieldsJoined = self._delimiter.join(fields)
55-
return f"8={self._sessionInfo['BeginString']}{self._delimiter}9={lenBody+len(fieldsJoined) + 2}{self._delimiter}{fieldsJoined}"
50+
fieldsJoined = self.delimiter.join(fields)
51+
return f"8={self._sessionInfo['BeginString']}{self.delimiter}9={lenBody+len(fieldsJoined) + 2}{self.delimiter}{fieldsJoined}"
5652

5753
def _getTrailer(self, headerAndBody):
5854
messageBytes = bytes(headerAndBody, "ascii")
@@ -63,8 +59,8 @@ def _getTrailer(self, headerAndBody):
6359
return f"10={str(checksum).zfill(3)}"
6460

6561
class LogonRequest(RequestMessage):
66-
def __init__(self, sessionInfo, delimiter = ""):
67-
super().__init__("A", sessionInfo, delimiter)
62+
def __init__(self, sessionInfo):
63+
super().__init__("A", sessionInfo)
6864
self.EncryptionScheme = 0
6965

7066
def _getBody(self):
@@ -75,53 +71,53 @@ def _getBody(self):
7571
fields.append(f"141=Y")
7672
fields.append(f"553={self._sessionInfo['Username']}")
7773
fields.append(f"554={self._sessionInfo['Password']}")
78-
return f"{self._delimiter.join(fields)}"
74+
return f"{self.delimiter.join(fields)}"
7975

8076

8177
class Heartbeat(RequestMessage):
82-
def __init__(self, sessionInfo, delimiter = ""):
83-
super().__init__("0", sessionInfo, delimiter)
78+
def __init__(self, sessionInfo):
79+
super().__init__("0", sessionInfo)
8480

8581
def _getBody(self):
8682
if hasattr(self, "TestReqId") is False:
8783
return None
8884
return f"112={self.TestReqId}"
8985

9086
class TestRequest(RequestMessage):
91-
def __init__(self, sessionInfo, delimiter = ""):
92-
super().__init__("1", sessionInfo, delimiter)
87+
def __init__(self, sessionInfo):
88+
super().__init__("1", sessionInfo)
9389

9490
def _getBody(self):
9591
return f"112={self.TestReqId}"
9692

9793
class LogoutRequest(RequestMessage):
98-
def __init__(self, sessionInfo, delimiter = ""):
99-
super().__init__("5", sessionInfo, delimiter)
94+
def __init__(self, sessionInfo):
95+
super().__init__("5", sessionInfo)
10096

10197
class ResendRequest(RequestMessage):
102-
def __init__(self, sessionInfo, delimiter = ""):
103-
super().__init__("2", sessionInfo, delimiter)
98+
def __init__(self, sessionInfo):
99+
super().__init__("2", sessionInfo)
104100

105101
def _getBody(self):
106102
fields = []
107103
fields.append(f"7={self.BeginSeqNo}")
108104
fields.append(f"16={self.EndSeqNo}")
109-
return f"{self._delimiter.join(fields)}"
105+
return f"{self.delimiter.join(fields)}"
110106

111107
class SequenceReset(RequestMessage):
112-
def __init__(self, sessionInfo, delimiter = ""):
113-
super().__init__("4", sessionInfo, delimiter)
108+
def __init__(self, sessionInfo):
109+
super().__init__("4", sessionInfo)
114110

115111
def _getBody(self):
116112
fields = []
117113
if hasattr(self, "GapFillFlag"):
118114
fields.append(f"123={self.GapFillFlag}")
119115
fields.append(f"36={self.NewSeqNo}")
120-
return f"{self._delimiter.join(fields)}"
116+
return f"{self.delimiter.join(fields)}"
121117

122118
class MarketDataRequest(RequestMessage):
123-
def __init__(self, sessionInfo, delimiter = ""):
124-
super().__init__("V", sessionInfo, delimiter)
119+
def __init__(self, sessionInfo):
120+
super().__init__("V", sessionInfo)
125121

126122
def _getBody(self):
127123
fields = []
@@ -134,11 +130,11 @@ def _getBody(self):
134130
fields.append(f"269={self.MDEntryType}")
135131
fields.append(f"146={self.NoRelatedSym}")
136132
fields.append(f"55={self.Symbol}")
137-
return f"{self._delimiter.join(fields)}"
133+
return f"{self.delimiter.join(fields)}"
138134

139135
class NewOrderSingle(RequestMessage):
140-
def __init__(self, sessionInfo, delimiter = ""):
141-
super().__init__("D", sessionInfo, delimiter)
136+
def __init__(self, sessionInfo):
137+
super().__init__("D", sessionInfo)
142138

143139
def _getBody(self):
144140
fields = []
@@ -158,16 +154,81 @@ def _getBody(self):
158154
fields.append(f"721={self.PosMaintRptID}")
159155
if hasattr(self, "Designation"):
160156
fields.append(f"494={self.Designation}")
161-
return f"{self._delimiter.join(fields)}"
157+
return f"{self.delimiter.join(fields)}"
162158

163159
class OrderStatusRequest(RequestMessage):
164-
def __init__(self, sessionInfo, delimiter = ""):
165-
super().__init__("H", sessionInfo, delimiter)
160+
def __init__(self, sessionInfo):
161+
super().__init__("H", sessionInfo)
166162

167163
def _getBody(self):
168164
fields = []
169165
fields.append(f"11={self.ClOrdID}")
170166
if hasattr(self, "Side"):
171167
fields.append(f"54={self.Side}")
172-
return f"{self._delimiter.join(fields)}"
168+
return f"{self.delimiter.join(fields)}"
173169

170+
class OrderMassStatusRequest(RequestMessage):
171+
def __init__(self, sessionInfo):
172+
super().__init__("AF", sessionInfo)
173+
174+
def _getBody(self):
175+
fields = []
176+
fields.append(f"584={self.MassStatusReqID}")
177+
fields.append(f"585={self.MassStatusReqType}")
178+
if hasattr(self, "IssueDate"):
179+
fields.append(f"225={self.IssueDate.strftime('%Y%m%d-%H:%M:%S')}")
180+
return f"{self.delimiter.join(fields)}"
181+
182+
class RequestForPositions(RequestMessage):
183+
def __init__(self, sessionInfo):
184+
super().__init__("AN", sessionInfo)
185+
186+
def _getBody(self):
187+
fields = []
188+
fields.append(f"710={self.PosReqID}")
189+
if hasattr(self, "PosMaintRptID"):
190+
fields.append(f"721={self.PosMaintRptID}")
191+
return f"{self.delimiter.join(fields)}"
192+
193+
class OrderCancelRequest(RequestMessage):
194+
def __init__(self, sessionInfo):
195+
super().__init__("F", sessionInfo)
196+
197+
def _getBody(self):
198+
fields = []
199+
fields.append(f"41={self.OrigClOrdID}")
200+
if hasattr(self, "OrderID"):
201+
fields.append(f"37={self.OrderID}")
202+
fields.append(f"11={self.ClOrdID}")
203+
return f"{self.delimiter.join(fields)}"
204+
205+
class OrderCancelReplaceRequest(RequestMessage):
206+
def __init__(self, sessionInfo):
207+
super().__init__("G", sessionInfo)
208+
209+
def _getBody(self):
210+
fields = []
211+
fields.append(f"41={self.OrigClOrdID}")
212+
if hasattr(self, "OrderID"):
213+
fields.append(f"37={self.OrderID}")
214+
fields.append(f"11={self.ClOrdID}")
215+
fields.append(f"38={self.OrderQty}")
216+
if hasattr(self, "Price"):
217+
fields.append(f"44={self.Price}")
218+
if hasattr(self, "StopPx"):
219+
fields.append(f"99={self.StopPx}")
220+
if hasattr(self, "ExpireTime"):
221+
fields.append(f"126={self.ExpireTime.strftime('%Y%m%d-%H:%M:%S')}")
222+
return f"{self.delimiter.join(fields)}"
223+
224+
class SecurityListRequest(RequestMessage):
225+
def __init__(self, sessionInfo):
226+
super().__init__("x", sessionInfo)
227+
228+
def _getBody(self):
229+
fields = []
230+
fields.append(f"320={self.SecurityReqID}")
231+
fields.append(f"559={self.SecurityListRequestType}")
232+
if hasattr(self, "Symbol"):
233+
fields.append(f"55={self.Symbol}")
234+
return f"{self.delimiter.join(fields)}"

0 commit comments

Comments
 (0)