Finanzen

Aufgabe

Implementieren Sie eine Website, über die Benutzer Aktien “kaufen” und “verkaufen” können, ähnlich wie unten beschrieben.

finance

Hintergrund

Wenn Sie sich nicht ganz sicher sind, was es bedeutet, Aktien (d. h. Anteile eines Unternehmens) zu kaufen und zu verkaufen, finden Sie hier ein Tutorial.

Sie sind dabei, C$50 Finance zu implementieren, eine Web-App, mit der Sie Portfolios von Aktien verwalten können. Dieses Tool ermöglicht es Ihnen nicht nur, die Preise echter Aktien und die Werte von Portfolios zu überprüfen, sondern auch Aktien zu kaufen (okay, „kaufen“) und zu verkaufen (okay, „verkaufen“), indem die Preise der Aktien abgefragt werden.

Es gibt tatsächlich Tools (eines davon ist bekannt als IEX), mit denen Sie Aktienkurse über deren API (Application Programming Interface) herunterladen können, indem Sie URLs wie diese verwenden: https://api.iex.cloud/v1/data/core/quote/nflx?token=API_KEY.

Beachten Sie, dass das Symbol von Netflix (NFLX) in dieser URL eingebettet ist; so weiß IEX, wessen Daten zurückgegeben werden sollen. Dieser Link liefert tatsächlich keine Daten, da IEX die Verwendung eines API-Schlüssels erfordert. Wenn er jedoch Daten liefern würde, bekämen Sie eine Antwort im JSON-Format (JavaScript Object Notation), die etwa so aussieht:

{
  "avgTotalVolume":6787785,
  "calculationPrice":"tops",
  "change":1.46,
  "changePercent":0.00336,
  "close":null,
  "closeSource":"official",
  "closeTime":null,
  "companyName":"Netflix Inc.",
  "currency":"USD",
  "delayedPrice":null,
  "delayedPriceTime":null,
  "extendedChange":null,
  "extendedChangePercent":null,
  "extendedPrice":null,
  "extendedPriceTime":null,
  "high":null,
  "highSource":"IEX real time price",
  "highTime":1699626600947,
  "iexAskPrice":460.87,
  "iexAskSize":123,
  "iexBidPrice":435,
  "iexBidSize":100,
  "iexClose":436.61,
  "iexCloseTime":1699626704609,
  "iexLastUpdated":1699626704609,
  "iexMarketPercent":0.00864679844447232,
  "iexOpen":437.37,
  "iexOpenTime":1699626600859,
  "iexRealtimePrice":436.61,
  "iexRealtimeSize":5,
  "iexVolume":965,
  "lastTradeTime":1699626704609,
  "latestPrice":436.61,
  "latestSource":"IEX real time price",
  "latestTime":"9:31:44 AM",
  "latestUpdate":1699626704609,
  "latestVolume":null,
  "low":null,
  "lowSource":"IEX real time price",
  "lowTime":1699626634509,
  "marketCap":192892118443,
  "oddLotDelayedPrice":null,
  "oddLotDelayedPriceTime":null,
  "open":null,
  "openTime":null,
  "openSource":"official",
  "peRatio":43.57,
  "previousClose":435.15,
  "previousVolume":2735507,
  "primaryExchange":"NASDAQ",
  "symbol":"NFLX",
  "volume":null,
  "week52High":485,
  "week52Low":271.56,
  "ytdChange":0.4790450244167119,
  "isUSMarketOpen":true
}

Beachten Sie, wie zwischen den geschweiften Klammern eine durch Kommas getrennte Liste von Schlüssel-Wert-Paaren steht, wobei jedes Schlüssel-Wert-Paar durch einen Doppelpunkt getrennt ist. Wir werden etwas sehr Ähnliches mit unserer eigenen Aktien-Datenbank-API machen.

Bevor Sie beginnen

Öffnen Sie VS Code entsprechend Ihrem Setup, klicken Sie auf Ihr Terminalfenster und führen Sie cd aus. Die Eingabeaufforderung Ihres Terminalfensters sollte ungefähr wie folgt aussehen:

$

Führen Sie den Befehl

wget https://inf.zone/download/exercises/09/finance.zip

aus, um in Ihrem Codespace die ZIP finance.zip herunterzuladen.

Nun können Sie

unzip finance.zip

ausführen, um die ZIP in den Ordner finance zu entpacken.

Sie benötigen die ZIP-Datei nicht mehr, daher können Sie den Befehl

rm finance.zip

ausführen und bei der Aufforderung mit „y“ gefolgt von der Eingabetaste antworten, um die heruntergeladene ZIP-Datei zu entfernen.

Geben Sie nun den Befehl

cd finance

ein und drücken Sie anschließend die Eingabetaste, um in dieses Verzeichnis zu wechseln (d. h., es zu öffnen). Ihre Eingabeaufforderung sollte nun wie folgt aussehen:

finance/ $

Falls alles erfolgreich war, führen Sie den folgenden Befehl aus:

ls

Sie sollten nun die folgenden Dateien und Ordner sehen:

app.py  users.json  helpers.py  static/  templates/

Falls es zu Problemen kommt, wiederholen Sie dieselben Schritte und versuchen Sie herauszufinden, wo ein Fehler aufgetreten sein könnte!

Ausführen

Starten Sie den integrierten Webserver von Flask (im Verzeichnis finance/):

$ flask run

Besuchen Sie die URL, die von Flask ausgegeben wird, um den Code in Aktion zu sehen. Sie können sich jedoch vorerst noch nicht einloggen oder registrieren!

Verständnis

app.py

Öffnen Sie die Datei app.py. Am Anfang der Datei befinden sich eine Reihe von Importen, darunter das SQL-Modul von CS50 und einige Hilfsfunktionen. Mehr dazu später.

Nach der Konfiguration von Flask sehen Sie, dass die Datei das Caching von Antworten deaktiviert (vorausgesetzt, Sie befinden sich im Debugging-Modus, was standardmäßig in Ihrem Code50-Codespace der Fall ist). Dies stellt sicher, dass Änderungen an einer Datei auch dann berücksichtigt werden, wenn der Browser sie ansonsten nicht erkennen würde.

Danach wird Jinja mit einem benutzerdefinierten „Filter“ namens usd konfiguriert, einer Funktion (definiert in helpers.py), die das Formatieren von Werten als US-Dollar (USD) erleichtert.

Außerdem wird Flask so konfiguriert, dass Sitzungen im lokalen Dateisystem (d. h. auf der Festplatte) gespeichert werden, anstatt sie in (digital signierten) Cookies zu speichern, was Flasks Standard ist.

Danach folgen mehrere Routen, von denen nur drei vollständig implementiert sind: login, logout und register.

Lesen Sie sich zunächst die Implementierung von login durch. Beachten Sie, wie check_password_hash verwendet wird, um die Hashes der Benutzerpasswörter zu vergleichen.

Ebenso speichert login, dass ein Benutzer eingeloggt ist, indem dessen user_id, ein INTEGER, in der Sitzung session gespeichert wird. So können andere Routen in dieser Datei überprüfen, welcher Benutzer, falls überhaupt, eingeloggt ist.

Schließlich leitet login den Benutzer nach erfolgreichem Einloggen auf „/“ weiter und bringt ihn so zur Startseite.

logout hingegen leert einfach die Sitzung, wodurch der Benutzer effektiv ausgeloggt wird.

register ermöglicht neuen Benutzern die Registrierung mit einem Startkapital von 10.000 USD. Der Benutzername muss dabei einzigartig sein. Nach erfolgreicher Registrierung wird der Benutzer automatisch angemeldet und zur Startseite weitergeleitet.

Beachten Sie, dass die meisten Routen mit @login_required dekoriert sind (eine Funktion, die ebenfalls in helpers.py definiert ist). Dieser Dekorator stellt sicher, dass ein Benutzer, der versucht, eine dieser Routen zu besuchen, zuerst auf die Login-Seite umgeleitet wird, um sich einzuloggen.

Außerdem unterstützen die meisten Routen sowohl GET als auch POST. Trotzdem geben die meisten von ihnen (zumindest vorerst!) nur eine „Entschuldigung“ zurück, da sie noch nicht implementiert sind.

users.json

Die Datei users.json speichert alle registrierten Nutzer und ihre zugehörigen Daten in einem JSON-Format. Dabei ist die Struktur folgendermaßen aufgebaut:

  • Die Schlüssel des JSON-Objekts sind die eindeutigen IDs der Nutzer (als Strings).
  • Die Werte sind weitere Objekte (Dictionaries), die Informationen über die Nutzer enthalten:
    • “username”: Der Benutzername des Nutzers (String).
    • “password”: Das gehashte Passwort des Nutzers (String).
    • “cash”: Das verfügbare Kapital des Nutzers (Float, in US-Dollar).

Beispielinhalt der Datei:

{
    "1": {
        "username": "Alice",
        "password": "hashed_password_1",
        "cash": 10000.0
    },
    "2": {
        "username": "Bob",
        "password": "hashed_password_2",
        "cash": 10000.0
    }
}

Wenn die Datei users.json in Python mit der Funktion json.load() eingelesen wird, wird das JSON-Objekt als ein Dictionary interpretiert.

helpers.py

Werfen Sie als Nächstes einen Blick auf helpers.py. Hier finden Sie die Implementierung von apology. Beachten Sie, dass es letztendlich eine Vorlage namens apology.html rendert.

In dieser Datei ist auch eine weitere Funktion namens escape definiert, die spezielle Zeichen in Entschuldigungen ersetzt. Indem escape innerhalb von apology definiert wird, ist die Funktion nur für apology zugänglich; keine anderen Funktionen können oder müssen sie aufrufen.

Als Nächstes folgt login_required. Wenn diese Funktion etwas kryptisch wirkt, machen Sie sich keine Sorgen! Falls Sie sich jemals gefragt haben, wie eine Funktion eine andere Funktion zurückgeben kann, ist dies ein Beispiel.

Danach kommt lookup, eine Funktion, die für ein gegebenes Symbol (z. B. NFLX) ein Aktienangebot für ein Unternehmen in Form eines dict zurückgibt. Dieses dict enthält drei Schlüssel:

  • name (Wert: str),
  • price (Wert: float) und
  • symbol (Wert: str, eine kanonisierte, d. h. großgeschriebene Version eines Aktiensymbols, unabhängig davon, wie das Symbol ursprünglich übergeben wurde).

Beachten Sie, dass diese Preise nicht „in Echtzeit“ sind, sich aber im Laufe der Zeit ändern, ähnlich wie in der realen Welt!

Zuletzt finden Sie die Funktion usd, die einfach einen float als USD formatiert (z. B. wird 1234.56 als $1,234.56 formatiert).

static/

Werfen Sie einen Blick in den Ordner static/, in dem sich die Datei styles.css befindet. Dort finden Sie erste CSS-Definitionen, die Sie nach Belieben ändern können.

templates/

Sehen Sie sich schließlich den Ordner templates/ an. In login.html und register.html finden Sie im Wesentlichen ein HTML-Formular, das mit Bootstrap gestaltet wurde.

In apology.html finden Sie eine Vorlage für eine Entschuldigung. Erinnern Sie sich, dass apology in helpers.py zwei Argumente entgegennimmt: message, das als Wert für bottom an render_template übergeben wird, und optional code, das als Wert für top übergeben wird.

Beachten Sie, wie diese Werte letztendlich in apology.html verwendet werden!

Zuletzt gibt es layout.html. Diese Datei ist etwas größer als gewöhnlich, was hauptsächlich daran liegt, dass sie eine schicke, mobilfreundliche „Navbar“ (Navigationsleiste) enthält, die ebenfalls auf Bootstrap basiert. Beachten Sie, dass sie einen Block namens main definiert, in den Vorlagen (einschließlich apology.html, login.html und register.html) eingefügt werden. Sie enthält außerdem Unterstützung für Flasks „message flashing“, sodass Sie Nachrichten von einer Route zur nächsten für den Benutzer anzeigen können.

ℹ️

Bootstrap ist ein beliebtes, open-source Framework für die Entwicklung von responsiven, mobilen Websites. Es enthält eine Sammlung von vorgefertigten Design-Elementen wie Layouts, Buttons, Formularen und Navigationselementen, die es Entwicklern ermöglichen, schnell und einfach ansprechende Webseiten zu erstellen. Bootstrap nutzt HTML, CSS und JavaScript und hilft dabei, die Komplexität des Webdesigns zu verringern, indem es einheitliche, gut getestete Styles und Komponenten zur Verfügung stellt, die auf verschiedenen Bildschirmgrößen gut funktionieren.

Beachten Sie, dass in den Templates login.html, register.html und layout.html zwar Bootstrap verwendet wird, Sie jedoch zum Lösen dieser Aufgabe nicht damit arbeiten müssen. Sie können wie gewohnt eigenen HTML-, CSS- und JavaScript-Code schreiben.

Spezifikation

Finden Sie einen geeigneten Weg die nötigen Daten mit möglichst wenig Redundanz zu speichern. Hierfür können sie auch neue JSON-Files anlegen. (Es kann hilfreich sein die Daten mit einem Index zu speichern.)

quote

Implementieren Sie die Funktionalität von quote, sodass Benutzer den aktuellen Preis einer Aktie abrufen können:

  • Verlangen Sie, dass der Benutzer ein Aktiensymbol eingibt, implementiert als Textfeld mit dem Namen symbol.
  • Übermitteln Sie die Eingaben des Benutzers mit POST an /quote.
  • Erstellen Sie zwei neue Vorlagen (z. B. quote.html und quoted.html). Wenn ein Benutzer /quote mit GET aufruft, rendern Sie eine der Vorlagen, die ein HTML-Formular enthält, das mit POST an /quote gesendet wird. Als Antwort auf ein POST-Anfrage kann quote die zweite Vorlage rendern, in der ein oder mehrere Werte aus lookup eingebettet werden.

buy

Implementieren Sie die Funktionalität von buy, sodass Benutzer Aktien kaufen können:

  • Verlangen Sie, dass der Benutzer ein Aktiensymbol eingibt, implementiert als Textfeld mit dem Namen symbol. Zeigen Sie eine Entschuldigung an, wenn die Eingabe leer bleibt oder das Symbol nicht existiert (basierend auf dem Rückgabewert von lookup).
  • Verlangen Sie, dass der Benutzer eine Anzahl von Aktien eingibt, implementiert als Textfeld mit dem Namen shares. Zeigen Sie eine Entschuldigung an, wenn die Eingabe keine positive Ganzzahl ist.
  • Übermitteln Sie die Eingaben des Benutzers mit POST an /buy.
  • Nach Abschluss leiten Sie den Benutzer zur Startseite um.
  • Wahrscheinlich werden Sie lookup verwenden, um den aktuellen Preis der Aktie abzurufen.
  • Wahrscheinlich werden Sie den aktuellen Bargeldbestand des Benutzers aus der Liste users mit abfragen.
  • Speichern Sie genügend Informationen, um zu wissen, wer was zu welchem Preis und wann gekauft hat.
  • Zeigen Sie eine Entschuldigung an, ohne den Kauf abzuschließen, wenn der Benutzer sich die Anzahl der Aktien zum aktuellen Preis nicht leisten kann.

index

Implementieren Sie die Funktionalität von index, sodass eine HTML-Tabelle angezeigt wird, die für den aktuell eingeloggten Benutzer zusammenfasst:

  • Welche Aktien der Benutzer besitzt.
  • Die Anzahl der Aktien, die er besitzt.
  • Den aktuellen Preis jeder Aktie.
  • Den Gesamtwert jeder Position (d. h. Anzahl der Aktien mal Preis).
  • Den aktuellen Bargeldbestand des Benutzers sowie einen Gesamtsaldo (d. h. Gesamtwert der Aktien + Bargeld).
  • Wahrscheinlich werden Sie lookup für jede Aktie aufrufen.

sell

Implementieren Sie die Funktionalität von sell, sodass Benutzer Aktien verkaufen können, die sie besitzen:

  • Verlangen Sie, dass der Benutzer ein Aktiensymbol eingibt, implementiert als Dropdown-Menü mit dem Namen symbol. Zeigen Sie eine Entschuldigung an, wenn der Benutzer keine Aktie auswählt oder (irgendwie, nach Übermittlung) keine Anteile an dieser Aktie besitzt.
  • Verlangen Sie, dass der Benutzer eine Anzahl von Aktien eingibt, implementiert als Textfeld mit dem Namen shares. Zeigen Sie eine Entschuldigung an, wenn die Eingabe keine positive Ganzzahl ist oder wenn der Benutzer nicht genügend Anteile der Aktie besitzt.
  • Übermitteln Sie die Eingaben des Benutzers mit POST an /sell.
  • Nach Abschluss leiten Sie den Benutzer zur Startseite um.

history

Implementieren Sie die Funktionalität von history, sodass eine HTML-Tabelle angezeigt wird, die alle Transaktionen eines Benutzers zusammenfasst. Jede Zeile sollte Folgendes enthalten:

  • Ob eine Aktie gekauft oder verkauft wurde.
  • Das Symbol der Aktie.
  • Kauf- oder Verkaufspreis.
  • Die Anzahl der gekauften oder verkauften Aktien.
  • Datum und Uhrzeit der Transaktion.

persönliche Anpassung

Implementieren Sie mindestens eine persönliche Anpassung Ihrer Wahl:

  • Erlauben Sie Benutzern, ihr Passwort zu ändern.
  • Erlauben Sie Benutzern, zusätzliches Bargeld auf ihr Konto zu laden.
  • Erlauben Sie Benutzern, Aktien direkt über die Startseite (index) zu kaufen oder zu verkaufen, ohne die Symbole manuell eingeben zu müssen.
  • Implementieren Sie eine andere Funktion mit vergleichbarem Umfang.

Testen

Testen Sie Ihre Web-App manuell, indem Sie die folgenden Schritte durchführen:

  • Registrieren Sie einen neuen Benutzer und überprüfen Sie, ob die Portfolio-Seite mit den korrekten Informationen geladen wird.
  • Fordern Sie einen Kurs mit einem gültigen Aktiensymbol an.
  • Kaufen Sie eine Aktie mehrfach und prüfen Sie, ob das Portfolio die korrekten Gesamtsummen anzeigt.
  • Verkaufen Sie alle oder einige Anteile einer Aktie und überprüfen Sie erneut das Portfolio.
  • Überprüfen Sie, ob die Seite „Verlauf“ alle Transaktionen des angemeldeten Benutzers anzeigt.

Testen Sie auch ungewöhnliche Nutzungsfälle, indem Sie folgende Szenarien prüfen:

  • Geben Sie Buchstabenfolgen in Formulare ein, die nur Zahlen erwarten.
  • Geben Sie 0 oder negative Zahlen in Formulare ein, die nur positive Zahlen erwarten.
  • Geben Sie Gleitkommazahlen in Formulare ein, die nur Ganzzahlen erwarten.
  • Versuchen Sie, mehr Bargeld auszugeben, als der Benutzer hat.
  • Versuchen Sie, mehr Anteile einer Aktie zu verkaufen, als der Benutzer besitzt.
  • Geben Sie ein ungültiges Aktiensymbol ein.
  • Geben Sie potenziell gefährliche Zeichen wie ’ und ; in SQL-Abfragen ein, um sicherzustellen, dass Ihre Anwendung gegen SQL-Injection geschützt ist.

HTML-Validierung

Überprüfen Sie die Gültigkeit Ihres HTML-Codes, indem Sie auf den „I ♥ VALIDATOR“-Button im Footer jeder Seite klicken. Dadurch wird Ihr HTML an validator.w3.org gesendet und überprüft.

Um Ihren Code mit check50 zu testen, führen Sie folgenden Befehl aus:

check50 -l cs50/problems/2024/x/finance
ℹ️
check50 testet Ihr gesamtes Programm als Einheit. Wenn Sie es ausführen, bevor alle erforderlichen Funktionen implementiert sind, kann es Fehler in Funktionen melden, die eigentlich korrekt sind, aber von noch nicht implementierten Funktionen abhängen.

Style

style50 app.py

Lösung von CS50

Sie können Ihre App gerne anders gestalten, aber hier sehen Sie, wie die Lösung von CS50 aussieht!

https://finance.cs50.net/

Registrieren Sie sich gerne für ein Konto und spielen Sie ein wenig herum. Verwenden Sie jedoch kein Passwort, das Sie auf anderen Seiten verwenden.

Es ist in Ordnung, sich den HTML- und CSS-Code von CS50 anzusehen.

Tipps

  • Um einen Wert als US-Dollar-Betrag (mit Cent-Angabe auf zwei Dezimalstellen) zu formatieren, können Sie den usd-Filter in Ihren Jinja-Templates verwenden (geben Sie Werte als {{ value | usd }} anstatt {{ value }} aus).
  • Sie können zusätzliche statische Dateien zu static/ hinzufügen.
  • Wahrscheinlich möchten Sie die Jinja-Dokumentation konsultieren, wenn Sie Ihre Vorlagen implementieren.
  • Es ist völlig in Ordnung, andere zu bitten, Ihre Seite auszuprobieren (und Fehler zu provozieren).
  • Sie können das Design der Seiten anpassen, z. B. über: