This application will periodically fetch stock prices from the Alpha Vantage API and store them in a database. The data can be accessed through a REST API.
This is just a PoC, should not be used in production.
After cloning the repository, you need to install the dependencies using composer (refer to the composer documentation to install the tool):
composer install
Clone the env file and edit it to match your environment:
cp .env.example .env
vim .env
You need to specify credential for the MySQL
connection and for the Alpha Vantage
API.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=stock_market
DB_USERNAME=application
DB_PASSWORD=application
STOCK_MARKET_ALPHA_VANTAGE_API_KEY=demo
STOCK_MARKET_ALPHA_VANTAGE_API_URL=https://www.alphavantage.co
The application leverages the Laravel Cache
module.
The ttl of the cached data can be set-up using the provided environment variable:
STOCK_MARKET_CACHE_TTL=60
By default the cache it's configured to use the file
driver.
If you want to use a different driver, you can change it in the .env
file:
For redis (the application already requires the needed predis/predis):
CACHE_DRIVER=redis
REDIS_HOST=
REDIS_PASSWORD=
REDIS_PORT=6379
Generate the application key:
php artisan key:generate
Run the migrations:
php artisan migrate
Fill in the default Symbols:
php artisan app:fill-symbols
At this time you can run the application using either a local web server (for example Laravel Valet or Laravel Herd) or using the built-in PHP web server:
php artisan serve
The project can be run using Docker
.
In the repository, a simple docker-compose.yml
is provided.
It uses mysql
for the database and a custom php8.2-apache
image for the web server.
Due to time contraints, the docker image is not optimized for production.
A better solution would involve using nginx or caddy as a web server, and a separate container for php-fpm.
If you want to use docker, use the provided .env file:
cp .env.docker .env
Then run:
docker-compose up --build
At this point you need to run all the commands in the previous section, but using docker:
docker-compose exec php composer install
docker-compose exec php php artisan key:generate
docker-compose exec php php artisan migrate
docker-compose exec php php artisan app:fill-symbols
To fetch the pricing, you need to run the scheduler:
docker-compose exec php php artisan schedule:work
or fetch one time
docker-compose exec php php artisan app:fetch-pricing
Remember to adapt the .env
file to match your environment.
By default the api will point to the host machine on port 5000
, on which
the mock server will be running (if started).
The mapped port for the application is 8085
. So, once started, you can access the application
using the browser and navigating to http://localhost:8085
.
Once you have the list of the Symbol defined in the database, you can start fetching the stock pricing using the following command:
php artisan schedule:work
Each minute the pricing for all the symbols will be fetched and stored in the database. The intraDay (with 1min interval) will be used.
If you want to manually fetch the pricing you can run:
php artisan app:fetch-pricing
The project has a very simple web interface, that you can access using the browser and navigating to the the app url of your project.
The web interface has a dashboard that shows the latest pricing for all the symbols in the database, with the price variation from the previous period.
At ths point you can start using the REST API.
For simplicity, in the repository there is a Postman
collection that you can use to test the API.
You can find it in the var
folder.
Edit the variables of the collection to match your environment.
There are three endpoints available:
This endpoint will return the list of Symbols
available in the database.
The response will be a JSON array with shape:
[
{
"symbol": "AAPL",
"name": "Apple Inc."
}
]
This endpoint will return the pricing history for the given Symbol
.
The response will be a JSON array with shape:
{
"data": [
{
"datetime": "2023-12-03 15:01:00",
"open": "0.0000",
"high": "0.0000",
"low": "0.0000",
"close": "0.0000",
"volume": "0"
}
]
}
This endpoint will return the latest pricing for the given Symbol
,
and the price variation from the previous period.
This API endpoints uses the Laravel Cache
to avoid hitting the database every time.
The response will be a JSON array with shape:
{
"current": {
"datetime": "2023-12-03 15:01:00",
"open": "0.0000",
"high": "0.0000",
"low": "0.0000",
"close": "0.0000",
"volume": "0"
},
"previous": {
"datetime": "2023-12-03 15:00:00",
"open": "0.0000",
"high": "0.0000",
"low": "0.0000",
"close": "0.0000",
"volume": "0"
},
"change": "0.0000",
"change_percent": "0.0000"
}
The application is covered by unit and feature tests. The tests have been created using the pest framework, a wrapper around phpunit. To launch tests, just run:
./vendor/bin/pest
or use the composer shortcut:
composer test
The application is covered by static analysis using phpstan. To launch the static analysis, just run:
./vendor/bin/phpstan
or use the composer shortcut:
composer static
You can test the application, also without the real Alpha Vantage API key. Just use the provided mock server.
If you want to test the application, without using the real Alpha Vantage API endpoint, you can use the provided python mock server.
Just enter the mock
folder, activate a python virtual environment, install requirements and
you are ready to start the server:
cd mock
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python server.py
The mock service will listen on the default python flask port 5000
You can specify this mock server in the .env
file:
STOCK_MARKET_ALPHA_VANTAGE_API_KEY=mock
STOCK_MARKET_ALPHA_VANTAGE_API_URL=http://127.0.0.1:5000
The Api Key can be anything, it will not be checked by the mock server.
If you're just interested in testing the application, without fetching any data from the API, you can fill the database with fake pricing using the following command:
php artisan db:seed Database\\Seeders\\PriceSeeder
The previous command will fill the pricing for all the Symbols
in the database.
This works only if you already have the Symbols
data in the database (see the installation section).
If you want to use also fake Symbols
data, you can run:
php artisan db:seed Database\\Seeders\\SymbolSeeder
The application is built using the Laravel framework
.
The main logic for the Alpha Vantage API is located in the app/AlphaVantage
folder.
The code has been written to be statically typed, using the PHPStan
tool.
The architecture of each file is tested during the Unit testing.
strict_types has been used to enforce strict typing among the application.
It leverage the Laravel Cache
to avoid hitting the database every time the /api/ticker/{symbol}
endpoint is called.
This is the main class that interacts with the Alpha Vantage API. It will handle communication with the API and data transformation. Results for every call will be a Laravel Collection of StockPrice objects.
Dependency injection for the class is handle in the app\Providers\AppServiceProvider.php
file.
This is the representation of a single Stock Price. It is valid either for the intraDay and daily pricing. It contains data fetched from the API, transormed in the right data type.
This Enum contains the functions of the Alpha Vantage API supported by the
application.
The method jsonKey()
provides a convenient way of establishing the key of the
json response that will be used to fetch the data.
This is a Laravel Facade, with root accessors to the Alpha Vantage Client. This is used to easily access the Alpha Vantage Client from the application.
The configuration for the application is stored in the config/stock_market.php
file.
Each configuration key will be available in the application using the config()
helper,
and can be set-up using environment variables.
This command will fill the database with the default symbols. Usage:
php artisan app:fill-symbols
This command will fetch the pricing for all the symbols in the database. Usage:
php artisan app:fetch-pricing
This command is scheduled (in the app/Console/Kernel.php
file) to run every minute.