{"id":19761360,"url":"https://github.com/jcass77/wtfix","last_synced_at":"2025-04-19T14:55:51.012Z","repository":{"id":48487508,"uuid":"159778780","full_name":"jcass77/WTFIX","owner":"jcass77","description":"The Pythonic Financial Information eXchange (FIX) client.","archived":false,"fork":false,"pushed_at":"2024-07-07T15:45:21.000Z","size":701,"stargazers_count":27,"open_issues_count":5,"forks_count":13,"subscribers_count":3,"default_branch":"develop","last_synced_at":"2025-04-18T05:14:59.063Z","etag":null,"topics":["client","engine","financial","fintech","fix","quickfix"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jcass77.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-30T06:29:19.000Z","updated_at":"2024-07-07T15:53:34.000Z","dependencies_parsed_at":"2024-11-12T03:40:53.993Z","dependency_job_id":"4496dc49-5ca3-4cfb-92c3-a12d03a7fa1a","html_url":"https://github.com/jcass77/WTFIX","commit_stats":{"total_commits":374,"total_committers":2,"mean_commits":187.0,"dds":0.002673796791443861,"last_synced_commit":"7f23635e2325ca2f53b138f48f40d0e4b5ffde8e"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcass77%2FWTFIX","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcass77%2FWTFIX/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcass77%2FWTFIX/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcass77%2FWTFIX/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jcass77","download_url":"https://codeload.github.com/jcass77/WTFIX/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249718732,"owners_count":21315099,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","engine","financial","fintech","fix","quickfix"],"created_at":"2024-11-12T03:40:40.907Z","updated_at":"2025-04-19T14:55:50.968Z","avatar_url":"https://github.com/jcass77.png","language":"Python","readme":"## WARNING: This project is no longer actively maintained.\n\nIt has been a few years since I have supported FIX connections in production, and I am no longer making use of this\ncodebase myself anymore.\n\nWe have an [issue open](https://github.com/jcass77/WTFIX/issues/10) to look for a new maintainer. Please\ncomment there if you are interested in becoming involved.\n\n# WTF(ix)\n\nThe Pythonic Financial Information eXchange (FIX) client that you have been looking for.\n\n[![PyPI version shields.io](https://img.shields.io/pypi/v/wtfix.svg)](https://pypi.python.org/pypi/wtfix/)\n[![PyPI pyversions](https://img.shields.io/pypi/pyversions/wtfix.svg)](https://pypi.python.org/pypi/wtfix/)\n[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/jcass77/wtfix/Python%20package)](https://github.com/jcass77/WTFIX/actions?query=workflow%3A%22Python+package%22)\n[![Codecov](https://img.shields.io/codecov/c/github/jcass77/wtfix?token=YNpau2mYT3)](https://codecov.io/gh/jcass77/wtfix)\n[![PyPI license](https://img.shields.io/pypi/l/wtfix.svg)](COPYING)\n[![Code style:black](https://img.shields.io/badge/code%20style-black-black)](https://pypi.org/project/black)\n\n## Background\n\nWTF(ix) is a simple and easy to use FIX client for Python. It can also be adapted to plug into various crypto\nexchanges that do not adhere to the formal FIX (or any other) specification.\n\nWTF(ix) does not require you to alter your development stack, deploy a database, compile a C++ codebase and figure out\nweird API bindings, or wrangle with arcane XML file definitions.\n\n## Project Highlights and Goals\n\n- Batteries included - comes with everything that you need to connect to a FIX server and start sending and receiving\nmessages in minutes. Provides default implementations for:\n    - Authentication\n    - Maintaining a heartbeat\n    - Sequence number management and resend requests\n    - Message storage and retrieval\n- Fast, easy to understand message processing pipeline based on a modern ``async and await`` implementation.\n- Easily extendable architecture - modular 'apps' can be added to the pipeline stack to add custom message processing\nroutines or new application features.\n\n    ```python\n    PIPELINE_APPS = [\n        \"wtfix.apps.utils.PipelineTerminationApp\", # Post-processing / cleanup\n        \"my_app.apps.SecretAlgoTradingRecipe\",     # \u003c-- Your application logic\n        \"wtfix.apps.api.RESTfulServiceApp\",        # REST API for sending messages\n        \"wtfix.apps.brokers.RedisPubSubApp\",       # Redis Pub/Sub broker for sending / receiving messages\n        \"wtfix.apps.admin.HeartbeatApp\",           # Heartbeat monitoring and maintenance\n        \"wtfix.apps.admin.AuthenticationApp\",      # Login / logout handling\n        \"wtfix.apps.admin.SeqNumManagerApp\",       # Message gap detection and filling\n        \"wtfix.apps.store.MessageStoreApp\",        # Store messages (caching or persistence)\n        \"wtfix.apps.utils.InboundLoggingApp\",      # Log inbound messages\n        \"wtfix.apps.parsers.RawMessageParserApp\",  # Message parsing: Logon (A): {BeginString (8): FIX.4.4 | BodyLength (9): 99 | MsgType (35): A | MsgSeqNum (34): 1 | SenderCompID (49): SENDER | SendingTime (52): 20190305-08:45:45.979 | TargetCompID (56): TARGET | EncryptMethod (98): 0 | HeartBtInt (108): 30 | Username (553): USERNAME | Password (554): PASSWORD | ResetSeqNumFlag (141): Y | CheckSum (10): 94}\n        \"wtfix.apps.utils.OutboundLoggingApp\",     # Log outbound messages\n        \"wtfix.apps.wire.WireCommsApp\",            # Raw message encoding / decoding: b'8=FIX.4.4\\x019=99\\x0135=A\\x0134=1\\x0149=SENDER\\x0152=20190305-08:42:32.793\\x0156=TARGET\\x0198=0\\x01108=30\\x01553=USERNAME\\x01554=PASSWORD\\x01141=Y\\x0110=081\\x01'\n        \"wtfix.apps.sessions.ClientSessionApp\",    # HTTP session management\n    ]\n    ```\n- Messages can be cached in memory or saved to a Redis message store for later retrieval. Alternatively you can add\nyour own message storage solution using the provided interfaces.\n- Send messages directly from the pipeline, via 3rd party applications using a REST API, or by publishing them to\na Redis Pub/Sub channel for immediate delivery.\n\n- Provides a convenient ``@on`` decorator for fine-grained control over which apps will respond to which types of messages:\n\n    ```python\n    from wtfix.apps.base import MessageTypeHandlerApp, on\n    from wtfix.conf import settings\n\n    logger = settings.logger\n\n    class SecretAlgoTradingRecipe(MessageTypeHandlerApp):\n\n        @on(context.protocol.MsgType.Logon)  # Only invoked when 'Logon (type A)' messages are received.\n        def on_logon(self, message):\n            self.send_security_definition_request()\n            return message\n\n        def on_receive(self, message):  # Invoked for every type of message.\n          logger.info(f\"Received message {message}!\")\n    ```\n\n- Provides custom `Field` and `FieldMap` types for working with FIX tags and field values. These types are 'pythonic',\nimplementing many of the standard protocols, and behave as expected when you integrate them in existing Python\ncode.\n- A simple message tag syntax, with various convenience methods, for quick access to commonly\nused message attributes.\n\n    ```python\n    \u003e\u003e\u003e from wtfix.message import admin\n\n    # Instantiate a new 'Logon' message\n    \u003e\u003e\u003e logon_msg = admin.LogonMessage(\"my_username\", \"my_password\", heartbeat_int=30)\n\n    # Short, concise string representation\n    \u003e\u003e\u003e str(logon_msg)\n    'A: {(35, A) | (98, 0) | (108, 30) | (553, my_username) | (554, my_password)}'\n\n    # Pretty print tag names by using the 't' formatting option\n    \u003e\u003e\u003e f\"{logon_msg:t}\"\n    'Logon (A): {MsgType (35): A | EncryptMethod (98): 0 | HeartBtInt (108): 30 | Username (553): my_username | Password (554): my_password}'\n\n    # Example of getting the message type\n    \u003e\u003e\u003e logon_msg.type\n    'A'\n\n    # Example of getting the message type name\n    \u003e\u003e\u003e logon_msg.name\n    'Logon'\n\n    # Look up the sequence number\n    \u003e\u003e\u003e logon_msg.seq_num\n    1\n\n    # Various ways for accessing the different fields that make up the message. Fields are just\n    # (tag, value) namedtuples.\n    \u003e\u003e\u003e logon_msg[108]  # Using old school tag number\n    Field(108, '30')\n\n    \u003e\u003e\u003e logon_msg[context.protocol.Tag.HeartBtInt]  # Using the tag name as per the FIX specification\n    Field(108, '30')\n\n    \u003e\u003e\u003e logon_msg.HeartBtInt  # Using tag name shortcut\n    Field(108, '30')\n    ```\n- A pragmatic [unicode sandwich](https://nedbatchelder.com/text/unipain.html) based approach to encoding / decoding\nmessages mean that you never need to deal with byte sequences directly.\n\n    ```python\n    from wtfix.message.field import Field\n    from wtfix.message.message import generic_message_factory\n\n    # Create a new Message from a byte sequence received over the wire\n    \u003e\u003e\u003e fields = Field.fields_frombytes(b\"35=A\\x0198=0\\x01108=30\\x01553=my_username\\x01554=my_password\\x01\")\n    \u003e\u003e\u003e logon_msg = generic_message_factory(*fields)\n\n    \u003e\u003e\u003e str(logon_msg)\n    'A: {(35, A) | (98, 0) | (108, 30) | (553, my_username) | (554, my_password)}'\n\n    # Fields are tuples of (tag, value) pairs\n    \u003e\u003e\u003e username = logon_msg.Username\n\n    \u003e\u003e\u003e username.tag\n    553\n\n    \u003e\u003e\u003e username.value\n    \"my_username\"\n\n    # Fields behave just like Python's built-in types, and most operations can be performed directly\n    # on a field's 'value' attribute.\n    \u003e\u003e\u003e username += \"_123\"\n    \u003e\u003e\u003e username\n    Field(553, 'my_username_123')\n    ```\n- Access the underlying byte sequence when you need it by casting the message back to `bytes`:\n\n    ```python\n    \u003e\u003e\u003e bytes(logon_msg)\n    b'35=A\\x0198=0\\x01108=30\\x01553=my_username\\x01554=my_password\\x01'\n    ```\n-  It is easy to add Fields to a Message: simply assign the tag value:\n\n    ```python\n    \u003e\u003e\u003e logon_msg.PossDupFlag = \"Y\"\n    \u003e\u003e\u003e f\"{logon_msg:t}\"\n    'Logon (A): {MsgType (35): A | EncryptMethod (98): 0 | HeartBtInt (108): 30 | Username (553): my_username | Password (554): my_password | PossDupFlag (43): Y}'\n\n    # Most FIX field types can be cast to equivalent Python built-in types to make processing easier\n    \u003e\u003e\u003e bool(logon_msg.PossDupFlag) is True\n    True\n\n    ```\n- A very forgiving approach to parsing repeating groups of message tags; with or without pre-defined templates:\n\n    ```python\n    from wtfix.protocol.common import Tag, MsgType\n    from wtfix.message.message import generic_message_factory\n\n    # If you provide a group template, then messages are stored in an 'OrderedDict' for fast lookups\n    \u003e\u003e\u003e msg = generic_message_factory((Tag.MsgType, MsgType.ExecutionReport), (Tag.NoMiscFees, 2), (Tag.MiscFeeAmt, 10.00), (Tag.MiscFeeType, 2), (Tag.MiscFeeAmt, 20.00), (Tag.MiscFeeType, \"A\"), group_templates={Tag.NoMiscFees: [Tag.MiscFeeAmt, Tag.MiscFeeType,]})\n    \u003e\u003e\u003e msg.data\n    OrderedDict([(35, Field(35, '8')), (136, Group(Field(136, '2'), Field(137, '10.0'), Field(139, '2'), Field(137, '20.0'), Field(139, 'A')))])\n\n    # Get 'NoMiscFees' group\n    \u003e\u003e\u003e group = msg.NoMiscFees\n    \u003e\u003e\u003e f\"{group:t}\"\n    '[NoMiscFees (136): 2] | [MiscFeeAmt (137): 10.0 | MiscFeeType (139): 2] | [MiscFeeAmt (137): 20.0 | MiscFeeType (139): A]'\n\n    # Determine the number of instances in the group\n    \u003e\u003e\u003e group.size\n    2\n\n    # Retrieve the second group instance\n    \u003e\u003e\u003e group.instances[1]\n    FieldList(Field(137, '20.0'), Field(139, 'A'))\n\n    # Without a pre-defined group template, WTFIX falls back to using a (slightly slower) list structure for representing message fields internally\n    \u003e\u003e\u003e msg = generic_message_factory((Tag.MsgType, MsgType.ExecutionReport), (Tag.NoMiscFees, 2), (Tag.MiscFeeAmt, 10.00), (Tag.MiscFeeType, 2), (Tag.MiscFeeAmt, 20.00), (Tag.MiscFeeType, \"A\"))\n    \u003e\u003e\u003e msg.data\n    [Field(35, '8'), Field(136, '2'), Field(137, '10.0'), Field(139, '2'), Field(137, '20.0'), Field(139, 'A')]\n\n    ```\n\n## Getting Started\n\n- Install the project's dependencies (e.g. `pip install -r requirements/local.txt`), preferably in a Python virtual\n  environment that has been created specifically for that purpose.\n- Run the test suite with `pytest` to verify the installation.\n- Create a `.env` file in the project's root directory that contains at least the following configuration settings:\n\n    ```python\n    # Supports different configuration settings for local development, staging, or production environments.\n    WTFIX_SETTINGS_MODULE=config.settings.local\n\n    HOST=             # Required. The FIX server hostname or IP address\n    PORT=             # Required. The port on the FIX server to connect to\n\n    SENDER=           # Required. SENDER_COMP_ID (tag 49).\n    TARGETD=          # Required. TARGET_COMP_ID (tag 56).\n\n    USERNAME=         # Required. Username to use for Logon messages (tag 553).\n    PASSWORD=         # Required. Password to use for logon messages (tag 554).\n\n    PYTHONASYNCIODEBUG=0  # Set to '1' for detailed debugging messages.\n    ```\n\n- Start the FIX client with `python runclient.py`. The default implementation will log in to the FIX server and maintain a steady heartbeat.\n- Use `Ctrl-C` to quit. This will trigger a `Logout` message to be sent before the pipeline is terminated.\n\n## Project Resources\n\n- [Deploying](docs/deploying.md)\n- [Changelog](docs/changelog.md)\n- [Release procedures](docs/releasing.md)\n\n## Need More?\n\nWTF(ix) is open source, free, and fully functional. However if you are a corporate intending to make use of WTF(ix)\ncommercially, then you may need more support and assurance than can be offered by the community here.\n\nCommercial support is available - get in touch if you need help with:\n\n- Getting started and development best practices\n- Integrating WTF(ix) with the exchange of your choice (FIX, websocket stream, or REST API)\n- Sample code (including persisting messages and off-loading tasks to background processing libraries)\n- Code reviews\n- Running unmonitored instances in production\n- Problem investigation and identification\n\n## Inspired By\n\n- [slowbreak](https://pypi.org/project/slowbreak/)'s message processing pipeline and ``@on`` decorator\n- [simplefix](https://github.com/da4089/simplefix)'s approach to raw message parsing\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcass77%2Fwtfix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjcass77%2Fwtfix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcass77%2Fwtfix/lists"}