{"id":26940523,"url":"https://github.com/joanroig/candlesticks-api","last_synced_at":"2026-05-05T21:35:38.402Z","repository":{"id":38424179,"uuid":"489628806","full_name":"joanroig/candlesticks-api","owner":"joanroig","description":"Example implementation of a Node.js REST API that consumes live data via WebSockets and enables users to view price histories.","archived":false,"fork":false,"pushed_at":"2024-09-12T19:46:45.000Z","size":11222,"stargazers_count":2,"open_issues_count":11,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-09-13T09:32:26.552Z","etag":null,"topics":["candlesticks","node","nodejs","openapi","swagger","template-project","typescript","websockets"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joanroig.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2022-05-07T09:27:21.000Z","updated_at":"2023-06-11T11:48:49.000Z","dependencies_parsed_at":"2023-10-14T19:48:14.925Z","dependency_job_id":"f333db1b-2167-43db-84d6-cae83e577117","html_url":"https://github.com/joanroig/candlesticks-api","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/joanroig%2Fcandlesticks-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joanroig%2Fcandlesticks-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joanroig%2Fcandlesticks-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joanroig%2Fcandlesticks-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joanroig","download_url":"https://codeload.github.com/joanroig/candlesticks-api/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246837628,"owners_count":20841906,"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":["candlesticks","node","nodejs","openapi","swagger","template-project","typescript","websockets"],"created_at":"2025-04-02T15:18:53.587Z","updated_at":"2026-05-05T21:35:38.370Z","avatar_url":"https://github.com/joanroig.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Candlesticks API\n\nExample system that enables users to view price histories.\nIt receives updates from a partner service, transform these updates and provide the aggregated data through REST endpoints.\nPerformance tests and configurations for JMeter and Artillery are provided.\n\n\u003e The implementation is based on [assumptions](ASSUMPTIONS.md) to develop an MVP within 5 days.\n\n| CI                                                                                                                                                                          | Statements                                                                                 | Branches                                                                       | Functions                                                                                | Lines                                                                            |\n| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |\n| [![Tests CI](https://github.com/joanroig/candlesticks-api/actions/workflows/tests.yml/badge.svg)](https://github.com/joanroig/candlesticks-api/actions/workflows/tests.yml) | ![Statements](https://img.shields.io/badge/statements-96.12%25-brightgreen.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-78.82%25-red.svg?style=flat) | ![Functions](https://img.shields.io/badge/functions-91.91%25-brightgreen.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-95.91%25-brightgreen.svg?style=flat) |\n\n---\n\n## Content\n\n- Intro and terminology\n- Setup\n- Future Development Discussion\n\n## Intro and terminology\n\n#### Instruments and Quotes\n\nEvery asset that can be traded is represented by an “instrument”, which has a unique identifier (ISIN).\nEach time the instrument price changes, an update message called “quote” is broadcasted for this instrument to inform about the change.\n\n#### What is a candlestick?\n\nA [candlestick](https://en.wikipedia.org/wiki/Candlestick_chart) is a representation that describes the price movement for a given instrument in a fixed amount of time, usually one minute.\nWe will be using a simplified version of candlesticks for this system.\n\nThe basic idea is that we don't need to know about _all_ prices changes within a given timeframe.\nUsually we want them grouped in 1 minute chunks, because we are more interested in some key data points within any given minute.\nIn theory, a candlestick “contains” all quotes, where the timestamp of the quote is higher than the openTimestamp and lower than the closeTimestamp (`openTimestamp \u003c= quoteTimestamp \u003c closeTimestamp`).\nHowever, for each candle for each given minute, we only present the following data points to the user:\n\n- the first quotes price, that was received (openPrice)\n- the last quotes, that was received (closePrice)\n- the highest quote price that was observed (highPrice)\n- the lowest quote price that was observed (lowPrice)\n- the timestamp when the candlestick was opened (openTimestamp)\n- the timestamp when the candlestick was closed (closeTimestamp)\n\n##### Example\n\nAssume the following (simplified) data was received for an instrument:\n\n```\n@2019-03-05 13:00:05 price: 10\n@2019-03-05 13:00:06 price: 11\n@2019-03-05 13:00:13 price: 15\n@2019-03-05 13:00:19 price: 11\n@2019-03-05 13:00:32 price: 13\n@2019-03-05 13:00:49 price: 12\n@2019-03-05 13:00:57 price: 12\n@2019-03-05 13:01:00 price: 9\n```\n\nThe resulting minute candle would have these attributes:\n\n```\nopenTimestamp: 2019-03-05 13:00:00\nopenPrice: 10\nhighPrice: 15\nlowPrice: 10\nclosePrice: 12\ncloseTimestamp: 13:01:00\n```\n\nNote that the last received quote with a price of 9 is not part of this candlestick anymore, but belongs to the new candlestick.\n\n### Input data\n\nThe input data is received through a websocket stream from a partner service.\nThere are two types of input messages:\n\n- Instrument additions/deletions, which adds or removes an instrument from our catalog\n- Instrument price updates, giving the most recent price for a specific instrument\n\n### Output (Aggregated-Price History)\n\nThe output is the aggregated price history endpoint.\nIt should provide a 30 minutes quotes history in the form of minute candlesticks (check information below) for any requested instrument.\n\nEnd-users of the service are interested in a specific instrument's price history, and they want it in a format that is easy to read.\nHence, we should provide candlesticks.\nTo get these candlesticks, the user needs to provide the instrument id (ISIN) as a query parameter (e.g. `http://localhost:9000/candlesticks?isin={ISIN}`).\n\nThe system only needs to return the candlesticks for the last 30 minutes, including the most recent prices.\nIf there weren't any quotes received for more than a minute, instead of missing candlesticks in the 30-minute window, values from the previous candle are reused.\n\n# Setup\n\nWe will use Node.js with Typescript and Express to build the API.\n\n## Framework Requirements\n\n- Node 16 environment\n- NPM and Yarn\n- An IDE\n\n### Running the Partner Service\n\nTo run a partner service, you can either use docker-compose. Docker v3 or above will require slight changes to the docker-compose files.\n\n```\ndocker-compose up -d\n```\n\nor Java\n\n```\njava -jar partner-service-1.0.1-all.jar --port=8032\n```\n\nor Yarn\n\n```\nyarn start:partner-service\n```\n\n### Running the app\n\nInstall the dependencies first:\n\n```\nyarn\n```\n\nTo run the app you can use the following yarn commands\n\n```\nyarn build \u0026\u0026 yarn start\n```\n\nTo run the app for development purposes, you can use the following yarn command\n\n```\nyarn start:dev\n```\n\nOnce the server is running, you can check the results at\n\n```\nhttp://localhost:9000/candlesticks?isin={ISIN}\n```\n\n### Running the tests\n\nRun the tests with:\n\n```\nyarn test\n```\n\nTo get the coverage and open the lcov-report:\n\n```\nyarn test:coverage\n```\n\nThe coverage report will be available in the root folder.\n\nYou can also check the coverage from the continuous integration workflow:\nhttps://github.com/joanroig/candlesticks-api/actions/workflows/tests.yml\n\n### Environment configuration\n\nUse the config folder to specify the configuration of the app depending on the environment. The configuration file name should match the node environment value.\n\nExample to configure a production environment in a Dockerfile step, which will use production.json:\n\n```\n# Set node environment to production\nENV NODE_ENV production\n```\n\n### PartnerService\n\nThis repository includes a runnable JAR (the partner service) that provides the websockets mentioned below.\nRunning the jar in a terminal with `-h` or `--help` will print how to use it.\nThe default port is `8080`, but we use `8032` for the following examples.\n\nOnce started, it provides two websocket streams (`ws://localhost:8032`), plus a website preview of how the stream look (http://localhost:8032).\n\n- `/instruments` provides the currently available instruments with their ISIN and a Description\n\n  - when connecting to the stream, it gives all currently available Instruments\n  - once connected, it streams the addition/removal of instruments\n  - Our partners assured us, that ISINS are unique, but can in rare cases be reused once no Instrument with that ISIN is available anymore (has been deleted, etc.)\n\n- `/quotes` provides the most current price for an instrument every few seconds per available instrument\n\nIf you restart the PartnerService, you will have to clean up any data you might have persisted, since it will generate new ISINs and does not retain state from any previous runs.\n\n#### /instrument Specification\n\nThe `/instruments` websocket provides all currently active instruments `onConnect`, as well as a stream of add/delete events of instruments.\nWhen you receive a `DELETE` event, the instrument is no longer available and will not receive any more quotes (beware of out of order messages on the /quotes stream)\nThe instruments are uniquely identified by their isin. Beware, ISINs can be reused _after_ an instrument has been deleted.\nIn any case, you would see a regular ADD event for this new instrument, even when it reuses an ISIN.\n\n```\n{\n    // The type of the event. ADD if an instrument is ADDED\n    // DELETE if an instrument is deleted\n    \"type\": \"DELETE\"\n    {\n        //The Payload\n        \"data\": {\n            //The Description of the instrument\n            \"description\": \"elementum eos accumsan orci constituto antiopam\",\n            //The ISIN of this instrument\n            \"isin\": \"LS242I164451\"\n        }\n    }\n}\n```\n\n#### /quotes Specification\n\nThe `/quotes` Websocket provides prices for available instruments at an arbitrary rate.\nIt only streams prices for available instruments (beware of out of order messages).\n\n```\n{\n    // The type of the event.\n    // QUOTE if an new price is available for an instrument identified by the ISIN\n    \"type\": \"QUOTE\"\n    {\n        //The Payload\n        \"data\": {\n            //The price of the instrument with arbitray precision\n            \"price\": 1365.25,\n            //The ISIN of this instrument\n            \"isin\": \"LS242I164451\"\n        }\n    }\n}\n```\n\n## Future Development\n\nThe following questions will be taken into account when integrating the system in a production environment.\n\n- How could this system be changed to provide scaling capabilities to 50.000 (or more) available instruments, each streaming quotes between once per second and every few seconds?\n- How could this system be built in a way that supports failover capabilities so that multiple instances of the system could run simultaneously?\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoanroig%2Fcandlesticks-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoanroig%2Fcandlesticks-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoanroig%2Fcandlesticks-api/lists"}