capital-gains

capital-gains is a CLI to calculate the tax to be paid on profits or losses from operations in the stock market.

index

Disclaimer

back^

This is a code challenge test that I’ve done for a banking company.

Rationale

back^

I chose Rust to construct this application. The key argument for this decision is that Rust’s CLI ecosystem is one of the best available today. Add to that the fact that Rust is a very efficient, stable, and safe language. All of this has resulted in an increasing number of adopters, more use inside businesses, and even its inclusion in the Linux kernel.

In order to reduce the core application libraries to the minimum, I’ve used only a few dependencies crates for this application, as you can see in the Cargo.toml. Which are:

Building

back^

capital-gains is written in Rust, so you’ll need to grab a Rust installation in order to compile it. To build capital-gains from source, run:

make rs-build

Installation

back^

Archives of precompiled binaries for capital-gains are available for Windows, macOS, and Linux.

To install capital-gains on your local machine, install Rust and run:

make rs-install

If you don’t want to install Rust on your machine, you may build the Docker image with the command:

make docker-build-local

The Docker image is constructed using a distroless image. Therefore is very small and more secure, because contains only the necessary code to run the application:

$ docker images

REPOSITORY      TAG       IMAGE ID       CREATED         SIZE
capital-gains   local     9a7201859b1e   2 minutes ago   24.7MB

Usage

back^

capital-gains follows the Command Line Interface Guidelines. To get help about the CLI usage, run:

capital-gains --help
# or
docker run -i capital-gains:local --help

capital-gains is a CLI to calculate the tax to be paid on profits or losses from
operations in the stock financial market.

Usage: capital-gains < [TRANSACTIONS]

Arguments:
  [TRANSACTIONS]
          A list of financial stock market operations that are JSON formatted
          and separated by lines that need to be inputted using standard input.

          [default: -]

Options:
  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

Examples:
  capital-gains <src/data/input-*.json
  <src/data/input-*.json capital-gains
  cat src/data/input-*.json | capital-gains

  docker run -i capital-gains:local <src/data/input-*.json
  <src/data/input-*.json docker run -i capital-gains:local
  cat src/data/input-*.json | docker run -i capital-gains:local

Testing

back^

To test the application, you have two options:

  1. Install Rust and run make rs-tests;
  2. Run the tests within Docker with make docker-build-test;

Performance

back^

Here are the performance results measured with hyperfine according to input size:

Command Mean [ms] Min [ms] Max [ms] Relative
capital-gains [ input_size=10^0 ] 15.1 ± 2.2 11.5 24.8 1.01 ± 0.18
capital-gains [ input_size=10^1 ] 14.9 ± 1.5 12.1 19.7 1.00
capital-gains [ input_size=10^2 ] 18.9 ± 2.2 15.5 25.5 1.26 ± 0.20
capital-gains [ input_size=10^3 ] 52.6 ± 6.8 40.6 61.7 3.52 ± 0.58
capital-gains [ input_size=10^4 ] 276.5 ± 16.1 243.1 294.9 18.53 ± 2.16
capital-gains [ input_size=10^5 ] 2622.1 ± 193.7 2420.4 3020.5 175.75 ± 22.02
capital-gains [ input_size=10^6 ] 35029.2 ± 8242.0 24562.3 44798.0 2347.88 ± 601.36

The results above were measured on the following machine:

inxi -Cmz

Memory:
  RAM: total: 31.04 GiB used: 13.96 GiB (45.0%)
  RAM Report:
    permissions: Unable to run dmidecode. Root privileges required.
CPU:
  Info: 10-core (2-mt/8-st) model: 13th Gen Intel Core i5-1345U bits: 64
    type: MST AMCP cache: L2: 6.5 MiB
  Speed (MHz): avg: 984 min/max: 400/4700:3500 cores: 1: 1262 2: 1130
    3: 977 4: 400 5: 1400 6: 1063 7: 1389 8: 1034 9: 904 10: 974 11: 886
    12: 400

Memory

back^

No memory leaks were found using Valgrind. We don’t have to worry about the data presented in the still reachable section, here’s why. To execute the memory test, run:

make valgrind-test

==499495== Memcheck, a memory error detector
==499495== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==499495== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==499495== Command: capital-gains
==499495==
==499495==
==499495== HEAP SUMMARY:
==499495==     in use at exit: 8,192 bytes in 1 blocks
==499495==   total heap usage: 47 allocs, 46 frees, 17,539 bytes allocated
==499495==
==499495== LEAK SUMMARY:
==499495==    definitely lost: 0 bytes in 0 blocks
==499495==    indirectly lost: 0 bytes in 0 blocks
==499495==      possibly lost: 0 bytes in 0 blocks
==499495==    still reachable: 8,192 bytes in 1 blocks
==499495==         suppressed: 0 bytes in 0 blocks
==499495== Reachable blocks (those to which a pointer was found) are not shown.
==499495== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==499495==
==499495== For lists of detected and suppressed errors, rerun with: -s
==499495== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Dependencies

back^

Deps: Core

back^

Deps: Dev

back^

Make Recipes

back^

Run make to view all possible recipes to run within the project:

bash-all               Run all bash tests
bash-check             Check format bash code
bash-deps              Install bash dependencies
bash-fmt               Format bash code
bash-lint              Check lint bash code
comments-tidy          Tidy comments within code
doc-changelog          Write CHANGELOG.md
doc-readme             Write README.md
docker-build-local     Docker build local image
docker-build-test      Docker build test image and run tests
dprint-check           Dprint check
dprint-fmt             Dprint format
help                   Display this help screen
makefile-descriptions  Check if all Makefile rules have descriptions
rs-audit               Audit Cargo.lock
rs-audit-fix           Update Cargo.toml to fix vulnerable dependency requirement
rs-bench               Benchmark binary
rs-build               Build binary
rs-cargo-deps          Install cargo dependencies
rs-check               Run check
rs-dev                 Run check in watch mode
rs-doc                 Open app documentation
rs-fix                 Fix rust code
rs-fmt                 Format rust code
rs-fmt-fix             Format fix rust code
rs-install             Install binary
rs-lint                Lint rust code
rs-lint-fix            Fix lint rust code
rs-outdated            Display when dependencies are out of date
rs-tests               Run tests
rs-uninstall           Uninstall binary
rs-update-cargo        Update dependencies
rs-update-rustup       Update rust
typos                  Check typos
typos-fix              Fix typos
valgrind-test          Valgrind test for memory leaks

How to Release

back^

To generate a new version, you need to follow these steps:

  1. In the main branch, you must bump the version inside the Cargo.toml file.
  2. Run make rs-check so that the version is changed in the Cargo.lock file.
  3. Run the command git add -A && git commit -m "release: bump version".
  4. Run the command git tag -a <your.new.version> -m "version <your.new.version>".
  5. Run the command make doc-changelog && make doc-readme.
  6. Run the command git add -A && git commit -m "release: <your.new.version>".
  7. Run git push to main.

Documentation

back^

This documentation is generated by shell scripts. Please check out dev directory for more information.