# Finanzen

Quelle: https://dev.inf.zone/exercises/13/finance/

## Aufgabe

**Auf einen Blick**

- **Was:** Sie vervollständigen C$50 Finance, eine Web-App mit Flask, in der Benutzer zum Schein Aktien kaufen und verkaufen – zu echten Kursen.
- **Codegerüst:** Ordner `finance` mit `app.py`, `helpers.py`, `users.json`, `static/` und `templates/`. Registrieren, Einloggen und Ausloggen funktionieren schon.
- **Ihre Arbeit:** die Routen `quote`, `buy`, `index`, `sell` und `history` in `app.py` samt Templates, dazu eine persönliche Anpassung.
- **Daten:** Welche Aktien jemand besitzt und was er gekauft und verkauft hat, speichern Sie selbst in JSON-Dateien (keine Datenbank).
- **Fehlerfälle:** Bei ungültiger Eingabe zeigt die App eine Entschuldigung (`apology`) an und ändert nichts.

### So funktioniert der Aktienhandel in der App

1. **Symbol:** Jede Aktie hat ein Kürzel, etwa `NFLX` für Netflix.
2. **Kurs:** `lookup(symbol)` aus `helpers.py` fragt den aktuellen Preis bei einem Dienst von CS50 ab (`https://finance.cs50.io/quote?symbol=NFLX`, ohne API-Schlüssel). Die Kurse sind nicht in Echtzeit, ändern sich aber mit der Zeit.
3. **Startkapital:** Wer sich registriert, bekommt 10.000 US-Dollar Bargeld.
4. **Kaufen:** Anzahl × aktueller Kurs wird vom Bargeld abgezogen. Reicht das Bargeld nicht, findet der Kauf nicht statt.
5. **Verkaufen:** Anzahl × aktueller Kurs wird dem Bargeld gutgeschrieben. Verkaufen kann man nur Aktien, die man besitzt.
6. **Gesamtwert:** Bargeld plus, für jede Aktie, Anzahl × aktueller Kurs.

**Beispiel** (Kurse erfunden):

| Schritt            | Kurs NFLX | NFLX im Besitz | Bargeld    | Wert der Aktien | Gesamt     |
| ------------------ | --------- | -------------- | ---------- | --------------- | ---------- |
| registrieren       | –         | 0              | $10,000.00 | $0.00           | $10,000.00 |
| 10 NFLX kaufen     | $72.00    | 10             | $9,280.00  | $720.00         | $10,000.00 |
| Kurs steigt        | $75.00    | 10             | $9,280.00  | $750.00         | $10,030.00 |
| 4 NFLX verkaufen   | $75.00    | 6              | $9,580.00  | $450.00         | $10,030.00 |

Der Dienst antwortet im JSON-Format, einer Liste von Schlüssel-Wert-Paaren in geschweiften Klammern:

```
{"companyName":"Netflix Inc. Common Stock","latestPrice":71.35,"symbol":"NFLX"}
```

Für ein unbekanntes Symbol antwortet er mit einer Fehlermeldung (`{"error":"Invalid symbol"}`). `lookup` macht daraus ein `dict` mit den Schlüsseln `name` (`str`), `price` (`float`) und `symbol` (`str`, immer großgeschrieben, egal wie es übergeben wurde). Ist das Symbol unbekannt oder der Dienst nicht erreichbar, gibt `lookup` `None` zurück. Im selben JSON-Format speichert die App auch ihre eigenen Daten, etwa die Benutzer in `users.json`.

Was Aktien überhaupt sind, steht am Ende der Seite unter „Zum Weiterlesen“.

## Demo

![finance](https://dev.inf.zone/exercises/13/finance/./finance.png)

So sieht die Lösung von CS50 aus: [finance.cs50.net](https://finance.cs50.net/). Registrieren Sie sich gern und probieren Sie die App aus, verwenden Sie aber kein Passwort, das Sie auch anderswo nutzen. Sie dürfen sich den HTML- und CSS-Code dieser Lösung ansehen; Ihre App darf aber auch anders aussehen.

## Aufgabenmaterial

Für diese Aufgabe werden Sie ein von uns zur Verfügung gestelltes Codegerüst vervollständigen.

**Aufgabenmaterial herunterladen**

Öffnen Sie VS Code entsprechend Ihrem [Setup](https://dev.inf.zone/extras/setup/), klicken Sie auf Ihr Terminalfenster und führen Sie `cd` aus. Die Eingabeaufforderung Ihres Terminalfensters sollte ungefähr wie folgt aussehen:

```bash
$
```

Führen Sie den Befehl
```bash
wget https://dev.inf.zone/download/exercises/13/finance.zip
```
aus, um in Ihrem Codespace die ZIP `finance.zip` herunterzuladen.

Nun können Sie
```bash
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
```bash
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
```bash
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:
```bash
finance/ $
```

Falls alles erfolgreich war, führen Sie den folgenden Befehl aus:
```bash
ls
```
Sie sollten nun die folgenden Dateien und Ordner sehen:
```
app.py  helpers.py  static/  templates/  users.json
```
Falls es zu Problemen kommt, wiederholen Sie dieselben Schritte und versuchen Sie herauszufinden, wo ein Fehler aufgetreten sein könnte!

Starten Sie im Ordner `finance` den eingebauten Webserver von Flask und öffnen Sie die URL, die er ausgibt:

```bash
flask run
```

Registrieren und Einloggen funktionieren bereits; alle übrigen Seiten zeigen vorerst nur eine Entschuldigung („TODO“).

| Datei        | Inhalt |
| ------------ | ------ |
| `app.py`     | die Routen. Fertig: `login`, `logout`, `register`. Noch `apology("TODO")`: `index`, `quote`, `buy`, `sell`, `history`. Dazu `read_users` und `write_users` für `users.json`. |
| `helpers.py` | `apology(message, code=400)`, `login_required`, `lookup(symbol)`, `usd(value)` |
| `users.json` | die Benutzer, anfangs leer (`{}`) |
| `static/`    | `styles.css` – dürfen Sie beliebig ändern |
| `templates/` | `layout.html` (Navigationsleiste, Block `main`, Flash-Meldungen), `login.html`, `register.html`, `apology.html` |

**Überblick über das Codegerüst**

**`app.py`:** Nach den Importen (darunter `json` und die Hilfsfunktionen aus `helpers.py`) wird [Jinja](https://jinja.palletsprojects.com/en/stable/) mit dem Filter `usd` konfiguriert, der Werte als US-Dollar formatiert. Flask speichert Sitzungen im lokalen Dateisystem statt, wie sonst üblich, in (digital signierten) Cookies. `after_request` schaltet das Caching von Antworten ab, damit der Browser Änderungen immer sieht.

- `read_users` liest `users.json` ein und gibt den Inhalt als Dictionary zurück (oder ein leeres Dictionary, falls die Datei fehlt); `write_users` schreibt ein solches Dictionary zurück in die Datei.
- `login` vergleicht mit `check_password_hash` das eingegebene Passwort mit dem gespeicherten Hash. Nach erfolgreichem Login merkt sich die Route die ID des Benutzers (den Schlüssel seines Eintrags in `users.json`) als `session["user_id"]` und leitet auf `/` weiter. So können andere Routen prüfen, wer eingeloggt ist.
- `logout` leert die Sitzung.
- `register` legt einen neuen Eintrag in `users.json` an, mit 10.000 US-Dollar Startkapital. Der Benutzername muss eindeutig sein, das Passwort muss zweimal gleich eingegeben werden. Danach ist der Benutzer eingeloggt und landet auf der Startseite.
- Die Routen `index`, `quote`, `buy`, `sell` und `history` sind mit `@login_required` dekoriert: Wer nicht eingeloggt ist, landet auf der Login-Seite.

**`helpers.py`:** `apology` rendert die Vorlage `apology.html`; die innere Funktion `escape` ersetzt dafür Sonderzeichen in der Meldung und ist nur innerhalb von `apology` sichtbar. `login_required` ist ein Beispiel für eine Funktion, die eine Funktion zurückgibt – keine Sorge, wenn sie kryptisch wirkt. `lookup` ist oben unter „So funktioniert …“ beschrieben. `usd` formatiert einen `float` als US-Dollar, etwa `1234.56` als `$1,234.56`.

**`templates/`:** `login.html` und `register.html` enthalten im Wesentlichen ein HTML-Formular, gestaltet mit [Bootstrap](https://getbootstrap.com/). `apology` übergibt `message` als `bottom` und `code` als `top` an `apology.html`; schauen Sie sich an, wie die Vorlage die beiden Werte verwendet. `layout.html` enthält eine Navigationsleiste für Handy und Desktop und definiert den Block `main`, in den die anderen Vorlagen ihren Inhalt einfügen. Außerdem zeigt es Flasks [Flash-Meldungen](https://flask.palletsprojects.com/en/stable/quickstart/#message-flashing) an, mit denen Sie eine Nachricht von einer Route zur nächsten weitergeben können.

## Spezifikation

### Daten speichern

`users.json` ist ein JSON-Objekt. Die Schlüssel sind die IDs der Benutzer (als Strings), die Werte Objekte mit diesen Feldern:

| Feld         | Typ    | Inhalt                         |
| ------------ | ------ | ------------------------------ |
| `"username"` | String | Benutzername                   |
| `"hash"`     | String | gehashtes Passwort             |
| `"cash"`     | Float  | verfügbares Bargeld in US-Dollar |

```json
{
    "1": {
        "username": "Alice",
        "hash": "hashed_password_1",
        "cash": 10000.0
    },
    "2": {
        "username": "Bob",
        "hash": "hashed_password_2",
        "cash": 10000.0
    }
}
```

`read_users` liest die Datei mit `json.load()` als Dictionary ein. JSON kennt als Schlüssel nur Strings: Nach dem Einlesen heißt der Schlüssel `"1"`, nicht `1`. `session["user_id"]` enthält genau diesen String, Sie können also direkt `users[session["user_id"]]` schreiben. Änderungen landen erst in der Datei, wenn Sie `write_users` aufrufen.

Das Codegerüst speichert bisher nur die Benutzer mit ihrem Bargeld. Für die folgenden Routen müssen Sie außerdem festhalten, welche Aktien ein Benutzer besitzt und welche Käufe und Verkäufe er getätigt hat:

- Speichern Sie diese Daten in JSON-Dateien, mit möglichst wenig Redundanz.
- Sie können die Einträge in `users.json` um weitere Felder ergänzen oder neue JSON-Dateien anlegen; `read_users` und `write_users` zeigen, wie man solche Dateien liest und schreibt.
- Jeder gespeicherte Eintrag muss sich über die ID einem Benutzer zuordnen lassen.

### Routen im Überblick

| Route          | Methoden  | Zweck                                |
| -------------- | --------- | ------------------------------------ |
| `/quote`       | GET, POST | Kurs einer Aktie anzeigen            |
| `/buy`         | GET, POST | Aktien kaufen                        |
| `/` (`index`)  | GET       | Portfolio des Benutzers als Tabelle  |
| `/sell`        | GET, POST | Aktien verkaufen                     |
| `/history`     | GET       | alle Käufe und Verkäufe als Tabelle  |

Wo unten „Entschuldigung“ steht, rufen Sie `apology` mit einer passenden Meldung auf.

### quote

- **GET:** Rendern Sie eine Vorlage (z. B. `quote.html`) mit einem Formular, das ein Textfeld `symbol` per POST an `/quote` sendet.
- **POST:** Entschuldigung, wenn das Symbol leer ist oder nicht existiert (`lookup` gibt `None` zurück). Sonst rendern Sie eine zweite Vorlage (z. B. `quoted.html`), in die Sie einen oder mehrere Werte aus `lookup` einbetten.

### buy

- **GET:** Formular mit einem Textfeld `symbol` und einem Textfeld `shares` (Anzahl der Aktien), das per POST an `/buy` sendet.
- **POST, Entschuldigung**
  - wenn `symbol` leer ist oder das Symbol nicht existiert (Rückgabewert von `lookup`),
  - wenn `shares` keine positive Ganzzahl ist,
  - wenn sich der Benutzer die Aktien zum aktuellen Kurs nicht leisten kann; der Kauf findet dann nicht statt.
- **POST, sonst:** Ziehen Sie den Preis vom Bargeld ab und schreiben Sie den neuen Bargeldbestand mit `write_users` zurück. Speichern Sie genug, um zu wissen, wer was zu welchem Preis und wann gekauft hat. Leiten Sie danach auf die Startseite um.

### index

Eine HTML-Tabelle, die für den eingeloggten Benutzer zusammenfasst:

- welche Aktien er besitzt und wie viele jeweils,
- den aktuellen Kurs jeder Aktie,
- den Gesamtwert jeder Position (Anzahl × Kurs),
- sein aktuelles Bargeld und den Gesamtwert (Aktien + Bargeld).

### sell

- **GET:** Formular mit einem Dropdown-Menü `symbol` für die Aktien des Benutzers und einem Textfeld `shares`, das per POST an `/sell` sendet.
- **POST, Entschuldigung**
  - wenn keine Aktie ausgewählt ist oder der Benutzer (irgendwie, nach dem Absenden) keine Anteile an dieser Aktie besitzt,
  - wenn `shares` keine positive Ganzzahl ist oder der Benutzer nicht so viele Anteile besitzt.
- **POST, sonst:** Schreiben Sie den Erlös zum aktuellen Kurs dem Bargeld gut, verringern Sie den Bestand und speichern Sie den Verkauf. Leiten Sie danach auf die Startseite um.

### history

Eine HTML-Tabelle mit allen Transaktionen des Benutzers, eine Zeile pro Transaktion:

- gekauft oder verkauft,
- Symbol,
- Kauf- oder Verkaufspreis,
- Anzahl der Aktien,
- Datum und Uhrzeit.

### Persönliche Anpassung

Implementieren Sie mindestens eine dieser Erweiterungen:

- Benutzer können ihr Passwort ändern.
- Benutzer können zusätzliches Bargeld auf ihr Konto laden.
- Benutzer können Aktien direkt auf der Startseite (`index`) kaufen oder verkaufen, ohne das Symbol eintippen zu müssen.
- eine andere Funktion mit vergleichbarem Umfang.

## Hilfestellung

Klicken Sie auf die folgenden Tipps, um einige Ratschläge zu erhalten. Versuchen Sie aber zunächst, selbst so weit wie möglich zu kommen.

**Daten in JSON-Dateien**

- Für eine neue JSON-Datei können Sie sich zwei Funktionen nach dem Vorbild von `read_users` und `write_users` schreiben.
- Rufen Sie die Schreibfunktion nach jeder Änderung auch wirklich auf. Prüfen Sie nach einem Kauf oder Verkauf in der Datei selbst, ob der neue Eintrag angekommen ist.
- Für `buy` werden Sie den aktuellen Kurs mit `lookup` und das Bargeld mit `read_users` lesen; für `index` rufen Sie `lookup` für jede Aktie auf.

**Templates und Gestaltung**

- Beträge in US-Dollar mit zwei Nachkommastellen geben Sie in Jinja mit dem Filter `usd` aus: `{{ value | usd }}` statt `{{ value }}`.
- Die [Jinja-Dokumentation](https://jinja.palletsprojects.com/en/stable/) hilft beim Schreiben der Vorlagen.
- Zusätzliche statische Dateien legen Sie in `static/` ab.
- Die mitgelieferten Vorlagen verwenden Bootstrap; Sie müssen aber nicht damit arbeiten und können wie gewohnt eigenes HTML, CSS und JavaScript schreiben.
- Das Aussehen können Sie anpassen, z. B. mit:
  - [bootswatch.com](https://bootswatch.com)
  - [getbootstrap.com/docs/5.3/content](https://getbootstrap.com/docs/5.3/content/)
  - [getbootstrap.com/docs/5.3/components](https://getbootstrap.com/docs/5.3/components/)
  - [memegen.link](https://memegen.link)

## Testen

Testen Sie Ihre Web-App von Hand. Es ist völlig in Ordnung, andere zu bitten, Ihre Seite auszuprobieren (und Fehler zu provozieren).

**Normalfälle**

- Registrieren Sie einen neuen Benutzer und prüfen Sie, ob die Portfolio-Seite mit den richtigen Angaben lädt.
- Fragen Sie den Kurs zu einem gültigen Aktiensymbol ab.
- Kaufen Sie dieselbe Aktie mehrmals und prüfen Sie, ob das Portfolio die richtigen Summen anzeigt.
- Verkaufen Sie alle oder einige Anteile einer Aktie und prüfen Sie das Portfolio erneut.
- Prüfen Sie, ob der Verlauf alle Transaktionen des eingeloggten Benutzers zeigt.

**Sonderfälle** – jeweils sollte eine Entschuldigung erscheinen:

- Buchstaben, wo nur Zahlen erwartet werden,
- 0 oder negative Zahlen, wo nur positive Zahlen erwartet werden,
- Gleitkommazahlen, wo nur Ganzzahlen erwartet werden,
- mehr Bargeld ausgeben, als der Benutzer hat,
- mehr Anteile verkaufen, als der Benutzer besitzt,
- ein ungültiges Aktiensymbol.

### Korrektheit

Führen Sie in Ihrem Terminal den folgenden Befehl aus, um die Korrektheit Ihrer Arbeit zu überprüfen:

```bash
check50 -l inf-zone/exercises/2026/finance
```

### HTML-Validierung

Prüfen Sie Ihr HTML mit dem „I ♥ VALIDATOR“-Knopf im Footer jeder Seite. Er schickt das HTML der Seite an [validator.w3.org](https://validator.w3.org).

### Style

Führen Sie den folgenden Befehl aus, um den Stil Ihres Codes mit `style50` zu analysieren:

```bash
style50 app.py
```

## Abgeben

Geben Sie im Ordner `finance` ab:

```bash
inf upload finance
```

Danach sehen Sie, welche Tests Ihr Programm besteht und ob die Übung für die Bonuspunkte zählt. Wie Sie `inf` installieren und sich anmelden, steht unter [Abgeben mit `inf`](https://dev.inf.zone/faq/uebung-solutions/#inf-installieren).

## Zum Weiterlesen: Aktien und Bootstrap

Für die Lösung der Aufgabe nicht nötig.

Eine Aktie ist ein Anteil an einem Unternehmen. Wenn Sie sich nicht ganz sicher sind, was es bedeutet, Aktien zu kaufen und zu verkaufen, finden Sie [hier](https://www.investopedia.com/articles/basics/06/invest1000.asp) ein Tutorial. C$50 Finance bildet das nach: Sie sehen echte Kurse und den Wert Ihres Portfolios, „kaufen“ und „verkaufen“ aber nur zum Schein.

Die Kurse kommen über eine API (Application Programming Interface): eine Schnittstelle, die ein Programm über URLs abfragt, statt eine Webseite für Menschen auszuliefern. Das Symbol steckt dabei in der URL; so weiß der Dienst, welche Daten er liefern soll.

[Bootstrap](https://getbootstrap.com/) ist ein beliebtes Open-Source-Framework für Websites, die sich an verschiedene Bildschirmgrößen anpassen. Es bringt fertige Gestaltungselemente mit – Layouts, Buttons, Formulare, Navigationselemente –, die auf HTML, CSS und JavaScript aufbauen und auf Handy wie Desktop gut funktionieren.
