tebakaja commited on
Commit
94ed9e1
·
0 Parent(s):

feat: Crafting Data pipeline, Models, and Restful API

Browse files
.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
+ <!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
23
+ [![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](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&region=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
+ }