Blick auf den Wohnzimmertisch

Wer Kinder im Grundschulalter hat, erfährt zwangsläufig, dass ein fußballerisches Großereignis naht. Sobald die Abziehbildchen aus dem Hause Panini (oder zumindest die leeren Rückseiten) wild in der gesamten Wohnung verstreut liegen, weiß man: Es ist wieder soweit. Für Eltern (und Lehrer), denen dieser Sammelwahn ein Dorn im Auge ist, gibt es aber zumindest eine tröstliche Nachricht: Wer sich ein wenig mit dem Panini-Universum beschäftigt, lernt dabei mehr über das Investment-Banking als in so manch einem Universitätsseminar (zumindest wenn es sich um ein Seminar der BWL handelt…)

Was kostet das klebrige Vergnügen?

Aufmerksam auf die Gemeinsamkeiten zwischen Panini und Investment-Banking wurde ich jüngst durch einen Beitrag in der Welt, der sich damit beschäftigt, wie hoch der Erwartungswert der Ausgaben ist, die man tätigen muss, um das Sammelalbum zu komplettieren.In Kurzform lauten die “Spielregeln” wie folgt:

  • Es gibt 680 Klebebildchen, Panini beteuert, dass jedes Bild in gleichen Mengen produziert wird und die Wahrscheinlichkeit, dass ein Bild in einem Tütchen enthalten ist, für jeden Spieler identisch ist (sogar für Christiano Ronaldo…)
  • Das Sammelalbum selbst kostet 2 EUR.
  • Eine 5er-Packung Bildchen kostet 70 Cent, wobei niemals ein Bild mehrfach in einer Packung enthalten ist.

Zwei Experten, zwei Meinungen

Der besagte Artikel in der Welt zitiert zwei Experten, die gleich zwei unterschiedliche Antworten geben: Ein Experte kommt zu dem Ergebnis, dass im Schnitt 522 EUR verausgabt werden, bis das Album komplett ist, ein zweiter Experte kommt auf ein Ergebnis von 672 EUR.

Nanu?!? – Werden Sie sich denken. Hier handelt es sich doch um eine mathematische Problemstellung, für die es eine – und nur eine – korrekte Lösung geben kann. Mal abgesehen davon, dass es entgegen weitläufiger Ansicht auch in der Mathematik zu mehreren “richtigen” Lösungen für ein Problem kommen kann (z.B. beim Bertrand Paradox): Ein Experte wird sich vermutlich irren. Interessanterweise haben die beiden Mathematikprofessoren unterschiedliche Pfade beschritten: Der erste Professor hat versucht, die Aufgabe mit Hilfe des klassischen Instrumentariums der Wahrscheinlichkeitsrechnung zu lösen.

Der zweite Professor hat einen gänzlich anderen Weg eingeschlagen:

Behrends habe dies mit einer Computersimulation herausgefunden, in der er eine Million Mal den Versuch durchspielen ließ, ein komplettes Album zu füllen.

Die Monte-Carlo-Simulation

Der zweite Professor hat damit genau auf das “Toolkit” zurückgegriffen, was im Investmentbanking unter dem Begriff “Monte Carlo Simulation” bekannt ist. Es eignet sich besonders dann, wenn eine detailgetreue Modellierung komplexer Problemstellungen erforderlich ist und ist aus dem modernen Derivatpricing nicht mehr wegzudenken.

Grundsätzliche Idee

Kernstück dieser Lösung ist ein Zufallszahlengenerator und die Erkenntnis, dass eine (hier durch den Mittelwert) normierte unabhängig identisch verteilte Zufallsvariable wie z.B. die Kosten, die bis zur Komplettierung des Sammelalbums anfallen, bei ausreichend häufiger Wiederholung des Experiments gegen den Erwartungswert konvergiert, sofern dieser endlich ist. (Das ist mein Versuch, das zweite kolmogorovsche Gesetz der großen Zahlen griffig zu formulieren – Vollblutmathematiker hätten zu dieser Formulierung gewiss noch die eine oder andere Verbesserung…)

Vorgehensweise

Ein einzelner Simulationslauf umfasst folgende Schritte:

  1. Mit Hilfe eines Zufallszahlengenerators lässt man ein Computerprogramm jeweils 5 überschneidungsfreie Zahlen aus dem Bereich 1 bis 680 ziehen. Dies simuliert den Kauf einer Packung Panini-Bilder. Man kann sich das auch als Lottoziehung von 5 aus 680 Kugeln vorstellen.
  2. Dies führt man so lange durch, bis jedes der 680 Bilder einmal gezogen wurde. Jeder simulierte Kauf eines Panini-Päckchens erhöht die Ausgaben um 0,70 EUR.
  3. Die bis zur Komplettierung des Albums aufgelaufene Summe der Ausgaben stellt somit eine Realisation der Zufallsvariable “Kosten für ein komplettes Album” dar.

Die Schritte 1-3 werden nun massenhaft (z.B. 1 Mio. mal) durchgeführt, die Summe der Ausgaben aus Schritt 3 jeweils aufaddiert und am Ende durch die Anzahl der Simulationsläufe geteilt (mithin: gemittelt). Damit erhält man einen ziemlich guten Schätzer für die Ausgaben, mit denen man im Schnitt für ein Panini Album rechnen muss.

Ein solches Monte-Carlo-Programm können geübte Programmierer in weniger als einer Stunde aufsetzen. Ich habe das Problem mal in Python ausprogrammiert, da diese Sprache komfortablerweise bereits alle nötigen “Bauteile” (Zufallsgenerator, Listenverwaltung usw.) bereithält.

import random

NUMSTICKERS = 680
STICKERS_IN_PACKAGE = 5
PRICE_PER_PACKAGE = 0.7
BOOKLET_PRICE = 2.0
NUMCOLLECTORS = 1
SIMULATION_RUNS = 10000

ALL_STICKERS = tuple(range(NUMSTICKERS))

class Collection(object):
    def __init__(self):
        self.totalCost = BOOKLET_PRICE
        self.needed = [1] * NUMSTICKERS
        self.doubles = [0] * NUMSTICKERS
        
    def isComplete(self):
        for n in self.needed:
            if n==1:
                return False
        return True
    
    def buyPackage(self):
        self.totalCost += PRICE_PER_PACKAGE
        stickersBought = random.sample(ALL_STICKERS, STICKERS_IN_PACKAGE)
        for sticker in stickersBought:
            if self.needed[sticker]==1:
                self.needed[sticker]=0
            else:
                self.doubles[sticker] += 1            
                
class Collector():
    def __init__(self):
        self.collection = Collection()
        self.isActive = True
        
    def buyPackage(self):
        self.collection.totalCost += PRICE_PER_PACKAGE
        stickersBought = random.sample(ALL_STICKERS, STICKERS_IN_PACKAGE)
        for sticker in stickersBought:
            if self.collection.needed[sticker] == 1:
                self.collection.needed[sticker] = 0
            else:
                self.collection.doubles[sticker] += 1
        if self.collection.isComplete():
            self.isActive = False

sum = 0.0
for i in range(SIMULATION_RUNS):
    c = Collector()
    while c.isActive:
        c.buyPackage()
    sum += c.collection.totalCost
print("Estimator for expected value:", sum/SIMULATION_RUNS)

Der Nachteil von Python besteht allerdings darin, dass es nicht sonderlich schnell läuft. Das Komplettieren eines einzelnen Albums benötigt auf meinem Celeron 877 Prozessor rund 40 ms, so dass ich mich auf 10’000 Simulationsläufe beschränke.

Der Erwartungswert für das Komplettieren eines Albums (inklusive Erwerb des leeren Sammelhefts für 2 EUR) beläuft sich dabei auf 677 EUR, was schon recht nah an den 672 EUR des zweiten Professors liegt.

Die Ergebnisse der Monte-Carlo-Simulation wurden mittlerweile auch durch eine analytische Herleitung bestätigt. Dem ersten Professor ist demnach ein Fehler bei der Modellierung der 5er-Päckchen unterlaufen. Eine korrigierte Rechnung, die auf im Mittel rund 963 Päckchen (also 963 x 0,7 = 674 EUR) kommt, findet sich bei Stackexchange.

Warum sind Broker nützlich?

Bislang enthalten alle Simulationen eine ziemlich realitätsfremde Annahme: ein Sammler kauft für sich alleine so lange Bilder, bis sein Album schließlich komplett ist. Doppelte Bilder wandern umgehend in den Müll – das sind bei 963 Päckchen, die im Mittel erforderlich sind, 6 von 7 gekauften Bildern.

Die Wirklichkeit sieht jedoch so aus, dass diese Bilder mit anderen getauscht werden. Hat man erst einmal das Grundprogramm geschrieben, so lassen sich diese Details aus der Wirklichkeit durch einige kleine Erweiterungen einbauen:

import random
import itertools

NUMSTICKERS = 680
STICKERS_IN_PACKAGE = 5
PRICE_PER_PACKAGE = 0.7
BOOKLET_PRICE = 2.0
STICKERS_ORDERABLE_DIRECTLY = 0
MAIL_ORDER_COST = 10.0

NUMCOLLECTORS = 5
SIMULATION_RUNS = 100

ALL_STICKERS = tuple(range(NUMSTICKERS))

class Collection(object):
    def __init__(self):
        self.totalCost = BOOKLET_PRICE
        self.needed = [1] * NUMSTICKERS
        self.doubles = [0] * NUMSTICKERS
        
    def isComplete(self):
        if sum(self.needed) <= STICKERS_ORDERABLE_DIRECTLY:
            return True
        return False
                
class Collector():
    def __init__(self, group):
        self.isActive = True
        self.collection = Collection()
        self.circle = group
        self.enrollGroup()
        self.moneySpent = BOOKLET_PRICE
        
    def buyPackage(self):
        self.moneySpent += PRICE_PER_PACKAGE
        stickersBought = random.sample(ALL_STICKERS, STICKERS_IN_PACKAGE)
        for sticker in stickersBought:
            if self.collection.needed[sticker] == 1:
                self.collection.needed[sticker] = 0
            else:
                self.collection.doubles[sticker] += 1
        if self.collection.isComplete():
            if STICKERS_ORDERABLE_DIRECTLY > 0:
                self.moneySpent += MAIL_ORDER_COST
            self.circle.groupAmountSpent += self.moneySpent
            self.leaveGroup()


    def enrollGroup(self):
        if not self in self.circle.collectors:
            self.circle.collectors.append(self)
        
    def leaveGroup(self):
        self.isActive = False
        if self in self.circle.collectors:
            self.circle.collectors.remove(self)

class Group():
    
    @staticmethod
    def produceIndexSet(countArray):
        """Helper function which produces a set of numbers from index array"""
        indexSet = set()
        for i, val in enumerate(countArray):
            if (val>0):
                indexSet.add(i)
        return indexSet
    
    def __init__(self):
        self.collectors = []
        self.cardsTraded = 0
        self.groupAmountSpent = 0.0
        
    def hasActiveCollectors(self):
        return len(self.collectors) > 0

    def bilateralExchange(self):
        combos = list(itertools.combinations(self.collectors, 2))
        for participant in combos:
            aHas = Group.produceIndexSet(participant[0].collection.doubles)
            aNeeds = Group.produceIndexSet(participant[0].collection.needed)
            bHas = Group.produceIndexSet(participant[1].collection.doubles)
            bNeeds = Group.produceIndexSet(participant[1].collection.needed)
            aCouldGiveToB = list(aHas & bNeeds)
            bCouldGiveToA = list(aNeeds & bHas)
            numberOfTradableCards = min(len(aCouldGiveToB), len(bCouldGiveToA))
            for cardNumber in aCouldGiveToB[:numberOfTradableCards]:
                participant[0].collection.doubles[cardNumber] -= 1
                participant[1].collection.needed[cardNumber] = 0
            for cardNumber in bCouldGiveToA[:numberOfTradableCards]:
                participant[1].collection.doubles[cardNumber] -= 1
                participant[0].collection.needed[cardNumber] = 0
            self.cardsTraded += numberOfTradableCards

for i in range(SIMULATION_RUNS):
    theHood = Group()
    folks = [Collector(theHood) for i in range(NUMCOLLECTORS)]
    while(theHood.collectors):
        for folk in theHood.collectors:
            folk.buyPackage()
        theHood.bilateralExchange()

print('Number of internally traded cards:', theHood.cardsTraded)
print('Average amount spent', theHood.groupAmountSpent/NUMCOLLECTORS)

Bereits bei 2 Tauschpartnern erhalte ich (bei 1000 Simulationsläufen mit Tauschen) einen deutlich niedrigeren Schätzer für den Erwartungswert der Kosten eines vollständigen Albums i.H.v. 567 EUR. Finden sich 5 Tauschpartner so verringern sich die mittleren Ausgaben auf 255 EUR.

Voraussetzung für einen schnellen und optimalen Tausch ist allerdings, dass es jemanden gibt, der nachhält, welche doppelten Bilder jeder Teilnehmer hat und welche Bilder jeder noch benötigt. Genau diese Funktion erfüllt der Kursmakler (neuhochdeutsch “Broker”). Die einzelnen Marktteilnehmer melden Positionen, die sie an- oder verkaufen wollen und der Broker führt passende Meldungen zusammen.

Makler sind “KM-Systeme”

Die Simulationsergebnisse verdeutlichen zwei Sachverhalte:

  1. Durch Tauschen können die Sammelkosten deutlich gesenkt werden.
  2. Je mehr Mitglieder untereinander tauschen, desto stärker ist die Ersparnis jedes einzelnen Mitgliedes.

Insbesondere der zweite Punkt hatte eine besondere Bedeutung für den Konzentrationsprozess bei den Kursmaklern. Broker sind Kritische-Masse-Systeme: je mehr Kunden das System nutzen, desto besser funktioniert es – und das nicht etwa nur weil die Fixkosten auf mehr Geschäfte umgelegt werden können, sondern vielmehr weil der unter 2. beschriebene Netzwerkeffekt sich entfaltet. Es ist ähnlich wie bei WhatsApp: Es gibt eine Menge von Instant Messenging Systemen die mindestens genauso komfortabel wie WhatsApp sind und dabei weniger ausfall- oder spionageanfällig sind. Die Vorteilhaftigkeit von WhatsApp für jeden einzelnen ergibt sich vielmehr daraus, dass alle anderen dieses System nutzen.

Gewiefte Broker wie Michael Spencer erkannten dies Ende der 90er Jahre und begannen, ihren Kundenkreis dadurch zu erweitern, dass sie in großem Stil kleine Maklerfirmen aufkauften und deren Kunden an das eigene Netzwerk anschlossen.

Die Fintechs sind die neuen Broker

Wie so oft im Prozess der kreativen Zerstörung wird jedoch das Geschäftsmodell der klassischen “Voice Brokerage”, bei dem die Geschäfte per Standleitung gemakelt werden, vom technischen Fortschritt bedroht: Wie auch im Panini-Spiel wird der Leistungskern – nämlich die Verwaltung und Auswertung der Informationen durch der IT erbracht. Die Software hält Buch darüber, welcher Sammler welche Bilder doppelt hat und welche er noch benötigt. Und es ist die Software, die schneller als jeder Mensch passende Tauschpartner zusammenführt.

Nichts anderes widerfuhr auch bei den Brokern: Die Makler selbst waren lediglich die Schnittstelle, über die die Banken ihre Suchmeldungen ins System einspeisten. Es war nur ein kleiner Schritt, die bestehende Software um eine Internetschnittstelle zu erweitern, über die nunmehr die Banken ihre Aufträge direkt in das Matching-System der Makler einspeisen konnten.

Obgleich die großen Voice-Broker eigentlich in einer idealen Lage gewesen wären, diesen Wandel zu vollziehen – sie verfügten bereits über Kunden und Matching-Software – haben sie diese Entwicklung jedoch weitgehend verschlafen – vermutlich auch deshalb, weil die dort beschäftigten (wohl gesättigten) Senior Partner der großen Maklergesellschaften keine besondere Lust verspürten, sich durch eine Mitwirkung am neuen elektronischen Handel noch schneller überflüssig zu machen. Abgeräumt haben den Markt Firmen wie TradeWeb (über die ein Großteil des Interbanken-Zinshandels läuft) oder 360T (Devisengeschäfte).

Dieser Beitrag erschien zuerst auf dem Hobbykeller-Blog.