├── .gitignore ├── Makefile ├── README.md ├── pyblinktrade ├── __init__.py ├── json_encoder.py ├── message.py ├── message_builder.py ├── project_options.py ├── signals.py ├── test_signals.py └── utils.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | files.txt 26 | 27 | # Pycharm IDE dir 28 | .idea 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | python setup.py bdist_egg 3 | 4 | install: 5 | python setup.py develop 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pybrlinktrade - Utilities for Blinktrade plataform 2 | -------------------------------------------------------------------------------- /pyblinktrade/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blinktrade/pyblinktrade/0fa81bc46d6f6cbadf68bd4d57326d036091e374/pyblinktrade/__init__.py -------------------------------------------------------------------------------- /pyblinktrade/json_encoder.py: -------------------------------------------------------------------------------- 1 | import json 2 | import datetime 3 | import decimal 4 | 5 | class JsonEncoder(json.JSONEncoder): 6 | def default(self, obj): 7 | if isinstance(obj, datetime.datetime): 8 | return obj.strftime('%Y-%m-%d %H:%M:%S') 9 | elif isinstance(obj, datetime.date): 10 | return obj.strftime('%Y-%m-%d') 11 | if isinstance(obj, datetime.time): 12 | return obj.strftime('%H:%M:%S') 13 | if isinstance(obj, decimal.Decimal): 14 | return str(obj) 15 | return json.JSONEncoder.default(self, obj) 16 | -------------------------------------------------------------------------------- /pyblinktrade/message.py: -------------------------------------------------------------------------------- 1 | __author__ = 'rodrigo' 2 | import json 3 | 4 | class InvalidMessageException(Exception): 5 | def __init__(self, raw_message, json_message=None, tag=None, value=None): 6 | super(InvalidMessageException, self).__init__() 7 | self.raw_message = raw_message 8 | self.json_message = json_message 9 | self.tag = tag 10 | self.value = value 11 | def __str__(self): 12 | return 'Invalid Message' 13 | 14 | class InvalidMessageLengthException(InvalidMessageException): 15 | def __str__(self): 16 | return 'Invalid message length' 17 | 18 | class InvalidMessageTypeException(InvalidMessageException): 19 | def __str__(self): 20 | return 'Invalid Message Type (%s)' % str(self.tag) 21 | 22 | class InvalidMessageMissingTagException(InvalidMessageException): 23 | def __str__(self): 24 | return 'Missing tag %s' % str(self.tag) 25 | 26 | class InvalidMessageFieldException(InvalidMessageException): 27 | def __str__(self): 28 | return 'Invalid value tag(%s)=%s'%(self.tag, self.value) 29 | 30 | class BaseMessage(object): 31 | MAX_MESSAGE_LENGTH = 10024*1000 32 | def __init__(self, raw_message): 33 | self.raw_message = raw_message 34 | 35 | def has(self, attr): 36 | raise NotImplementedError() 37 | 38 | def get(self, attr, default): 39 | raise NotImplementedError() 40 | 41 | def set(self, attr, value): 42 | raise NotImplementedError() 43 | 44 | def is_valid(self): 45 | raise NotImplementedError() 46 | 47 | 48 | class JsonMessage(BaseMessage): 49 | MAX_MESSAGE_LENGTH = 10024*1000 50 | def raise_exception_if_required_tag_is_missing(self, tag): 51 | if tag not in self.message: 52 | raise InvalidMessageMissingTagException(self.raw_message, self.message, tag) 53 | 54 | def raise_exception_if_not_a_integer(self, tag): 55 | val = self.get(tag) 56 | if not type(val) == int: 57 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 58 | 59 | def raise_exception_if_not_a_number(self, tag): 60 | val = self.get(tag) 61 | if not( type(val) == float or type(val) == int): 62 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 63 | 64 | def raise_exception_if_empty(self, tag): 65 | val = self.get(tag) 66 | if not val : 67 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 68 | 69 | def raise_exception_if_not_string(self, tag): 70 | val = self.get(tag) 71 | if not( type(val) == str or type(val) == unicode): 72 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 73 | 74 | def raise_exception_if_not_greater_than_zero(self, tag): 75 | self.raise_exception_if_not_a_number(tag) 76 | val = self.get(tag) 77 | if not val > 0: 78 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 79 | 80 | def raise_exception_if_optional_field_is_a_negative_number(self, tag): 81 | val = self.message.get(tag) 82 | if val: 83 | if not( type(val) == float or type(val) == int) or val < 0: 84 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 85 | 86 | def raise_exception_if_not_in(self, tag, list): 87 | val = self.get(tag) 88 | if val not in list : 89 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 90 | 91 | def raise_exception_if_length_is_greater_than(self, tag, length): 92 | val = self.get(tag) 93 | if val is not None and len(val) > length: 94 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 95 | 96 | def raise_exception_if_length_is_less_than(self, tag, length): 97 | val = self.get(tag) 98 | if len(val) < length: 99 | raise InvalidMessageFieldException(self.raw_message, self.message, tag, val) 100 | 101 | def __str__(self): 102 | return str(self.message) 103 | 104 | def toJSON(self): 105 | return self.message 106 | 107 | def __init__(self, raw_message): 108 | super(JsonMessage, self).__init__(raw_message) 109 | self.valid = False 110 | 111 | # make sure a malicious users didn't send us more than 4096 bytes 112 | if len(raw_message) > self.MAX_MESSAGE_LENGTH: 113 | raise InvalidMessageLengthException(raw_message) 114 | 115 | 116 | # parse the message 117 | self.message = json.loads(raw_message) 118 | 119 | if 'MsgType' not in self.message: 120 | raise InvalidMessageTypeException(raw_message, self.message) 121 | 122 | self.type = self.message['MsgType'] 123 | del self.message['MsgType'] 124 | 125 | self.valid_message_types = { 126 | # User messages based on the Fix Protocol 127 | '0': 'Heartbeat', 128 | '1': 'TestRequest', 129 | 'B': 'News', 130 | 'C': 'Email', 131 | 'V': 'MarketDataRequest', 132 | 'W': 'MarketDataFullRefresh', 133 | 'X': 'MarketDataIncrementalRefresh', 134 | 'Y': 'MarketDataRequestReject', 135 | 'BE': 'UserRequest', 136 | 'BF': 'UserResponse', 137 | 'D': 'NewOrderSingle', 138 | 'F': 'OrderCancelRequest', 139 | '8': 'ExecutionReport', 140 | '9': 'OrderCancelReject', 141 | 'x': 'SecurityListRequest', 142 | 'y': 'SecurityList', 143 | 'e': 'SecurityStatusRequest', 144 | 'f': 'SecurityStatus', 145 | 146 | # User messages 147 | 'U0': 'Signup', 148 | 'U2': 'UserBalanceRequest', 149 | 'U3': 'UserBalanceResponse', 150 | 'U4': 'OrdersListRequest', 151 | 'U5': 'OrdersListResponse', 152 | 'U6': 'WithdrawRequest', 153 | 'U7': 'WithdrawResponse', 154 | 'U9': 'WithdrawRefresh', 155 | 156 | 'U10': 'CreatePasswordResetRequest', 157 | 'U11': 'CreatePasswordResetResponse', 158 | 'U12': 'ProcessPasswordResetRequest', 159 | 'U13': 'ProcessPasswordResetResponse', 160 | 'U16': 'EnableDisableTwoFactorAuthenticationRequest', 161 | 'U17': 'EnableDisableTwoFactorAuthenticationResponse', 162 | 163 | 'U18': 'DepositRequest', 164 | 'U19': 'DepositResponse', 165 | 'U23': 'DepositRefresh', 166 | 167 | 'U20': 'DepositMethodsRequest', 168 | 'U21': 'DepositMethodsResponse', 169 | 170 | 171 | 'U24': 'WithdrawConfirmationRequest', 172 | 'U25': 'WithdrawConfirmationResponse', 173 | 'U26': 'WithdrawListRequest', 174 | 'U27': 'WithdrawListResponse', 175 | 'U28': 'BrokerListRequest', 176 | 'U29': 'BrokerListResponse', 177 | 178 | 'U30': 'DepositListRequest', 179 | 'U31': 'DepositListResponse', 180 | 181 | 'U32': 'TradeHistoryRequest', 182 | 'U33': 'TradeHistoryResponse', 183 | 184 | 'U34': 'LedgerListRequest', 185 | 'U35': 'LedgerListResponse', 186 | 187 | 'U36': 'TradersRankRequest', 188 | 'U37': 'TradersRankResponse', 189 | 190 | 'U38': 'UpdateProfile', 191 | 'U39': 'UpdateProfileResponse', 192 | 'U40': 'ProfileRefresh', 193 | 194 | 'U42': 'PositionRequest' , 195 | 'U43': 'PositionResponse' , 196 | 197 | 'U44': 'ConfirmTrustedAddressRequest' , 198 | 'U45': 'ConfirmTrustedAddressResponse' , 199 | 'U46': 'SuggestTrustedAddressPublish' , 200 | 201 | 'U48': 'DepositMethodRequest', 202 | 'U49': 'DepositMethodResponse', 203 | 204 | 'U50': 'APIKeyListRequest', 205 | 'U51': 'APIKeyListResponse', 206 | 'U52': 'APIKeyCreateRequest', 207 | 'U53': 'APIKeyCreateResponse', 208 | 'U54': 'APIKeyRevokeRequest', 209 | 'U55': 'APIKeyRevokeResponse', 210 | 211 | 'U56': 'GetCreditLineOfCreditRequest', 212 | 'U57': 'GetCreditLineOfCreditResponse', 213 | 'U58': 'PayCreditLineOfCreditRequest', 214 | 'U59': 'PayCreditLineOfCreditResponse', 215 | 'U60': 'LineOfCreditListRequest', 216 | 'U61': 'LineOfCreditListResponse', 217 | 'U62': 'EnableCreditLineOfCreditRequest', 218 | 'U63': 'EnableCreditLineOfCreditResponse', 219 | 'U65': 'LineOfCreditRefresh', 220 | 221 | 'U70': 'CancelWithdrawalRequest', 222 | 'U71': 'CancelWithdrawalResponse', 223 | 224 | 'U72': 'CardListRequest', 225 | 'U73': 'CardListResponse', 226 | 'U74': 'CardCreateRequest', 227 | 'U75': 'CardCreateResponse', 228 | 'U76': 'CardDisableRequest', 229 | 'U77': 'CardDisableResponse', 230 | 'U78': 'WithdrawCommentRequest', 231 | 'U79': 'WithdrawCommentResponse', 232 | 233 | # Broker messages 234 | 'B0': 'ProcessDeposit', 235 | 'B1': 'ProcessDepositResponse', 236 | 'B2': 'CustomerListRequest', 237 | 'B3': 'CustomerListResponse', 238 | 'B4': 'CustomerDetailRequest', 239 | 'B5': 'CustomerDetailResponse', 240 | 'B6': 'ProcessWithdraw', 241 | 'B7': 'ProcessWithdrawResponse', 242 | 'B8': 'VerifyCustomerRequest', 243 | 'B9': 'VerifyCustomerResponse', 244 | 'B11': 'VerifyCustomerRefresh', 245 | 'B12': 'ClearingHistoryRequest', 246 | 'B13': 'ClearingHistoryResponse', 247 | 'B14': 'ProcessClearingRequest', 248 | 'B15': 'ProcessClearingResponse', 249 | 'B17': 'ProcessClearingRefresh', 250 | 'B20': 'StatementRecordAddRequest', 251 | 'B21': 'StatementRecordAddResponse', 252 | 'B23': 'StatementRecordAddRefresh', 253 | 'B24': 'BankAccountListRequest', 254 | 'B25': 'BankAccountListResponse', 255 | 'B26': 'StatementRecordsMatchRequest', 256 | 'B27': 'StatementRecordsMatchResponse', 257 | 'B28': 'StatementRecordsListRequest', 258 | 'B29': 'StatementRecordsListResponse', 259 | 260 | # System messages 261 | 'S0': 'AccessLog', 262 | 263 | 'S2': 'AwayMarketTickerRequest', 264 | 'S3': 'AwayMarketTickerResponse', 265 | 'S4': 'AwayMarketTickerPublish', 266 | 267 | 'S6': 'RestAPIRequest', 268 | 'S7': 'RestAPIResponse', 269 | 270 | 'S8': 'SetInstrumentDefinitionRequest', 271 | 'S9': 'SetInstrumentDefinitionResponse', 272 | 273 | 'S10': 'DocumentPublish', 274 | 275 | 'S12': 'DocumentListRequest', 276 | 'S13': 'DocumentListResponse', 277 | 278 | 'S14' : 'CryptoWithdrawNetworkFeeTransferRequest', 279 | 'S15' : 'CryptoWithdrawNetworkFeeTransferResponse', 280 | 281 | 'S16' : 'UserLogonReport', 282 | 'S17' : 'UserLogonReportAck', 283 | 284 | 'S20': 'BrokerCreateRequest', 285 | 'S21': 'BrokerCreateResponse', 286 | 'S22': 'BrokersListRequest', 287 | 'S23': 'BrokersListResponse', 288 | 'S24': 'BrokerAccountsListRequest', 289 | 'S25': 'BrokerAccountsListResponse', 290 | 'S26': 'BrokerDeleteRequest', 291 | 'S27': 'BrokerDeleteResponse', 292 | 293 | 'S30': 'AccountCreateRequest', 294 | 'S31': 'AccountCreateResponse', 295 | 'S32': 'AccountDeleteRequest', 296 | 'S33': 'AccountDeleteResponse', 297 | 298 | 'S34': 'GetSystemSavedDataRequest', 299 | 'S35': 'GetSystemSavedDataResponse', 300 | 301 | 'S36': 'SystemSaveDataRequest', 302 | 'S37': 'SystemSaveDataResponse', 303 | 304 | 'S38': 'TradingSessionStatusChangeRequest', 305 | 'S39': 'TradingSessionStatusChangeResponse', 306 | 307 | 'S40': 'SystemCheck2FARequest', 308 | 'S41': 'SystemCheck2FAResponse', 309 | 310 | # Administrative messages 311 | 'A0': 'DbQueryRequest', 312 | 'A1': 'DbQueryResponse', 313 | 314 | 'I0': 'UpdateBalanceRequest', 315 | 'I1': 'UpdateBalanceResponse', 316 | 'I2': 'FundTransferReport', 317 | 318 | 'ERROR': 'ErrorMessage', 319 | } 320 | 321 | 322 | def make_helper_is_message_type( tag): 323 | def _method(self): 324 | return self.type == tag 325 | return _method 326 | 327 | for k,v in self.valid_message_types.iteritems(): 328 | _method = make_helper_is_message_type(k) 329 | setattr(JsonMessage, 'is' + v, _method) 330 | 331 | 332 | #validate Type 333 | if self.type not in self.valid_message_types: 334 | raise InvalidMessageTypeException(raw_message, self.message, self.type) 335 | 336 | # validate all fields 337 | if self.type == '0': #Heartbeat 338 | self.raise_exception_if_required_tag_is_missing('TestReqID') 339 | 340 | elif self.type == '1': # TestRequest 341 | self.raise_exception_if_required_tag_is_missing('TestReqID') 342 | 343 | elif self.type == 'V': #MarketData Request 344 | self.raise_exception_if_required_tag_is_missing('MDReqID') 345 | self.raise_exception_if_required_tag_is_missing('SubscriptionRequestType') 346 | self.raise_exception_if_required_tag_is_missing('MarketDepth') 347 | 348 | subscriptionRequestType = self.message.get('SubscriptionRequestType') 349 | if subscriptionRequestType == '1': 350 | self.raise_exception_if_required_tag_is_missing('MDUpdateType') 351 | 352 | 353 | #TODO: Validate all fields of MarketData Request Message 354 | 355 | elif self.type == 'Y': 356 | self.raise_exception_if_required_tag_is_missing('MDReqID') 357 | #TODO: Validate all fields of MarketData Request Cancel Message 358 | 359 | elif self.type == 'BE': #logon 360 | self.raise_exception_if_required_tag_is_missing('BrokerID') 361 | self.raise_exception_if_required_tag_is_missing('UserReqID') 362 | self.raise_exception_if_required_tag_is_missing('Username') 363 | self.raise_exception_if_not_string('Username') 364 | self.raise_exception_if_required_tag_is_missing('UserReqTyp') 365 | if self.message.get('BrokerID') == 4: 366 | raise InvalidMessageFieldException(self.raw_message, self.message, "Broker", "FOXBIT") 367 | 368 | reqId = self.message.get('UserReqTyp') 369 | if reqId in ('1', '3'): 370 | self.raise_exception_if_required_tag_is_missing('Password') 371 | 372 | if reqId == '3': 373 | self.raise_exception_if_required_tag_is_missing('NewPassword') 374 | 375 | token = self.message.get('Token') 376 | if 'Token' in self.message: 377 | if token is None or len(token.strip()) == 0: 378 | raise InvalidMessageFieldException(self.raw_message, self.message, "Token", "") 379 | 380 | #TODO: Validate all fields of Logon Message 381 | 382 | elif self.type == 'U0': #Signup 383 | # Username must be between 3 bytes and 10 bytes 384 | self.raise_exception_if_required_tag_is_missing('Username') 385 | self.raise_exception_if_not_string('Username') 386 | self.raise_exception_if_length_is_less_than('Username', 3) 387 | self.raise_exception_if_length_is_greater_than('Username', 15) 388 | 389 | # password is greater than 8 bytes 390 | self.raise_exception_if_required_tag_is_missing('Password') 391 | self.raise_exception_if_not_string('Password') 392 | 393 | # check the Email 394 | self.raise_exception_if_required_tag_is_missing('Email') 395 | self.raise_exception_if_empty('Email') 396 | #TODO: create a function to verify the email is valid 397 | 398 | # check the BrokerID 399 | self.raise_exception_if_required_tag_is_missing('BrokerID') 400 | self.raise_exception_if_not_a_integer('BrokerID') 401 | 402 | # Disabling Invalid Brokers 403 | if self.message.get('BrokerID') not in (-1,8999999,1,3,5,8,9,11): 404 | raise InvalidMessageFieldException(self.raw_message, self.message, "Broker", "FOXBIT") 405 | 406 | elif self.type == 'U10': # Create Password Reset Request 407 | self.raise_exception_if_required_tag_is_missing('BrokerID') 408 | self.raise_exception_if_required_tag_is_missing('Email') 409 | 410 | elif self.type == 'U12': # Process Password Reset Request 411 | self.raise_exception_if_required_tag_is_missing('Token') 412 | self.raise_exception_if_required_tag_is_missing('NewPassword') 413 | 414 | elif self.type == 'U16': #Enable Disable Two Factor Authentication 415 | self.raise_exception_if_required_tag_is_missing('Enable') 416 | 417 | elif self.type == 'U18': # Deposit Request 418 | self.raise_exception_if_required_tag_is_missing('DepositReqID') 419 | self.raise_exception_if_optional_field_is_a_negative_number('Value') 420 | 421 | if "DepositMethodID" not in self.message and "DepositID" not in self.message and 'Currency' not in self.message: 422 | raise InvalidMessageMissingTagException(self.raw_message, self.message, "DepositID,DepositMethodID,Currency") 423 | 424 | elif self.type == 'U19': # Deposit Response 425 | self.raise_exception_if_required_tag_is_missing('DepositReqID') 426 | self.raise_exception_if_required_tag_is_missing('DepositID') 427 | 428 | 429 | elif self.type == 'U20': # Request Deposit Methods 430 | self.raise_exception_if_required_tag_is_missing('DepositMethodReqID') 431 | 432 | elif self.type == 'U48': # Deposit Method Request 433 | self.raise_exception_if_required_tag_is_missing('DepositMethodReqID') 434 | self.raise_exception_if_required_tag_is_missing('DepositMethodID') 435 | 436 | 437 | elif self.type == 'D': #New Order Single 438 | self.raise_exception_if_required_tag_is_missing('ClOrdID') 439 | self.raise_exception_if_not_string('ClOrdID') 440 | 441 | self.raise_exception_if_required_tag_is_missing('Symbol') 442 | self.raise_exception_if_empty('Symbol') 443 | 444 | self.raise_exception_if_required_tag_is_missing('Side') 445 | self.raise_exception_if_not_in('Side', ( '1', '2' )) # Only BUY and SELL sides 446 | 447 | self.raise_exception_if_required_tag_is_missing('OrdType') 448 | 449 | self.raise_exception_if_not_in('OrdType', ( '1', '2', '3', '4', 'P' )) # market, limited, stop market, stop limited and pegged order 450 | 451 | if self.get('OrdType') == '2': 452 | self.raise_exception_if_required_tag_is_missing('Price') # price is required for limited orders 453 | self.raise_exception_if_not_a_integer('Price') 454 | self.raise_exception_if_not_greater_than_zero('Price') 455 | elif self.get('OrdType') == '3': 456 | self.raise_exception_if_required_tag_is_missing('StopPx') # stop price is required for stop orders 457 | self.raise_exception_if_not_a_integer('StopPx') 458 | self.raise_exception_if_not_greater_than_zero('StopPx') 459 | elif self.get('OrdType') == '4': 460 | self.raise_exception_if_required_tag_is_missing('StopPx') # stop price is required for stop orders 461 | self.raise_exception_if_not_a_integer('StopPx') 462 | self.raise_exception_if_not_greater_than_zero('StopPx') 463 | self.raise_exception_if_required_tag_is_missing('Price') # price is required for stop limit orders 464 | self.raise_exception_if_not_a_integer('Price') 465 | self.raise_exception_if_not_greater_than_zero('Price') 466 | elif self.get('OrdType') == 'P': 467 | self.raise_exception_if_required_tag_is_missing('PegPriceType') 468 | self.raise_exception_if_not_a_integer('PegPriceType') 469 | 470 | self.raise_exception_if_required_tag_is_missing('OrderQty') 471 | self.raise_exception_if_not_a_integer('OrderQty') 472 | self.raise_exception_if_not_greater_than_zero('OrderQty') 473 | 474 | #TODO: Validate all fields of New Order Single Message 475 | 476 | elif self.type == 'B': # News 477 | self.raise_exception_if_required_tag_is_missing('Headline') 478 | self.raise_exception_if_required_tag_is_missing('LinesOfText') 479 | self.raise_exception_if_required_tag_is_missing('Text') 480 | 481 | self.raise_exception_if_empty('Headline') 482 | self.raise_exception_if_not_a_integer('LinesOfText') 483 | self.raise_exception_if_not_greater_than_zero('LinesOfText') 484 | self.raise_exception_if_empty('Text') 485 | 486 | elif self.type == 'C': # Email 487 | self.raise_exception_if_required_tag_is_missing('EmailThreadID') 488 | self.raise_exception_if_required_tag_is_missing('Subject') 489 | self.raise_exception_if_required_tag_is_missing('EmailType') 490 | 491 | elif self.type == 'x': # Security List Request 492 | self.raise_exception_if_required_tag_is_missing('SecurityReqID') 493 | self.raise_exception_if_required_tag_is_missing('SecurityListRequestType') 494 | self.raise_exception_if_not_a_integer('SecurityListRequestType') 495 | self.raise_exception_if_not_in('SecurityListRequestType', (0,1,2,3,4)) 496 | 497 | elif self.type == 'y': # Security List 498 | self.raise_exception_if_required_tag_is_missing('SecurityReqID') 499 | self.raise_exception_if_required_tag_is_missing('SecurityResponseID') 500 | self.raise_exception_if_required_tag_is_missing('SecurityRequestResult') 501 | 502 | elif self.type == 'F': #Order Cancel Request 503 | has_cl_ord_id = "ClOrdID" in self.message or 'OrigClOrdID' in self.message 504 | has_order_id = "OrderID" in self.message 505 | 506 | if not has_cl_ord_id and not has_order_id: 507 | self.raise_exception_if_required_tag_is_missing('Side') 508 | self.raise_exception_if_not_in('Side', ('1', '2')) 509 | 510 | if has_cl_ord_id: 511 | if "ClOrdID" in self.message: 512 | self.raise_exception_if_not_string('ClOrdID') 513 | if "OrigClOrdID" in self.message: 514 | self.raise_exception_if_not_string('OrigClOrdID') 515 | 516 | elif self.type == 'U2' : # User Balance 517 | self.raise_exception_if_required_tag_is_missing('BalanceReqID') 518 | 519 | self.raise_exception_if_not_a_integer('BalanceReqID') 520 | self.raise_exception_if_not_greater_than_zero('BalanceReqID') 521 | 522 | #TODO: Validate all fields of Request For Balance Message 523 | elif self.type == 'U4': # Orders List 524 | self.raise_exception_if_required_tag_is_missing('OrdersReqID') 525 | self.raise_exception_if_empty('OrdersReqID') 526 | 527 | elif self.type == 'U6': # Withdraw Request 528 | self.raise_exception_if_required_tag_is_missing('WithdrawReqID') 529 | self.raise_exception_if_required_tag_is_missing('Amount') 530 | self.raise_exception_if_required_tag_is_missing('Currency') 531 | self.raise_exception_if_required_tag_is_missing('Method') 532 | 533 | self.raise_exception_if_not_a_integer('WithdrawReqID') 534 | self.raise_exception_if_not_greater_than_zero('WithdrawReqID') 535 | 536 | self.raise_exception_if_not_a_number('Amount') 537 | self.raise_exception_if_not_greater_than_zero('Amount') 538 | 539 | self.raise_exception_if_empty('Method') 540 | 541 | if self.get('Type') == 'CRY': 542 | self.raise_exception_if_required_tag_is_missing('Wallet') 543 | self.raise_exception_if_empty('Wallet') 544 | elif self.get('Type') == 'BBT': 545 | self.raise_exception_if_required_tag_is_missing('Amount') 546 | self.raise_exception_if_required_tag_is_missing('BankNumber') 547 | self.raise_exception_if_required_tag_is_missing('BankName') 548 | self.raise_exception_if_required_tag_is_missing('AccountName') 549 | self.raise_exception_if_required_tag_is_missing('AccountNumber') 550 | self.raise_exception_if_required_tag_is_missing('AccountBranch') 551 | self.raise_exception_if_required_tag_is_missing('CPFCNPJ') 552 | 553 | self.raise_exception_if_empty('BankNumber') 554 | self.raise_exception_if_empty('BankName') 555 | self.raise_exception_if_empty('AccountName') 556 | self.raise_exception_if_empty('AccountNumber') 557 | self.raise_exception_if_empty('AccountBranch') 558 | self.raise_exception_if_empty('CPFCNPJ') 559 | elif self.type == 'U7': # WithdrawResponse 560 | self.raise_exception_if_required_tag_is_missing('WithdrawReqID') 561 | self.raise_exception_if_not_a_integer('WithdrawReqID') 562 | self.raise_exception_if_not_greater_than_zero('WithdrawReqID') 563 | 564 | self.raise_exception_if_required_tag_is_missing('WithdrawID') 565 | self.raise_exception_if_not_a_integer('WithdrawID') 566 | elif self.type == 'U8': #WithdrawRefresh 567 | self.raise_exception_if_required_tag_is_missing('WithdrawID') 568 | self.raise_exception_if_not_a_integer('WithdrawID') 569 | 570 | 571 | elif self.type == 'U24': # WithdrawConfirmationRequest 572 | self.raise_exception_if_required_tag_is_missing('WithdrawReqID') 573 | self.raise_exception_if_not_a_integer('WithdrawReqID') 574 | self.raise_exception_if_not_greater_than_zero('WithdrawReqID') 575 | 576 | elif self.type == 'U25': # WithdrawConfirmationResponse 577 | self.raise_exception_if_required_tag_is_missing('WithdrawReqID') 578 | 579 | elif self.type == 'U26': # Withdraw List Request 580 | self.raise_exception_if_required_tag_is_missing('WithdrawListReqID') 581 | self.raise_exception_if_empty('WithdrawListReqID') 582 | self.raise_exception_if_length_is_greater_than('StatusList', len([0, 1, 2, 4, 8])) # 0-Pending, 1-Unconfirmed, 2-In-progress, 4-Complete, 8-Cancelled 583 | 584 | elif self.type == 'U27': # Withdraw List Response 585 | self.raise_exception_if_required_tag_is_missing('WithdrawListReqID') 586 | self.raise_exception_if_empty('WithdrawListReqID') 587 | 588 | elif self.type == 'U28': # Broker List Request 589 | self.raise_exception_if_required_tag_is_missing('BrokerListReqID') 590 | self.raise_exception_if_empty('BrokerListReqID') 591 | elif self.type == 'U29': # Broker List Response 592 | self.raise_exception_if_required_tag_is_missing('BrokerListReqID') 593 | self.raise_exception_if_empty('BrokerListReqID') 594 | 595 | elif self.type == 'U30': # DepositList Request 596 | self.raise_exception_if_required_tag_is_missing('DepositListReqID') 597 | self.raise_exception_if_empty('DepositListReqID') 598 | self.raise_exception_if_length_is_greater_than('StatusList', len([0, 1, 2, 4, 8])) # 0-Pending, 1-Unconfirmed, 2-In-progress, 4-Complete, 8-Cancelled 599 | 600 | elif self.type == 'U31': # DepositList Response 601 | self.raise_exception_if_required_tag_is_missing('DepositListReqID') 602 | self.raise_exception_if_empty('DepositListReqID') 603 | 604 | elif self.type == 'U32': # Trade History Request 605 | self.raise_exception_if_required_tag_is_missing('TradeHistoryReqID') 606 | self.raise_exception_if_empty('TradeHistoryReqID') 607 | elif self.type == 'U33': # Trade History Response 608 | self.raise_exception_if_required_tag_is_missing('TradeHistoryReqID') 609 | self.raise_exception_if_empty('TradeHistoryReqID') 610 | 611 | elif self.type == 'U34': # LedgerList Request 612 | self.raise_exception_if_required_tag_is_missing('LedgerListReqID') 613 | self.raise_exception_if_empty('LedgerListReqID') 614 | elif self.type == 'U35': # LedgerList Response 615 | self.raise_exception_if_required_tag_is_missing('LedgerListReqID') 616 | self.raise_exception_if_empty('LedgerListReqID') 617 | 618 | 619 | elif self.type == 'U38': # Update User Profile Request 620 | self.raise_exception_if_required_tag_is_missing('UpdateReqID') 621 | self.raise_exception_if_empty('UpdateReqID') 622 | elif self.type == 'U39': # Update User Profile Response 623 | self.raise_exception_if_required_tag_is_missing('UpdateReqID') 624 | self.raise_exception_if_empty('UpdateReqID') 625 | 626 | self.raise_exception_if_required_tag_is_missing('Profile') 627 | self.raise_exception_if_empty('Profile') 628 | 629 | elif self.type == 'U40': # Profile Refresh 630 | self.raise_exception_if_required_tag_is_missing('Profile') 631 | self.raise_exception_if_empty('Profile') 632 | 633 | 634 | elif self.type == 'U42': # Position Request 635 | self.raise_exception_if_required_tag_is_missing('PositionReqID') 636 | self.raise_exception_if_empty('PositionReqID') 637 | 638 | 639 | elif self.type == 'U44': # Confirm Trusted Address Request 640 | self.raise_exception_if_required_tag_is_missing('ConfirmTrustedAddressReqID') 641 | self.raise_exception_if_empty('ConfirmTrustedAddressReqID') 642 | elif self.type == 'U45': # Confirm Trusted Address Response 643 | self.raise_exception_if_required_tag_is_missing('ConfirmTrustedAddressReqID') 644 | self.raise_exception_if_empty('ConfirmTrustedAddressReqID') 645 | elif self.type == 'U46': # Suggest Trusted Address Publish 646 | self.raise_exception_if_required_tag_is_missing('SuggestTrustedAddressReqID') 647 | self.raise_exception_if_empty('SuggestTrustedAddressReqID') 648 | 649 | 650 | elif self.type == 'U50': # APIKey List Request 651 | self.raise_exception_if_required_tag_is_missing('APIKeyListReqID') 652 | self.raise_exception_if_empty('APIKeyListReqID') 653 | elif self.type == 'U51': # APIKey List Response 654 | self.raise_exception_if_required_tag_is_missing('APIKeyListReqID') 655 | self.raise_exception_if_empty('APIKeyListReqID') 656 | 657 | elif self.type == 'U52': # APIKey Create Request 658 | self.raise_exception_if_required_tag_is_missing('APIKeyCreateReqID') 659 | self.raise_exception_if_required_tag_is_missing('Label') 660 | self.raise_exception_if_empty('APIKeyCreateReqID') 661 | self.raise_exception_if_empty('Label') 662 | 663 | elif self.type == 'U53': # APIKey Create Response 664 | self.raise_exception_if_required_tag_is_missing('APIKeyCreateReqID') 665 | self.raise_exception_if_required_tag_is_missing('APIKey') 666 | self.raise_exception_if_required_tag_is_missing('APISecret') 667 | self.raise_exception_if_required_tag_is_missing('APIPassword') 668 | self.raise_exception_if_empty('APIKey') 669 | self.raise_exception_if_empty('APISecret') 670 | self.raise_exception_if_empty('APIPassword') 671 | 672 | 673 | elif self.type == 'U54': # APIKey Revoke Request 674 | self.raise_exception_if_required_tag_is_missing('APIKeyRevokeReqID') 675 | self.raise_exception_if_required_tag_is_missing('APIKey') 676 | self.raise_exception_if_empty('APIKeyRevokeReqID') 677 | self.raise_exception_if_empty('APIKey') 678 | elif self.type == 'U55': # APIKey Revoke Response 679 | self.raise_exception_if_required_tag_is_missing('APIKeyRevokeReqID') 680 | self.raise_exception_if_empty('APIKeyRevokeReqID') 681 | 682 | 683 | elif self.type == 'U70': # Cancel Withdrawal Request 684 | self.raise_exception_if_required_tag_is_missing('WithdrawCancelReqID') 685 | self.raise_exception_if_required_tag_is_missing('WithdrawID') 686 | 687 | self.raise_exception_if_empty('WithdrawCancelReqID') 688 | self.raise_exception_if_not_a_integer('WithdrawID') 689 | 690 | elif self.type == 'U72': # Card List Request 691 | self.raise_exception_if_required_tag_is_missing('CardListReqID') 692 | self.raise_exception_if_empty('CardListReqID') 693 | 694 | elif self.type == 'U74': # Card Create Request 695 | self.raise_exception_if_required_tag_is_missing('CardCreateReqID') 696 | self.raise_exception_if_required_tag_is_missing('Instructions') 697 | self.raise_exception_if_empty('CardCreateReqID') 698 | 699 | self.raise_exception_if_empty('Instructions') 700 | 701 | elif self.type == 'U76': # Card Disable Response 702 | self.raise_exception_if_required_tag_is_missing('CardDisableReqID') 703 | self.raise_exception_if_required_tag_is_missing('CardID') 704 | self.raise_exception_if_empty('CardDisableReqID') 705 | self.raise_exception_if_empty('CardID') 706 | 707 | elif self.type == 'U78': # WithdrawCommentRequest WithdrawReqID 708 | self.raise_exception_if_required_tag_is_missing('WithdrawReqID') 709 | self.raise_exception_if_not_a_integer('WithdrawReqID') 710 | self.raise_exception_if_not_greater_than_zero('WithdrawReqID') 711 | self.raise_exception_if_required_tag_is_missing('WithdrawID') 712 | self.raise_exception_if_required_tag_is_missing('Message') 713 | 714 | elif self.type == 'U79': # WithdrawCommentResponse 715 | self.raise_exception_if_required_tag_is_missing('WithdrawReqID') 716 | self.raise_exception_if_required_tag_is_missing('WithdrawID') 717 | 718 | 719 | 720 | elif self.type == 'B0': # Deposit Payment Confirmation 721 | self.raise_exception_if_required_tag_is_missing('ProcessDepositReqID') 722 | self.raise_exception_if_empty('ProcessDepositReqID') 723 | 724 | self.raise_exception_if_required_tag_is_missing('Action') 725 | self.raise_exception_if_not_in('Action', ['CONFIRM', 'CANCEL', 'PROGRESS', 'COMPLETE', 'MATCH']) 726 | 727 | 728 | elif self.type == 'B2': # Customer List Request 729 | self.raise_exception_if_required_tag_is_missing('CustomerListReqID') 730 | self.raise_exception_if_empty('CustomerListReqID') 731 | 732 | pass 733 | elif self.type == 'B3': # Customer List Response 734 | pass 735 | 736 | elif self.type == 'B4': # Customer Detail Request 737 | pass 738 | elif self.type == 'B5': # Customer Detail Response 739 | pass 740 | 741 | elif self.type == 'B6': # Process Withdraw 742 | self.raise_exception_if_required_tag_is_missing('ProcessWithdrawReqID') 743 | self.raise_exception_if_not_a_integer('ProcessWithdrawReqID') 744 | self.raise_exception_if_not_greater_than_zero('ProcessWithdrawReqID') 745 | 746 | if 'StatementRecordID' not in self.message or self.get('Action') != 'MATCH': 747 | self.raise_exception_if_required_tag_is_missing('WithdrawID') 748 | self.raise_exception_if_not_a_integer('WithdrawID') 749 | self.raise_exception_if_not_greater_than_zero('WithdrawID') 750 | 751 | self.raise_exception_if_required_tag_is_missing('Action') 752 | self.raise_exception_if_not_in('Action', ['CANCEL', 'PROGRESS', 'COMPLETE', 'MATCH']) 753 | 754 | 755 | elif self.type == 'B7': # Process Withdraw 756 | self.raise_exception_if_required_tag_is_missing('ProcessWithdrawReqID') 757 | self.raise_exception_if_not_a_integer('ProcessWithdrawReqID') 758 | self.raise_exception_if_not_greater_than_zero('ProcessWithdrawReqID') 759 | self.raise_exception_if_required_tag_is_missing('Status') 760 | 761 | elif self.type == 'B8': # Verify Customer Request 762 | self.raise_exception_if_required_tag_is_missing('VerifyCustomerReqID') 763 | self.raise_exception_if_required_tag_is_missing('ClientID') 764 | self.raise_exception_if_required_tag_is_missing('Verify') 765 | 766 | self.raise_exception_if_not_a_integer('VerifyCustomerReqID') 767 | self.raise_exception_if_not_greater_than_zero('VerifyCustomerReqID') 768 | 769 | self.raise_exception_if_not_a_integer('Verify') 770 | self.raise_exception_if_not_in('Verify', [0,1,2,3,4,5]) 771 | 772 | self.raise_exception_if_not_a_integer('ClientID') 773 | self.raise_exception_if_not_greater_than_zero('ClientID') 774 | 775 | if 'VerificationData' in self.message: 776 | self.raise_exception_if_empty('VerificationData') 777 | 778 | elif self.type == 'B9': # Verify Customer Response 779 | self.raise_exception_if_required_tag_is_missing('VerifyCustomerReqID') 780 | 781 | elif self.type == 'B12': # Clearing History Request 782 | self.raise_exception_if_required_tag_is_missing('ClearingHistoryReqID') 783 | self.raise_exception_if_required_tag_is_missing('Page') 784 | self.raise_exception_if_required_tag_is_missing('PageSize') 785 | self.raise_exception_if_not_a_integer('ClearingHistoryReqID') 786 | self.raise_exception_if_not_a_integer('Page') 787 | self.raise_exception_if_not_a_integer('PageSize') 788 | 789 | elif self.type == 'B13': # Clearing History Response 790 | self.raise_exception_if_required_tag_is_missing('ClearingHistoryReqID') 791 | 792 | elif self.type == 'B14': # Process Clearing Request 793 | self.raise_exception_if_required_tag_is_missing('ProcessClearingReqID') 794 | self.raise_exception_if_required_tag_is_missing('Action') 795 | 796 | elif self.type == 'B15': # Process Clearing Response 797 | self.raise_exception_if_required_tag_is_missing('ProcessClearingReqID') 798 | self.raise_exception_if_required_tag_is_missing('ClearingProcessID') 799 | self.raise_exception_if_required_tag_is_missing('ClearingStatus') 800 | self.raise_exception_if_required_tag_is_missing('PartyBrokerID') 801 | self.raise_exception_if_required_tag_is_missing('CounterPartyBrokerID') 802 | self.raise_exception_if_required_tag_is_missing('PartyBrokerSettlementAccount') 803 | self.raise_exception_if_required_tag_is_missing('CounterPartyBrokerSettlementAccount') 804 | 805 | 806 | elif self.type == 'B17': # Process Clearing Refresh 807 | self.raise_exception_if_required_tag_is_missing('ClearingProcessID') 808 | self.raise_exception_if_required_tag_is_missing('ClearingStatus') 809 | self.raise_exception_if_required_tag_is_missing('PartyBrokerID') 810 | self.raise_exception_if_required_tag_is_missing('CounterPartyBrokerID') 811 | self.raise_exception_if_required_tag_is_missing('PartyBrokerSettlementAccount') 812 | self.raise_exception_if_required_tag_is_missing('CounterPartyBrokerSettlementAccount') 813 | 814 | elif self.type == 'B20': # Add Bank Statement Record 815 | self.raise_exception_if_required_tag_is_missing('StatementRecordAddReqID') 816 | self.raise_exception_if_required_tag_is_missing('StatementRecordID') 817 | self.raise_exception_if_required_tag_is_missing('BankAccountCode') 818 | self.raise_exception_if_required_tag_is_missing('DateTime') 819 | self.raise_exception_if_required_tag_is_missing('Amount') 820 | self.raise_exception_if_required_tag_is_missing('Operation') 821 | self.raise_exception_if_not_a_integer('StatementRecordID') 822 | self.raise_exception_if_not_a_integer('Amount') 823 | self.raise_exception_if_not_greater_than_zero('Amount') 824 | 825 | elif self.type == 'B24': # Bank Account List Request 826 | self.raise_exception_if_required_tag_is_missing('BankAccountListReqID') 827 | 828 | elif self.type == 'B26': # Match Statement Records Request 829 | self.raise_exception_if_required_tag_is_missing('MatchStmntRcrdsReqID') 830 | self.raise_exception_if_required_tag_is_missing('SR1ID') 831 | self.raise_exception_if_required_tag_is_missing('SR2ID') 832 | 833 | elif self.type == 'B28': # Statement Record List Request 834 | self.raise_exception_if_required_tag_is_missing('StatementRecordListReqID') 835 | 836 | elif self.type == 'S2': # Away Market Ticker Request 837 | self.raise_exception_if_required_tag_is_missing('AwayMarketTickerReqID') 838 | self.raise_exception_if_required_tag_is_missing('Market') 839 | self.raise_exception_if_required_tag_is_missing('Symbol') 840 | self.raise_exception_if_required_tag_is_missing('BestBid') 841 | self.raise_exception_if_required_tag_is_missing('BestAsk') 842 | self.raise_exception_if_required_tag_is_missing('LastPx') 843 | self.raise_exception_if_required_tag_is_missing('HighPx') 844 | self.raise_exception_if_required_tag_is_missing('LowPx') 845 | self.raise_exception_if_required_tag_is_missing('Volume') 846 | self.raise_exception_if_required_tag_is_missing('VWAP') 847 | 848 | self.raise_exception_if_not_a_integer('AwayMarketTickerReqID') 849 | self.raise_exception_if_empty('Market') 850 | self.raise_exception_if_empty('Symbol') 851 | self.raise_exception_if_not_a_integer('BestBid') 852 | self.raise_exception_if_not_a_integer('BestBid') 853 | self.raise_exception_if_not_a_integer('BestAsk') 854 | self.raise_exception_if_not_a_integer('LastPx') 855 | self.raise_exception_if_not_a_integer('HighPx') 856 | self.raise_exception_if_not_a_integer('LowPx') 857 | self.raise_exception_if_not_a_integer('Volume') 858 | self.raise_exception_if_not_a_integer('VWAP') 859 | 860 | 861 | elif self.type == 'S6': #Rest Api Request 862 | self.raise_exception_if_required_tag_is_missing('RestAPIReqID') 863 | self.raise_exception_if_required_tag_is_missing('APIKey') 864 | self.raise_exception_if_required_tag_is_missing('Signature') 865 | self.raise_exception_if_required_tag_is_missing('Payload') 866 | self.raise_exception_if_required_tag_is_missing('DigestMod') 867 | self.raise_exception_if_required_tag_is_missing('Nonce') 868 | self.raise_exception_if_required_tag_is_missing('Message') 869 | self.raise_exception_if_required_tag_is_missing('RemoteIP') 870 | 871 | self.raise_exception_if_not_a_integer('RestAPIReqID') 872 | self.raise_exception_if_empty('APIKey') 873 | self.raise_exception_if_empty('Signature') 874 | self.raise_exception_if_empty('Payload') 875 | self.raise_exception_if_empty('DigestMod') 876 | self.raise_exception_if_not_a_integer('Nonce') 877 | self.raise_exception_if_not_greater_than_zero('Nonce') 878 | self.raise_exception_if_empty('Message') 879 | self.raise_exception_if_empty('RemoteIP') 880 | 881 | elif self.type == 'S8': #Set/Update Instrument definition 882 | self.raise_exception_if_required_tag_is_missing('UpdateReqID') 883 | self.raise_exception_if_required_tag_is_missing('Symbol') 884 | self.raise_exception_if_required_tag_is_missing('MinPrice') 885 | self.raise_exception_if_required_tag_is_missing('MaxPrice') 886 | 887 | self.raise_exception_if_not_a_integer('UpdateReqID') 888 | self.raise_exception_if_not_a_integer('MinPrice') 889 | self.raise_exception_if_not_a_integer('MaxPrice') 890 | 891 | elif self.type == 'S9': #Instrument definition 892 | self.raise_exception_if_required_tag_is_missing('UpdateReqID') 893 | self.raise_exception_if_required_tag_is_missing('Symbol') 894 | 895 | elif self.type == 'S12': #Document List Request 896 | self.raise_exception_if_required_tag_is_missing('DocumentListReqID') 897 | self.raise_exception_if_required_tag_is_missing('Page') 898 | self.raise_exception_if_required_tag_is_missing('PageSize') 899 | self.raise_exception_if_required_tag_is_missing('DocumentName') 900 | self.raise_exception_if_required_tag_is_missing('Since') 901 | self.raise_exception_if_not_a_integer('DocumentListReqID') 902 | self.raise_exception_if_not_a_integer('Page') 903 | self.raise_exception_if_not_a_integer('PageSize') 904 | self.raise_exception_if_empty('DocumentName') 905 | self.raise_exception_if_not_a_integer('Since') 906 | 907 | elif self.type == 'S14': # Crypto Network Fee Charge Request 908 | self.raise_exception_if_required_tag_is_missing('CryptoNetworkFeeChargeReqID') 909 | self.raise_exception_if_required_tag_is_missing('ClientID') 910 | self.raise_exception_if_required_tag_is_missing('Currency') 911 | self.raise_exception_if_required_tag_is_missing('Amount') 912 | 913 | self.raise_exception_if_not_a_integer('Amount') 914 | self.raise_exception_if_not_greater_than_zero('Amount') 915 | 916 | elif self.type == 'S16': # User Logon Report 917 | self.raise_exception_if_required_tag_is_missing('LogonRptReqID') 918 | self.raise_exception_if_required_tag_is_missing('UserReqID') 919 | self.raise_exception_if_required_tag_is_missing('BrokerID') 920 | self.raise_exception_if_required_tag_is_missing('ClientID') 921 | self.raise_exception_if_required_tag_is_missing('IsApiKey') 922 | #self.raise_exception_if_required_tag_is_missing('UserReqType') 923 | #self.raise_exception_if_required_tag_is_missing('CancelOnDisconnect') 924 | #self.raise_exception_if_required_tag_is_missing('PermissionList') 925 | 926 | elif self.type == 'S34': #GetSystemSavedData 927 | self.raise_exception_if_required_tag_is_missing("GetSystemSavedDataReqID") 928 | self.raise_exception_if_not_a_integer('GetSystemSavedDataReqID') 929 | self.raise_exception_if_required_tag_is_missing("Key") 930 | self.raise_exception_if_not_string("Key") 931 | 932 | 933 | elif self.type == 'S36': # SystemSaveData 934 | self.raise_exception_if_required_tag_is_missing("SystemSaveDataReqID") 935 | self.raise_exception_if_not_a_integer('SystemSaveDataReqID') 936 | self.raise_exception_if_required_tag_is_missing("Key") 937 | self.raise_exception_if_not_string("Key") 938 | self.raise_exception_if_required_tag_is_missing("Data") 939 | self.raise_exception_if_not_string("Data") 940 | 941 | elif self.type == 'S38': # TradingSessionStatusChangeRequest 942 | self.raise_exception_if_required_tag_is_missing("ReqID") 943 | self.raise_exception_if_required_tag_is_missing("TradSesStatus") 944 | self.raise_exception_if_not_a_integer("TradSesStatus") 945 | 946 | elif self.type == 'S40': # SystemCheck2FARequest 947 | self.raise_exception_if_required_tag_is_missing("SessionID") 948 | self.raise_exception_if_required_tag_is_missing("SecondFactor") 949 | self.raise_exception_if_not_string("SecondFactor") 950 | 951 | 952 | 953 | def __contains__(self, value): 954 | return value in self.message 955 | 956 | def __getitem__(self, key): 957 | return self.message[key] 958 | 959 | def __setitem__(self, key, val): 960 | return self.set(key, val) 961 | 962 | def has(self, attr): 963 | return attr in self.message 964 | 965 | def get(self, attr , default=None): 966 | if attr not in self.message: 967 | return default 968 | return self.message[attr] 969 | 970 | def set(self, attr, value): 971 | self.message[attr] = value 972 | self.raw_message = json.dumps( dict(self.message.items() + {'MsgType' : self.type}.items() ) ) 973 | return self 974 | -------------------------------------------------------------------------------- /pyblinktrade/message_builder.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | 4 | class MessageBuilder(object): 5 | @staticmethod 6 | def testRequestMessage(request_id=None): 7 | if request_id: 8 | return {'MsgType': '1', 'TestReqID': request_id } 9 | else: 10 | return {'MsgType': '1', 'TestReqID': int(time.time()*1000)} 11 | 12 | @staticmethod 13 | def login(broker_id, user, password, second_factor=None): 14 | if not user or not password: 15 | raise ValueError('Invalid parameters') 16 | 17 | loginMsg = { 18 | 'UserReqID': 'initial', 19 | 'MsgType' : 'BE', 20 | 'BrokerID': broker_id, 21 | 'Username': user, 22 | 'Password': password, 23 | 'UserReqTyp': '1' 24 | } 25 | if second_factor: 26 | loginMsg['SecondFactor'] = second_factor 27 | 28 | return loginMsg 29 | 30 | @staticmethod 31 | def getDepositList(status_list, opt_filter=None, client_id=None, page=0, page_size=100,opt_request_id=None): 32 | if not opt_request_id: 33 | opt_request_id = random.randint(1,10000000) 34 | 35 | msg = { 36 | "MsgType":"U30", 37 | "DepositListReqID":opt_request_id, 38 | "Page":page, 39 | "PageSize":page_size, 40 | "StatusList":status_list 41 | } 42 | if client_id: 43 | msg["ClientID"] = client_id 44 | 45 | if opt_filter: 46 | msg["Filter"] = opt_filter 47 | return msg 48 | 49 | @staticmethod 50 | def updateProfile(update_dict, opt_user_id=None,opt_request_id=None): 51 | if not opt_request_id: 52 | opt_request_id = random.randint(1,10000000) 53 | 54 | msg = { 55 | "MsgType":"U38", 56 | "UpdateReqID":opt_request_id, 57 | "Fields": update_dict 58 | } 59 | if opt_user_id: 60 | msg["UserID"] = opt_user_id 61 | return msg 62 | 63 | @staticmethod 64 | def getWithdrawList(status_list, opt_filter=None, client_id=None, page=0, page_size=100,opt_request_id=None): 65 | if not opt_request_id: 66 | opt_request_id = random.randint(1,10000000) 67 | 68 | msg = { 69 | "MsgType":"U26", 70 | "WithdrawListReqID":opt_request_id, 71 | "Page":page, 72 | "PageSize":page_size, 73 | "StatusList":status_list, 74 | } 75 | if client_id: 76 | msg["ClientID"] = client_id 77 | if opt_filter: 78 | msg["Filter"] = opt_filter 79 | 80 | return msg 81 | 82 | @staticmethod 83 | def getBrokerList(status_list, country=None, page=None, page_size=100, opt_request_id=None): 84 | if not opt_request_id: 85 | opt_request_id = random.randint(1,10000000) 86 | 87 | msg = { 88 | 'MsgType' : 'U28', 89 | 'BrokerListReqID': opt_request_id 90 | } 91 | if page: 92 | msg['Page'] = page 93 | 94 | if page_size: 95 | msg['PageSize'] = page_size 96 | 97 | if status_list: 98 | msg['StatusList'] = status_list 99 | 100 | if country: 101 | msg['Country'] = country 102 | 103 | return msg 104 | 105 | @staticmethod 106 | def verifyCustomer(broker_id, client_id, verify, verification_data, opt_request_id=None): 107 | if not opt_request_id: 108 | opt_request_id = random.randint(1,10000000) 109 | 110 | return { 111 | 'MsgType': 'B8', 112 | 'VerifyCustomerReqID': opt_request_id, 113 | 'BrokerID': broker_id, 114 | 'ClientID': client_id, 115 | 'Verify': verify, 116 | 'VerificationData': verification_data 117 | } 118 | 119 | @staticmethod 120 | def processDeposit(action,opt_request_id = None,opt_secret=None,opt_depositId=None,opt_reasonId=None, 121 | opt_reason=None,opt_amount=None,opt_percent_fee=None,opt_fixed_fee=None): 122 | 123 | if not opt_request_id: 124 | opt_request_id = random.randint(1,10000000) 125 | 126 | msg = { 127 | 'MsgType': 'B0', 128 | 'ProcessDepositReqID': opt_request_id, 129 | 'Action': action 130 | } 131 | 132 | if opt_secret: 133 | msg['Secret'] = opt_secret 134 | 135 | if opt_depositId: 136 | msg['DepositID'] = opt_depositId 137 | 138 | if opt_reasonId: 139 | msg['ReasonID'] = opt_reasonId 140 | 141 | if opt_reason: 142 | msg['Reason'] = opt_reason 143 | 144 | if opt_amount: 145 | msg['Amount'] = opt_amount 146 | 147 | if opt_percent_fee: 148 | msg['PercentFee'] = opt_percent_fee 149 | 150 | if opt_fixed_fee: 151 | msg['FixedFee'] = opt_fixed_fee 152 | 153 | return msg 154 | 155 | @staticmethod 156 | def requestBalances(request_id = None, client_id = None): 157 | if not request_id: 158 | request_id = random.randint(1,10000000) 159 | msg = { 160 | 'MsgType': 'U2', 161 | 'BalanceReqID': request_id 162 | } 163 | if client_id: 164 | msg['ClientID'] = client_id 165 | return msg 166 | 167 | @staticmethod 168 | def requestPositions(request_id = None, client_id = None): 169 | if not request_id: 170 | request_id = random.randint(1,10000000) 171 | msg = { 172 | 'MsgType': 'U42', 173 | 'PositionReqID': request_id 174 | } 175 | if client_id: 176 | msg['ClientID'] = client_id 177 | return msg 178 | 179 | 180 | @staticmethod 181 | def requestMarketData(request_id, symbols, entry_types, subscription_type='1', market_depth=0 ,update_type = '1'): 182 | if not symbols or not entry_types: 183 | raise ValueError('Invalid parameters') 184 | 185 | return { 186 | 'MsgType' : 'V', 187 | 'MDReqID': request_id, 188 | 'SubscriptionRequestType': subscription_type, 189 | 'MarketDepth': market_depth, 190 | 'MDUpdateType': update_type, # 191 | 'MDEntryTypes': entry_types, # bid , offer, trade 192 | 'Instruments': symbols 193 | } 194 | 195 | @staticmethod 196 | def processWithdraw(action, withdrawId, request_id=None, reasonId=None, reason=None, data=None,percent_fee=None,fixed_fee=None): 197 | if not request_id: 198 | request_id = random.randint(1,10000000) 199 | 200 | msg = { 201 | 'MsgType': 'B6', 202 | 'ProcessWithdrawReqID': request_id, 203 | 'WithdrawID': withdrawId, 204 | 'Action': action 205 | } 206 | 207 | if reasonId: 208 | msg['ReasonID'] = reasonId 209 | 210 | if reason: 211 | msg['Reason'] = reason 212 | 213 | if data: 214 | msg['Data'] = data 215 | 216 | if percent_fee: 217 | msg['PercentFee'] = percent_fee 218 | 219 | if fixed_fee: 220 | msg['FixedFee'] = fixed_fee 221 | 222 | return msg 223 | 224 | @staticmethod 225 | def sendLimitedBuyOrder(symbol, qty, price, clientOrderId ): 226 | if not symbol or not qty or not qty or not price or not clientOrderId: 227 | raise ValueError('Invalid parameters') 228 | 229 | if qty <= 0 or price <= 0: 230 | raise ValueError('Invalid qty or price') 231 | 232 | return { 233 | 'MsgType': 'D', 234 | 'ClOrdID': str(clientOrderId), 235 | 'Symbol': symbol, 236 | 'Side': '1', 237 | 'OrdType': '2', 238 | 'Price': price, 239 | 'OrderQty': qty 240 | } 241 | 242 | @staticmethod 243 | def sendLimitedSellOrder(symbol, qty, price, clientOrderId ): 244 | if not symbol or not qty or not qty or not price or not clientOrderId: 245 | raise ValueError('Invalid parameters') 246 | 247 | if qty <= 0 or price <= 0: 248 | raise ValueError('Invalid qty or price') 249 | 250 | return { 251 | 'MsgType': 'D', 252 | 'ClOrdID': str(clientOrderId), 253 | 'Symbol': symbol, 254 | 'Side': '2', 255 | 'OrdType': '2', 256 | 'Price': price, 257 | 'OrderQty': qty 258 | } 259 | 260 | -------------------------------------------------------------------------------- /pyblinktrade/project_options.py: -------------------------------------------------------------------------------- 1 | class ProjectOptions(object): 2 | def __init__(self, config, section): 3 | self.config = config 4 | self.section = section 5 | 6 | def make_getters(tag): 7 | @property 8 | def _getter(self): 9 | raw_str = self.config.get(self.section, tag) 10 | try: 11 | return self.config.getint(self.section, tag) 12 | except Exception: 13 | pass 14 | try: 15 | return self.config.getfloat(self.section, tag) 16 | except Exception: 17 | pass 18 | try: 19 | return self.config.getboolean(self.section, tag) 20 | except Exception: 21 | pass 22 | 23 | return raw_str 24 | return _getter 25 | 26 | for k,v in self.items(): 27 | _getter = make_getters(k) 28 | setattr(ProjectOptions, k ,_getter) 29 | 30 | def has_option(self, attribute): 31 | return self.config.has_option(self.section, attribute) 32 | def get(self, attribute): 33 | return self.config.get(self.section, attribute) 34 | def getint(self, attribute): 35 | return self.config.getint(self.section, attribute) 36 | def getfloat(self, attribute): 37 | return self.config.getfloat(self.section, attribute) 38 | def getboolean(self, attribute): 39 | return self.config.getboolean(self.section, attribute) 40 | def items(self): 41 | return self.config.items(self.section) 42 | def options(self): 43 | return self.config.options(self.section) -------------------------------------------------------------------------------- /pyblinktrade/signals.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | import inspect 3 | import traceback 4 | import logging 5 | import threading 6 | 7 | class Signal(): 8 | signal_error = None 9 | _lock = threading.RLock() 10 | 11 | def __init__(self): 12 | self._functions = weakref.WeakSet() 13 | self._methods = weakref.WeakKeyDictionary() 14 | 15 | self._methods_subs = {} 16 | self._functions_subs = {} 17 | 18 | if not Signal.signal_error: 19 | Signal.signal_error = 1 20 | Signal.signal_error = Signal() 21 | 22 | def connect(self, slot, sender=None): 23 | if sender: 24 | if inspect.ismethod(slot): 25 | if sender not in self._methods_subs: 26 | self._methods_subs[sender] = weakref.WeakKeyDictionary() 27 | 28 | if slot.__self__ not in self._methods_subs[sender]: 29 | self._methods_subs[sender][slot.__self__] = set() 30 | 31 | self._methods_subs[sender][slot.__self__].add(slot.__func__) 32 | else: 33 | if sender not in self._functions_subs: 34 | self._functions_subs[sender] = weakref.WeakSet() 35 | self._functions_subs[sender].add(slot) 36 | else: 37 | if inspect.ismethod(slot): 38 | if slot.__self__ not in self._methods: 39 | self._methods[slot.__self__] = set() 40 | self._methods[slot.__self__].add(slot.__func__) 41 | else: 42 | self._functions.add(slot) 43 | 44 | def disconnect(self, slot, sender=None): 45 | if sender: 46 | if inspect.ismethod(slot): 47 | if sender in self._methods_subs: 48 | if slot.__self__ in self._methods_subs[sender]: 49 | self._methods_subs[sender][slot.__self__].remove(slot.__func__) 50 | if len(self._methods_subs[sender][slot.__self__])== 0: 51 | del self._methods_subs[sender][slot.__self__] 52 | if len(self._methods_subs[sender]) ==0: 53 | del self._methods_subs[sender] 54 | else: 55 | if sender in self._functions_subs: 56 | self._functions_subs[sender].remove(slot) 57 | if len(self._functions_subs[sender]) == 0: 58 | del self._functions_subs[sender] 59 | else: 60 | if inspect.ismethod(slot): 61 | if slot.__self__ in self._methods: 62 | del self._methods[slot.__self__][slot.__func__] 63 | else: 64 | self._functions.remove(slot) 65 | 66 | def __call__(self, sender, data=None, error_signal_on_error=True): 67 | with self._lock: 68 | sent = False 69 | errors = [] 70 | 71 | def publish_functions(functions): 72 | for func in functions: 73 | try: 74 | func(sender, data) 75 | sent = True 76 | 77 | # pylint: disable=W0702 78 | except: 79 | errors.append(traceback.format_exc()) 80 | publish_functions(self._functions) 81 | if sender in self._functions_subs: 82 | publish_functions(self._functions_subs[sender]) 83 | if not self._functions_subs[sender]: 84 | del self._functions_subs[sender] 85 | 86 | 87 | 88 | def publish_methods( methods ): 89 | for obj, funcs in methods.items(): 90 | for func in funcs: 91 | try: 92 | func(obj, sender, data) 93 | sent = True 94 | 95 | # pylint: disable=W0702 96 | except: 97 | errors.append(traceback.format_exc()) 98 | publish_methods(self._methods) 99 | 100 | if sender in self._methods_subs: 101 | publish_methods(self._methods_subs[sender]) 102 | if not self._methods_subs[sender]: 103 | del self._methods_subs[sender] 104 | 105 | 106 | for error in errors: 107 | if error_signal_on_error: 108 | Signal.signal_error(self, (error), False) 109 | else: 110 | logging.critical(error) 111 | 112 | return sent 113 | 114 | -------------------------------------------------------------------------------- /pyblinktrade/test_signals.py: -------------------------------------------------------------------------------- 1 | __author__ = 'rodrigo' 2 | 3 | import unittest 4 | 5 | from signals import Signal 6 | 7 | signal_calls = [] 8 | 9 | def onSignalFunction(sender, data): 10 | signal_calls.append( ( 'function', sender, data ) ) 11 | 12 | class A(object): 13 | def onSignalMethod(self, sender, data): 14 | signal_calls.append( ( 'A::onSignalMethod', sender, data ) ) 15 | 16 | class B(object): 17 | def __init__(self, signal): 18 | signal.connect(self.onSignalMethod) 19 | 20 | def onSignalMethod(self, sender, data): 21 | signal_calls.append( ( 'B::onSignalMethod', sender, data ) ) 22 | 23 | 24 | class TestSignal(unittest.TestCase): 25 | def setUp(self): 26 | self.sig_function = Signal() 27 | self.sig_method = Signal() 28 | self.sig_function.connect(onSignalFunction) 29 | 30 | global signal_calls 31 | signal_calls = [] 32 | 33 | def test_signal_function(self): 34 | self.sig_function('sender1', 'data1') 35 | self.assertEqual(1, len(signal_calls)) 36 | 37 | self.assertEqual( 'function', signal_calls[0][0] ) 38 | self.assertEqual( 'sender1', signal_calls[0][1] ) 39 | self.assertEqual( 'data1', signal_calls[0][2] ) 40 | 41 | 42 | def test_signal_method(self): 43 | a = A() 44 | self.sig_method.connect(a.onSignalMethod) 45 | 46 | self.sig_method('sender1', 'data1') 47 | 48 | self.assertEqual(1, len(signal_calls)) 49 | self.assertEqual( 'A::onSignalMethod', signal_calls[0][0] ) 50 | self.assertEqual( 'sender1', signal_calls[0][1] ) 51 | self.assertEqual( 'data1', signal_calls[0][2] ) 52 | 53 | del a 54 | self.sig_method('sender1', 'data1') 55 | self.assertEqual(1, len(signal_calls)) 56 | 57 | b = B(self.sig_method) 58 | self.sig_method('sender2', 'data2') 59 | 60 | self.assertEqual(2, len(signal_calls)) 61 | self.assertEqual( 'B::onSignalMethod', signal_calls[1][0] ) 62 | self.assertEqual( 'sender2', signal_calls[1][1] ) 63 | self.assertEqual( 'data2', signal_calls[1][2] ) 64 | 65 | del b 66 | self.sig_method('sender3', 'data3') 67 | self.assertEqual(2, len(signal_calls)) 68 | 69 | def test_sender_signal_method(self): 70 | a = A() 71 | self.sig_method.connect(a.onSignalMethod, 'sender1') 72 | 73 | self.sig_method('sender1', 'data1') 74 | 75 | self.assertEqual(1, len(signal_calls)) 76 | self.assertEqual( 'A::onSignalMethod', signal_calls[0][0] ) 77 | self.assertEqual( 'sender1', signal_calls[0][1] ) 78 | self.assertEqual( 'data1', signal_calls[0][2] ) 79 | 80 | 81 | self.sig_method('sender2', 'data2') 82 | self.assertEqual(1, len(signal_calls)) 83 | 84 | self.sig_method('sender1', 'data2') 85 | self.assertEqual(2, len(signal_calls)) 86 | 87 | del a 88 | self.sig_method('sender1', 'data3') 89 | self.assertEqual(2, len(signal_calls)) 90 | 91 | def test_sender_signal_function(self): 92 | def on_signal_func(sender, data): 93 | signal_calls.append( ( 'function', sender, data ) ) 94 | 95 | self.sig_method.connect( on_signal_func, 'sender1' ) 96 | self.sig_method('sender1', 'data1') 97 | 98 | self.assertEqual(1, len(signal_calls)) 99 | self.assertEqual( 'function', signal_calls[0][0] ) 100 | self.assertEqual( 'sender1', signal_calls[0][1] ) 101 | self.assertEqual( 'data1', signal_calls[0][2] ) 102 | 103 | self.sig_method('sender2', 'data2') 104 | self.assertEqual(1, len(signal_calls)) 105 | -------------------------------------------------------------------------------- /pyblinktrade/utils.py: -------------------------------------------------------------------------------- 1 | import types 2 | 3 | 4 | def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): 5 | """ 6 | Returns a bytestring version of 's', encoded as specified in 'encoding'. 7 | 8 | If strings_only is True, don't convert (some) non-string-like objects. 9 | """ 10 | if strings_only and isinstance(s, (types.NoneType, int)): 11 | return s 12 | if not isinstance(s, basestring): 13 | try: 14 | return str(s) 15 | except UnicodeEncodeError: 16 | if isinstance(s, Exception): 17 | # An Exception subclass containing non-ASCII data that doesn't 18 | # know how to print itself properly. We shouldn't raise a 19 | # further exception. 20 | return ' '.join([smart_str(arg, encoding, strings_only, 21 | errors) for arg in s]) 22 | return unicode(s).encode(encoding, errors) 23 | elif isinstance(s, unicode): 24 | return s.encode(encoding, errors) 25 | elif s and encoding != 'utf-8': 26 | return s.decode('utf-8', errors).encode(encoding, errors) 27 | else: 28 | return s 29 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | version = "0.9.3" 6 | 7 | setup( 8 | name="pyblinktrade", 9 | version=version, 10 | packages = [ 11 | "pyblinktrade", 12 | ], 13 | author="Rodrigo Souza", 14 | install_requires=[], 15 | author_email='r@blinktrade.com', 16 | url='https://github.com/blinktrade/pyblinktrade', 17 | license='http://www.gnu.org/copyleft/gpl.html', 18 | description='Blinktrade python api library' 19 | ) 20 | --------------------------------------------------------------------------------