{"id":16559387,"url":"https://github.com/anders94/order-matching-engine","last_synced_at":"2025-03-21T11:31:32.448Z","repository":{"id":37533952,"uuid":"183641395","full_name":"anders94/order-matching-engine","owner":"anders94","description":"An exchange order matching engine implemented within PostgreSQL stored procedures.","archived":false,"fork":false,"pushed_at":"2023-07-28T13:43:57.000Z","size":114,"stargazers_count":28,"open_issues_count":0,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-18T00:46:15.829Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anders94.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2019-04-26T14:18:26.000Z","updated_at":"2024-11-13T18:34:10.000Z","dependencies_parsed_at":"2024-10-28T10:21:13.349Z","dependency_job_id":null,"html_url":"https://github.com/anders94/order-matching-engine","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anders94%2Forder-matching-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anders94%2Forder-matching-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anders94%2Forder-matching-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anders94%2Forder-matching-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anders94","download_url":"https://codeload.github.com/anders94/order-matching-engine/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244790781,"owners_count":20510808,"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":[],"created_at":"2024-10-11T20:25:50.253Z","updated_at":"2025-03-21T11:31:32.146Z","avatar_url":"https://github.com/anders94.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# order-matching-engine\n\nThis order matching engine is an exchange implemented within PostgreSQL as functions. Database migrations are handled by `db-migrate` and `tape` is used for tests.\n\nThis project is in its *early stages*.\n\n## Why?\n\nIsn't an in-memory matching engine going to be faster? Absolutely, but there are other tradeoffs. Initially I simply wanted to see if this was possible. It turns out it is not only possible but fairly robust and, critically, a much simpler architecture. On failure, you can get back to the latest state simply by replaying the transaction log. There is no ambiguity about differing flavors of matching application logic on multiple servers because the database has only one. This design guarentees transactional integrity while keeping critical infrastructure simple and easily recoverable.\n\n## Performance\n\nCommodity hardware (2.9 GHz Core i5) with over 25k offers supports roughly 200 transactions per second.\n\n## Prerequisites\n\n* PostgreSQL: version 11 or greater (with stored procedures support)\n* Node.js (with `npm`)\n\n## Setup\n\nInstall node packages:\n```\nnpm install\n```\n\nInstall `db-migrate` globally so migrations can be run from the command line:\n```\nnpm install -g db-migrate\n```\n\nCreate the database user: (superuser permissions are necessary to add the pgcrypto extension in the first migration but may be revoked later with `ALTER ROLE ome nosuperuser;`)\n```\ncreate user ome with password 'super-secret-password' superuser;\n```\n\nEdit `database.json` to taste.\n\nSet environment variables for passwords you intend to use: (it might be handy to keep this in a file you source - git will ignore a file called `environment`)\n```\nexport ADMIN_PASSWORD=\"super-secret-password\" # this password is only used in the next step to create the databases\nexport DEV_PASSWORD=\"super-secret-password\"\nexport TEST_PASSWORD=\"super-secret-password\"\nexport PROD_PASSWORD=\"super-secret-password\"\n```\n\nCreate databases:\n```\ndb-migrate db:create ome_dev -e bootstrap\ndb-migrate db:create ome_test -e bootstrap\ndb-migrate db:create ome_prod -e bootstrap\n```\n\nBring database schema to the current version: (run all migrations that haven't yet been run) This defaults to the dev environment.\n```\ndb-migrate up\n```\n\n## Test\n\nCreate the test schema:\n```\ndb-migrate up -e test\n```\n\nRun the tests:\n```\nnpm test\n```\n\n## Test Data\n\nYou can add a set of testing data with:\n```\ndb-migrate up:test-data\n```\n\nand remove it with:\n```\ndb-migrate down:test-data\n```\n\n## Try it\n\nReset the dev database with a bunch of test orders:\n```\ndb-migrate down:test-data  # in case there is old data in there\ndb-migrate up:test-data\n```\n\nFor the rest of this, you'll need to execute SQL directly:\n```\npsql ome_dev -U ome\n```\n\nSee the current state of the book:\n```\nome_dev=# SELECT side, price, amount, unfilled, amount - unfilled AS filled FROM offers WHERE market_id = (SELECT id FROM markets WHERE base_symbol = 'BTC' AND quote_symbol = 'USD') AND active = TRUE ORDER BY price;\n side |         price         |       amount       |      unfilled      |       filled       \n------+-----------------------+--------------------+--------------------+--------------------\n buy  | 4990.0000000000000000 | 1.2130000000000000 | 1.2130000000000000 | 0.0000000000000000\n buy  | 4995.0000000000000000 | 0.9020000000000000 | 0.9020000000000000 | 0.0000000000000000\n buy  | 4995.0000000000000000 | 0.2830000000000000 | 0.2830000000000000 | 0.0000000000000000\n buy  | 4999.5000000000000000 | 1.1210000000000000 | 1.1210000000000000 | 0.0000000000000000\n sell | 5001.0000000000000000 | 0.8160000000000000 | 0.8160000000000000 | 0.0000000000000000\n sell | 5005.0000000000000000 | 1.3750000000000000 | 1.3750000000000000 | 0.0000000000000000\n sell | 5010.0000000000000000 | 0.9230000000000000 | 0.9230000000000000 | 0.0000000000000000\n(7 rows)\n```\n\nSubmit a limit order to buy 0.5 BTC at $5010.00:\n```\nome_dev=# SELECT match_limit_order((SELECT id FROM users WHERE email = 'user-a@example.com' AND obsolete = FALSE), (SELECT id FROM markets WHERE base_symbol = 'BTC' AND quote_symbol = 'USD' AND obsolete = FALSE), 'buy', 5010.0, 0.5, 'fills', 'offer');\nNOTICE:  starting limit order\nNOTICE:  Found sell match (be53e94f-3aad-4955-8c4f-a0e21e5cc7f6,\"2019-05-03 19:11:42.53733\",e3fd6060-1de2-4ada-81e1-3ac538bb6a65,9b4719da-1bf3-4540-803d-e3d771793a3e,sell,5001.0000000000000000,0.8160000000000000,0.8160000000000000,t)\nNOTICE:    amount_remaining 0.5000000000000000 \u003c match.unfilled 0.8160000000000000 = this offer is NOT completely filled by this order\nNOTICE:    order complete\nNOTICE:  Found sell match (944fc03d-3dd7-49f6-9fca-585892c39b67,\"2019-05-03 19:11:42.53733\",f93b0c98-0d7b-4606-837f-9fb0a6503674,9b4719da-1bf3-4540-803d-e3d771793a3e,sell,5005.0000000000000000,1.3750000000000000,1.3750000000000000,t)\nNOTICE:  Found sell match (08bcc46f-fb53-4c3d-8590-bff854fd37cc,\"2019-05-03 19:11:42.53733\",f93b0c98-0d7b-4606-837f-9fb0a6503674,9b4719da-1bf3-4540-803d-e3d771793a3e,sell,5010.0000000000000000,0.9230000000000000,0.9230000000000000,t)\n match_limit_order \n-------------------\n(0 rows)\n```\n\nSee the updated order book:\n```\nome_dev=# SELECT side, price, amount, unfilled, amount - unfilled AS filled FROM offers WHERE market_id = (SELECT id FROM markets WHERE base_symbol = 'BTC' AND quote_symbol = 'USD') AND active = TRUE ORDER BY price;\n side |         price         |       amount       |      unfilled      |       filled       \n------+-----------------------+--------------------+--------------------+--------------------\n buy  | 4990.0000000000000000 | 1.2130000000000000 | 1.2130000000000000 | 0.0000000000000000\n buy  | 4995.0000000000000000 | 0.9020000000000000 | 0.9020000000000000 | 0.0000000000000000\n buy  | 4995.0000000000000000 | 0.2830000000000000 | 0.2830000000000000 | 0.0000000000000000\n buy  | 4999.5000000000000000 | 1.1210000000000000 | 1.1210000000000000 | 0.0000000000000000\n sell | 5001.0000000000000000 | 0.8160000000000000 | 0.3160000000000000 | 0.5000000000000000\n sell | 5005.0000000000000000 | 1.3750000000000000 | 1.3750000000000000 | 0.0000000000000000\n sell | 5010.0000000000000000 | 0.9230000000000000 | 0.9230000000000000 | 0.0000000000000000\n(7 rows)\n```\n\nSee the fill:\n```\nome_dev=# select created, market_id, offer_id, maker_user_id, taker_user_id, price, amount from fills;\n          created           |              market_id               |               offer_id               |            maker_user_id             |            taker_user_id             |         price         |       amount       \n----------------------------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------+-----------------------+--------------------\n 2019-05-03 19:12:22.096189 | 9b4719da-1bf3-4540-803d-e3d771793a3e | be53e94f-3aad-4955-8c4f-a0e21e5cc7f6 | e3fd6060-1de2-4ada-81e1-3ac538bb6a65 | 047747c9-307d-47c6-9f99-07a3598e238b | 5001.0000000000000000 | 0.5000000000000000\n(1 row)\n```\n\nSubmit a limit order to sell 2.5 BTC at $4993.00:\n```\nome_dev=# SELECT match_limit_order((SELECT id FROM users WHERE email = 'user-a@example.com' AND obsolete = FALSE), (SELECT id FROM markets WHERE base_symbol = 'BTC' AND quote_symbol = 'USD' AND obsolete = FALSE), 'sell', 4993.0, 2.5, 'fills', 'offer');\nNOTICE:  starting limit order\nNOTICE:  Found buy match (9aa784a7-9c2a-4e47-915c-414dc5ef94ba,\"2019-05-03 19:11:42.53733\",25c8a195-7936-4ac2-9d17-39348210dc87,9b4719da-1bf3-4540-803d-e3d771793a3e,buy,4999.5000000000000000,1.1210000000000000,1.1210000000000000,t)\nNOTICE:    amount_remaining 2.5000000000000000 \u003e= match.filled 1.1210000000000000 = this offer is NOT completely filled by this order\nNOTICE:  Found buy match (f846ef7a-4cda-4395-bd50-52382269591d,\"2019-05-03 19:11:42.53733\",047747c9-307d-47c6-9f99-07a3598e238b,9b4719da-1bf3-4540-803d-e3d771793a3e,buy,4995.0000000000000000,0.9020000000000000,0.9020000000000000,t)\nNOTICE:    amount_remaining 1.3790000000000000 \u003e= match.filled 0.9020000000000000 = this offer is NOT completely filled by this order\nNOTICE:  Found buy match (2be31a29-5f47-48b1-bd0e-e152c72db6de,\"2019-05-03 19:11:42.53733\",25c8a195-7936-4ac2-9d17-39348210dc87,9b4719da-1bf3-4540-803d-e3d771793a3e,buy,4995.0000000000000000,0.2830000000000000,0.2830000000000000,t)\nNOTICE:    amount_remaining 0.4770000000000000 \u003e= match.filled 0.2830000000000000 = this offer is NOT completely filled by this order\nNOTICE:  INSERT INTO offers (user_id, market_id, side, price, amount) VALUES (047747c9-307d-47c6-9f99-07a3598e238b, 9b4719da-1bf3-4540-803d-e3d771793a3e, sell, 4993.0, 0.1940000000000000);\n match_limit_order \n-------------------\n(0 rows)\n```\n\nSee the resulting order book: (notice the new sell offer for 0.194 which is the unfilled remainder)\n```\nome_dev=# SELECT side, price, amount, unfilled, amount - unfilled AS filled FROM offers WHERE market_id = (SELECT id FROM markets WHERE base_symbol = 'BTC' AND quote_symbol = 'USD') AND active = TRUE ORDER BY price;\n side |         price         |       amount       |      unfilled      |       filled       \n------+-----------------------+--------------------+--------------------+--------------------\n buy  | 4990.0000000000000000 | 1.2130000000000000 | 1.2130000000000000 | 0.0000000000000000\n sell | 4993.0000000000000000 | 0.1940000000000000 | 0.1940000000000000 | 0.0000000000000000\n sell | 5001.0000000000000000 | 0.8160000000000000 | 0.3160000000000000 | 0.5000000000000000\n sell | 5005.0000000000000000 | 1.3750000000000000 | 1.3750000000000000 | 0.0000000000000000\n sell | 5010.0000000000000000 | 0.9230000000000000 | 0.9230000000000000 | 0.0000000000000000\n(5 rows)\n```\n\nSee all the fills: (we got more than one fill for this larger order)\n```\nome_dev=# select created, market_id, offer_id, maker_user_id, taker_user_id, price, amount from fills;\n          created           |              market_id               |               offer_id               |            maker_user_id             |            taker_user_id             |         price         |       amount       \n----------------------------+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------+-----------------------+--------------------\n 2019-05-03 19:12:22.096189 | 9b4719da-1bf3-4540-803d-e3d771793a3e | be53e94f-3aad-4955-8c4f-a0e21e5cc7f6 | e3fd6060-1de2-4ada-81e1-3ac538bb6a65 | 047747c9-307d-47c6-9f99-07a3598e238b | 5001.0000000000000000 | 0.5000000000000000\n 2019-05-03 19:13:15.470796 | 9b4719da-1bf3-4540-803d-e3d771793a3e | 9aa784a7-9c2a-4e47-915c-414dc5ef94ba | 25c8a195-7936-4ac2-9d17-39348210dc87 | 047747c9-307d-47c6-9f99-07a3598e238b | 4999.5000000000000000 | 1.1210000000000000\n 2019-05-03 19:13:15.470796 | 9b4719da-1bf3-4540-803d-e3d771793a3e | f846ef7a-4cda-4395-bd50-52382269591d | 047747c9-307d-47c6-9f99-07a3598e238b | 047747c9-307d-47c6-9f99-07a3598e238b | 4995.0000000000000000 | 0.9020000000000000\n 2019-05-03 19:13:15.470796 | 9b4719da-1bf3-4540-803d-e3d771793a3e | 2be31a29-5f47-48b1-bd0e-e152c72db6de | 25c8a195-7936-4ac2-9d17-39348210dc87 | 047747c9-307d-47c6-9f99-07a3598e238b | 4995.0000000000000000 | 0.2830000000000000\n(4 rows)\n```\n\nThe stored procedure returns 2 cursors which contain any fills the order had as well as the offer if one was created:\n```\nBEGIN;\n  SELECT match_limit_order((SELECT id FROM users WHERE email = 'user-a@example.com' AND obsolete = FALSE), (SELECT id FROM markets WHERE base_symbol = 'BTC' AND quote_symbol = 'USD' AND obsolete = FALSE), 'buy', 5010.0, 5.0, 'fills', 'offer');\n  FETCH ALL IN \"fills\";\n  FETCH ALL IN \"offer\";\nCOMMIT;\n```\n\nHere's an example:\n```\nome_dev=# BEGIN;\nBEGIN\nome_dev=# SELECT match_limit_order((SELECT id FROM users WHERE email = 'user-a@example.com' AND obsolete = FALSE), (SELECT id FROM markets WHERE base_symbol = 'BTC' AND quote_symbol = 'USD' AND obsolete = FALSE), 'buy', 5010.0, 5.0, 'fills', 'offer');\nNOTICE:  starting limit order\nNOTICE:  Found sell match (3cf62411-8440-4591-aafa-b0ea9231f972,\"2019-05-05 07:16:49.145293\",bd8e7731-3b11-4a84-a92f-19a47bdf251d,b14ee127-161c-4e92-8942-ba73394f05ef,sell,5001.0000000000000000,0.8160000000000000,0.8160000000000000,t)\nNOTICE:    amount_remaining 5.0000000000000000 \u003e= match.filled 0.8160000000000000 = this offer is completely filled by this order\nNOTICE:  Found sell match (ffde512f-b3d1-4953-aa7c-9be10866c17a,\"2019-05-05 07:16:49.145293\",e7c2f9bb-fd0c-440d-a237-15c502177add,b14ee127-161c-4e92-8942-ba73394f05ef,sell,5005.0000000000000000,1.3750000000000000,1.3750000000000000,t)\nNOTICE:    amount_remaining 4.1840000000000000 \u003e= match.filled 1.3750000000000000 = this offer is completely filled by this order\nNOTICE:  Found sell match (0027f128-73e0-4d3a-81f4-091d8b6b06f9,\"2019-05-05 07:16:49.145293\",e7c2f9bb-fd0c-440d-a237-15c502177add,b14ee127-161c-4e92-8942-ba73394f05ef,sell,5010.0000000000000000,0.9230000000000000,0.9230000000000000,t)\nNOTICE:    amount_remaining 2.8090000000000000 \u003e= match.filled 0.9230000000000000 = this offer is completely filled by this order\nNOTICE:  INSERT INTO offers (user_id, market_id, side, price, amount) VALUES (394d8efa-10da-45cf-ae76-e7bc75bcd772, b14ee127-161c-4e92-8942-ba73394f05ef, buy, 5010.0, 1.8860000000000000);\n match_limit_order \n-------------------\n offer\n fills\n(2 rows)\n\nome_dev=# FETCH ALL IN \"fills\";\n               fill_id                |         price         |       amount       \n--------------------------------------+-----------------------+--------------------\n 1f72a005-8056-414a-8809-746bcb8c0524 | 5001.0000000000000000 | 0.8160000000000000\n 978f32b1-3853-4db7-befa-bc22f1b7c5f9 | 5005.0000000000000000 | 1.3750000000000000\n fff962e8-44a3-4191-b107-e9e14cbbca0e | 5010.0000000000000000 | 0.9230000000000000\n(3 rows)\n\nome_dev=# FETCH ALL IN \"offer\";\n                  id                  | side |         price         |       amount       \n--------------------------------------+------+-----------------------+--------------------\n 3d54ef06-5dcf-4d22-834b-3bf4b1b5628e | buy  | 5010.0000000000000000 | 1.8860000000000000\n(1 row)\n\nome_dev=# COMMIT;\nCOMMIT\nome_dev=#\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanders94%2Forder-matching-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanders94%2Forder-matching-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanders94%2Forder-matching-engine/lists"}