From 56c027461966d42aa3103e761a0e5943a1798cab Mon Sep 17 00:00:00 2001 From: Antoine Boucher Date: Mon, 3 Apr 2023 20:10:24 +0000 Subject: [PATCH 1/3] update init script --- marketwatch/__init__.py | 29 ++++++++++++++ marketwatch/game.py | 83 ++++++++++++++++++++++++++++++++++++++++ test/test_marketwatch.py | 13 +++++++ 3 files changed, 125 insertions(+) diff --git a/marketwatch/__init__.py b/marketwatch/__init__.py index 161ed19..642c144 100644 --- a/marketwatch/__init__.py +++ b/marketwatch/__init__.py @@ -261,6 +261,7 @@ def get_games(self): ) return games_data + @auth def create_game(self, name: str, start_date: int, end_date: int, **kwargs) -> dict: """ Create a game on MarketWatch. @@ -318,6 +319,34 @@ def create_game(self, name: str, start_date: int, end_date: int, **kwargs) -> di # Return information about created game return self.get_game(name) + # @auth + # def modify_game(self, game_id: str, **kwargs) -> dict: + # game_settings = self.get_game_settings(game_id) + # headers = {'Content-Type': 'application/json'} + # # Construct payload with default and optional parameters + # payload = { + # "name": kwargs.get('name', 'testgame'), + # "uri": kwargs.get('uri', 'testgame'), + # "startDateUtc": kwargs.get('startDateUtc', 1635619200), + # "endDateUtc": kwargs.get('endDateUtc', 1635619200), + # "allowJoinAfterStart": kwargs.get('allowJoinAfterStart', True), + # "privacyPortfolios": kwargs.get('privacyPortfolios', 'public'), + # "privacyGame": kwargs.get('privacyGame', 'public'), + # "allowComment": kwargs.get('allowComment', True), + # "description": kwargs.get('description', ''), + # "startingAmount": kwargs.get('startingAmount', 100000), + # "commissionPerTrade": kwargs.get('commissionPerTrade', 10), + # "creditInterestRate": kwargs.get('creditInterestRate', 0), + # "debitInterestRate": kwargs.get('debitInterestRate', 0), + # "minimumTradePrice": kwargs.get('minimumTradePrice', 2), + # "maximumTradePrice": kwargs.get('maximumTradePrice', 500000), + # "allowShortSelling": kwargs.get('allowShortSelling', True), + # "marginEnabled": kwargs.get('marginEnabled', True), + # "allowLimitOrders": kwargs.get('allowLimitOrders', False), + # "allowStopOrders": kwargs.get('allowStopOrders', False), + # "allowPartialShares": kwargs.get('allowPartialShares', False), + # } + @auth def get_game(self, game_id: str) -> list: diff --git a/marketwatch/game.py b/marketwatch/game.py index 2152af4..5e12248 100644 --- a/marketwatch/game.py +++ b/marketwatch/game.py @@ -30,6 +30,89 @@ def __init__(self, email: str, password: str, game_id: str) -> None: self._id = game_id self.ledger_id = super().get_ledger_id(self._id) + def create_game(self, name: str, start_date: int, end_date: int, **kwargs) -> dict: + """ + Creates a game on MarketWatch. + + :param name: str + :param start_date: int + :param end_date: int + :param kwargs: dict + + """ + return super().create_game(name, start_date, end_date, **kwargs) + + def buy(self, symbol: str, quantity: int, order_type: str = 'market', **kwargs) -> dict: + """ + Buy a stock on MarketWatch. + + :param symbol: str + :param quantity: int + :param order_type: str + :param kwargs: dict + :return: dict + + """ + return super().buy(self._id, symbol, quantity, order_type, **kwargs) + + def sell(self, symbol: str, quantity: int, order_type: str = 'market', **kwargs) -> dict: + """ + Sell a stock on MarketWatch. + + :param symbol: str + :param quantity: int + :param order_type: str + :param kwargs: dict + :return: dict + + """ + return super().sell(self._id, symbol, quantity, order_type, **kwargs) + + def short(self, symbol: str, quantity: int, order_type: str = 'market', **kwargs) -> dict: + """ + Short a stock on MarketWatch. + + :param symbol: str + :param quantity: int + :param order_type: str + :param kwargs: dict + :return: dict + + """ + return super().short(self._id, symbol, quantity, order_type, **kwargs) + + def cover(self, symbol: str, quantity: int, order_type: str = 'market', **kwargs) -> dict: + """ + Cover a stock on MarketWatch. + + :param symbol: str + :param quantity: int + :param order_type: str + :param kwargs: dict + :return: dict + + """ + return super().cover(self._id, symbol, quantity, order_type, **kwargs) + + def cancel(self, order_id: str) -> dict: + """ + Cancel an order on MarketWatch. + + :param order_id: str + :return: dict + + """ + return super().cancel_order(self._id, order_id) + + def cancel_all(self) -> dict: + """ + Cancel all orders on MarketWatch. + + :return: dict + + """ + return super().cancel_all_orders(self._id) + @property def settings(self) -> dict: """ diff --git a/test/test_marketwatch.py b/test/test_marketwatch.py index 294ab72..84d987d 100644 --- a/test/test_marketwatch.py +++ b/test/test_marketwatch.py @@ -412,3 +412,16 @@ def test_add_to_watchlist(authenticated_marketwatch): assert watchlist["Items"][0]["ChartingSymbol"] == "STOCK/US/XNAS/AAPL" mw.delete_watchlist_item(watchlist["Id"], "AAPL") mw.delete_watchlist(watchlist["Id"]) + +# def test_create_game(marketwatch): +# # Créer un nouveau jeu avec les paramètres spécifiés +# name = "Mon nouveau jeu" +# start_balance = 100000 +# duration = 30 +# game_id = marketwatch.create_game(name, start_balance, duration) + +# # Vérifier que le jeu a été créé avec succès +# assert isinstance(game_id, int) + +# # Supprimer le jeu nouvellement créé +# marketwatch.delete_game(game_id) \ No newline at end of file From de34da50b67fa11691b7502cbd212cb744cd98ab Mon Sep 17 00:00:00 2001 From: Antoine Boucher Date: Mon, 3 Apr 2023 23:20:25 +0000 Subject: [PATCH 2/3] basic api for creating card for website --- api/.dockerignore | 3 ++ api/Dockerfile | 12 +++++ api/Procfile | 1 + api/api/__init__.py | 0 api/api/app.py | 74 +++++++++++++++++++++++++++ api/api/templates/card.html.j2 | 32 ++++++++++++ api/api/templates/game.html.j2 | 35 +++++++++++++ api/api/templates/leaderboard.html.j2 | 29 +++++++++++ api/api/templates/portfolio.html.j2 | 40 +++++++++++++++ api/app.json | 30 +++++++++++ api/docker-compose.yml | 10 ++++ api/requirements.txt | 5 ++ api/setup.md | 71 +++++++++++++++++++++++++ api/vercel.json | 8 +++ 14 files changed, 350 insertions(+) create mode 100644 api/.dockerignore create mode 100644 api/Dockerfile create mode 100644 api/Procfile create mode 100644 api/api/__init__.py create mode 100644 api/api/app.py create mode 100644 api/api/templates/card.html.j2 create mode 100644 api/api/templates/game.html.j2 create mode 100644 api/api/templates/leaderboard.html.j2 create mode 100644 api/api/templates/portfolio.html.j2 create mode 100644 api/app.json create mode 100644 api/docker-compose.yml create mode 100644 api/requirements.txt create mode 100644 api/setup.md create mode 100644 api/vercel.json diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 0000000..b5f7b6a --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1,3 @@ +.dockerignore +Dockerfile +__pycache__ \ No newline at end of file diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..e23d94a --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,12 @@ +# syntax=docker/dockerfile:1 + +FROM python:3.10.0 + +WORKDIR /api + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt --no-cache-dir + +COPY api/ . + +CMD ["unicorn", "marketwatch:app", "--bind", "0.0.0.0:5000", "--workers", "4", "--threads", "2"] \ No newline at end of file diff --git a/api/Procfile b/api/Procfile new file mode 100644 index 0000000..ee7b5f3 --- /dev/null +++ b/api/Procfile @@ -0,0 +1 @@ +web: uvicorn app.marketwatch:app --reload \ No newline at end of file diff --git a/api/api/__init__.py b/api/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api/app.py b/api/api/app.py new file mode 100644 index 0000000..b5c1a5c --- /dev/null +++ b/api/api/app.py @@ -0,0 +1,74 @@ +"""Marketwatch API""" + +import os +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse, FileResponse +from fastapi.templating import Jinja2Templates + +from marketwatch import MarketWatch + + +MARKETWATCH_USERNAME = os.environ.get("MARKETWATCH_USERNAME") +MARKETWATCH_PASSWORD = os.environ.get("MARKETWATCH_PASSWORD") +MARKETWATCH_GAME_ID = os.environ.get("MARKETWATCH_GAME_ID") + +app = FastAPI() + +templates = Jinja2Templates(directory="templates") + + +@app.get("/game/{game_id}", response_class=HTMLResponse) +async def game(request: Request, game_id: str = MARKETWATCH_GAME_ID): + """ + Get the current marketwatch data + + :param request: The request object + :return: The marketwatch data + """ + mw = MarketWatch(MARKETWATCH_USERNAME, MARKETWATCH_PASSWORD) + data = mw.get_game(game_id=game_id) + return templates.TemplateResponse("game.html.j2", {"request": request, "data": data}) + +@app.get("/portfolio/{game_id}", response_class=HTMLResponse) +async def portfolio(request: Request, game_id: str = MARKETWATCH_GAME_ID): + """ + Get the current marketwatch data + + :param request: The request object + :return: The marketwatch data + """ + mw = MarketWatch(MARKETWATCH_USERNAME, MARKETWATCH_PASSWORD) + data = mw.get_portfolio(game_id=game_id) + return templates.TemplateResponse("porfolio.html.j2", {"request": request, "data": data}) + +@app.get("/leaderboard/{game_id}", response_class=HTMLResponse) +async def leaderboard(request: Request, game_id: str = MARKETWATCH_GAME_ID): + """ + Get the current marketwatch data + + :param request: The request object + :return: The marketwatch data + """ + mw = MarketWatch(MARKETWATCH_USERNAME, MARKETWATCH_PASSWORD) + data = mw.get_leaderboard(game_id=game_id) + return templates.TemplateResponse("leaderboard.html.j2", {"request": request, "data": data}) + + +@app.get("/card/{game_id}", response_class=HTMLResponse) +async def card(request: Request, game_id: str = MARKETWATCH_GAME_ID): + """ + Get the current marketwatch data + + :param request: The request object + :return: The marketwatch data + """ + mw = MarketWatch(MARKETWATCH_USERNAME, MARKETWATCH_PASSWORD) + game = mw.get_game(game_id=game_id) + + return templates.TemplateResponse("card.html.j2", {"request": request, "data": game}) + + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=5000) diff --git a/api/api/templates/card.html.j2 b/api/api/templates/card.html.j2 new file mode 100644 index 0000000..ad20282 --- /dev/null +++ b/api/api/templates/card.html.j2 @@ -0,0 +1,32 @@ + + + +
+
+
+

{{ data.title }}

+

{{ data.time }}

+

View game on MarketWatch

+
    +
  • Start date: {{ data.start_date }}
  • +
  • End date: {{ data.end_date }}
  • +
  • Number of players: {{ data.players }}
  • +
  • Game creator: {{ data.creator }}
  • +
+

Your current standings:

+
    +
  • Rank: {{ data.rank }}
  • +
  • Portfolio value: {{ data.portfolio_value }}
  • +
  • Gain percentage: {{ data.gain_percentage }}
  • +
  • Gain: {{ data.gain }}
  • +
  • Return: {{ data.return }}
  • +
  • Cash remaining: {{ data.cash_remaining }}
  • +
  • Buying power: {{ data.buying_power }}
  • +
  • Shorts reserve: {{ data.shorts_reserve }}
  • +
  • Cash borrowed: {{ data.cash_borrowed }}
  • +
+
+
+
+
+
\ No newline at end of file diff --git a/api/api/templates/game.html.j2 b/api/api/templates/game.html.j2 new file mode 100644 index 0000000..61741bb --- /dev/null +++ b/api/api/templates/game.html.j2 @@ -0,0 +1,35 @@ + + + + {{ data.title }} + + + +
+
+

{{ data.title }}

+

{{ data.time }}

+

View game on MarketWatch

+
    +
  • Start date: {{ data.start_date }}
  • +
  • End date: {{ data.end_date }}
  • +
  • Number of players: {{ data.players }}
  • +
  • Game creator: {{ data.creator }}
  • +
+

Your current standings:

+
    +
  • Rank: {{ data.rank }}
  • +
  • Portfolio value: {{ data.portfolio_value }}
  • +
  • Gain percentage: {{ data.gain_percentage }}
  • +
  • Gain: {{ data.gain }}
  • +
  • Return: {{ data.return }}
  • +
  • Cash remaining: {{ data.cash_remaining }}
  • +
  • Buying power: {{ data.buying_power }}
  • +
  • Shorts reserve: {{ data.shorts_reserve }}
  • +
  • Cash borrowed: {{ data.cash_borrowed }}
  • +
+
+ +
+ + \ No newline at end of file diff --git a/api/api/templates/leaderboard.html.j2 b/api/api/templates/leaderboard.html.j2 new file mode 100644 index 0000000..0e30d85 --- /dev/null +++ b/api/api/templates/leaderboard.html.j2 @@ -0,0 +1,29 @@ +
+
+

{{ data.game_id }} Leaderboard

+ + + + + + + + + + + + + {% for player in data.players %} + + + + + + + + + {% endfor %} + +
RankPlayerPortfolio ValueGain PercentageTransactionsGain
{{ player.rank }}{{ player.player }}{{ player.portfolio_value }}{{ player.gain_percentage }}{{ player.transactions }}{{ player.gain }}
+
+
\ No newline at end of file diff --git a/api/api/templates/portfolio.html.j2 b/api/api/templates/portfolio.html.j2 new file mode 100644 index 0000000..446fca7 --- /dev/null +++ b/api/api/templates/portfolio.html.j2 @@ -0,0 +1,40 @@ + + + + + {{ data.game_id }} Leaderboard + + + + +
+
+

{{ data.game_id }} Leaderboard

+ + + + + + + + + + + + + {% for player in data.players %} + + + + + + + + + {% endfor %} + +
RankPlayerPortfolio ValueGain PercentageTransactionsGain
{{ player.rank }}{{ player.player }}{{ player.portfolio_value }}{{ player.gain_percentage }}{{ player.transactions }}{{ player.gain }}
+
+
+ + \ No newline at end of file diff --git a/api/app.json b/api/app.json new file mode 100644 index 0000000..a139667 --- /dev/null +++ b/api/app.json @@ -0,0 +1,30 @@ +{ + "name": "marketwatch", + "description": "Marketwatch is a web app that allows users to track their favorite stocks and view their performance over time.", + "scripts": { + "postdeploy": "gunicorn --workers=1 api.spotify:app" + }, + "env": { + "MARKETWATCH_USERNAME": { + "required": true + }, + "MARKETWATCH_PASSWORD": { + "required": true + }, + "MARKETWATCH_GAME_ID": { + "required": true + } + }, + "formation": { + "web": { + "quantity": 1 + } + }, + "addons": [], + "buildpacks": [ + { + "url": "heroku/python" + } + ], + "stack": "heroku-20" +} \ No newline at end of file diff --git a/api/docker-compose.yml b/api/docker-compose.yml new file mode 100644 index 0000000..3fd6584 --- /dev/null +++ b/api/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.8' +services: + spotify-readme: + build: . + ports: + - "5000:5000" + volumes: + - ./api:/api +volumes: + persistent: \ No newline at end of file diff --git a/api/requirements.txt b/api/requirements.txt new file mode 100644 index 0000000..ca37df9 --- /dev/null +++ b/api/requirements.txt @@ -0,0 +1,5 @@ +fastapi +uvicorn +httpx +marketwatch +jinja2 \ No newline at end of file diff --git a/api/setup.md b/api/setup.md new file mode 100644 index 0000000..c813643 --- /dev/null +++ b/api/setup.md @@ -0,0 +1,71 @@ +# Deployment + +## Environment variables + +MARKETWATCH_USERNAME +MARKETWATCH_PASSWORD +MARKETWATCH_GAME_ID + +## Deploy to Vercel + +1. Fork this repository +2. Create a Vercel project and link it to your forked repository +3. Add the following environment variables in your Vercel project settings +4. Deploy the project + +https://vercel.com///settings/environment-variables + +## Deploy to Heroku + +1. Create a Heroku application via the Heroku CLI or Heroku Dashboard +2. Connect the app with your GitHub repository and enable automatic builds +3. After the build is completed, start the Flask server by executing `heroku ps:scale web=1` +4. Alternatively, you can click the "Deploy to Heroku" button above to automatically start the deployment process +5. Add the following environment variables to your Heroku application: + +[![Marketwatch](https://USER_NAME.vercel.app/api/marketwatch)](https://marketwatch.com/game/{ID}) + +## Run locally with Docker + +1. Install Docker +2. Add the following environment variables +3. Open a terminal in the root folder of the repository and execute: + +```bash +docker-compose up -d +``` + + +4. Navigate to `http://localhost:5000/` in your web browser to access the service +5. To stop the service, execute: + + +```bash +docker-compose down +``` + + +## Customization + +### Hide the EQ bar + +Remove the `#` in front of `contentBar` in line 81 of the current master to hide the EQ bar when you're not currently playing anything. + +### Status String + +Add a string saying either "Vibing to:" or "Last seen playing:". + +### Change height + +Change the `height` to `height + 40` (or whatever margin-top is set to). + +### Theme Templates + +If you want to change the widget theme, you can do so by changing the `current-theme` property in the `templates.json` file. The available themes are: + +- light +- dark + +If you wish to customize further, you can add your own customized `marketwatch.html.j2` file to the `templates` folder and add the theme and file name to the `templates` dictionary in the `templates.json` file. + + diff --git a/api/vercel.json b/api/vercel.json new file mode 100644 index 0000000..7d27feb --- /dev/null +++ b/api/vercel.json @@ -0,0 +1,8 @@ +{ + "redirects": [ + { + "source": "/", + "destination": "https://github.com/antoinebou12/marketwatch" + } + ] + } \ No newline at end of file From 63642ce6b5a00109fb419bd6484462f95deb5c81 Mon Sep 17 00:00:00 2001 From: Antoine Boucher Date: Mon, 3 Apr 2023 19:39:04 -0400 Subject: [PATCH 3/3] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e23f350..bdf6841 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ ![PyPI - License](https://img.shields.io/pypi/l/marketwatch) ![PyPI - Downloads](https://img.shields.io/pypi/dm/marketwatch) ![GitHub last commit](https://img.shields.io/github/last-commit/antoinebou12/marketwatch) -[![Publish 📦 to PyPI](https://github.com/antoinebou12/marketwatch/actions/workflows/python-publish.yml/badge.svg?branch=main)](https://github.com/antoinebou12/marketwatch/actions/workflows/python-publish.yml) [![Python Test and Build](https://github.com/antoinebou12/marketwatch/actions/workflows/python-test.yml/badge.svg)](https://github.com/antoinebou12/marketwatch/actions/workflows/python-test.yml) ![Coverage](https://raw.githubusercontent.com/antoinebou12/marketwatch/main/.github/badge/coverage.svg)