-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from SamuelGuillemet/develop
🔖 Bump version to 0.3.0
- Loading branch information
Showing
123 changed files
with
13,392 additions
and
675 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[flake8] | ||
max-line-length = 120 | ||
|
||
extend-exclude = | ||
.git | ||
.venv | ||
data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.venv/ | ||
# .vscode/ | ||
data/* | ||
!data/.gitkeep | ||
**/__pycache__/ | ||
storage/** | ||
latency*.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[MASTER] | ||
init-hook='import sys; sys.path.append(".")' | ||
|
||
[MESSAGES CONTROL] | ||
disable=missing-module-docstring, missing-function-docstring, missing-class-docstring, too-few-public-methods, too-many-arguments, too-many-instance-attributes, too-many-locals, logging-fstring-interpolation, redefined-builtin, arguments-renamed | ||
|
||
[FORMAT] | ||
good-names=i,j,k,ex,Run,_,pk,x,y,e,f | ||
max-line-length = 120 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"configurations": [ | ||
{ | ||
"name": "Python: File", | ||
"type": "python", | ||
"request": "launch", | ||
"program": "${file}", | ||
"justMyCode": true | ||
}, | ||
{ | ||
"name": "Main", | ||
"type": "python", | ||
"request": "launch", | ||
"program": "${workspaceFolder}/broker_quickfix_client/main.py", | ||
"console": "integratedTerminal", | ||
"justMyCode": true | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"files.exclude": { | ||
"**/.git": true, | ||
"**/.svn": true, | ||
"**/.hg": true, | ||
"**/CVS": true, | ||
"**/.DS_Store": true, | ||
"**/Thumbs.db": true, | ||
"**/__pycache__": true, | ||
"**/.pytest_cache": true, | ||
"**/.mypy_cache": true, | ||
"**/.mypy*": true, | ||
"**/.venv": true, | ||
"**/storage/": true, | ||
}, | ||
"python.analysis.autoImportCompletions": true, | ||
"python.analysis.typeCheckingMode": "basic", | ||
"python.analysis.inlayHints.callArgumentNames": "partial", | ||
"python.analysis.inlayHints.variableTypes": true, | ||
"python.analysis.inlayHints.functionReturnTypes": true, | ||
"editor.formatOnSave": true, | ||
"isort.check": true, | ||
"isort.args": ["--profile", "black"], | ||
"[python]": { | ||
"editor.codeActionsOnSave": { | ||
"source.organizeImports": "explicit", | ||
"source.fixAll": "explicit" | ||
}, | ||
"editor.defaultFormatter": "ms-python.black-formatter", | ||
"editor.codeLens": true | ||
}, | ||
"search.exclude": { | ||
"**/dist": true, | ||
"**/.venv": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# Quickfix client | ||
|
||
This repository comprises a Python-based client application designed to interact with a broker's system using the QuickFIX library. The architecture is structured as follows: | ||
|
||
## Global Architecture Overview | ||
|
||
### Entry Point | ||
- **main.py**: This file serves as the entry point of the application. It initializes and starts the main functionality. | ||
|
||
### Functionality Modules | ||
- **application.py**: Contains the core logic of the quickfix application. | ||
- **constant.py**: Stores constants used throughout the application. | ||
- **decorators.py**: Defines decorators used within the application. | ||
|
||
### Handlers | ||
- **handlers/**: This directory holds modules responsible for handling specific message types. `execution_report.py` focuses on handling execution reports from the broker's system. | ||
|
||
### Utility Modules | ||
- **utils/**: Houses utility modules utilized across the application: | ||
- `loader.py`: Handles loading operations. | ||
- `logger.py`: Provides logging functionality. | ||
- `quickfix.py`: Offers utility functions for interfacing with the QuickFIX library. | ||
|
||
### Wrappers | ||
- **wrappers/**: Encapsulates and extends functionality from the QuickFIX library: | ||
- `enums.py`: Houses enumerations and constants related to QuickFIX. | ||
- `execution_report.py`: Wrapper dedicated to store execution reports as python objects. | ||
- `new_order_single.py`: Wrapper facilitating the handling of new single orders, for example its creation and storage as a python object. | ||
|
||
The architecture follows a modular approach, segregating functionalities into discrete modules and directories. It aims to streamline interactions with the QuickFIX library and broker system by providing wrappers and utilities while maintaining clear separation of concerns. | ||
|
||
|
||
## Specifications | ||
|
||
In order to interact flawlessly with the quickfix application, you can define callback functions when instantiating the application. These callbacks are defined as follows: | ||
|
||
```python | ||
execution_handler = ExecutionReportHandler( | ||
on_filled_report=lambda report: logger.info(f"Filled: {report}"), | ||
on_rejected_report=lambda report: logger.info(f"Rejected: {report}"), | ||
) | ||
application, initiator = setup() | ||
application.set_execution_report_handler(execution_handler) | ||
``` | ||
|
||
Callbacks function should take as input a python object representing the execution report. For example we have: | ||
|
||
```python | ||
@dataclass | ||
class FilledExecutionReport: | ||
order_id: int | ||
client_order_id: int | ||
symbol: str | ||
side: SideEnum | ||
type: OrderTypeEnum | ||
leaves_quantity: int | ||
price: float | ||
cum_quantity: int | ||
|
||
|
||
@dataclass | ||
class RejectedExecutionReport: | ||
order_id: int | ||
client_order_id: int | ||
symbol: str | ||
side: SideEnum | ||
type: OrderTypeEnum | ||
leaves_quantity: int | ||
reject_reason: OrderRejectReasonEnum | ||
``` | ||
|
||
In this example, we define two callbacks, one for filled reports and one for rejected reports. These callbacks are called whenever the application receives a filled or rejected report from the broker's system. | ||
|
||
To send new orders, you can use the following function: | ||
|
||
```python | ||
order = NewOrderSingle.new_market_order(0, SideEnum.SELL, 1, "ACGL") | ||
application.send(order) | ||
``` | ||
|
||
If you want to store the order as a python object, you can use the following function: | ||
|
||
```python | ||
python_order = order.get_order() | ||
``` | ||
|
||
This function returns a `Order` dataclass object, which contains the order as a python object. This object is defined as follows: | ||
|
||
```python | ||
@dataclass | ||
class Order: | ||
order_id: int | None | ||
client_order_id: int | ||
symbol: str | ||
side: SideEnum | ||
price: float | None | ||
quantity: int | ||
``` | ||
|
||
--- | ||
|
||
## Installation | ||
|
||
To install the client, you need to run the following commands: | ||
|
||
```bash | ||
$ poetry install | ||
``` | ||
|
||
## Running the client | ||
|
||
To run the client, you need to run the following command: | ||
|
||
```bash | ||
(.venv) $ python broker_quickfix_client/main.py | ||
``` |
Empty file.
130 changes: 130 additions & 0 deletions
130
clients/quickfix-client/broker_quickfix_client/application.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
# pylint: disable=unused-argument,invalid-name,super-init-not-called | ||
|
||
import logging | ||
from time import sleep | ||
|
||
from quickfix import ( | ||
Application, | ||
FileStoreFactory, | ||
Message, | ||
MsgType, | ||
MsgType_ExecutionReport, | ||
MsgType_Logon, | ||
MsgType_MarketDataSnapshotFullRefresh, | ||
MsgType_OrderCancelReject, | ||
Password, | ||
Session, | ||
SessionID, | ||
SocketInitiator, | ||
Username, | ||
) | ||
|
||
from broker_quickfix_client.handlers.execution_report import ExecutionReportHandler | ||
from broker_quickfix_client.handlers.order_cancel_reject import OrderCancelRejectHandler | ||
from broker_quickfix_client.utils.logger import setup_logs | ||
from broker_quickfix_client.utils.quickfix import log_quick_fix_message, set_settings | ||
|
||
logger = logging.getLogger("client.application") | ||
|
||
|
||
class ClientApplication(Application): | ||
session_id: SessionID | None = None | ||
|
||
execution_report_handler = ExecutionReportHandler() | ||
order_cancel_reject_handler = OrderCancelRejectHandler() | ||
|
||
username: str | None = None | ||
password: str | None = None | ||
|
||
def set_execution_report_handler( | ||
self, execution_report_handler: ExecutionReportHandler | ||
): | ||
self.execution_report_handler = execution_report_handler | ||
|
||
def set_order_cancel_reject_handler( | ||
self, order_cancel_reject_handler: OrderCancelRejectHandler | ||
): | ||
self.order_cancel_reject_handler = order_cancel_reject_handler | ||
|
||
def onCreate(self, sessionId: SessionID): | ||
pass | ||
|
||
def onLogon(self, sessionId: SessionID): | ||
self.session_id = sessionId | ||
|
||
def onLogout(self, sessionId: SessionID): | ||
pass | ||
|
||
def toAdmin(self, message: Message, sessionId: SessionID): | ||
log_quick_fix_message(message, "Sending") | ||
if message.getHeader().getField(MsgType()).getString() == MsgType_Logon: | ||
message.setField(Username(self.username)) | ||
message.setField(Password(self.password)) | ||
|
||
def toApp(self, message: Message, sessionId: SessionID): | ||
log_quick_fix_message(message, "Sending", logging.INFO) | ||
|
||
def fromAdmin(self, message: Message, sessionId: SessionID): | ||
log_quick_fix_message(message, "Received") | ||
|
||
def fromApp(self, message: Message, sessionId: SessionID): | ||
log_quick_fix_message(message, "Received", logging.INFO) | ||
|
||
msg_type = message.getHeader().getField(MsgType()).getString() | ||
|
||
if msg_type == MsgType_ExecutionReport: | ||
self.execution_report_handler.handle_execution_report(message) | ||
elif msg_type == MsgType_OrderCancelReject: | ||
self.order_cancel_reject_handler.handle_order_cancel_reject(message) | ||
elif msg_type == MsgType_MarketDataSnapshotFullRefresh: | ||
logger.info("Market data snapshot full refresh received") | ||
else: | ||
logger.warning(f"Unknown message type: {msg_type}") | ||
|
||
def send(self, message: Message): | ||
return Session.sendToTarget(message, self.session_id) | ||
|
||
def get_session_id(self): | ||
return self.session_id | ||
|
||
def set_credentials(self, username, password): | ||
self.username = username | ||
self.password = password | ||
|
||
|
||
def build_initiator(username: str, application: ClientApplication) -> SocketInitiator: | ||
settings = set_settings(username) | ||
store_factory = FileStoreFactory(settings) | ||
initiator = SocketInitiator(application, store_factory, settings) | ||
return initiator | ||
|
||
|
||
def start_initiator(initiator: SocketInitiator, application: ClientApplication): | ||
if not application.username or not application.password: | ||
raise ValueError("Username and password must be set before starting initiator") | ||
|
||
initiator.start() | ||
# Wait for the session to logon before sending messages. | ||
while not application.get_session_id(): | ||
sleep(0.1) | ||
|
||
|
||
def setup( | ||
username: str, | ||
password: str, | ||
execution_report_handler: ExecutionReportHandler | None = None, | ||
order_cancel_reject_handler: OrderCancelRejectHandler | None = None, | ||
): | ||
setup_logs("client") | ||
setup_logs("quickfix") | ||
application = ClientApplication() | ||
|
||
application.set_credentials(username, password) | ||
if execution_report_handler: | ||
application.set_execution_report_handler(execution_report_handler) | ||
if order_cancel_reject_handler: | ||
application.set_order_cancel_reject_handler(order_cancel_reject_handler) | ||
|
||
initiator = build_initiator(username, application) | ||
start_initiator(initiator, application) | ||
return application, initiator |
Oops, something went wrong.