Zahlenreihen taugen vielleicht für gelangweilte Buchhalter, nicht aber für Programmierer. Die bevorzugen Grafiken, besonders wenn sie den Wertverlauf ihres Aktienportfolios beschreiben. Das vorgestellte Perl-Skript zeichnet Turmdiagramme und hilft die Performance im Auge zu behalten.
“Entscheidend ist, was hinten rauskommt”, bemerkte schon Helmut Kohl [1]. Auch bei der Vermögensanlage zählt nicht, was eine einzelne Aktie im Depot treibt, sondern wie sich ein Portfolio entwickelt. Wer das Risiko streuen will, sorgt dafür, dass die Einzelposten aus möglichst diversifizierten Branchen stammen. Die großen Finanzseiten im Web zeigen zwar ansprechende Grafiken der Kursentwicklung einzelner Posten oder vergleichen zwei Wertpapiere, bieten aber kein Tool an, das die Kursentwicklung aller Posten eines Portfolios auf einen Blick zeigt.
Ein in Perl geschriebenes Skript schafft Abhilfe. Abbildung 1 zeigt die Konfigurationsdatei »pofo1.txt« eines Portfolios im Texteditor. Jede Zeile beschreibt einen Kauf (»in«) oder einen Verkauf (»out«) eines Aktienpostens, beginnend mit dem Datum in ISO-Schreibweise, danach die Transaktionsart, das Tickersymbol und die Anzahl. Auch Bargeldtransaktionen nimmt die Datei auf, statt des Tickersymbols steht hier »cash«, als Aktion verwendet es »chk« (Check).

Abbildung 1: Ein Anleger investierte im Januar 2007 ganze 20 000 Dollar in sein Depot (erste Zeile). Mit dem Geld kaufte er dann eine Mischung aus Internetaktien (die folgenden vier Zeilen).
Tippfaul
Damit die Depotverwaltung nicht zur sinnfreien Tipparbeit gerät, berechnet das Skript selbstständig die Kosten und Erlöse der Aktientransaktionen zum jeweiligen Tageskurs und passt den Bargeldbestand an. Gebühren berücksichtigt es nicht – wer die Abweichung ausgleichen will, korrigiert den Saldo manuell mit einem »cash«-Eintrag und der Aktion »chk«. Das Tool verfolgt neben den in den Grafiken gezeigten US-Aktien auch deutsche Wertpapiere, »SIE1.de« ist zum Beispiel das Symbol der Siemens-Aktie an der Xetra-Börse in Euro.
Im Portfolio in Abbildung 1 lagen also am 1.1.2007 genau 20 000 Dollar Bargeld (oder Euro, je nach Währung der Aktien im Depot). Neun Tage später hat sein Besitzer 50 Amazon-, 20 IBM-, 10 Google- und 200 Motorola-Aktien jeweils zum Tageskurs erworben. Während des restlichen Jahres ließ der Anleger seine Finger vom Depot. Die Kursentwicklung dieser vier Aktienpakete stellt Abbildung 3 dar. Während die Amazon- und die Google-Aktien kräftig anzogen, schwächelte Motorola und das Gesamtergebnis am Jahresende litt leicht darunter. In Summe verbuchte das Portfolio jedoch einen leichten Gewinn.
Glückliches Händchen
Anders der Inhaber des Depots in Abbildung 2: Auch er investierte zu Jahresanfang 20 000 Dollar und erwarb sofort 200 Aktien der amerikanischen Drogeriekette CVS (nicht zu verwechseln mit dem gleichnamigen Versionskontrollsystem). Gut eine Woche später kaufte der Spekulant 150 Amazon-Aktien, die er bereits vier Monate später wieder losschlug. Im September sah er einen Kursschub der Google-Aktie voraus und deckte sich mit 30 Stück ein.

Abbildung 2: Dieser aktive Anleger wechselte die Positionen seines Portfolios mehrmals im Jahr. Käufe sind an der Aktion »in« zu erkennen, Verkäufe sind mit »out« bezeichnet.
Der Graph in Abbildung 4 offenbart einen deutlich höheren Gewinn und zeigt auch, dass das Stapeln der Aktienverläufe zu verwirrenden Verschiebungen führt. Die Reihenfolge der Einzelposten bleibt immer gleich. Am Kauf- oder Verkaufstag größerer Posten springen deren Balken aber, besonders der pinkfarbene Bereich am Boden, der den Bargeldbestand symbolisiert.
Die Depotgraphen beider Anleger (Abbildungen 3 und 4) stammen vom Perl-Skript »pofo« (Listing 2), das die Daten der Spekulationskäufe aus Konfigurationsdateien bezieht. Wie das Skript funktioniert steht weiter unten im Artikel. Die Eingabefiles sind in den Abbildungen 1 und 2 zu sehen. Das Skript nimmt sie auf der Kommandozeile entgegen und »pofo pofo1.txt« schreibt nach einer Denkpause die Bilddatei »positions.png« mit dem Graphen.

Abbildung 3: Das per Perl-Skript aus den Daten in Abbildung 1 erzeugt Diagramm stapelt den Kursverlauf jeder einzelnen Aktie aufeinander. Damit ist sowohl die Entwicklung des Depots zu sehen als auch die Performance jedes Einzelpostens.

Abbildung 4: Der Depotbesitzer aus Abbildung 2 beweist ein glückliches Händchen und erspielte am Ende des Jahres einen erklecklichen Gewinn. Die Sprünge in den Kurven entstehen durch An- und Verkäufe.
Tagesaktuell
Für jeden Tag im Graphen ermittelt Pofo die Zusammensetzung des Portfolios, holt die Tageskurse und multipliziert sie mit den Stückzahlen. Das eigentlich zur Darstellung von Netzwerkverkehr und Rechnerauslastung gedachte RRD-Tool (Round Robin Database, [2]) gießt die Tagesdaten in eine übersichtliche Turmgrafik. Den verschiedenen Aktien ordnet es aus einer vorgegebenen Palette Farben zu und erklärt diese Zuweisungen in einer Legende am unteren Rand.
Die historischen Tageskurse aller bekannten Aktien sind online verfügbar, allerdings wäre das Skript unerträglich langsam, falls es diese tatsächlich einzeln für jeden dargestellten Tag einholen würde. Stattdessen bemüht es lieber das Modul »CachedQuote« aus Listing 1. Bei der ersten Frage nach dem Tageskurs einer Aktie holt es gleich alle Kurse in einem Zeitfenster herein, das von einem Jahr in der Vergangenheit bis zum aktuellen Datum reicht. Die nicht sofort gebrauchten Werte lagert es in einer SQLite-Datenbank [3].
|
Listing 1: |
|---|
001 ###########################################
002 package CachedQuote;
003 # Cache stock closing prices
004 # Mike Schilli, 2007 (m@perlmeister.com)
005 ###########################################
006 use strict;
007 use warnings;
008 use Cache::Historical;
009 use Log::Log4perl qw(:easy);
010 use Finance::QuoteHist::Yahoo;
011
012 ###########################################
013 sub new {
014 ###########################################
015 my($class, %options) = @_;
016
017 my $self = {
018 file => "/tmp/cached-quote.dat",
019 %options,
020 };
021
022 $self->{cache} = Cache::Historical->new(
023 sqlite_file => $self->{file});
024
025 bless $self, $class;
026 }
027
028 ###########################################
029 sub quote {
030 ###########################################
031 my($self, $date, $key) = @_;
032
033 my $quote = $self->{cache}->get(
034 $date, $key);
035
036 return $quote if defined $quote;
037 $self->quote_refresh( $date, $key );
038
039 return $self->{cache}->get_interpolated(
040 $date, $key);
041 }
042
043 ###########################################
044 sub quote_refresh {
045 ###########################################
046 my($self, $date, $symbol) = @_;
047
048 my($from, $to) =
049 $self->{cache}->time_range($symbol);
050
051 my $upd = $self->{cache}->
052 since_last_update($symbol);
053
054 # Date available, no refresh
055 if(defined $to and defined $from and
056 $date <= $to and $date >= $from) {
057 DEBUG "Date within, no refresh";
058 return 1;
059 }
060
061 if(defined $date and defined $to and
062 defined $upd and $date > $to and
063 $upd->delta_days < 1) {
064 DEBUG "Date ($date) above cached",
065 " range ($from-$to), but cache ",
066 "is up-to-date.";
067 return 1;
068 }
069
070 my $start = $date->clone->subtract(
071 years => 1 );
072 if(defined $start and defined $from and
073 $start > $from and $to > $start) {
074 # no need to refresh old data
075 $start = $to;
076 }
077
078 $self->quotes_fetch(
079 $start,
080 DateTime->today(),
081 $symbol);
082 }
083
084 ###########################################
085 sub quotes_fetch {
086 ###########################################
087 my($self, $start, $end, $symbol) = @_;
088
089 DEBUG "Refreshing $symbol ",
090 "($start - $end)";
091
092 my $q = Finance::QuoteHist::Yahoo->new(
093 symbols => [$symbol],
094 start_date => date_format($start),
095 end_date => date_format($end),
096 );
097
098 foreach my $row ($q->quotes()) {
099 my($symbol, $date, $open, $high, $low,
100 $close, $volume) = @$row;
101
102 $self->{cache}->set( dt_parse($date),
103 $symbol, $close );
104 }
105 }
106
107 ###########################################
108 sub date_format {
109 ###########################################
110 my($dt) = @_;
111 return $dt->strftime("%m/%d/%Y");
112 }
113
114 ###########################################
115 sub dt_parse {
116 ###########################################
117 my($string) = @_;
118 my $fmt =
119 DateTime::Format::Strptime->new(
120 pattern => "%Y/%m/%d");
121 $fmt->parse_datetime($string);
122 }
123
124 1;
|
Geschwindigkeitsschub
Fordert der Client – wie vorausgesehen – tatsächlich den nächsten Tageskurs an, dann liest »CachedQuote« den Wert einfach aus seinem Datenbankspeicher statt ihn erneut übers Netz zu laden. Der Client bemerkt davon nichts, außer dass das Modul nachfolgende Requests um ein Vielfaches schneller beantwortet.
Falls ein Kunde den Kurs einer Aktie an einem Sonntag einholt, stellt »CachedQuote« fest, dass die geforderten Daten zwar im Zeitfenster liegen, aber für diesen Tag kein Kurs vorhanden ist, da die Börsen der Welt an Wochenenden und Feiertagen nicht arbeiten. In diesem Fall ist »CachedQuote« so schlau, statt eines Datenlochs den letzten vorliegenden Kurs auszuliefern.
Aktienkurs
Das Modul »CachedQuote.pm« holt die Aktienkurse mit dem CPAN-Modul Finance::QuoteHist::Yahoo vom Netz (Listing 1, Zeile 10 sowie 92 bis 104). Als Tageswert nimmt der Cache immer den mit »close« bezeichneten Schlusskurs, den er in der Variablen »$close« ablegt. Pro Web-Request kann der kontaktierte Yahoo-Server für eine Aktie die Kursdaten vieler Jahre liefern. Das nutzt »CachedQuote.pm« und schickt einen Request an den Server, der die Daten von einem Jahr vor dem verlangten Zeitpunkt bis zum aktuellen Datum umfasst (Zeilen 70 und 80). Liegen schon Daten im Cache, verzichtet das Modul auf einen erneuten Request (Zeile 36).
Zum Speichern und späteren Wiederfinden der Kursdaten verwendet »CachedQuote.pm« das CPAN-Modul Cache::Historical. Es bietet mit »set(Datum, Schlüssel, Wert)« eine komfortable Schnittstelle zum Setzen datumsbasierter Werte und liefert die gespeicherten Werte mit den Methoden »get(Datum, Schlüssel)« und »get_interpolated(Datum, Schlüssel)« wieder zurück. Der Parameter »Schlüssel« funktioniert dabei wie der Key in einem Hash. Fehlt ein Kurs für einen Tag, greift »get_interpolated()« auf den letzten verfügbaren Kurs vor dem angegebenen Datum zurück.
Leichte Datenbank
Cache::Historical verwendet hinter den Kulissen eine SQLite-Datenbank, auf die es mit dem Modul DBD::SQLite zugreift. SQLite ist keine freie Software, sondern steht unter einer Public-Domain-Lizenz und das CPAN-Modul liefert kurzerhand den Sourcecode der dateibasierten Datenbank gleich mit. Charmanterweise erlaubt SQLite Abfragen in SQL-Syntax, verzichtet jedoch auf einen Datenbankserver und schreibt stattdessen direkt in eine lokale Datei.
»CachedQuote.pm« stellt die SQLite-Datenbankdatei in Zeile 18 auf »/tmp/cached-quote.dat« ein. Wer den Cache nicht im gefährlichen temporären Verzeichnis haben möchte, überschreibt die Vorgabe im Aufruf des Konstruktors mit »new(sqlite_file => “Dateiname”)«.
Auf Kurs
Die Funktion »quote()« (Zeile 29) versucht zunächst, den geforderten Kurs mit »get()« einzuholen (Zeilen 33 bis 34). Schlägt diese Aktion fehl, dann hinterlässt die Get-Methode einen undefinierten Wert, was Zeile 36 erkennt. Das Modul versucht anschließend per »quote_refresh()«, den Cache rund um das geforderte Datum aufzufrischen. Anschließend sollte »get_interpolated()« in jedem Fall einen ordentlichen Wert zurückliefern.
Zudem unterscheidet der Code, ob ein Tageskurs nur nicht verfügbar ist, weil die Börse an diesem Tag geschlossen war, oder ob der Bereich noch nicht im Cache liegt. Falls das ansteuernde Skript an einem Sonntag läuft, soll das Modul nicht jedes Mal versuchen die neuesten Kurse vom Server zu holen, denn es wird bis zum Montag keine neuen geben.
Die Funktion »quote_refresh()« prüft daher in Zeile 52 mit »since_last_update()« die Zeitspanne seit dem letzten Auffrischen des Cache. Sie liegt als DateTime::Duration-Objekt vor und die Methode »delta_days()« rechnet diese in ganze Tage um. Ist der Cache noch keinen Tag alt, entfällt dieses Update (Zeilen 51, 52 sowie 61 bis 67) und der letzte verfügbare Kurs (üblicherweise vom Freitag) kommt zum Einsatz (Interpolate-Methode in Zeile 39).
Datum und Zeit
Das Interface des DateTime-Moduls vom CPAN ist so komfortabel, dass man eigentlich nichts anderes nutzen möchte – doch das zum Kursholen genutzte Modul Finance::QuoteHist::Yahoo besteht auf Datumsangaben im amerikanischen Format mm/dd/yyyy. Die Funktion »date_format()« ab Zeile 108 bemüht daher die Methode »strftime()« für die Umwandlung der »DateTime«-Objekte.
Den umgekehrten Fall, aus einem Datum im Format mm/dd/yyyy ein »DateTime«-Objekt zu machen, erledigt die Funktion »dt_parse()« ab Zeile 115. Das Modul DateTime::Format::Strptime definiert ein neues Format, dessen »parse_datetime()«-Methode einen hereingereichten String analysiert und im Erfolgsfall ein neues Objekt zurückgibt.
Um von einem »DateTime«-Objekt ein Kalenderjahr zurückzurechnen, genügt es, dessen Methode »subtract()« mit dem Parameter »years => 1« aufzurufen. Allerdings modifiziert dies das Objekt selbst. Wer den ursprünglichen Wert später noch braucht, kopiert den Inhalt vorher mit »clone()« in ein neues Objekt (Zeilen 25 und 26).
Zeilenweise Spekulation
Das Skript »pofo« (Listing 2) nimmt auf der Kommandozeile eine Konfigurationsdatei wie »pofo1.txt« aus Abbildung 1 entgegen. Die Funktion »cfg_read()« ab Zeile 142 arbeitet sich durch deren Zeilen, die jeweils eine Aktientransaktion beschreiben. Sie ignoriert Kommentare, die mit »#« beginnen (Zeile 153), und Zeilen, die nichts als Leerzeichen und Kommentare enthalten (Zeile 155).
|
Listing 2: |
|---|
001 #!/usr/bin/perl -w
002 use strict;
003 use CachedQuote;
004 use DateTime;
005 use RRDTool::OO;
006 use Log::Log4perl qw(:easy);
007 #Log::Log4perl->easy_init($DEBUG);
008
009 my @colors = qw(f35b78 e80707 7607e8
010 0a5316 073f6f 59b0fb);
011 my $cq = CachedQuote->new();
012
013 my($cfg_file) = @ARGV;
014 die "usage: $0 cfgfile" unless $cfg_file;
015
016 my @symbols;
017 my $acts = cfg_read($cfg_file, @symbols);
018 my %pos = ();
019
020 my $end = DateTime->today();
021 my $start = $end->clone->subtract(
022 years => 1);
023
024 for my $act (sort keys %$acts) {
025 next if $acts->{$act}->[0]->[0]
026 >= $start;
027 pos_add(%pos, $_) for @{$acts->{$act}};
028 }
029
030 my $counter = 0;
031 my %symbol_colors;
032 for (@symbols) {
033 my $idx = ($counter++ % @colors);
034 $symbol_colors{$_} = $colors[$idx];
035 }
036
037 unlink my $rrdfile = "holdings.rrd";
038 my $rrd = RRDTool::OO->new(
039 file => $rrdfile,
040 );
041
042 $rrd->create(
043 step => 24*3600,
044 start => $start->epoch() - 1,
045 map({
046 ( data_source => {
047 name => $_,
048 type => "GAUGE",
049 },
050 )} @symbols),
051 archive => { rows => 5000,
052 cfunc => "MAX" }
053 );
054
055 for(my $dt = $start->clone;
056 $dt <= $end;
057 $dt->add( days => 1)) {
058
059 if(exists $acts->{$dt}) {
060 pos_add(%pos, $_) for @{$acts->{$dt}};
061 }
062
063 my %parts = ();
064 my $total = sum_up(%pos, $dt, %parts);
065
066 $rrd->update(
067 time => $dt->epoch(),
068 values => %parts,
069 );
070 }
071
072 $rrd->graph(
073 width => 800,
074 height => 600,
075 lower_limit => 0,
076 image => "positions.png",
077 vertical_label => "Positions",
078 start => $start->epoch(),
079 end => $end->epoch(),
080 map { ( draw => {
081 type => "stack",
082 dsname => $_,
083 color => $symbol_colors{$_},
084 legend => $_,
085 } )
086 } @symbols,
087 );
088
089 ###########################################
090 sub sum_up {
091 ###########################################
092 my($all, $dt, $parts) = @_;
093
094 my $sum = 0;
095
096 for my $tick (keys %$all) {
097 my $q = 1;
098 $q = $cq->quote($dt, $tick) if
099 $tick ne 'cash';
100 my $add = $all->{$tick} * $q;
101 $parts->{$tick} = $add;
102 $sum += $add;
103
104 DEBUG "Add: $all->{$tick} $tick $add";
105 }
106 return $sum;
107 }
108
109 ###########################################
110 sub pos_add {
111 ###########################################
112 my($all, $pos) = @_;
113
114 my($dt, $act, $tick, $n) = @{ $pos };
115 DEBUG "Action: $act $n $tick";
116
117 my $q = 1;
118 $q = $cq->quote($dt, $tick) if
119 $tick ne 'cash';
120 my $val = $n * $q;
121
122 if($tick eq "cash") {
123 $all->{cash} += $val if $act eq "in";
124 $all->{cash} -= $val if $act eq "out";
125 $all->{cash} = $val if $act eq "chk";
126 } else {
127 die "chk: cash-only" if $act eq "chk";
128 if($act eq "in") {
129 $all->{$tick} += $n;
130 $all->{cash} -= $val;
131 } elsif($act eq "out") {
132 $all->{$tick} -= $n;
133 $all->{cash} += $val;
134 }
135 DEBUG "After: $tick: $all->{$tick}";
136 }
137
138 DEBUG "After: Cash: $all->{cash}";
139 }
140
141 ###########################################
142 sub cfg_read {
143 ###########################################
144 my($cfgfile, $symbols) = @_;
145
146 my %by_date = ();
147
148 open FILE, "<$cfgfile" or
149 die "Cannot open $cfgfile ($!)";
150
151 while(<FILE>) {
152 chomp;
153 s/#.*//;
154 my @fields = split ' ', $_;
155 next unless @fields; # empty line
156
157 my $dt = dt_parse( $fields[0] );
158 $fields[0] = $dt;
159
160 push @$symbols, $fields[2] unless
161 grep { $_ eq $fields[2] } @$symbols;
162
163 push @{ $by_date{ $dt } }, [ @fields ];
164 }
165
166 close FILE;
167 return %by_date;
168 }
169
170 ###########################################
171 sub dt_parse {
172 ###########################################
173 my($string) = @_;
174
175 my $fmt = DateTime::Format::Strptime->
176 new( pattern => "%Y-%m-%d" );
177 return $fmt->parse_datetime($string);
178 }
|
Formatwandler
Da die Datumsangaben im Format yyyy/mm/dd vorliegen, hat auch Pofo eine Funktion »dt_parse()« (Zeilen 171 bis 178), die das Format definiert (Zeile 176) und die Datumseinträge in »DateTime«-Objekte umwandelt. Als zusätzlichen Service nimmt die Funktion »cfg_read()« (Zeilen 142 bis 168) eine Referenz auf das Array »@symbols« entgegen, das sie mit allen auftretenden Aktientickersymbolen füllt und dabei Duplikate vermeidet (Zeilen 160, 161).
Die Funktion gibt eine Referenz auf den von ihr gefüllten Hash »%by_date« zurück. Die Schlüssel in diesem Hash sind Datumsangaben in Form stringifizierter »DateTime«-Objekte. Als Werte sind den Schlüsseln jeweils ein Array von Transaktionen zugeordnet, die an diesen Tagen stattgefunden haben (Zeile 163). Jede Transaktion besteht wiederum aus einem Array, das die Felder der zugehörigen Konfigurationszeile enthält, also Datum, Aktion, Tickersymbol und Anzahl der Aktien. Cash-Aktionen stehen auch dort, mit »cash« als Tickersymbol.
Posten für Posten
Um zu sehen, wie viele Aktien einer Sorte an einem bestimmten Tag im Depot liegen, muss das Skript alle Transaktionen abarbeiten, die bis zu diesem Datum im Portfolio erfolgten. Deshalb wühlt sich die For-Schleife ab Zeile 24 zunächst durch alle früheren Aktionen, also durch die Schlüssel im Hash, den bereits in Zeile 17 die Funktion »cfg_read()« geliefert hat.
Die Schleife in Zeile 27 ruft für jede Transaktion die Funktion »pos_add()« auf und markiert das Ergebnis als Depotinhalt in der Variablen »%pos«. Dieser Hash weist jedem im Portfolio enthaltenen Tickersymbol einen numerischen Wert zu. Bei Aktien ist dies die Anzahl, bei Bargeld einfach die Summe.
Aktienkäufe und -verkäufe lösen zusätzlich Bewegung im Bargeldposten aus, denn neue Aktien kosten Geld und der Erlös der verkauften wird dem Girokonto gutgeschrieben. Das erfolgt immer zum jeweiligen Tageskurs, die Daten hierzu liefert »CachedQuote.pm«.
RRD-Tool abstrakt
Mit der grafischen Darstellung der turmartig aufgeschichteten Einzelpositionen hilft RRD-Tool von Tobias Oetiker ([2] und [4]). Die ungewöhnliche Syntax dieses praktischen Tools versucht das objektorientierte CPAN-Modul RRDTool::OO etwas Perl-artiger und übersichtlicher zu gestalten.
Das RRD-Tool legt Daten von so genannten Data Sources in RRD-Archiven ab (Round Robin Databases), indem es die Messpunkte der Datenquellen kumuliert und über die eingestellte Schrittgröße mittelt. Das Pofo-Programm stellt den »step«-Parameter in Zeile 43 von Listing 2 auf 24 Stunden, damit die RRD-Datenbank nur ein Update pro Tag erwartet. Jeder Aktie weist das Programm eine eigene Datenquelle zu (Map-Aufruf in den Zeilen 45 bis 50).
Das RRD-Archiv hält bis zu 5000 Werte (Zeile 51), bevor das RRD-typische Überschreiben beginnt. Da jeder Tag einen Wert liefert, tritt dieser Fall erst bei dargestellten Zeitspannen von weit über zehn Jahren ein. Der Parameterwert »GAUGE« (sprich “Geydsch”, Zeile 48) legt fest, dass RRD-Tool die ankommenden Werte direkt übernimmt und nicht kumuliert – das wäre doppelt gemoppelt.
Allerdings weigert sich RRD-Tool, Werte für Zeiten anzunehmen, die vor dem letzten eingespeisten Tageswert liegen, und so löscht Pofo in Zeile 37 kurzerhand übrig gebliebene RRD-Dateien, die der Konstruktor von RRDTool::OO dann schnell neu erzeugt.
Farbenfroh
Zeile 9 in Listing 2 definiert eine frei gewählte Farbpalette mit RGB-Hexwerten. Aus dem Array »@colors« wählt Pofo in den Zeilen 32 bis 35 für jede dargestellte Aktie einen Wert aus, sodass der Betrachter sie in der Grafik auseinanderhalten kann. Der Hash »%symbol_colors« nimmt die Zuordnung von Symbol zu einer Farbe aus der Palette auf. Die Reihenfolge, in der die Aktionen in der Konfigurationsdatei stehen, bestimmt deren Darstellungsabfolge im Graphen.
Die For-Schleife ab Zeile 55 arbeitet sich durch alle im Graphen darzustellenden Tage. Jedes Mal prüft die If-Bedingung in Zeile 59, ob für diesen Tag Transaktionen vorliegen, und führt diese mit »pos_add()« aus, damit der globale Hash »%pos« die aktuelle Portfolio-Konfiguration enthält. Die Funktion »sum_up()« ermittelt anschließend den Tageswert des Portfolios und legt im Hash »%parts« unter den Schlüsseln der Aktienticker (oder »cash«) die Geldwerte der einzelnen Positionen ab.
Diesen Hash überreicht anschließend die »update()«-Methode des RRD-Objekts an die RRD-Datenbank unter dem Zeitstempel des gerade bearbeiteten Tages (Zeilen 66 bis 69). Die Methode »graph()« zeichnet anschließend den Graphen in der Datei »positions.png« und legt die Legende am unteren Bildrand an (Zeilen 72 bis 87). Das abgedruckte Listing zeigt nur sechs Aktienfarben, es spricht aber nichts dagegen, das Array »@colors« ab Zeile 9 mit neuen Farben zu erweitern.
Erweiterungen
In Zeile 22 setzt Pofo den dargestellten Zeitraum auf das vergangene Jahr. Für andere Zeiträume lassen sich die Variablen »$start« und »$end« modifizieren. Wer mehr Informationen über die internen Abläufe wünscht, streicht in Zeile 7 das Kommentarzeichen. Dann initialisiert sich Log4perl mit »easy_init()« und die über den Quellcode verstreuten Ausgaben der »DEBUG«-Anweisungen landen auf dem Bildschirm.
Mit Stocksplits weiß das Skript nichts anzufangen, denn dann ändern sich die historischen Kursdaten rückwirkend und der Cache enthält ungültige Daten. In diesem Fall löscht der Anleger einfach die Cache-Datei »/tmp/cached-quotes.dat« und verwirft damit den gesamten Cache. Ihn wieder aufzufüllen ist nicht übermäßig kostspielig, denn Web-Requests an den Finanzserver holen die Daten effizient in großen Mengen ein. Viel Spaß beim Spekulieren! (fjl)
|
Infos |
|---|
|
[1] Zitate von Helmut Kohl: [http://de.wikiquote.org/wiki/Helmut_Kohl] [2] RRD-Tool: [http://www.rrdtool.org] [3] Christoph Dalitz, “SQLite – Datenspeicher für Sparsame”: Linux-Magazin 09/04, S. 96 und [http://www.sqlite.org] [4] Michael Schilli, “Daten ausgesiebt – Messdaten mit RRD-Tool und Perl verwalten”: [https://www.linux-magazin.de/heft_abo/ausgaben/2004/06/daten_ausgesiebt] [5] Michael Schilli, “Vom Perl- zum Börsen-Profi”: Linux-Magazin 06/02, S. 110 [6] Listings zum Artikel: [ftp://ftp.linux-magazin.de/pub/listings/magazin/2008/03/Perl] |
|
Der Autor |
|---|
|
|






