Erstelle dein eigenes lemon.markets Dashboard mit Flutter


blog photo
Veröffentlicht von Marius Sprenger am 16. September 2021
Insights


Hallo zusammen! Mein Name ist Marius und ich bin Teil von lemon.markets, einem early-stage Startup aus dem Herzen Berlins. Wir arbeiten an einer Infrastruktur, mit der Du Deine eigene Trading Experience an der Börse bauen kannst. In diesem Blogbeitrag führe ich Dich durch ein Projekt unseres Community-Mitglieds Melanie. Dafür schauen wir uns an, wie man mit Flutter ein mobil-optimiertes Web Dashboard bauen kann. Hört sich das an als wäre es was für Dich? Dann lass uns keine Zeit verschwenden und direkt loslegen.

Die Community war und ist einer der wichtigsten Bestandteile von lemon.markets. Wir bauen ein Produkt für Entwickler:innen, daher ist es nur vernünftig, unsere Nutzer:innen ständig nach ihrem Input zu bestimmten Produktentscheidungen zu fragen. Wir sind auf dem besten Weg, eine aktive Community aufzubauen, die eng in unseren Entwicklungsprozess eingebunden ist. Natürlich werden wir auch weiterhin genau zuhören, was unsere Nutzer:innen sich wünschen.

Neben dem Feedback zu bestimmten API-Funktionen versuchen wir auch besser zu verstehen, was unsere Nutzer:innen tatsächlich bauen. Wir freuen uns zu sehen, wie all die Community-Projekte langsam zum Leben erwachen und wir sind immer bereit zu helfen, wo wir nur können. Vor einiger Zeit haben wir uns an unsere Beta-Nutzer:innen gewandt und gefragt, ob sie Lust haben ihre Projekten mit uns zu teilen, um andere (angehende) Nutzer:innen zu inspirieren. Melanie erzählte uns von ihrem “Flutter-Projekt”, bei dem sie an ihrem eigenen mobilen Dashboard arbeitet, mit dem sie ihr Portfolio bequem verwalten kann. Die Idee hat uns direkt total begeistert!

Melanie ist eine der ersten Personen, die wir bei lemon.markets ongeboarded haben und sie hat seitdem aktiv mitgewirkt. Mit Beginn der Pandemie in 2020 interessierte sie sich immer mehr für die Kapitalmärkte und den Wertpapierhandel.  Da sie jedoch Entwicklerin ist (tagsüber arbeitet sie als Java-Softwareentwicklerin im Logistikbereich) und über das entsprechende Mindset verfügt, war sie auf der Suche nach zuverlässigen Marktdaten und wollte ihre Trades automatisieren. Deshalb begann sie in Deutschland nach einem Anbieter zu suchen, der ihr diese Flexibilität bietet. Glücklicherweise stieß sie dabei auf lemon.markets und erkannte sofort die Vorteile des Produkts. Ohne zu zögern begann sie ihr erstes Projekt damit zu entwickeln: Ein eigenes Dashboard zum Handeln von Wertpapieren, das mit Flutter gebaut wurde.

Sie sagte uns, dass es Spaß macht ein Dashboard zu erstellen, das perfekt an ihre persönlichen Bedürfnisse angepasst ist, anstatt einen Kompromiss einzugehen und irgendeine (Web-)App zu nutzen. Mit lemon.markets kann sie ihr Projekt nach ihren eigenen Wünschen und Vorstellungen bauen.

Wenn sie nicht gerade an der Weiterentwicklung ihres Dashboards arbeitet (obwohl es schwer vorstellbar ist, dass man nicht jede freie Minute mit lemon.markets verbringt 😉), ist sie gerne in ihrem Garten, wo sie sich um ihr angepflanztes Gemüse kümmert. Außerdem nimmt sie gerne weitere technische Projekte in Angriff, wenn sie die Zeit dazu findet. Da sie verheiratet ist und zwei kleine Kinder hat, ist das allerdings nicht immer ganz leicht (es hat wirklich Spaß gemacht, mit Melanie direkt nach der Arbeit und bevor ihre Kinder von der Schule nach Hause kamen zu sprechen).

Wir lieben dieses Projekt, denn es beschreibt perfekt, wie wir selber über lemon.markets denken. Letztendlich sind wir ein API-first Unternehmen und verwenden den Großteil unserer Ressourcen darauf, stabile und leicht zu verwendende Programmierschnittstellen zu entwickeln. Auch wenn es immer eine Reihe von visuellen Berührungspunkten geben wird, werden diese immer nur Ergänzungen zu unseren APIs sein. Andere Unternehmen bieten erfolgreich ein Frontend-First-Erlebnis in der Finanzbranche, aber wir wollen etwas anderes machen: Wir wollen still und leise im Hintergrund arbeiten. Unsere Vision ist, dass in ein paar Jahren eine große Mehrheit der Europäer regelmäßig Aktien bei verschiedenen Anbietern handeln wird, ohne zu wissen, dass diese Anbieter "powered by lemon.markets" sind. Wir haben natürlich noch einen langen Weg vor uns, aber Projekte wie das von Melanie motivieren uns weiterzumachen und unser Produkt stetig zu verbessern/erweitern.

Mit Flutter arbeiten

Melanie hat sich entschieden, ihr eigenes Trading-Dashboard mit Flutter zu bauen. Bevor wir uns mit den Vorteilen von Flutter beschäftigen, sollten wir allerdings erst einmal klären, was Flutter überhaupt ist:

Flutter ist ein Open-Source UI Development Kit von Google, das 2017 veröffentlicht wurde. Es gewann schnell an Popularität, was die folgende Grafik bestätigt, die den Anteil der plattformübergreifenden mobilen Frameworks zeigt, die von Softwareentwickler:innen weltweit von 2019 bis 2021 genutzt wurden.

Source: Statista

Was bedeutet plattformübergreifendes Framework?

Angesichts der steigenden Zahl von Geräten und Betriebssystemen kann es eine zeit- und kostenintensive Herausforderung sein, Apps für jedes Gerät einzeln zu entwickeln. Aus diesem Grund sind in den letzten Jahren plattformübergreifende Frameworks entstanden. Diese Frameworks ermöglichen es Dir, den Code nur einmal zu schreiben und sorgen dafür, dass die Anwendungen/Software auf mehreren verschiedenen Plattformen wie Android, iOS, macOS, Windows oder Linux funktionieren. Natürlich hat die Nutzung dieser Frameworks stetig zugenommen (wir selbst nutzen React Native, um unsere mobile App zu entwickeln) und viele Entwickler:innen bauen damit ihre Projekte, die auf vielen Plattformen (fast) identisch funktionieren. 

Tatsächlich werden einige der am häufigsten genutzten Apps mit plattformübergreifenden Frameworks entwickelt, wie z.B. Facebook, Instagram und Tesla (React Native) oder Google Ads und Groupon (Flutter). Wie Du oben siehst, liefern sich React Native und Flutter ein Kopf-an-Kopf-Rennen, wenn es um den Anteil der Entwickler:innen geht, wobei Flutter React Native in 2021 knapp überholt hat. 

Warum also Flutter?

Wir wollen zwar nicht zu tief in die Diskussion über Flutter vs. React Native einsteigen (es gibt einige interessante Artikel hier, hier und hier), aber wir wollen kurz erklären, warum es eine gute Idee sein kann, ein Projekt mit Flutter zu bauen:

  • Die Dokumentation von Flutter ist sehr ausführlich und erleichtert es Dir mit Deinem Projekt einfach loszulegen. Außerdem gibt es den Flutter Doctor, der Dich durch die Einrichtung von Flutter führt, damit alles wie vorgesehen funktioniert und Du sofort ein neues Projekt bauen kannst. Im Allgemeinen erleichtern die umfangreichen Ressourcen den Einstieg in Flutter deutlich.
  • Die Hot-Reload-Funktion in Flutter ist ein extrem gutes Feature. Sie ermöglicht es Dir, Deine Code-Änderungen sofort umgesetzt und in Aktion zu sehen. Das beschleunigt den Entwicklungsprozess enorm und macht die Zusammenarbeit z. B. mit der Design Agentur äußerst angenehm.
  • Aufgrund der vielen Komponenten von Flutter, kannst Du eigentlich jede mobile App bauen, die Du Dir vorstellen kannst. Dank des mitgelieferten Widgets hast Du jedes UI-Element zur Verfügung, das Du brauchst und musst nicht immer auf Drittanbieter zurückgreifen. Außerdem gibt es eine breit gefächerte Community rund um das Framework, sodass eine Antwort auf jede erdenkliche Frage immer nur einen Klick entfernt ist.

Als wir mit Melanie über die Vorteile von Flutter gesprochen haben, erwähnte sie auch das reichhaltige GUI-Toolkit, mit dem Du sehr einfach schöne Benutzeroberflächen bauen kannst. Außerdem gefällt ihr, dass Du neben der mobilen App auch eine Web-App mit der gleichen Codebasis automatisch erstellen kannst. Und (was vielleicht einer der wichtigsten Argumente ist): Melanie hat eine Menge Spaß mit Flutter.

Erstellen eines lemon.markets Trading Dashboards

Wie bereits erwähnt, war eines von Melanies Hauptargumenten für die Nutzung von lemon.markets die Möglichkeit, ein eigenes Projekt damit zu bauen, in ihrem Fall ein Trading Dashboard. Melanie erkannte schnell den Bedarf an einem Flutter lemon.markets SDK, das ihr das Leben als Entwicklerin um einiges erleichtern würde.

First things first: Ein Flutter lemon.markets SDK

Als Ergebnis der Arbeit am Dashboard-Projekt entstand das Flutter lemon.markets SDK quasi als "Nebenprodukt". Wenn Du Deine eigene mobile App mit lemon.markets entwickeln möchtest, schau Dir gerne das entsprechende GitHub-Repository an.

Mit dem Flutter SDK kannst Du die meisten Funktionen der lemon.markets API bequem nutzen. Du kannst beispielsweise Deinen Status und Deine Spaces einsehen, Dein Portfolio abrufen, eine Order platzieren und aktivieren, frühere Orders und Transaktionen einsehen oder nach bestimmten Wertpapieren suchen. Dies ist ein optimaler Ausgangspunkt, um Deine eigene (mobile) Brokerage-App zu bauen und Du bist herzlich eingeladen auch zu contributen.

Next things next: Bauen des Dashboards

Du kannst Melanies Dashboard-Projekt hier finden. Zunächst siehst Du eine einfache Schnittstelle mit 3 Suchleisten in der unteren Leiste. Über das Einstellungssymbol hast Du die Möglichkeit, dem Dashboard einen Space hinzuzufügen. Du kannst auch bequem nach verschiedenen Wertpapieren suchen, indem Du den “Suche” Tab verwendest. Du kannst außerdem nach Aktien, Anleihen, ETFs usw. zu filtern.

Dashboard-Ansicht. Hier verwenden wir die Suchfunktion, um Tesla Wertpapiere zu finden.

Auf dem Portfolio Tab kannst Du Unterseiten für die Spaces aufrufen, die Du über Deine client ID/client secret mit dem Dashboard verbunden hast. Alle Elemente Deines Portfolios sind untereinander aufgelistet und Du kannst spezifische Informationen abrufen, indem Du auf sie klickst.

Auf der Unterseite die sich öffnet findest Du weitere Optionen. Du siehst eine Grafik, die die Performance des jeweiligen Wertpapiers zeigt und kannst diese nach Tag, Woche oder Monat filtern. Zusätzlich siehst du den aktuellen Preis und hast die Möglichkeit weitere Anteile zu kaufen oder bestehenden Anteile zu verkaufen.

Wenn Du auf den dritten Tab klickst, erhältst Du eine Übersicht über alle Deine Orders und Transaktionen. Du kannst bequem durch die Liste scrollen, um die wichtigsten Informationen auf einen Blick zu sehen. Ziemlich cool, oder?

Sooo: Wie kann ich das jetzt selber machen?

Eine ausführliche Anleitung der Implementierung des Flutter Dashboards würde den Rahmen dieses Blog Post sprengen. Dennoch wollen wir uns kurz ein paar der wichtigsten Schritte anschauen, damit Du ein allgemeines Verständnis für die Projektstruktur entwickelst. Eine beispielhafte Implementierung findest Du hier.

Bevor Du beginnst, solltest Du sicherstellen, dass das Flutter SDK und eine geeignete IDE installiert ist. Wenn Flutter neu für dich ist, solltest Du Dich zuerst mit der Dokumentation vertraut zu machen.

Hinzufügen des lemon.markets SDK zu deinem Projekt

Nachdem Du ein neues Projekt in Deiner IDE erstellt hast, musst Du das lemon.markets SDK installieren, um es in Deinem Projekt verwenden zu können. Da das SDK noch nicht auf pub.dev veröffentlicht ist, musst Du einen anderen Weg wählen:

Gehe zum SDK GitHub Repository und git clone das Projekt. Danach kannst Du das Paket der Datei pubspec.yaml hinzufügen:

1dependencies:
2  flutter:
3    sdk: flutter
4
5  lemon_markets_client:
6    path: "../lemon_market_client"

Alternativ kannst Du auch direkt auf die GitHub-URL verweisen:

1dependencies:
2  flutter:
3    sdk: flutter
4  lemon_markets_client:
5    git:
6      url: https://github.com/stormqueen23/lemonMarketsClient.git

Weitere Informationen wie Du Pakete zu Deiner App hinzufügst, findest Du hier. Jetzt musst Du nur noch Folgendes ausführen:

1flutter pub get

und Du kannst das SDK in Deinem Projekt verwenden.

Erste Schritte

Das Beispielprojekt verwendet neben dem lemon.markets SDK noch einige andere Pakete. Die wichtigsten sind provider und get_it. Das provider-Paket wird verwendet, um den Status der Anwendung zu speichern, und das get_it-Paket verwaltet alle Service Calls.

Als ersten Schritt wollen wir den Homescreen erstellen, der in der main.dart()-Datei zu finden ist und unser einziger Bildschirm in der gesamten App ist. Das können wir wie folgt tun:

1class LemonMarketsDashboardApp extends StatelessWidget {
2  @override
3  Widget build(BuildContext context) {
4    return MaterialApp(
5      title: 'Lemon markets dashboard',
6      theme: ThemeData(
7        primarySwatch: Colors.yellow,
8      ),
9      darkTheme: ThemeData(
10        brightness: Brightness.dark,
11        primarySwatch: Colors.yellow,
12        accentColor: Colors.yellow,
13        tabBarTheme: TabBarTheme(indicator: BoxDecoration(color: Colors.yellow), labelColor: Colors.grey[800], unselectedLabelColor: Colors.yellow),
14      ),
15      themeMode: ThemeMode.dark,
16      home: HomeScreen(),
17    );
18  }
19}

Da Du für die Nutzung des lemon.markets SDK ein lemon.markets-Konto bzw. Space-Zugangsdaten brauchst, wollen wir zwei verschiedene Widgets erstellen. Ein Widget für das Szenario mit bereitgestellten Space-Zugangsdaten und eins ohne Zugangsdaten.

  1. Es können keine Anmeldeinformationen gefunden werden (AddSpaceWidget)
  2. Anmeldeinformationen können gefunden werden (MainTabWidget)
1class HomeScreen extends StatelessWidget {
2  
3  HomeScreen({Key? key}) : super(key: key);
4  @override
5  Widget build(BuildContext context) {
6    return FutureBuilder(
7      future: context.read<LemonMarketsProvider>().init(),
8      builder: (context, projectSnap) {
9        if (projectSnap.connectionState != ConnectionState.done) {
10          return Scaffold(body: LemonLoadingWidget());
11        }
12        return HomeWidget();
13      },
14    );
15  }
16}
17class HomeWidget extends StatelessWidget {
18  const HomeWidget({Key? key}) : super(key: key);
19  @override
20  Widget build(BuildContext context) {
21    AuthData? currentSpace = context.watch<LemonMarketsProvider>().selectedSpace;
22    bool noSpace = currentSpace == null;
23    return noSpace
24        ? Scaffold(
25            appBar: AppBar(
26              centerTitle: true,
27              title: Text('- No Space -'),
28            ),
29            body: Center(child: AddSpaceWidget()),
30          )
31        : MainTabWidget(currentSpace: currentSpace);
32  }
33}

Der lemon.markets Provider

Um herauszufinden, welches Widget angezeigt werden soll, benötigen wir den LemonMarketsProvider. Er verwaltet alle Daten in der App wie beispielsweise den ausgewählten Space. Daher müssen wir den Provider auf einer hohen Ebene in unserem Widget Tree initialisieren, um die relevanten Daten für die gesamte App zu erhalten. Die init()-Methode prüft, ob die Authentifizierungsdaten gespeichert wurden oder nicht.

Wenn keine Authentifizierungsdaten gefunden werden können, wollen wir die Möglichkeit haben, die Anmeldedaten hinzuzufügen. Das AddSpaceWidget bietet zwei Eingabefelder um eine clientID/ ein clientSecret und einen Button, der diese Daten an unseren LemonMarketsProvider sendet:

1String? manualClientId;
2String? manualClientSecret;
3...
4ElevatedButton(
5	onPressed: manualClientId != null && manualClientSecret != null
6		? () => context.read<LemonMarketsProvider>().addSpace(manualClientId!, manualClientSecret!) : null,
7	child: Text('add'),
8)

Der LemonMarketsProvider sammelt dann alle Daten, die benötigt werden: (und merkt sich die Daten optional)

1class LemonMarketsProvider with ChangeNotifier {
2	LemonMarketService _marketService = GetIt.instance<LemonMarketService>();
3	
4	AuthData? selectedSpace;
5	...
6	
7	Future<void> addSpace(String clientId, String clientSecret) async {
8		AuthData authData = AuthData(clientId, clientSecret);
9		
10		AccessToken? _token = await _marketService.requestToken(authData.clientId, authData.clientSecret);
11		authData.token = _token;
12		
13		ResultList<Space>? spacesForId = await _marketService.getSpaces(authData.token!);
14		
15		if (spacesForId != null) {
16		  if (spacesForId.result.isNotEmpty) {
17			authData.spaceUuid = spacesForId.result.first.uuid;
18			authData.spaceName = spacesForId.result.first.name;
19		  }
20		}
21		
22		selectedSpace = authData;
23		notifyListeners();
24  }
25  
26}

Nach diesem ersten Schritt haben wir unseren ersten Kontakt mit dem lemon.markets SDK, da wir einen Zugriffstoken für die angegebene clientID und das clientSecret abrufen wollen. Nachdem wir den Token erhalten haben, wollen wir zusätzliche Informationen für den Space erhalten (z. B. die UUID, da wir diese für einige Endpunkte benötigen). Da das Ziel darin besteht, alle API-Calls in einem Dienst zu bündeln, haben wir den LemonMarketService.

Der LemonMarketService

Das ist der Ort, an dem alle SDK-Calls stattfinden. Als Erstes müssen wir eine  LemonMarkets Instanz erstellen. Über diese Klasse haben wir Zugriff auf alle Endpunkte der lemon.markets API, die das lemon.markets Flutter SDK unterstützt. Jetzt brauchen wir eine Funktion, um einen Zugriffstoken anzufordern und detaillierte Space Daten zu erhalten:

1class LemonMarketService {
2 
3  LemonMarkets _market = LemonMarkets();
4  Future<AccessToken?> requestToken(String clientId, String clientSecret) async {
5    try {
6      AccessToken result = await _market.requestToken(clientId, clientSecret);
7      return result;
8    } on LemonMarketsException catch (e) {
9      throw LemonMarketsError(e.toString());
10    }
11  }
12  
13  Future<ResultList<Space>?> getSpaces(AccessToken token) async {
14    try {
15      ResultList<Space> result = await _market.getSpaces(token);
16      return result;
17    } on LemonMarketsException catch (e) {
18      throw LemonMarketsError(e.toString());
19    }
20  }
21}

Jetzt haben wir alle erforderlichen Daten (Access Token und Space UUID), die es uns ermöglichen, weitere Requests zu stellen und so alle benötigten Daten zu erhalten.

The MainTabWidget

Nachdem Du Deine Anmeldedaten eingegeben hast, wirst du auf einen Bildschirm mit zwei Tabs geleitet. Der Tab "Suche" und der Tab "Portfolio". Beide Tabs haben ein oder mehrere Widgets für die Datenanzeige und einen Anbieter für deren Statusverwaltung. 

Die Suchfunktion

Der Tab "Suche" ist sehr einfach. Er enthält einen Dropdown-Button für die verschiedenen Wertpapiere, ein Textfeld und einen Button zum Starten der Suche. Die verschiedenen Sucharten werden durch das enum SearchType aus dem lemon.markets SDK dargestellt. In das Textfeld kannst Du die gewünschte Suchanfrage eingeben und anschließend auf den Button klicken, die die Suche startet. Im Widget:

1IconButton(
2  icon: Icon(
3    Icons.search,
4  ),
5  onPressed: () {
6    if (!searching) {
7      context.read<SearchProvider>().searchInstruments(widget.authData);
8    }
9  },
10)

Für die Suche benötigst Du den AccessToken, den Du im LemonMarketsProvider erhalten hast. Die Suche wird im SearchProvider durchgeführt, der sie an den MarketService weiterleitet:

1class SearchProvider with ChangeNotifier {
2  LemonMarketService marketService = GetIt.instance<LemonMarketService>();
3  
4  String? searchString;
5  SearchType searchType = SearchType.stock;
6  
7  List<Instrument> instruments = [];
8  String? previousUrl;
9  String? nextUrl;
10  
11  void searchInstruments(AuthData authData) {
12    marketService
13        .searchInstruments(authData.token!, search: searchString, type: searchType)
14        .then(
15          (result) {
16            if (result != null) {
17              this.instruments = result.result;
18              this.nextUrl = result.next;
19              this.previousUrl = result.previous;
20            }
21          },
22    );
23    notifyListeners();
24  }
25}

Der MarketService braucht noch eine weitere Methode:

1Future<ResultList<Instrument>?> searchInstruments(AccessToken token, {String? search, SearchType? type, bool? tradable, String? currency, String? limit, int? offset}) async {
2    try {
3      ResultList<Instrument> result = await _market.searchInstruments(token, currency: currency, limit: limit, offset: offset, query: search, tradable: tradable, types: type != null ? [type] : null);
4      return result;
5    } on LemonMarketsException catch (e) {
6      throw LemonMarketsError(e.toString());
7    }
8  }

Wie Du sehen kannst, führt die Suche nach Wertpapieren zu dem Typ ResultList. Dieser Typ aus dem lemon.markets SDK, enthält neben den aktuellen Ergebnissen auch eine URL für die vorherigen und nächsten Ergebnisse. Du kannst diese URL für die Paginierung verwenden. Je nachdem, ob die nächste (oder vorherige) URL gesetzt ist, wird ein Button angezeigt, die die Suche nach der nächsten URL im Widget auslöst:

1context.watch<SearchProvider>().nextUrl != null ? IconButton(
2   icon: Icon(
3      Icons.forward,
4   ),
5   onPressed: () {
6      if (!searching) {
7         context.read<SearchProvider>().searchNext(widget.authData);
8      }
9   },
10)
11: Container(),

Im SearchProvider:

1void searchNext(AuthData authData) {
2 if (nextUrl != null) {
3   marketService
4       .searchInstrumentsByUrl(authData.token!, nextUrl)
5       .then(
6         (result) {
7       if (result != null) {
8         this.instruments = result.result;
9         this.nextUrl = result.next;
10         this.previousUrl = result.previous;
11       }
12     },
13   );
14   notifyListeners();
15 }
16}

In the MarketService:

1Future<ResultList<Instrument>?> searchInstrumentsByUrl(AccessToken token, String url) async {
2   try {
3      ResultList<Instrument> result = await _market.searchInstrumentsByUrl(token, url);
4      return result;
5   } on LemonMarketsException catch (e) {
6      throw LemonMarketsError(e.toString());
7   }
8}

Für alle API-Calls, die den Typ ResultList anzeigen, gibt es eine zweite Methode, bei der Du nur den AccessToken und die URL als Parameter brauchst. Hierbei lautet die vollständige Funktion:

1searchInstruments(token, currency: currency, limit: limit, offset: offset, query: search, tradable: tradable, types: type != null ? [type] : null)

und die entsprechende Funktion nur mit der URL:

1searchInstrumentsByUrl(token, url)

Abschließend brauchen wir ein Widget, dass das Ergebnis unserer Wertpapiersuche anzeigt. In unserem Beispiel ist dies eine ListView:

1ListView(
2   children: _getAllInstruments(context),
3)
4...
5List<Card> _getAllInstruments(BuildContext context) {
6   List<Card> result = [];
7   context.watch<SearchProvider>().instruments.forEach((element) {
8      ListTile tile = ListTile(
9         title: Text('${element.title}'),
10         subtitle: Text('${element.isin}'),
11        );
12      result.add(Card(child: tile));
13   });
14   return result;
15}

So, das war's. Unsere Suchfunktion ist fertig. Zeit, sich ein wenig auszuruhen.

Der Portfolio Tab

Hast Du tief durchgeatmet? Gut, dann lass uns weitermachen.

Der Tab “Portfolio” ist etwas komplexer. Er sammelt zunächst Daten von verschiedenen Endpunkten, bevor er die (aggregierten) Informationen anzeigt. Ähnlich wie bei der "Suche" gibt es einen Provider, der die Daten speichert und verschiedene Widgets, um sie anzuzeigen.

Werfen wir einen Blick auf den Portfolio Provider. Während der init()-Methode sammelt er alle Daten, die benötigt werden, um die Grundstruktur der Portfolioansicht mit allen Elementen im Portfolio und dem Saldo des aktuellen Spaces anzuzeigen. Das heißt, er:

  1. empfängt den aktuellen Stand des Spaces (zum Anzeigen des Saldos und des zu investierenden Geldes)
  2. empfängt alle Portfolioelemente.
1class PortfolioProvider with ChangeNotifier {
2   ...
3   Future<void> init(AuthData authData) async {
4      spaceStateDetails = await _marketService.getSpaceState(authData);
5      items = await _marketService.getPortfolioItems(authData);
6      ...
7   }
8}

Hierfür brauchen wir zwei neue Methoden in unserem MarketService:

1Future<SpaceState?> getSpaceState(AuthData authData) async {
2 try {
3   SpaceState state = await _market.getSpaceState(authData.token!, authData.spaceUuid!);
4   return state;
5 } on LemonMarketsException catch (e) {
6   throw LemonMarketsError(e.toString());
7 }
8}
9Future<List<PortfolioItem>> getPortfolioItems(AuthData authData) async {
10 List<PortfolioItem> result = [];
11 try {
12   ResultList<PortfolioItem> tmp = await _market.getPortfolioItems(authData.token!, authData.spaceUuid!);
13   result.addAll(tmp.result);
14   String? nextUrl = tmp.next;
15   while (nextUrl != null) {
16     tmp = await _market.getPortfolioItems(authData.token!, nextUrl);
17     result.addAll(tmp.result);
18     nextUrl = tmp.next;
19   }
20 } on LemonMarketsException catch (e) {
21   throw LemonMarketsError(e.toString());
22 }
23 return result;
24}

Auch hier gibt das lemon.markets SDK etwas vom Typ ResultList für den Portfolio items Endpunkt zurück.

1ResultList<PortfolioItem> tmp = await _market.getPortfolioItems(authData.token!, authData.spaceUuid!);

Wir haben dies bereits in der Methode searchInstruments() gesehen, gehen hier aber etwas anders vor. Da wir alle Elemente für die Berechnung der aktuellen Portfoliosumme benötigen, wiederholen wir das Aufrufen der Funktion um alle Portfolioelemente zu erfassen bis keine Elemente mehr zu finden sind.

1while (nextUrl != null) {
2     tmp = await _market.getPortfolioItems(authData.token!, nextUrl);
3     ...
4}

Nachdem wir den Space-Status und die Portfolioelemente erhalten haben, verfügen wir nun über alle Daten, um die Grundstruktur unserer Portfolioliste anzuzeigen und können mit der Ausführung der UI beginnen. Wir verwenden einen FutureBuilder, um ein loadingWidget anzuzeigen, bis alle Daten in der init()-Methode gesammelt wurden.

1FutureBuilder(
2   future: context.read<PortfolioProvider>().init(widget.authData),
3   builder: (context, projectSnap) {
4     if (projectSnap.connectionState != ConnectionState.done) {
5       // space state and portfolio items are loading
6       return Center(child: CircularProgressIndicator());
7     }
8     // space state and portfolio items received!
9     return Padding(
10       padding: const EdgeInsets.all(16.0),
11       child: PortfolioWidget(),
12     );
13   },
14 ),

Der letzte Schritt der init()-Methode ist der Call der Endpunkte für den aktuellsten Kurs für jedes Portfolioelement und alle Orders. Wir verwenden den letzten Kurs, um den aktuellen Wert der einzelnen Portfolioelemente zu berechnen und anzuzeigen. Die Orders werden verwendet, um die Kauf- und Verkaufsdaten für jedes Element anzuzeigen. Wir müssen nicht auf das Ergebnis dieser Calls warten, da die UI in dem Moment aktualisiert werden kann, in dem ein Ergebnis empfangen wird. Im PortfolioProvider:

1Future<void> init(AuthData authData) async {
2   ...
3   _initLatestQuoteForItems(authData);
4   _initOrdersForItems(authData);
5}
6void _initLatestQuoteForItems(AuthData authData) {
7 items.forEach((element) {
8   _marketService.getLatestQuote(authData, element.instrument.isin).then((value) {
9     latestQuotes[element.instrument.isin] = value;
10     if (_latestQuotesInitialized()) {
11       _calculateSum();
12       sumInitialized = true;
13     }
14     notifyListeners();
15   });
16 });
17}
18void _initOrdersForItems(AuthData currentSpace) {
19 _marketService.getOrders(currentSpace, null, OrderStatus.executed).then((value) {
20   value.forEach((element) {
21     existingOrders[element.instrument.isin]?.add(element);
22     if (element.processedAt != null) {
23       notifyListeners();
24     }
25   });
26 });
27}

Deshalb brauchen wir zwei neue Methoden im MarketService:

1Future<Quote?> getLatestQuote(AuthData authData, String isin) async {
2 try {
3   Quote? result;
4   ResultList<Quote>? all = await _market.getLatestQuotes(authData.token!, [isin]);
5   result = all.result.where((element) => element.isin == isin).first;
6   return result;
7 } on LemonMarketsException catch (e) {
8   throw LemonMarketsError(e.toString());
9 }
10}
11Future<List<ExistingOrder>> getOrders(AuthData authData, OrderSide? side, OrderStatus? status) async {
12 List<ExistingOrder> result = [];
13 try {
14   ResultList<ExistingOrder> tmp = await _market.getOrders(authData.token!, authData.spaceUuid!, side: side, status: status);
15   result.addAll(tmp.result);
16   String? nextUrl = tmp.next;
17   while (nextUrl != null) {
18     tmp = await _market.getOrdersByUrl(authData.token!, nextUrl);
19     result.addAll(tmp.result);
20     nextUrl = tmp.next;
21   }
22 } on LemonMarketsException catch (e) {
23   throw LemonMarketsError(e.toString());
24 }
25 return result;
26}

Das Ergebnis des getOrders Endpunkts fällt wieder unter den Typ ResultList und wie wir bereits in der getPortfolioItems()-Methode gesehen haben, wollen wir alle abrufen, um alle Daten für den Kauf und Verkauf eines Wertpapiers herauszufinden. Also rufen wir den Endpunkt für die Orders immer wieder auf, bis es keine nextUrl mehr gibt.

Das ist so ziemlich alles, was wir brauchen, um den Tab Portfolio anzuzeigen. Abschließend benötigen wir noch einige Widgets, um unsere Daten anzuzeigen. Wir haben eine erweiterbare Kopfzeile, die die Summen für das Portfolio anzeigt und eine listView, die alle Informationen zu den Portfolioelementen anzeigt:

1class PortfolioWidget extends StatelessWidget {
2 
3  @override
4  Widget build(BuildContext context) {
5    return Column(
6      children: [
7        PortfolioHeaderWidget(),
8        Container(
9          height: 8,
10        ),
11        Expanded(
12          child: PortfolioItemsListWidget(),
13        ),
14      ],
15    );
16  }
17}

Uuuund damit ist unser kleines Beispiel-Tutorial abgeschlossen. Wie Du siehst, haben wir mit dem lemon.markets Flutter SDK ein Dashboard mit zwei Tabs (Suche und Portfolio) gebaut. Wir hoffen, Du konntest allen Schritten folgen und hast Lust, Deine eigene mobile App mit lemon.markets zu bauen 👩‍💻👨‍💻.

Mögliche Erweiterungen

Obwohl Melanie's Dashboard bereits viele unserer Endpunkte nutzt, um ein individuelles Trading Dashboard zu bauen, gibt es noch (fast) eine Million möglicher Erweiterungen und wir wollen diese im Folgenden kurz würdigen.

Du könntest zum Beispiel die zeitliche Entwicklung Deines Portfolios und einzelner Portfolioelemente anhand eines Diagramms darstellen. Oder, Du könntest ein schönes Tortendiagramm erstellen, um die Verteilung der Portfoliopositionen zu sehen.

Wie wäre es, wenn Du die Preisentwicklung für die Wertpapiere aktualisieren würdest, damit sie neben den OHLC-Daten auch die Kurse anzeigt? Oder vielleicht Echtzeit-Streaming der Kursdaten auf der Seite mit den Wertpapieren einfügen (das geht natürlich nur, wenn wir die Live-Marktdaten bereitstellen, wir arbeiten daran 😬).

Du könntest auch externe Datenquellen nutzen, um noch mehr Informationen über verschiedene Wertpapiere bereitzustellen. Wie wäre es, wenn Du auf den Unterseiten der einzelnen Wertpapiere einen Nachrichtenbereich einrichtest, in dem aktuelle Presseartikel angezeigt werden, z.B. wenn Tesla erneut eine verrückte Funktion vorstellt, an der sie arbeiten (menschenähnliche Roboter, die herumlaufen. Ernsthaft, Elon?).

Wir freuen uns und sind auch ein bisschen stolz, dass wir Melanies Flutter-Projekt vorstellen konnten. Wenn man so early-stage ist wie wir, ist es wirklich schön zu sehen, wie Community-Projekte entstehen und unsere Auffassung von lemon.markets perfekt verkörpern. Wir hoffen, Dir hat dieser Artikel gefallen und dass Du inspiriert wurdest, Dein eigenes Dashboard (oder allgemein Trading Projekt) zu bauen.

Noch einmal: Das GitHub Repo für den lemon.markets Flutter Wrapper findest Du hier, ein Beispielprojekt hier und die aktuelle Projektversion hier.

Und vergiss natürlich nicht, Dich hier bei lemon.markets anzumelden.

Wir freuen uns darauf, Dich schon bald auf lemon.markets begrüßen zu dürfen.

Marius und das 🍋.markets Team

Das könnte Dich auch interessieren

Mit OpenFIGI und lemon.markets ein Tickersymbol einer ISIN zuordnen

blog photo

Wenn Du an verschiedenen Börsen tradest, wirst Du feststellen, dass diese meist unterschiedliche Methoden haben um Wertpapiere zu identifizieren. Die US-Börsen verwenden beispielsweise häufig Ticker, während die deutschen Börsen auf eine ISIN verweisen. Und manchmal ist das Wechseln zwischen diesen Identifiern nicht so einfach, wie man es erwarten würde. Stattdessen kannst Du den Prozess automatisieren, indem Du einen (weniger als 10 Zeilen) Code schreibst, der die "Übersetzung" für Dich übernimmt. Lies weiter, um zu erfahren, wie Du die OpenFIGI- und lemon.markets-APIs nutzen kannst, um einem Ticker die entsprechenden ISIN zuzuordnen.

10 Fehler beim (automatisierten) Trading und wie Du sie vermeidest

blog photo

Hallo! Mein Name ist Joanne und ich bin Teil des lemon.markets-Teams in Berlin. Wir bauen eine Trading API, mit der Entwickler:innen ihre eigene Trading Experience gestalten können. Außerdem wollen wir eine Umgebung schaffen, in der Nutzer:innen sich zu der Schnittmenge von  Algorithmus-Entwicklung und dem Aktienmarkt austauschen können. Wir wollen zu dieser Diskussion auch etwas beitragen und haben deshalb eine Liste mit zehn Fehlern zusammengestellt, die Anfänger im automatisierten Handel (um ehrlich zu sein, manchmal auch erfahrene Profis) machen können. Damit sie Dir nicht passieren, haben wir zusätzlich erklärt, wie Du sie vermeiden kannst.

Integration von lemon.markets in Deinen Telegram-Bot (Teil 2 von 2)

blog photo

Hallo! Ich bin Joanne und gehöre zum Team von lemon.markets. Wir sind ein Berliner Start-up, das automatisiertes Trading über APIs ermöglicht. Unser Ziel ist es, Entwicklern und Entwicklerinnen alle Werkzeuge zur Verfügung zu stellen, mit denen sie ihre eigene Trading Experience an der Börse bauen können. Dabei gibt es hunderte Use-Cases für unser Produkt, wie beispielsweise ein eigenes Frontend zum Platzieren von Orders. Dafür kannst Du bei Null anfangen oder einen bereits existierenden Dienst wie beispielsweise Telegram nutzen. In diesem Artikel erweitere ich das Projekt, das wir in unserem letzten Artikel vorgestellt haben.

Tiefer eintauchen

Finde weitere Ressourcen für einen einfachen Einstieg

In unserer Dokumentation erfahrt Ihr mehr über unsere API-Struktur, die verschiedenen Endpunkte und spezifische Anwendungsfälle.

Austauschen

Tritt der lemon.markets Community bei

Tritt unserem Slack-Channel bei, um Dich aktiv an unserer Community zu beteiligen, Fragen an andere Nutzer:innen zu stellen und immer auf dem Laufenden zu bleiben.

Team unterstützen

Lust lemon.markets mit uns zu bauen?

Wir sind immer auf der Suche nach großartigen Ergänzungen für unser Team, die uns beim Aufbau einer Brokerage Infrastruktur für das 21. Jahrhundert helfen.

Products
Pricing
For Developers
SlackGithubBlog
© lemon.markets 2021Privacy PolicyImprint
All systems normal