Mit Perl gestrickte Turmgrafiken zeigen den zeitlichen Wertverlauf eines Aktienportfolios und helfen, Diversifizierung und Performance im Auge zu behalten.
``Entscheidend ist, was hinten rauskommt'' sagte schon Helmut Kohl. Auch bei der Vermögensanlage zählt nicht, was eine einzelne Aktie im Depot treibt, sondern wie sich ein Portfolio aus möglichst diversen Einzelposten entwickelt. Die großen Finanzseiten auf dem Internet zeigen zwar ansprechende Grafiken zur Kursentwicklung einzelner Posten und lassen sogar grafische Vergleiche zweier Wertpapiere zu, bieten aber kein Tool an, das die Kursentwicklung der Einzelposten eines Portfolios auf einen Blick zeigt.
Abbildung 1: Ein Anleger legt im Januar 20.000 Dollar in Internetaktien an. |
Abbildung 2: Starke Gewinner übertönen die Verlierer und am Jahresende verbleibt ein kleiner Gewinn. |
Ein in Perl handgestricktes Skript schafft Abhilfe. Abbildung 1
zeigt die Konfigurationsdatei pofo1.txt
eines Portfolios. Jede
Zeile stellt eine Kauf- ('in') oder Verkaufsaktion ('out') eines
Aktienpostens dar. Auch Bargeld kann man so rein- und rausschieben, statt
dem Aktienticker-Symbol steht bei diesen Aktionen 'cash'.
Damit das Ganze nicht in zu viel Tipparbeit ausartet, soll das Portfolio selbständig die Kosten und die Erlöse der Aktientransaktionen zum aktuellen Tageskurs ausrechnen und den Bargeldbestand entsprechend anpassen. Das stimmt natürlich nicht genau, da eventuell noch Gebühren anfallen, aber das lässt sich leicht korrigieren, in dem der aktuelle Kontostand mit dem Symbol ``cash'' und der Aktion ``chk'' und Datum gesetzt wird, damit der Saldo wieder stimmt.
Das Tool verfolgt natürlich auch deutsche Aktien, wenn die richtigen Tickersymbole vorliegen, ``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. Neun Tage später wurden 50 Amazon-, 20 IBM-, 10 Google- und 200 Motorola-Aktien zum Tageskurs erworben. Während des restlichen Jahres lehnte sich der Anleger zurück und ließ das Portfolio wachsen und gedeihen. Die Kursentwicklung dieser vier Aktienpakete lässt sich in Abbildung 2 grafisch verfolgen. Während die Amazon- und die Google-Aktie kräftig anzogen, schwächelte Motorola und das Gesamtergebnis am Jahresende litt leicht darunter. Unterm Strich konnte das Portfolio jedoch einen leichten Gewinn verbuchen.
Anders der Inhaber des Portfolios in Abbildung 3: Auch hier lagen zu Jahresanfang 20.000 Dollar, und sofort wurden 200 Aktien der amerikanischen Drogeriekette CVS (nicht zu verwechseln mit dem gleichnamigen Versionskontrollsystem) zum aktuellen Tageskurs gekauft. Gut eine Woche später kaufte der Spekulant 150 Amazon-Aktien, die er dann 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 3: Ein aktiver Anleger, der die Positionen mehrmals im Jahr wechselt und ein glückliches Händchen hat ... |
Abbildung 4: ... erspielt am Ende des Jahres einen erklecklichen Gewinn. |
Der Graph in Abbildung 4 bestätigt einen deutlich höheren Gewinn am Jahresende und zeigt auch, dass das Stapeln mehrerer Graphen zu verwirrenden Verschiebungen führen kann, obwohl die Reihenfolge der Einzelposten immer gleich bleibt.
Die Anzeige der Vermögensverhältnisse dieses glücklichen Depotbesitzers
entsteht mittels des Skripts pofo
, das die Daten über die getätigten
Spekulationskäufe aus den gezeigten Konfigurationsdateien bezieht.
Diese nimmt das Skript auf der Kommandozeile entgegen und pofo pofo1.txt
werkelt dann eine Weile vor sich hin und erzeugt schließlich
eine Bild-Datei positions.png
mit dem Graphen.
Für jeden dargestellten Tag ermittelt es die aktuelle Konfiguration des
Portfolios, holt die aktuellen Tageskurse ein und multipliziert diese mit
den Stückzahlen der Einzelposten. Das eigentlich zur Darstellung von
Netzwerkverkehr und Rechnerauslastung gedachte Tool rrdtool
verpackt
diese Tagesdaten in eine übersichtliche Turmgrafik. Den verschiedenen
Aktien ordnet es aus einer vorgegebenen Farbpalette zufällig Farben zu
und weist diese Zuweisungen in einer Legende am unteren Rand der Grafik aus.
Die historischen Tageskurse aller bekannten Aktien sind auf dem
Internet verfügbar, allerdings wäre das Skript unerträglich langsam falls es
diese tatsächlich einzeln für jeden dargestellten Tag einholen würde.
Statt dessen bemüht es das Modul CachedQuote
, das nicht nur den
angeforderten Tageskurs einer Aktie einholt, sondern gleich alle Kurse
in einem Zeitfenster, das von einem Jahr in der Vergangenheit bis zum
aktuellen Datum reicht. Die nicht sofort gebrauchten Werte speichert
es in einer SQLite-Datenbank zwischen. Fordert der Client
-- wie vorausgesehen -- tatsächlich den nächsten Tageskurs an, liest
CachedQuote
den Wert einfach aus seinem Speicher. Der Client bekommt
davon nichts mit, außer dass nachfolgende Requests um ein vielfaches
schneller gehen.
Falls ein Kunde den Kurs einer Aktie an einem Sonntag einholt, stellt
CachedQuote
fest, dass die geforderten Daten zwar im eingeholten
Zeitfenster liegen, aber für diesen Tag leider kein Kurs vorhanden ist,
da die Börsen der Welt an Sonntagen nicht arbeiten. In diesem Fall
ist CachedQuote
so schlau, statt einem Datenloch den letzten vorliegenden
Kurs (also vom Freitag, falls dieser nicht auf einen Feiertag fiel)
auszuliefern.
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;
Das Modul CachedQuote.pm
holt die Aktienkurse intern mit dem CPAN-Modul
Finance::QuoteHist::Yahoo vom Netz. Als
Tageswert nimmt der Cache immer den mit 'close' bezeichneten Schlusskurs.
Pro Web-Request kann der kontaktierte
Yahoo-Server für eine Aktie die Kursdaten vieler Jahre liefern.
Das nutzt CachedQuote.pm voll
aus, denn zu jedem per quote()
eingehenden Request für einen Tageskurs
schickt es einen Request an den Server, der die Daten von einem Jahr vor
dem verlangten Zeitpunkt bis zum aktuellen Datum umfasst.
Zum Speichern und späteren Wiederfinden der zwischengespeicherten Daten
verwendet CachedQuote.pm
das CPAN-Modul Cache::Historical. Es
bietet eine komfortable Schnittstelle zum Setzen datumsbasierter Daten
(set($dt, $value)
) und liefert
die gespeicherten Werte mit den Methoden get()
und get_interpolated()
später wieder
zurück. Fehlt ein Kurs für einen Tag, greift letztere Methode auf
den letzten verfügbaren Kurs vor dem angegebenen Datum zurück.
Cache::Historical wiederum 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.
Die Funktion quote()
versucht zuerst, den geforderten Kurs mit
get()
einzuholen. Schlägt dies fehl, was quote()
mit dem
Rückgabewert undef
quittiert, versucht quote_refresh()
, den
Cache rund um das geforderte Datum aufzufrischen. Anschließend
sollte get_interpolated()
in jedem Fall einen ordentlichen
Wert zurückliefern.
Die SQLite-Datei zum Zwischenspeichern der Daten stellt CachedQuote.pm
in Zeile 18 als /tmp/cached-quote.dat ein. Wer den Cache nicht im ständig
durch Löschung bedrohten
temporären Verzeichnis möchte, kann dies im Aufruf des Konstruktors
mit new(sqlite_file => "...")
überschreiben.
CachedQuote.pm unterscheidet weiterhin, 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 and einem Sonntag läuft, soll das Modul jedoch nicht jedesmal
versuchen, die neuesten Kurse vom Server zu holen, denn es wird bis
zum Montag keine neuen geben. Deshalb holt die Funktion quote_refresh()
auch noch mit since_last_update()
die Zeitspanne seit dem letzten
Auffrischen des Caches ein. Sie liegt als DateTime::Duration-Objekt vor
und die Methode delta_days()
rechnet diese in Tage mit Bruchteilen um.
Ist der Cache noch keinen Tag alt, entfällt der Update und der letzte
verfügbare Kurs (üblicherweise vom Freitag) kommt zum Einsatz.
Das Interface des Moduls DateTime vom CPAN ist so komfortabel, dass man
eigentlich gar nichts anderes mehr 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 die Methode strftime
für die Umwandlung der DateTime-Objekte.
Den umgekehrten Fall, dass aus einem Datum im Format mm/dd/yyyy ein
DateTime-Objekt entsteht, 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 DateTime-Objekt zurückgibt.
Um von einem DateTime-Objekt ein Jahr zurückzurechnen, ruft man dessen
Methode subtract()
mit den Parametern years => 1
auf. Allerdings
modifiziert dies das Objekt selbst. Wenn man den ursprünglichen
Wert später noch braucht, empfielt es sich, es vorher mit clone()
in ein weiteres Objekt zu retten.
Das Skript pofo
nimmt auf der Kommandozeile eine Konfigurationsdatei
wie pofo1.txt
aus Abbildung 1 entgegen und die Funktion cfg_read
ab Zeile 142 arbeitet sich durch deren Zeilen, die jeweils eine
Aktientransaktion beschreiben. Sie ignoriert Kommentare, die mit '#'
beginnen und Zeilen, die nichts als Leerzeichen und Kommentare enthalten.
Da die Datumsangaben im Format yyyy-mm-dd vorliegen, hat auch
pofo
eine Funktion dt_parse
, die diesmal wieder ein anderes
Format definiert und die Datumseinträge in DateTime-Objekte umwandelt.
Als zusätzlichen Service nimmt cfg_read()
eine Referenz auf den
Array @symbols
entgegen, den sie mit allen auftretenden
Aktientickersymbolen ohne Duplikate füllt. Sie gibt eine Referenz
auf den von ihr gefüllten Hash %by_date
zurück. Dieser enthält
als Schlüssel Datumsangaben als stringifizierte DateTime-Objekte.
Als Werte sind den Schlüsseln jeweils eine Reihe von Transaktionen
zugeordnet, die an diesen Tagen stattgefunden haben. Jede Transaktion
besteht wiederum
aus einem Array, der die Felder der zugehörigen Konfigurationszeile
enthalten, also Datum, Aktion, Tickersymbol und Anzahl der Aktien.
Cashaktionen stehen auch dort, mit cash
als Tickersymbol.
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 => tick_clean($_), 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 INFO "*** TOTAL *** = $total\n"; 066 067 $rrd->update( 068 time => $dt->epoch(), 069 values => \%parts, 070 ) if scalar keys %parts; 071 } 072 073 $rrd->graph( 074 width => 800, 075 height => 600, 076 lower_limit => 0, 077 image => "positions.png", 078 vertical_label => "Positions", 079 start => $start->epoch(), 080 end => $end->epoch(), 081 map { ( draw => { 082 type => "stack", 083 dsname => tick_clean($_), 084 color => $symbol_colors{$_}, 085 legend => $_, 086 } ) 087 } @symbols, 088 ); 089 090 ########################################### 091 sub sum_up { 092 ########################################### 093 my($all, $dt, $parts) = @_; 094 095 my $sum = 0; 096 097 for my $tick (keys %$all) { 098 my $q = 1; 099 $q = $cq->quote($dt, $tick) if 100 $tick ne 'cash'; 101 my $add = $all->{$tick} * $q; 102 $parts->{tick_clean($tick)} = $add; 103 $sum += $add; 104 105 DEBUG "Add: $all->{$tick} $tick $add"; 106 } 107 return $sum; 108 } 109 110 ########################################### 111 sub pos_add { 112 ########################################### 113 my($all, $pos) = @_; 114 115 my($dt, $act, $tick, $n) = @{ $pos }; 116 die "pos: @$pos" if ! defined $n; 117 DEBUG "Action: $act $n $tick"; 118 119 my $q = 1; 120 $q = $cq->quote($dt, $tick) if 121 $tick ne 'cash'; 122 my $val = $n * $q; 123 124 if($tick eq "cash") { 125 $all->{cash} += $val if $act eq "in"; 126 $all->{cash} -= $val if $act eq "out"; 127 $all->{cash} = $val if $act eq "chk"; 128 } else { 129 if($act eq "in") { 130 $all->{$tick} += $n; 131 $all->{cash} -= $val; 132 } elsif($act eq "out") { 133 $all->{$tick} -= $n; 134 $all->{cash} += $val; 135 } elsif($act eq "find") { 136 $all->{$tick} += $n; 137 } 138 DEBUG "After: $tick: $all->{$tick}"; 139 } 140 141 $all->{cash} ||= 0; 142 DEBUG "After: Cash: $all->{cash}"; 143 } 144 145 ########################################### 146 sub cfg_read { 147 ########################################### 148 my($cfgfile, $symbols) = @_; 149 150 my %by_date = (); 151 152 open FILE, "<$cfgfile" or 153 die "Cannot open $cfgfile ($!)"; 154 155 while(<FILE>) { 156 chomp; 157 s/#.*//; 158 my @fields = split ' ', $_; 159 next unless @fields; # empty line 160 161 my $dt = dt_parse( $fields[0] ); 162 $fields[0] = $dt; 163 164 push @$symbols, $fields[2] unless 165 grep { $_ eq $fields[2] } @$symbols; 166 167 push @{ $by_date{ $dt } }, [ @fields ]; 168 } 169 170 close FILE; 171 return \%by_date; 172 } 173 174 ########################################### 175 sub dt_parse { 176 ########################################### 177 my($string) = @_; 178 179 my $fmt = DateTime::Format::Strptime-> 180 new( pattern => "%Y-%m-%d" ); 181 return $fmt->parse_datetime($string); 182 } 183 184 ########################################### 185 sub tick_clean { 186 ########################################### 187 my($tick) = @_; 188 189 $tick =~ s/./_/g; 190 return $tick; 191 }
Um nun herauszufinden, wie viele Aktien einer bestimmten Sorte an einem
bestimmten Tag im Depot liegen, muss das Skript alle Transaktionen
abarbeiten, die bis zu diesem Datum im Portfolio erfolgt sind. Deshalb
wühlt sich die For-Schleife ab Zeile 24 zunächst durch alle Aktionen,
die vor dem darzustellenden Zeitfenster erfolgt sind. Die Funktion
pos_add()
fügt jeweils eine Transaktion durch und markiert das
Endergebnis als Portfolioinhalt in der Variablen %pos
. Dieser Hash
weist jedem im Portfolio enthaltenen Tickersymbol einen numerischen
Wert zu. Bei Aktien ist dies deren Anzahl, bei Bargeld einfach die Summe.
Aktienkäufe und -verkäufe lösen zusätzlich Bewegung im Bargeldposten
aus, denn neue Aktien müssen bezahlt werden und der Erlös der verkauften
wird dem Girokonto gutgeschrieben. Das erfolgt immer zum jeweiligen
Tageskurs, die Daten hierzu liefert CachedQuote.pm
.
Mit der grafischen Darstellung der turmartig aufgeschichteten
Einzelpositionen hilft rrdtool
von Tobias Oetiker ([3], [4]).
Die etwas ungewöhnliche Syntax dieses praktischen
Tools versucht das CPAN-Modul RRDTool::OO (``OO'' für ``Objekt-Orientiert''),
etwas Perl-artiger und übersichtlicher zu gestalten.
RRDTool legt Daten von ``Data Sources'' in RRD-Archiven ab, indem es
sie die Messpunkte der ``Data Sources'' aufkumuliert und über die
eingestellte step
-Dauer mittelt. pofo
stellt den Parameter
step
in Zeile 43 auf 24 Stunden, damit die
damit die RRD-Datenbank nur einen Update pro Tag erwartet.
Jeder Aktie wird eine ``Data Source'' zugeordnet und das
RRD-Archiv hält bis zu 5000 Werte, 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'') legt fest, dass die ankommenden
Werte direkt übernommen und nicht etwa von RRDTool aufkumuliert werden.
Allerdings weigert sich RRDTool, Werte für Zeiten anzunehmen, die
vor dem letzten eingespeisten Tageswert liegen und so löscht
pofo
in Zeile 37 kurzerhand übriggebliebene RRD-Dateien,
die der Konstruktor von RRDTool::OO dann schnell neu erzeugt.
Zeile 9 in pofo
definiert eine relativ zufällige Palette von
möglichst unterschiedlichen Farben, die als
RGB-Hexwerte vorliegen.
Aus dem Array @colors wählt pofo-report
für jede dargestellte Aktie einen Wert aus, so dass man diese in
der Grafik problemlos auseinanderhalten kann.
Der
Hash %symbol_colors weist jedem Symbol anschließend
eine Farbe aus dieser Palette zu.
Die Reihenfolge, in der die einzelnen Aktionen in der Konfigurationsdatei
stehen, bestimmt deren Darstellungsabfolge im Graph.
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 an diesem 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 (bzw. cash
) die
Geldwerte der einzelnen Positionen ab.
Diesen Hash überreicht anschließend die update()
-Methode des
RRD
-Objekts der RRD-Datenbank unter dem Zeitstempel des gerade
bearbeiteten Tages. Die Methode graph
zeichnet anschließend den
Graphen in der Datei positions.png
und legt die Legende am
unteren Bildrand an.
Wie aus dem Listing
ersichtlich, ist die Anzahl der darstellbaren Aktien zur Zeit auf 6
beschränkt, es spricht allerdings nichts dagegen, den Array @colors
einfach mit neuen Farbkombinationen zu erweiteren.
Gehen die Farben aus, kann pofo
auch dahingehend modifiziert werden,
dass nur diejenigen Tickersymbole Farben zugewiesen bekommen, die auch
im ausgewählten Zeitfenster dargestellt werden.
In Zeile 22 setzt pofo
den dargestellten Zeitraum auf das
zurückliegende Jahr, wer einen anderen Zeitraum darstellen möchte,
modifiziert einfach die Variablen $start und $end.
Wer mehr Informationen über die internen Abläufe braucht, kommentiert
die Zeile 7 in pofo
aus, damit Log4perl mit easy_init
seine
Initialisierung betreibt und die über das Skript und das CachedQuote-Module
verstreuten DEBUG-Anweisungen auf dem Bildschirm ausgibt während die
Berechnung läuft.
Ein Manko des Skripts sind Stocksplits.
Diese verarbeitet pofo
noch nicht, denn dann ändern sich die
historischen Kursdaten rückwirkend und der Cache enthält ungültige
Daten. In diesem Fall löscht der Anwender einfach die Cache-Datei
/tmp/cached-quotes.dat
und verwirft damit den gesamten Cache. Ihn
wieder aufzufüllen ist nicht übermässig kostspielig, denn
Web-Requests an den Finanz-Server holen die Daten effizient in
großen Mengen ein.
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |