Spaces:
Running
Running
Commit
·
94ed9e1
0
Parent(s):
feat: Crafting Data pipeline, Models, and Restful API
Browse files- .github/workflows/pipeline.yaml +57 -0
- .gitignore +13 -0
- .vercelignore +7 -0
- README.md +73 -0
- converter.py +40 -0
- coret-coretan.ipynb +0 -0
- datasets/.gitkeep +0 -0
- diagram/cryptocurrency_prediction.ai +0 -0
- diagram/cryptocurrency_prediction.jpg +0 -0
- diagram/icons/Yahoo!_Finance_logo_2021.png +0 -0
- diagram/icons/csv.png +0 -0
- diagram/icons/fastapi.png +0 -0
- diagram/icons/file.png +0 -0
- diagram/icons/github actions.png +0 -0
- diagram/icons/github.png +0 -0
- diagram/icons/golang.png +0 -0
- diagram/icons/keras.png +0 -0
- diagram/icons/nestjs.png +0 -0
- diagram/icons/typescript.png +0 -0
- diagram/icons/vercel.png +0 -0
- go.mod +3 -0
- main.py +23 -0
- models/.gitkeep +0 -0
- pickles/.gitkeep +0 -0
- postman/Yahoo Finance.postman_collection.json +69 -0
- postman/response.json +0 -0
- postman/symbols.json +54 -0
- posttrained/.gitkeep +0 -0
- pyproject.toml +15 -0
- pyvenv.cfg +3 -0
- requirements.txt +74 -0
- restful/controllers.py +50 -0
- restful/routes.py +19 -0
- restful/schemas.py +6 -0
- restful/services.py +19 -0
- restful/utilities.py +44 -0
- scraper.go +116 -0
- training.py +159 -0
- vercel.json +14 -0
.github/workflows/pipeline.yaml
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: pipeline
|
2 |
+
|
3 |
+
on:
|
4 |
+
push:
|
5 |
+
branches:
|
6 |
+
- main
|
7 |
+
tags:
|
8 |
+
- '*'
|
9 |
+
schedule:
|
10 |
+
- cron: "0 9 * * *"
|
11 |
+
# 16 - 7 = 9
|
12 |
+
|
13 |
+
jobs:
|
14 |
+
extraction_train_modeling:
|
15 |
+
name: Data Extraction, Training, and Modeling
|
16 |
+
runs-on: ubuntu-latest
|
17 |
+
|
18 |
+
steps:
|
19 |
+
- name: Set global directory
|
20 |
+
run: git config --global --add safe.directory /github/workspace
|
21 |
+
|
22 |
+
- uses: actions/checkout@v3
|
23 |
+
with:
|
24 |
+
persist-credentials: false
|
25 |
+
fetch-depth: 1
|
26 |
+
|
27 |
+
- name: Scraping Yahoo Finance
|
28 |
+
run: go run scraper.go
|
29 |
+
|
30 |
+
- name: Install Libraries
|
31 |
+
run: pip install -r requirements.txt
|
32 |
+
|
33 |
+
- name: Modeling and Training
|
34 |
+
run: python training.py
|
35 |
+
|
36 |
+
- name: Commit changes
|
37 |
+
run: |
|
38 |
+
git config --local user.email "[email protected]"
|
39 |
+
git config --local user.name "belajarqywok"
|
40 |
+
git add -A
|
41 |
+
git commit -m "Get Cryptocurrencies Data every 4:00 PM"
|
42 |
+
|
43 |
+
- name: Push changes
|
44 |
+
uses: ad-m/github-push-action@master
|
45 |
+
with:
|
46 |
+
github_token: ${{ secrets.GH_TOKEN }}
|
47 |
+
branch: main
|
48 |
+
|
49 |
+
deployment:
|
50 |
+
name: Deployment
|
51 |
+
runs-on: ubuntu-latest
|
52 |
+
needs: extraction_train_modeling
|
53 |
+
environment: Production
|
54 |
+
|
55 |
+
steps:
|
56 |
+
- name: Deployment
|
57 |
+
run: echo "coming soon..."
|
.gitignore
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Postman
|
2 |
+
/postman/dataset.url
|
3 |
+
|
4 |
+
# Environments
|
5 |
+
/bin
|
6 |
+
/Lib
|
7 |
+
/lib64
|
8 |
+
/Include
|
9 |
+
/Scripts
|
10 |
+
|
11 |
+
# Pycache
|
12 |
+
/__pycache__
|
13 |
+
/restful/__pycache__
|
.vercelignore
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/.github
|
2 |
+
|
3 |
+
/bin
|
4 |
+
/include
|
5 |
+
/lib
|
6 |
+
|
7 |
+
/postman
|
README.md
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<p align="center">
|
2 |
+
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
3 |
+
</p>
|
4 |
+
|
5 |
+
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
6 |
+
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
7 |
+
|
8 |
+
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
9 |
+
<p align="center">
|
10 |
+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
11 |
+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
12 |
+
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
13 |
+
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
14 |
+
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
15 |
+
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
16 |
+
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
17 |
+
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
18 |
+
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
19 |
+
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
20 |
+
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
21 |
+
</p>
|
22 |
+
<!--[](https://opencollective.com/nest#backer)
|
23 |
+
[](https://opencollective.com/nest#sponsor)-->
|
24 |
+
|
25 |
+
## Description
|
26 |
+
|
27 |
+
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
28 |
+
|
29 |
+
## Installation
|
30 |
+
|
31 |
+
```bash
|
32 |
+
$ npm install
|
33 |
+
```
|
34 |
+
|
35 |
+
## Running the app
|
36 |
+
|
37 |
+
```bash
|
38 |
+
# development
|
39 |
+
$ npm run start
|
40 |
+
|
41 |
+
# watch mode
|
42 |
+
$ npm run start:dev
|
43 |
+
|
44 |
+
# production mode
|
45 |
+
$ npm run start:prod
|
46 |
+
```
|
47 |
+
|
48 |
+
## Test
|
49 |
+
|
50 |
+
```bash
|
51 |
+
# unit tests
|
52 |
+
$ npm run test
|
53 |
+
|
54 |
+
# e2e tests
|
55 |
+
$ npm run test:e2e
|
56 |
+
|
57 |
+
# test coverage
|
58 |
+
$ npm run test:cov
|
59 |
+
```
|
60 |
+
|
61 |
+
## Support
|
62 |
+
|
63 |
+
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
64 |
+
|
65 |
+
## Stay in touch
|
66 |
+
|
67 |
+
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
68 |
+
- Website - [https://nestjs.com](https://nestjs.com/)
|
69 |
+
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
70 |
+
|
71 |
+
## License
|
72 |
+
|
73 |
+
Nest is [MIT licensed](LICENSE).
|
converter.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
"""
|
4 |
+
|
5 |
+
Data Mining Assignment - Group 5
|
6 |
+
|
7 |
+
"""
|
8 |
+
|
9 |
+
class JSONProcessor:
|
10 |
+
def __init__(self, input_file: str, output_file: str) -> None:
|
11 |
+
self.input_file: str = input_file
|
12 |
+
self.output_file: str = output_file
|
13 |
+
self.data = None
|
14 |
+
|
15 |
+
def load_json(self) -> None:
|
16 |
+
with open(self.input_file, 'r') as file:
|
17 |
+
self.data = json.load(file)
|
18 |
+
|
19 |
+
def extract_symbols(self) -> list:
|
20 |
+
if self.data is None:
|
21 |
+
raise ValueError("data not loaded. call load_json() first.")
|
22 |
+
quotes = self.data['finance']['result'][0]['quotes']
|
23 |
+
return [quote['symbol'] for quote in quotes]
|
24 |
+
|
25 |
+
def save_json(self, data: list) -> None:
|
26 |
+
with open(self.output_file, 'w') as file:
|
27 |
+
json.dump({'symbols': data}, file, indent = 4)
|
28 |
+
print(f'saved: {self.output_file}')
|
29 |
+
|
30 |
+
def main():
|
31 |
+
input_file = './postman/response.json'
|
32 |
+
output_file = './postman/symbols.json'
|
33 |
+
|
34 |
+
processor = JSONProcessor(input_file, output_file)
|
35 |
+
processor.load_json()
|
36 |
+
symbols = processor.extract_symbols()
|
37 |
+
processor.save_json(symbols)
|
38 |
+
|
39 |
+
|
40 |
+
if __name__ == "__main__": main()
|
coret-coretan.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
datasets/.gitkeep
ADDED
File without changes
|
diagram/cryptocurrency_prediction.ai
ADDED
The diff for this file is too large to render.
See raw diff
|
|
diagram/cryptocurrency_prediction.jpg
ADDED
![]() |
diagram/icons/Yahoo!_Finance_logo_2021.png
ADDED
![]() |
diagram/icons/csv.png
ADDED
![]() |
diagram/icons/fastapi.png
ADDED
![]() |
diagram/icons/file.png
ADDED
![]() |
diagram/icons/github actions.png
ADDED
![]() |
diagram/icons/github.png
ADDED
![]() |
diagram/icons/golang.png
ADDED
![]() |
diagram/icons/keras.png
ADDED
![]() |
diagram/icons/nestjs.png
ADDED
![]() |
diagram/icons/typescript.png
ADDED
![]() |
diagram/icons/vercel.png
ADDED
![]() |
go.mod
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
module cryptocurrency_prediction
|
2 |
+
|
3 |
+
go 1.20
|
main.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from fastapi import FastAPI
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
4 |
+
from restful.routes import route
|
5 |
+
|
6 |
+
REST = FastAPI(
|
7 |
+
title = "Cryptocurency Prediction Service",
|
8 |
+
version = "1.0"
|
9 |
+
)
|
10 |
+
|
11 |
+
# CORS Middleware
|
12 |
+
REST.add_middleware(
|
13 |
+
CORSMiddleware,
|
14 |
+
allow_origins = ["*"],
|
15 |
+
allow_methods = ["*"],
|
16 |
+
allow_headers = ["*"],
|
17 |
+
allow_credentials = True,
|
18 |
+
)
|
19 |
+
|
20 |
+
REST.include_router(
|
21 |
+
router = route,
|
22 |
+
prefix = '/crypto'
|
23 |
+
)
|
models/.gitkeep
ADDED
File without changes
|
pickles/.gitkeep
ADDED
File without changes
|
postman/Yahoo Finance.postman_collection.json
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"info": {
|
3 |
+
"_postman_id": "249fd388-44f6-45c2-9ad5-37da9c2af089",
|
4 |
+
"name": "Yahoo Finance",
|
5 |
+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
6 |
+
},
|
7 |
+
"item": [
|
8 |
+
{
|
9 |
+
"name": "cryptocurrencies",
|
10 |
+
"request": {
|
11 |
+
"method": "POST",
|
12 |
+
"header": [
|
13 |
+
{
|
14 |
+
"key": "Cookie",
|
15 |
+
"value": "GUC=AQEBCAFmWUlmh0IaaAQw&s=AQAAAH-PIsT_&g=ZlgBjg; A1=d=AQABBBR-S2YCEF6h7KkHtT6kUMd5eQmdvDIFEgEBCAFJWWaHZlpOb2UB_eMBAAcIFH5LZgmdvDI&S=AQAAAge4BvAFwzWWdJFVm5Wyq9k; A3=d=AQABBBR-S2YCEF6h7KkHtT6kUMd5eQmdvDIFEgEBCAFJWWaHZlpOb2UB_eMBAAcIFH5LZgmdvDI&S=AQAAAge4BvAFwzWWdJFVm5Wyq9k; axids=gam=y-BdfSS7lE2uLV0LrGZqbRPm.8FUDjf.82~A&dv360=eS1EdjNSYkpGRTJ1R2RYQTAwYnNhcFJmQ0ZZN3BtTmNGan5B&ydsp=y-wmHAUIFE2uKC4PXfccNh1ff.Lz1oO0cj~A&tbla=y-gt8RDdJE2uKuvojQP3_mil11ZyoZelyw~A; tbla_id=f1c3e4ae-853f-47af-ba52-d13fe18de92e-tuctd4c1d85; PRF=t%3DBTC-USD%252BETH-USD%252BLTC-USD%252BLTC-INR%252BCU%253DF%26newChartbetateaser%3D0%252C1718255372183; A1S=d=AQABBBR-S2YCEF6h7KkHtT6kUMd5eQmdvDIFEgEBCAFJWWaHZlpOb2UB_eMBAAcIFH5LZgmdvDI&S=AQAAAge4BvAFwzWWdJFVm5Wyq9k; cmp=t=1717308407&j=0&u=1---; gpp=DBAA; gpp_sid=-1",
|
16 |
+
"type": "text"
|
17 |
+
}
|
18 |
+
],
|
19 |
+
"body": {
|
20 |
+
"mode": "raw",
|
21 |
+
"raw": "{\"offset\":0,\"size\":50,\"sortType\":\"DESC\",\"sortField\":\"intradaymarketcap\",\"quoteType\":\"CRYPTOCURRENCY\",\"query\":{\"operator\":\"and\",\"operands\":[{\"operator\":\"eq\",\"operands\":[\"currency\",\"USD\"]},{\"operator\":\"eq\",\"operands\":[\"exchange\",\"CCC\"]}]},\"userId\":\"\",\"userIdType\":\"guid\"}",
|
22 |
+
"options": {
|
23 |
+
"raw": {
|
24 |
+
"language": "json"
|
25 |
+
}
|
26 |
+
}
|
27 |
+
},
|
28 |
+
"url": {
|
29 |
+
"raw": "https://query2.finance.yahoo.com/v1/finance/screener?crumb=55ovV9srjcg&lang=en-US®ion=US&formatted=true&corsDomain=finance.yahoo.com",
|
30 |
+
"protocol": "https",
|
31 |
+
"host": [
|
32 |
+
"query2",
|
33 |
+
"finance",
|
34 |
+
"yahoo",
|
35 |
+
"com"
|
36 |
+
],
|
37 |
+
"path": [
|
38 |
+
"v1",
|
39 |
+
"finance",
|
40 |
+
"screener"
|
41 |
+
],
|
42 |
+
"query": [
|
43 |
+
{
|
44 |
+
"key": "crumb",
|
45 |
+
"value": "55ovV9srjcg"
|
46 |
+
},
|
47 |
+
{
|
48 |
+
"key": "lang",
|
49 |
+
"value": "en-US"
|
50 |
+
},
|
51 |
+
{
|
52 |
+
"key": "region",
|
53 |
+
"value": "US"
|
54 |
+
},
|
55 |
+
{
|
56 |
+
"key": "formatted",
|
57 |
+
"value": "true"
|
58 |
+
},
|
59 |
+
{
|
60 |
+
"key": "corsDomain",
|
61 |
+
"value": "finance.yahoo.com"
|
62 |
+
}
|
63 |
+
]
|
64 |
+
}
|
65 |
+
},
|
66 |
+
"response": []
|
67 |
+
}
|
68 |
+
]
|
69 |
+
}
|
postman/response.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
postman/symbols.json
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"symbols": [
|
3 |
+
"BTC-USD",
|
4 |
+
"ETH-USD",
|
5 |
+
"USDT-USD",
|
6 |
+
"BNB-USD",
|
7 |
+
"SOL-USD",
|
8 |
+
"STETH-USD",
|
9 |
+
"USDC-USD",
|
10 |
+
"XRP-USD",
|
11 |
+
"DOGE-USD",
|
12 |
+
"ADA-USD",
|
13 |
+
"TON11419-USD",
|
14 |
+
"SHIB-USD",
|
15 |
+
"AVAX-USD",
|
16 |
+
"WSTETH-USD",
|
17 |
+
"WETH-USD",
|
18 |
+
"LINK-USD",
|
19 |
+
"WBTC-USD",
|
20 |
+
"DOT-USD",
|
21 |
+
"TRX-USD",
|
22 |
+
"WTRX-USD",
|
23 |
+
"BCH-USD",
|
24 |
+
"NEAR-USD",
|
25 |
+
"MATIC-USD",
|
26 |
+
"LTC-USD",
|
27 |
+
"PEPE24478-USD",
|
28 |
+
"EETH-USD",
|
29 |
+
"UNI7083-USD",
|
30 |
+
"ICP-USD",
|
31 |
+
"LEO-USD",
|
32 |
+
"DAI-USD",
|
33 |
+
"WEETH-USD",
|
34 |
+
"ETC-USD",
|
35 |
+
"EZETH-USD",
|
36 |
+
"APT21794-USD",
|
37 |
+
"RNDR-USD",
|
38 |
+
"BTCB-USD",
|
39 |
+
"HBAR-USD",
|
40 |
+
"WHBAR-USD",
|
41 |
+
"WBETH-USD",
|
42 |
+
"IMX10603-USD",
|
43 |
+
"KAS-USD",
|
44 |
+
"ATOM-USD",
|
45 |
+
"ARB11841-USD",
|
46 |
+
"MNT27075-USD",
|
47 |
+
"FIL-USD",
|
48 |
+
"WIF-USD",
|
49 |
+
"XLM-USD",
|
50 |
+
"USDE29470-USD",
|
51 |
+
"CRO-USD",
|
52 |
+
"AR-USD"
|
53 |
+
]
|
54 |
+
}
|
posttrained/.gitkeep
ADDED
File without changes
|
pyproject.toml
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[tool.poetry]
|
2 |
+
name = "cryptocurrency-prediction"
|
3 |
+
version = "0.1.0"
|
4 |
+
description = "Data Mining Assignment - Group 5"
|
5 |
+
authors = ["belajarqywok <[email protected]>"]
|
6 |
+
license = "MIT"
|
7 |
+
readme = "README.md"
|
8 |
+
|
9 |
+
[tool.poetry.dependencies]
|
10 |
+
python = "^3.9"
|
11 |
+
|
12 |
+
|
13 |
+
[build-system]
|
14 |
+
requires = ["poetry-core"]
|
15 |
+
build-backend = "poetry.core.masonry.api"
|
pyvenv.cfg
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
home = /usr/bin
|
2 |
+
include-system-site-packages = false
|
3 |
+
version = 3.10.12
|
requirements.txt
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
absl-py==2.1.0
|
2 |
+
annotated-types==0.7.0
|
3 |
+
anyio==4.4.0
|
4 |
+
astunparse==1.6.3
|
5 |
+
certifi==2024.2.2
|
6 |
+
charset-normalizer==3.3.2
|
7 |
+
click==8.1.7
|
8 |
+
dnspython==2.6.1
|
9 |
+
email_validator==2.1.1
|
10 |
+
exceptiongroup==1.2.1
|
11 |
+
fastapi==0.111.0
|
12 |
+
fastapi-cli==0.0.4
|
13 |
+
flatbuffers==24.3.25
|
14 |
+
gast==0.5.4
|
15 |
+
google-pasta==0.2.0
|
16 |
+
grpcio==1.64.0
|
17 |
+
h11==0.14.0
|
18 |
+
h5py==3.11.0
|
19 |
+
httpcore==1.0.5
|
20 |
+
httptools==0.6.1
|
21 |
+
httpx==0.27.0
|
22 |
+
idna==3.7
|
23 |
+
importlib_metadata==7.1.0
|
24 |
+
Jinja2==3.1.4
|
25 |
+
joblib==1.4.2
|
26 |
+
keras==3.3.3
|
27 |
+
libclang==18.1.1
|
28 |
+
Markdown==3.6
|
29 |
+
markdown-it-py==3.0.0
|
30 |
+
MarkupSafe==2.1.5
|
31 |
+
mdurl==0.1.2
|
32 |
+
ml-dtypes==0.3.2
|
33 |
+
namex==0.0.8
|
34 |
+
numpy==1.26.4
|
35 |
+
opt-einsum==3.3.0
|
36 |
+
optree==0.11.0
|
37 |
+
orjson==3.10.3
|
38 |
+
packaging==24.0
|
39 |
+
pandas==2.2.2
|
40 |
+
protobuf==4.25.3
|
41 |
+
pydantic==2.7.2
|
42 |
+
pydantic_core==2.18.3
|
43 |
+
Pygments==2.18.0
|
44 |
+
python-dateutil==2.9.0.post0
|
45 |
+
python-dotenv==1.0.1
|
46 |
+
python-multipart==0.0.9
|
47 |
+
pytz==2024.1
|
48 |
+
PyYAML==6.0.1
|
49 |
+
requests==2.32.3
|
50 |
+
rich==13.7.1
|
51 |
+
scikit-learn==1.5.0
|
52 |
+
scipy==1.13.1
|
53 |
+
shellingham==1.5.4
|
54 |
+
six==1.16.0
|
55 |
+
sniffio==1.3.1
|
56 |
+
starlette==0.37.2
|
57 |
+
tensorboard==2.16.2
|
58 |
+
tensorboard-data-server==0.7.2
|
59 |
+
tensorflow==2.16.1
|
60 |
+
tensorflow-io-gcs-filesystem==0.31.0
|
61 |
+
termcolor==2.4.0
|
62 |
+
threadpoolctl==3.5.0
|
63 |
+
typer==0.12.3
|
64 |
+
typing_extensions==4.12.1
|
65 |
+
tzdata==2024.1
|
66 |
+
ujson==5.10.0
|
67 |
+
urllib3==2.2.1
|
68 |
+
uvicorn==0.30.1
|
69 |
+
uvloop==0.19.0
|
70 |
+
watchfiles==0.22.0
|
71 |
+
websockets==12.0
|
72 |
+
Werkzeug==3.0.3
|
73 |
+
wrapt==1.16.0
|
74 |
+
zipp==3.19.1
|
restful/controllers.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from http import HTTPStatus
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
from restful.services import cryptocurrency_svc
|
4 |
+
from restful.schemas import CryptocurrencyPredictionSchema
|
5 |
+
|
6 |
+
|
7 |
+
# Cryptocurrency Controller
|
8 |
+
class cryptocurrency_controller:
|
9 |
+
# Cryptocurrency Service
|
10 |
+
__SERVICE = cryptocurrency_svc()
|
11 |
+
|
12 |
+
# Cryptocurrency Controller
|
13 |
+
def prediction(self, payload: CryptocurrencyPredictionSchema) -> JSONResponse:
|
14 |
+
try:
|
15 |
+
prediction: list = self.__SERVICE.prediction(
|
16 |
+
payload = payload
|
17 |
+
)
|
18 |
+
|
19 |
+
if not prediction :
|
20 |
+
return JSONResponse(
|
21 |
+
content = {
|
22 |
+
'message': 'Request Failed',
|
23 |
+
'status_code': HTTPStatus.BAD_REQUEST,
|
24 |
+
'data': None
|
25 |
+
},
|
26 |
+
status_code = HTTPStatus.BAD_REQUEST
|
27 |
+
)
|
28 |
+
|
29 |
+
return JSONResponse(
|
30 |
+
content = {
|
31 |
+
'message': 'Prediction Success',
|
32 |
+
'status_code': HTTPStatus.OK,
|
33 |
+
'data': {
|
34 |
+
'currency': payload.currency,
|
35 |
+
'predictions': prediction
|
36 |
+
}
|
37 |
+
},
|
38 |
+
status_code = HTTPStatus.OK
|
39 |
+
)
|
40 |
+
|
41 |
+
except Exception as error_message:
|
42 |
+
print(error_message)
|
43 |
+
return JSONResponse(
|
44 |
+
content = {
|
45 |
+
'message': 'Internal Server Error',
|
46 |
+
'status_code': HTTPStatus.INTERNAL_SERVER_ERROR,
|
47 |
+
'data': None
|
48 |
+
},
|
49 |
+
status_code = HTTPStatus.INTERNAL_SERVER_ERROR
|
50 |
+
)
|
restful/routes.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Body
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
from restful.controllers import cryptocurrency_controller
|
4 |
+
from restful.schemas import CryptocurrencyPredictionSchema
|
5 |
+
|
6 |
+
# Route
|
7 |
+
route = APIRouter()
|
8 |
+
|
9 |
+
# Controller
|
10 |
+
__CONTROLLER = cryptocurrency_controller()
|
11 |
+
|
12 |
+
# Cryptocurrency Prediction
|
13 |
+
@route.post(path = '/prediction', tags = ['machine_learning'])
|
14 |
+
async def cryptocurrency_pred_route(
|
15 |
+
payload: CryptocurrencyPredictionSchema = Body(...)
|
16 |
+
) -> JSONResponse:
|
17 |
+
# Cryptocurrency Controller
|
18 |
+
return __CONTROLLER.prediction(payload = payload)
|
19 |
+
|
restful/schemas.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
class CryptocurrencyPredictionSchema(BaseModel) :
|
4 |
+
days: int
|
5 |
+
currency: str
|
6 |
+
|
restful/services.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from restful.utilities import Utilities
|
2 |
+
from restful.schemas import CryptocurrencyPredictionSchema
|
3 |
+
|
4 |
+
class cryptocurrency_svc:
|
5 |
+
# Prediction Utilities
|
6 |
+
__PRED_UTILS = Utilities()
|
7 |
+
|
8 |
+
# Prediction Service
|
9 |
+
def prediction(self, payload: CryptocurrencyPredictionSchema) -> list:
|
10 |
+
days: int = payload.days
|
11 |
+
currency: str = payload.currency
|
12 |
+
|
13 |
+
result: list = self.__PRED_UTILS.cryptocurrency_prediction_utils(
|
14 |
+
days = days,
|
15 |
+
model_name = currency,
|
16 |
+
sequence_length = 60
|
17 |
+
)
|
18 |
+
|
19 |
+
return result
|
restful/utilities.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from joblib import load
|
3 |
+
from numpy import append, expand_dims
|
4 |
+
from pandas import read_json, to_datetime, Timedelta
|
5 |
+
|
6 |
+
from tensorflow.keras.models import load_model
|
7 |
+
|
8 |
+
|
9 |
+
class Utilities:
|
10 |
+
def __init__(self) -> None:
|
11 |
+
self.model_path = './models'
|
12 |
+
self.posttrained_path = './posttrained'
|
13 |
+
self.scaler_path = './pickles'
|
14 |
+
|
15 |
+
def cryptocurrency_prediction_utils(self,
|
16 |
+
days: int,sequence_length: int, model_name: str) -> list:
|
17 |
+
model_path = os.path.join(self.model_path, f'{model_name}.keras')
|
18 |
+
model = load_model(model_path)
|
19 |
+
|
20 |
+
dataframe_path = os.path.join(self.posttrained_path, f'{model_name}-posttrained.json')
|
21 |
+
dataframe = read_json(dataframe_path)
|
22 |
+
dataframe.set_index('Date', inplace = True)
|
23 |
+
|
24 |
+
minmax_scaler = load(os.path.join(self.scaler_path, f'{model_name}_minmax_scaler.pickle'))
|
25 |
+
standard_scaler = load(os.path.join(self.scaler_path, f'{model_name}_standard_scaler.pickle'))
|
26 |
+
|
27 |
+
lst_seq = dataframe[-sequence_length:].values
|
28 |
+
lst_seq = expand_dims(lst_seq, axis = 0)
|
29 |
+
|
30 |
+
predicted_prices = {}
|
31 |
+
last_date = to_datetime(dataframe.index[-1])
|
32 |
+
|
33 |
+
for _ in range(days):
|
34 |
+
predicted_price = model.predict(lst_seq)
|
35 |
+
last_date = last_date + Timedelta(days = 1)
|
36 |
+
|
37 |
+
predicted_prices[last_date] = minmax_scaler.inverse_transform(predicted_price)
|
38 |
+
predicted_prices[last_date] = standard_scaler.inverse_transform(predicted_prices[last_date])
|
39 |
+
|
40 |
+
lst_seq = append(lst_seq[:, 1:, :], [predicted_price], axis = 1)
|
41 |
+
|
42 |
+
values = [{'date': date.strftime('%Y-%m-%d'), 'price': float(price)} for date, price in predicted_prices.items()]
|
43 |
+
|
44 |
+
return values
|
scraper.go
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package main
|
2 |
+
|
3 |
+
import (
|
4 |
+
"encoding/json"
|
5 |
+
"fmt"
|
6 |
+
"io"
|
7 |
+
"io/ioutil"
|
8 |
+
"log"
|
9 |
+
"net/http"
|
10 |
+
"os"
|
11 |
+
"strconv"
|
12 |
+
"sync"
|
13 |
+
"time"
|
14 |
+
)
|
15 |
+
|
16 |
+
|
17 |
+
/*
|
18 |
+
|
19 |
+
Data Mining Assignment - Group 5
|
20 |
+
|
21 |
+
*/
|
22 |
+
|
23 |
+
|
24 |
+
type Symbols struct {
|
25 |
+
Symbols []string `json:"symbols"`
|
26 |
+
}
|
27 |
+
|
28 |
+
type Downloader struct {
|
29 |
+
symbols []string
|
30 |
+
}
|
31 |
+
|
32 |
+
|
33 |
+
/*
|
34 |
+
* New downloader
|
35 |
+
*/
|
36 |
+
func NewDownloader(symbols []string) *Downloader {
|
37 |
+
return &Downloader{
|
38 |
+
symbols: symbols,
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
|
43 |
+
/*
|
44 |
+
* Download dataset
|
45 |
+
*/
|
46 |
+
func (d *Downloader) Download(symbol string, wg *sync.WaitGroup) {
|
47 |
+
defer wg.Done()
|
48 |
+
|
49 |
+
unixTimestamp := getCurrentUnixTimestamp()
|
50 |
+
endpoint := fmt.Sprintf(
|
51 |
+
"https://query1.finance.yahoo.com/v7/finance/download/" +
|
52 |
+
"%s?period1=1410912000&period2=%s&interval=1d&events=history&includeAdjustedClose=true",
|
53 |
+
symbol, strconv.FormatInt(unixTimestamp, 10),
|
54 |
+
)
|
55 |
+
|
56 |
+
resp, err := http.Get(endpoint)
|
57 |
+
if err != nil {
|
58 |
+
log.Printf("[ERROR] error downloading %s: %v\n", symbol, err)
|
59 |
+
return
|
60 |
+
}
|
61 |
+
defer resp.Body.Close()
|
62 |
+
|
63 |
+
filename := fmt.Sprintf("./datasets/%s.csv", symbol)
|
64 |
+
file, err := os.Create(filename)
|
65 |
+
if err != nil {
|
66 |
+
log.Printf("[ERROR] error creating file for %s: %v\n", symbol, err)
|
67 |
+
return
|
68 |
+
}
|
69 |
+
defer file.Close()
|
70 |
+
|
71 |
+
_, err = io.Copy(file, resp.Body)
|
72 |
+
if err != nil {
|
73 |
+
log.Printf("[ERROR] error writing data for %s: %v\n", symbol, err)
|
74 |
+
return
|
75 |
+
}
|
76 |
+
|
77 |
+
fmt.Printf("[SUCCESS] saved: %s\n", symbol)
|
78 |
+
}
|
79 |
+
|
80 |
+
|
81 |
+
/*
|
82 |
+
* Get current UNIX timetamp
|
83 |
+
*/
|
84 |
+
func getCurrentUnixTimestamp() int64 {
|
85 |
+
now := time.Now().UTC()
|
86 |
+
return now.Unix()
|
87 |
+
}
|
88 |
+
|
89 |
+
|
90 |
+
func main() {
|
91 |
+
jsonFile, err := os.Open("./postman/symbols.json")
|
92 |
+
if err != nil {
|
93 |
+
log.Fatalf("[ERROR] failed to open JSON file: %v", err)
|
94 |
+
}
|
95 |
+
defer jsonFile.Close()
|
96 |
+
|
97 |
+
byteValue, err := ioutil.ReadAll(jsonFile)
|
98 |
+
if err != nil {
|
99 |
+
log.Fatalf("[ERROR] failed to read JSON file: %v", err)
|
100 |
+
}
|
101 |
+
|
102 |
+
var symbols Symbols
|
103 |
+
if err := json.Unmarshal(byteValue, &symbols); err != nil {
|
104 |
+
log.Fatalf("[ERROR] failed to unmarshal JSON: %v", err)
|
105 |
+
}
|
106 |
+
|
107 |
+
var wg sync.WaitGroup
|
108 |
+
downloader := NewDownloader(symbols.Symbols)
|
109 |
+
|
110 |
+
for _, symbol := range symbols.Symbols {
|
111 |
+
wg.Add(1)
|
112 |
+
go downloader.Download(symbol, &wg)
|
113 |
+
}
|
114 |
+
|
115 |
+
wg.Wait()
|
116 |
+
}
|
training.py
ADDED
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import joblib
|
4 |
+
import numpy as np
|
5 |
+
import pandas as pd
|
6 |
+
from sklearn.preprocessing import StandardScaler, MinMaxScaler
|
7 |
+
from tensorflow.keras.models import Sequential
|
8 |
+
from tensorflow.keras.layers import LSTM, Dense, Dropout
|
9 |
+
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
|
10 |
+
|
11 |
+
|
12 |
+
"""
|
13 |
+
|
14 |
+
Data Mining Assignment - Group 5
|
15 |
+
|
16 |
+
"""
|
17 |
+
|
18 |
+
|
19 |
+
from warnings import filterwarnings
|
20 |
+
filterwarnings('ignore')
|
21 |
+
|
22 |
+
class DataProcessor:
|
23 |
+
def __init__(self, datasets_path):
|
24 |
+
self.datasets_path = datasets_path
|
25 |
+
self.datasets = self._get_datasets()
|
26 |
+
|
27 |
+
def _get_datasets(self):
|
28 |
+
return sorted([
|
29 |
+
item for item in os.listdir(self.datasets_path)
|
30 |
+
if os.path.isfile(os.path.join(self.datasets_path, item)) and item.endswith('.csv')
|
31 |
+
])
|
32 |
+
|
33 |
+
@staticmethod
|
34 |
+
def create_sequences(df, sequence_length):
|
35 |
+
labels, sequences = [], []
|
36 |
+
for i in range(len(df) - sequence_length):
|
37 |
+
seq = df.iloc[i:i + sequence_length].values
|
38 |
+
label = df.iloc[i + sequence_length].values[0]
|
39 |
+
sequences.append(seq)
|
40 |
+
labels.append(label)
|
41 |
+
return np.array(sequences), np.array(labels)
|
42 |
+
|
43 |
+
@staticmethod
|
44 |
+
def preprocess_data(dataframe):
|
45 |
+
for col in dataframe.columns:
|
46 |
+
if dataframe[col].isnull().any():
|
47 |
+
if dataframe[col].dtype == 'object':
|
48 |
+
dataframe[col].fillna(dataframe[col].mode()[0], inplace = True)
|
49 |
+
else:
|
50 |
+
dataframe[col].fillna(dataframe[col].mean(), inplace = True)
|
51 |
+
return dataframe
|
52 |
+
|
53 |
+
@staticmethod
|
54 |
+
def scale_data(dataframe, scaler_cls):
|
55 |
+
scaler = scaler_cls()
|
56 |
+
dataframe['Close'] = scaler.fit_transform(dataframe[['Close']])
|
57 |
+
return scaler, dataframe
|
58 |
+
|
59 |
+
class ModelBuilder:
|
60 |
+
@staticmethod
|
61 |
+
def build_model(input_shape):
|
62 |
+
model = Sequential([
|
63 |
+
LSTM(50, return_sequences = True, input_shape = input_shape),
|
64 |
+
Dropout(0.2),
|
65 |
+
LSTM(50, return_sequences = False),
|
66 |
+
Dropout(0.2),
|
67 |
+
Dense(1)
|
68 |
+
])
|
69 |
+
model.compile(optimizer = 'adam', loss = 'mean_squared_error')
|
70 |
+
return model
|
71 |
+
|
72 |
+
class Trainer:
|
73 |
+
def __init__(self, model, model_file, sequence_length, epochs, batch_size):
|
74 |
+
self.model = model
|
75 |
+
self.model_file = model_file
|
76 |
+
self.sequence_length = sequence_length
|
77 |
+
self.epochs = epochs
|
78 |
+
self.batch_size = batch_size
|
79 |
+
|
80 |
+
def train(self, X_train, y_train, X_test, y_test):
|
81 |
+
early_stopping = EarlyStopping(monitor = 'val_loss', patience = 5, mode = 'min')
|
82 |
+
|
83 |
+
model_checkpoint = ModelCheckpoint(
|
84 |
+
filepath = self.model_file,
|
85 |
+
save_best_only = True,
|
86 |
+
monitor = 'val_loss',
|
87 |
+
mode = 'min'
|
88 |
+
)
|
89 |
+
|
90 |
+
history = self.model.fit(
|
91 |
+
X_train, y_train,
|
92 |
+
epochs = self.epochs,
|
93 |
+
batch_size = self.batch_size,
|
94 |
+
validation_data = (X_test, y_test),
|
95 |
+
callbacks = [early_stopping, model_checkpoint]
|
96 |
+
)
|
97 |
+
|
98 |
+
return history
|
99 |
+
|
100 |
+
class PostProcessor:
|
101 |
+
@staticmethod
|
102 |
+
def inverse_transform(scaler, data):
|
103 |
+
return scaler.inverse_transform(data)
|
104 |
+
|
105 |
+
@staticmethod
|
106 |
+
def save_json(filename, data):
|
107 |
+
with open(filename, 'w') as f:
|
108 |
+
json.dump(data, f)
|
109 |
+
|
110 |
+
def main():
|
111 |
+
datasets_path = './datasets'
|
112 |
+
models_path = './models'
|
113 |
+
posttrained = './posttrained'
|
114 |
+
pickle_file = './pickles'
|
115 |
+
|
116 |
+
sequence_length = 60
|
117 |
+
epochs = 200
|
118 |
+
batch_size = 32
|
119 |
+
|
120 |
+
data_processor = DataProcessor(datasets_path)
|
121 |
+
|
122 |
+
for dataset in data_processor.datasets:
|
123 |
+
print(f"[TRAINING] {dataset.replace('.csv', '')} ")
|
124 |
+
|
125 |
+
dataframe = pd.read_csv(os.path.join(datasets_path, dataset), index_col='Date')[['Close']]
|
126 |
+
model_file = os.path.join(models_path, f"{dataset.replace('.csv', '')}.keras")
|
127 |
+
|
128 |
+
dataframe = data_processor.preprocess_data(dataframe)
|
129 |
+
standard_scaler, dataframe = data_processor.scale_data(dataframe, StandardScaler)
|
130 |
+
minmax_scaler, dataframe = data_processor.scale_data(dataframe, MinMaxScaler)
|
131 |
+
|
132 |
+
sequences, labels = data_processor.create_sequences(dataframe, sequence_length)
|
133 |
+
input_shape = (sequences.shape[1], sequences.shape[2])
|
134 |
+
model = ModelBuilder.build_model(input_shape)
|
135 |
+
|
136 |
+
train_size = int(len(sequences) * 0.8)
|
137 |
+
X_train, X_test = sequences[:train_size], sequences[train_size:]
|
138 |
+
y_train, y_test = labels[:train_size], labels[train_size:]
|
139 |
+
|
140 |
+
trainer = Trainer(model, model_file, sequence_length, epochs, batch_size)
|
141 |
+
trainer.train(X_train, y_train, X_test, y_test)
|
142 |
+
|
143 |
+
dataframe_json = {'Date': dataframe.index.tolist(), 'Close': dataframe['Close'].tolist()}
|
144 |
+
|
145 |
+
PostProcessor.save_json(
|
146 |
+
os.path.join(posttrained, f'{dataset.replace(".csv", "")}-posttrained.json'),
|
147 |
+
dataframe_json
|
148 |
+
)
|
149 |
+
|
150 |
+
joblib.dump(minmax_scaler, os.path.join(pickle_file, f'{dataset.replace(".csv", "")}_minmax_scaler.pickle'))
|
151 |
+
joblib.dump(standard_scaler, os.path.join(pickle_file, f'{dataset.replace(".csv", "")}_standard_scaler.pickle'))
|
152 |
+
|
153 |
+
model.load_weights(model_file)
|
154 |
+
model.save(model_file)
|
155 |
+
|
156 |
+
print("\n\n")
|
157 |
+
|
158 |
+
if __name__ == "__main__":
|
159 |
+
main()
|
vercel.json
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"builds": [
|
3 |
+
{
|
4 |
+
"src": "main.py",
|
5 |
+
"use": "@vercel/python"
|
6 |
+
}
|
7 |
+
],
|
8 |
+
"routes": [
|
9 |
+
{
|
10 |
+
"src": "/(.*)",
|
11 |
+
"dest": "main.py"
|
12 |
+
}
|
13 |
+
]
|
14 |
+
}
|