NodeJS command line app to find portfolio details based on the given parameters. The parameters include token and date.
This app uses :-
- NodeJS 14.17.0.
- Free Crypto API from CryptoCompareAPI to fetch the latest currency value.
- Mocha/Chai/Sinon for unit tests.
-
git clone
[email protected]:murtaza-bagwala/portfolio-calculator.git
-
Download transactions.csv file and put it in root folder.
-
Rename
.env.example
to.env
and replaceCRYPTO_COMPARE_API_KEY
with the generated key. -
npm install
. -
npm install -g .
to install the script globally. -
portfolioCalculatorInCurrency -t "BTC" -d "2022-11-02"
to run the command line script to fetch the portfolios converted in USD. -
npm run test
to run the tests.
I am using yargs
to fetch the command line arguments and then validating the received arguments in .bin/start.js
. for example if date
is not valid then it throws an error and shows you the exact format which is YYYY-MM-DD
. Also, currently as our transactions.csv only contains 3 types of cryptocurrencies BTC
, ETH
and XRP
, thats why I am validating that too but we can remove that validation if we need to support any other cryptocurrencies in future. For now currency is set as USD
by default but in future we can take it as an argument too, rest of the code is generalized with default set as USD
.
I have created plain javascript class Portfolio
to store portfolio details like token
and amount
and some of the methods which operates on the amount
attribute.
I am using currency.js
for the monetary operations as there are floating point issues with the representation of the decimals in binary. Added the precision upto 6 digits after decimal. Having currency.js
making application little bit slower as each time we add or subtract, it creates new currency object in memory but I think for application like this which deals with monetary assets correctness is more important than performance.
Now once the arguments are validated I use them to create strategy as I am using strategy pattern to invoke strategies/algorithm based on the received parameters, for that I have created various strategies in ./strategies
folder. These strategies parses received record from CSV
and performs the buisness logic accordingly.
So we have 4 types of strategies :-
If no option is passed then this strategy will be called to calculate the portfolio values converted to the USD
for all the tokens.
If date option is passed then this strategy will be called to calculate the portfolio values converted to the USD
for the given date.
If token option is passed then this strategy will be called to calculate the portfolio value converted to the latest USD
for the given token.
If token and date both options are passed then this strategy will be called to calculate the portfolio value converted to the USD
for the given date and token.
To parse this kind of big CSV file I am using Reactive approach using NodeJS streams
API. Generally reactive/event-driven approaches work on 3 events data
, error
and close
and I am passing strategy
as a callback function to CSVParser
for data
event. Code can be found in ./parsers/CSVParser.js
.
Once we compute the portfolio values for tokens then we call the external service to fetch the latest conversion rate.
I am using NodeJS https
module to call an external API because, I prefer to use built-in libraries instead of adding external dependencies to keep application light and avoid dependencies hell.
I am using CryptoCompareAPI APIs to fetch the latest currency rate and currency rate based on the timestamp.
Code for this can be found in services/CryptoAPI.js
, I have created a generalized function called as getCryptoToCurrencyConversionRate
which takes options={token: "BTC", timestamp: 153649404}}
and currency=USD
type as arguments and returns currency value. we can pass any currency symbol but currently it defaults to USD
.
There are various pure utility functions I have created like :-
DateValidator
to validate the date and its format.DateToTimestampConvertor
converts date to timestamp in epoch, So basically from commandline we get dateString and then we convert it to timestamp and add86399
seconds to it to get a last timestamp of particluar day for example if dateString is2022-11-03
then in timestamp it will be1667556454
equals to2022-11-03:T23:59:59
. This is done to ensure we get correct conversion rate from external service for that day.PortfolioCalculator
adds or subtracts the amount based on the transaction type.PortfolioCreator
it creates or updates aportfoliosByToken
map based on the received token, so if token is present it updates the value otherwise it creates new token as a key and adds portfolio details as a value.
I have written testcases for all the modules, used expect
for assertions and sinon stub
to stub external API calls.
Tests can be found in ./test
.
Added eslint
and prettier
for formattng and conventions.