{"id":27287744,"url":"https://github.com/bbougon/trade-engine","last_synced_at":"2025-06-11T16:06:48.047Z","repository":{"id":60302518,"uuid":"542224662","full_name":"bbougon/trade-engine","owner":"bbougon","description":null,"archived":false,"fork":false,"pushed_at":"2022-10-04T07:07:50.000Z","size":44,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-11T20:32:42.233Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","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/bbougon.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}},"created_at":"2022-09-27T18:07:59.000Z","updated_at":"2022-09-27T18:08:29.000Z","dependencies_parsed_at":"2023-01-19T02:10:19.241Z","dependency_job_id":null,"html_url":"https://github.com/bbougon/trade-engine","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bbougon/trade-engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbougon%2Ftrade-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbougon%2Ftrade-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbougon%2Ftrade-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbougon%2Ftrade-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bbougon","download_url":"https://codeload.github.com/bbougon/trade-engine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbougon%2Ftrade-engine/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259293163,"owners_count":22835549,"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":"2025-04-11T20:27:19.547Z","updated_at":"2025-06-11T16:06:48.029Z","avatar_url":"https://github.com/bbougon.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Backend challenge\n\n\n## Project\n\n### Onboarding\n[Miro Board](https://miro.com/app/board/uXjVPSu3um4=/)\n\n[Logbook](https://github.com/bbougon/trade-engine/wiki/logkbook)\n\n**Install :**\n- Have a `3.1.2` ruby version (through [rbenv](https://github.com/rbenv/rbenv) for example)\n- Run `bundle install`\n- Run `rake` and check everything is running fine:\n  ```shell\n  Run options: --seed 16219\n  \n  # Running:\n  \n  ......\n  \n  Finished in 0.001239s, 4842.6150 runs/s, 7263.9225 assertions/s.\n  \n  6 runs, 9 assertions, 0 failures, 0 errors, 0 skips\n  Running RuboCop...\n  Inspecting 13 files\n  .............\n  \n  13 files inspected, no offenses detected\n  ```\n\n### Guidelines\n\n- Solve the levels in ascending order\n- Only do one commit per level and include the `.git` when submiting your test\n\n### Pointers\n\nYou can have a look at the higher levels, but please do the simplest thing that could work for the level you're currently solving.\n\nThe levels become more complex over time, so you will probably have to re-use some code and adapt it to the new requirements.\nA good way to solve this is by using OOP, adding new layers of abstraction when they become necessary and possibly write tests so you don't break what you have already done.\n\nDon't hesitate to write [shameless code](http://red-badger.com/blog/2014/08/20/i-spent-3-days-with-sandi-metz-heres-what-i-learned/) at first, and then refactor it in the next levels.\n\nFor higher levels we are interested in seeing code that is clean, extensible and robust, so don't overlook edge cases, use exceptions where needed, ...\n\nPlease also note that:\n\n- All prices and amounts are stored as BigDecimal\n- Running `irb` and `require_relative 'main' ` from the level folder must be able to interact with your program but of course feel free to add more files if needed.\n\n### Intro\n\nWe want to build a simplified trade engine for exchanging currencies\n\nWe have currencies Bitcoin (BTC) and Euro (EUR)\n\nWe have one market BTC/EUR (BTC called base currency and EUR called quote currency)\nIt's possible to add an order to a market to buy or sell BTC. A market maintains 2 dedicated sets to store buy orders (bids) and sell orders (asks).\n\nAn order is composed by :\n\n* a btc amount (amount to buy or sell BTC)\n* a price (in EUR per BTC)\n* a side (buy or sell)\n\n### Level 1\n\n* We can submit multiple 'buy' orders and 'sell' orders to a market.\n* We can cancel an order with his id.\n* Market depth can be displayed (containing all orders submitted to the market (bids and asks))\n* A market provides the market price (it is the average between the first buy order with maximum price and the first sell order with minimum price).\n\n```ruby\n  \u003e market.submit(order_a) # buy order with price 1.40 € and btc_amount 3.375\n=\u003e 1 # returns order id\n\n  \u003e market.submit(order_b)\n=\u003e 2\n\n  \u003e market.market_price\n=\u003e 2.5\n\n  \u003e pp market.market_depth\n{\"bids\"=\u003e\n  [[\"2.00\", \"1.00000000\"], # price €, BTC amount\n   [\"1.80\", \"1.50000000\"],\n   [\"1.60\", \"2.25000000\"],\n   [\"1.40\", \"3.37500000\"], # \u003c- here is order_a\n   [\"1.20\", \"5.06250000\"],\n   [\"1.00\", \"7.59375000\"],\n   [\"0.80\", \"11.39062500\"],\n   [\"0.60\", \"17.08593750\"]],\n \"base\"=\u003e\"BTC\",\n \"quote\"=\u003e\"EUR\",\n \"asks\"=\u003e\n  [[\"3.00\", \"1.00000000\"],\n   [\"3.20\", \"1.50000000\"],\n   [\"3.40\", \"2.25000000\"],\n   [\"3.60\", \"3.37500000\"],\n   [\"3.80\", \"5.06250000\"],\n   [\"4.00\", \"7.59375000\"],\n   [\"4.20\", \"11.39062500\"],\n   [\"4.40\", \"17.08593750\"]]}\n\n  \u003e market.cancel_order(1)\n=\u003e true\n\n  \u003e pp market.market_depth\n{\"bids\"=\u003e\n  [[\"2.00\", \"1.00000000\"], \n   [\"1.80\", \"1.50000000\"],\n   [\"1.60\", \"2.25000000\"], \n   [\"1.20\", \"5.06250000\"],  # \u003c- order_a is no longer present.\n   [\"1.00\", \"7.59375000\"],\n   [\"0.80\", \"11.39062500\"],\n   [\"0.60\", \"17.08593750\"]],\n \"base\"=\u003e\"BTC\",\n \"quote\"=\u003e\"EUR\",\n \"asks\"=\u003e\n  [[\"3.00\", \"1.00000000\"],\n   [\"3.20\", \"1.50000000\"],\n   [\"3.40\", \"2.25000000\"],\n   [\"3.60\", \"3.37500000\"],\n   [\"3.80\", \"5.06250000\"],\n   [\"4.00\", \"7.59375000\"],\n   [\"4.20\", \"11.39062500\"],\n   [\"4.40\", \"17.08593750\"]]}\n\n```\n\n### Level 2\n\nTo use this market a user has a BTC balance and a EUR balance.\nAn order has an initial state set to 'created'.\n\nThe aim of the matching engine is to process orders and to match them. To perform a match we look up the first buy order with maximum price (bids) and the first sell order with minimum price (asks). Two orders match when:\n\n* order sides are different\n* if the price of both exactly match\n* if the amount of both exactly match (*which will always be 1 to simplify the algorithm*)\n\nFor example with BTC/EUR market, when two orders matches, the engine should fill both orders and update user balances:\n\n* both orders state change to 'filled'\n* the seller BTC balance is decreased by his sell order BTC amount\n* the seller EUR balance is increased by his sell order BTC amount * price\n* the buyer BTC balance is increased by his buy order BTC amount\n* the buyer EUR balance is decreased by his buy order BTC amount * price\n* both orders are removed from the market (asks and bids)\n\nAdapt code to perform matching order.\n\n```ruby\n # buyer EUR balance is 45000 and BTC balance is 0\n # seller BTC balance is 3 and EUR balance is 0\n \n  \u003e market.submit(order_a) # order_a buys 1 BTC at 27000€ per BTC\n=\u003e 1 # returns order id\n\n  \u003e market.submit(order_b) # order_b sells 1 BTC at 27000€ per BTC\n=\u003e 2\n\n  \u003e pp market.market_depth\n{\"bids\"=\u003e [[\"27000.00\", \"1.00000000\"]],\n \"base\"=\u003e\"BTC\",\n \"quote\"=\u003e\"EUR\",\n \"asks\"=\u003e [[\"27000.00\", \"1.00000000\"]]\n}\n\n  \u003e market.match\n=\u003e 1 # return number of matches\n\n  \u003e pp market.market_depth\n{\"bids\"=\u003e [],\n \"base\"=\u003e\"BTC\",\n \"quote\"=\u003e\"EUR\",\n \"asks\"=\u003e []\n}\n\n # buyer EUR balance is 18000 and BTC balance is 1\n # seller BTC balance is 2 and EUR balance is 27000\n\n```\n\n### Level 3\n\nFor each match we want to take a fee. An additional 0.25% from (price * amount) will be debited from seller EUR balance and buyer EUR balance and will be credited on eur balance to a 'fee' user.\n\nExample : \nIf there is a match at 1 BTC with 27000€ per BTC then fees are (1 * 27000) * 0.25% = 67.5 euros. Each user contributes (33.75€ per user) to pays this fee to a fee user.\n\n```ruby\n# buyer EUR balance is 45000 and BTC balance is 0\n# seller BTC balance is 3 and EUR balance is 0\n \n  \u003e market.submit(order_a) # order_a buys 1 BTC at 27000€ per BTC\n=\u003e 1 # returns order id\n\n  \u003e market.submit(order_b) # order_b sells 1 BTC at 27000€ per BTC\n=\u003e 2\n\n \u003e market.match\n=\u003e 1 # return number of matches\n\n\n # buyer EUR balance is 17966.25 and BTC balance is 1\n # seller BTC balance is 2 and EUR balance is 26966,25\n # fee user EUR balance is 67.5 €\n\n```\n\n### Level 4\n\nWe have a BTC/EUR market with matching engine and we want to have a new market ETH/EUR using the same matching engine.\n\nAdapt your code to reflect this change.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbbougon%2Ftrade-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbbougon%2Ftrade-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbbougon%2Ftrade-engine/lists"}