DOIF für Aktienkurse über Yahoo Finance und S-Investor (non blocking)(Codeschnipsel)

Begonnen von mumpitzstuff, 12 Juli 2023, 21:23:30

Vorheriges Thema - Nächstes Thema

mumpitzstuff

History:
13.04.24: - S-Investor Webseite wurde geändert und der Code wurde angepasst
          - Yahoo API wurde geändert es konnte aber bisher keine Lösung gefunden werden (bis auf weiteres muss deshalb S-Investor verwendet werden
          - das Attribut stocks wurde geändert und enthält ein alternatives Symbol, welches auch leer gelassen werden kann. Der zusätzliche Doppelunkt darf aber nicht      entfallen!
17.03.24: - Yahoo API Änderung nachgezogen
05.02.24: - Yahoo API Änderung nachgezogen
23.11.23: - Xetra und Tradegate Variante wieder lauffähig gemacht, nachdem die Webseite geändert wurde
24.07.23: - diverse Bugs und Probleme wurden behoben
          - es kann jetzt zwischen 4 APIs gewählt werden (2 Yahoo Abfragemöglichkeiten, XETRA und Tradegate Abfrage über S-Investor)
20.07.23: - Probleme beim Einlesen des Stocks Attributes behoben
          - Abfragezeiten wurden zeitlich auf übliche Börsenzeiten beschränkt
14.07.23: - prozentuale Änderung war zu hoch und wurde behoben
          - ein DOIF Template für Charts wurde hinzugefügt
13.07.23: - update Problem hoffentlich behoben
          - Umstellung des Codes auf eine andere Yahoo API die momentan nicht geblockt wird
          - jeder Yahoo Request wird um 10s verzögert, um ein Blocking zu vermeiden
          - der Update Zyklus wurde auf 30min erhöht


Hier habe ich mal eine erste Version zusammen gebaut, um über DOIF eine tabellarische Übersicht von Aktienkursen darzustellen. Ich denke den Code kann jeder für sich relativ leicht anpassen und erweitern. Falls ihr noch Ideen dazu habt und ich entsprechend Zeit finde, kann ich mir das auch eventuell ansehen.

defmod doif_Stocks DOIF subs\
{\
  use utf8;;\
  use Blocking;;\
  use Scalar::Util qw(looks_like_number);;\
  use LWP::UserAgent;;\
  use HTTP::Request::Common;;\
  use HTTP::CookieJar::LWP;;\
  use JSON qw(decode_json);;\
  use HTML::Entities;;\
  use HTML::TreeBuilder;;\
  use Web::Scraper;;\
  use Data::Dumper qw(Dumper);;\
  \
  $_stocks = ::AttrVal("$SELF", 'stocks', '');;\
  $_api = ::AttrVal("$SELF", 'api', 'YahooJSON1');;\
  \
  sub startUpdate($)\
  {\
    my $name = shift;;\
    $_stocks = ::AttrVal($name, 'stocks', '');;\
    $_api = ::AttrVal($name, 'api', 'YahooJSON1');;\
    my @stocks = split(',', $_stocks);;\
    ## max 3 retries (always wait 10s between each request)\
    my $timeout = (scalar(@stocks) * 65) + 60;;\
    \
    ::Log3 $name, 5, $name.': stocks = '.$_stocks;;\
    ::Log3 $name, 5, $name.': api = '.$_api;;\
    \
    if (defined($_blockingcalls{'PID_UPDATE'.$name}))\
    {\
      ::Log3 $name, 3, $name.': Blocking call already running (update).';;\
\
      ::BlockingKill($_blockingcalls{'PID_UPDATE'.$name});;\
    }\
\
    ::Log3 $name, 3, $name.': Blocking call started (update).';;\
    \
    $_blockingcalls{'PID_UPDATE'.$name} = ::BlockingCall('DOIF::doUpdate', $name, 'DOIF::endUpdate', $timeout, 'DOIF::abortUpdate', $name);;\
  }\
\
  sub DOIF::doUpdate($)\
  {\
    my $name = shift;;\
    my $output = '';;\
    my $url_template = '';;   \
    my @stocks = split(',', $_stocks);;\
    \
    if ($_api eq 'YahooJSON1')\
    {\
      $url_template = 'https://query2.finance.yahoo.com/v7/finance/options/?symbols=#REPLACE#';; \
    }    \
    elsif ($_api eq 'YahooJSON2')\
    {\
      $url_template = 'https://query2.finance.yahoo.com/v11/finance/quoteSummary/?symbols=#REPLACE#&modules=price';;\
    }\
    elsif ($_api eq 'S-InvestorXETRA')\
    {\
      $url_template = 'https://web.s-investor.de/app/detail.htm?INST_ID=0003329&boerse=GER&isin=#REPLACE#';;\
    }\
    elsif ($_api eq 'S-InvestorTRADEGATE')\
    {\
      $url_template = 'https://web.s-investor.de/app/detail.htm?INST_ID=0003329&boerse=TDG&isin=#REPLACE#';;\
    }\
\
    foreach my $stock (@stocks)\
    {\
      my ($stock_symbol, $stock_symbol_alt, $stock_count, $stock_initvalue) = split(':', $stock);;\
      my $url = $url_template;;\
      \
      ## use alternative symbol if needed\
      if ((($_api =~ /YahooJSON/) && length($stock_symbol) == 12 && looks_like_number(substr($stock_symbol, -10))) ||\
          (($_api =~ /S-Investor/) && !(length($stock_symbol) == 12 && looks_like_number(substr($stock_symbol, -10)))))\
      {\
        $url =~ s/#REPLACE#/$stock_symbol_alt/;;\
      }\
      else\
      {\
        $url =~ s/#REPLACE#/$stock_symbol/;;\
      }\
      \
      my $ua = LWP::UserAgent->new;;\
      my $reply;;\
      \
      ##$ua->agent('Mozilla/5.0 (Windows NT 10.0;; Win64;; x64;; rv:109.0) Gecko/20100101 Firefox/115.0');;\
      $ua->agent('Mozilla/5.0 (Macintosh;; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36');;\
      \
      ## retry up to 3 times\
      for (my $i = 0;; $i < 3;; $i++)\
      {\
        if (($_api eq 'YahooJSON1') || ($_api eq 'YahooJSON2'))\
        {\
          my $cookie_jar = HTTP::CookieJar::LWP->new();;\
          $ua->cookie_jar($cookie_jar);;\
          \
          ## get cookie\
          $reply = $ua->request(GET 'https://login.yahoo.com');;\
          \
          ## get crumb\
          $reply = $ua->request(GET 'https://query2.finance.yahoo.com/v1/test/getcrumb');;\
          \
          my $crumb = '';;\
          ##if (200 == $reply->code)\
          ##{\
            $crumb = $reply->content;;\
          ##}\
          \
          $reply = $ua->request(GET $url.'&crumb='.$crumb);;\
          \
          if (200 == $reply->code)\
          {\
            my $json_data = JSON::decode_json $reply->content;;\
            my $json_data_count;;\
            $json_data_count = scalar @{ $json_data->{'optionChain'}{'result'} } if ($_api eq 'YahooJSON1');;\
            $json_data_count = scalar @{ $json_data->{'quoteSummary'}{'result'} } if ($_api eq 'YahooJSON2');;\
\
            if ($json_data_count >= 1)\
            {\
              my $json_resources_price;;\
              my $json_symbol = '';;\
              my $json_shortname = '';;\
              my $json_longname = '';;\
              my $json_type = '';;\
              my $json_currency = '';;\
              my $json_exchangeSymbol = '';;\
              my $json_exchangeName = '';;\
              my $json_delay = '';;\
              my $json_marketState = '';;\
              my $json_timestamp = '';;\
              my $json_volume = '';;\
              my $json_open = '';;\
              my $json_high = '';;\
              my $json_low = '';;\
              my $json_close = '';;\
              my $json_change = '';;\
              my $json_changep = '';;\
              my $json_price = '';;              \
              \
              $json_resources_price  = $json_data->{'optionChain'}{'result'}[0]{'quote'} if ($_api eq 'YahooJSON1');;\
              $json_resources_price  = $json_data->{'quoteSummary'}{'result'}[0]{'price'} if ($_api eq 'YahooJSON2');;\
              $json_symbol           = (exists($json_resources_price->{'symbol'}) ? $json_resources_price->{'symbol'} : '');;\
              $json_shortname        = (exists($json_resources_price->{'shortName'}) ? $json_resources_price->{'shortName'} : '');;\
              $json_longname         = (exists($json_resources_price->{'longName'}) ? $json_resources_price->{'longName'} : '');;\
              $json_type             = (exists($json_resources_price->{'quoteType'}) ? $json_resources_price->{'quoteType'} : '');;\
              $json_currency         = (exists($json_resources_price->{'currency'}) ? $json_resources_price->{'currency'} : '');;\
              $json_exchangeSymbol   = (exists($json_resources_price->{'exchange'}) ? $json_resources_price->{'exchange'} : '');;\
              $json_exchangeName     = (exists($json_resources_price->{'exchangeName'}) ? $json_resources_price->{'exchangeName'} : '');;\
              $json_delay            = (exists($json_resources_price->{'exchangeDataDelayedBy'}) ? $json_resources_price->{'exchangeDataDelayedBy'} : '');;\
              $json_marketState      = (exists($json_resources_price->{'marketState'}) ? $json_resources_price->{'marketState'} : '');;\
              \
              if ($_api eq 'YahooJSON1')\
              {\
                $json_timestamp           = (exists($json_resources_price->{'regularMarketTime'}) ? $json_resources_price->{'regularMarketTime'} : '');;\
                $json_volume              = (exists($json_resources_price->{'regularMarketVolume'}) ? $json_resources_price->{'regularMarketVolume'} : '');;\
                $json_open                = (exists($json_resources_price->{'regularMarketOpen'}) ? $json_resources_price->{'regularMarketOpen'} : '');;\
                $json_high                = (exists($json_resources_price->{'regularMarketDayHigh'}) ? $json_resources_price->{'regularMarketDayHigh'} : '');;\
                $json_low                 = (exists($json_resources_price->{'regularMarketDayLow'}) ? $json_resources_price->{'regularMarketDayLow'} : '');;\
                $json_close               = (exists($json_resources_price->{'regularMarketPreviousClose'}) ? $json_resources_price->{'regularMarketPreviousClose'} : '');;\
                $json_change              = (exists($json_resources_price->{'regularMarketChange'}) ? $json_resources_price->{'regularMarketChange'} : '');;\
                $json_changep             = (exists($json_resources_price->{'regularMarketChangePercent'}) ? $json_resources_price->{'regularMarketChangePercent'} : '');;\
                $json_price               = (exists($json_resources_price->{'regularMarketPrice'}) ? $json_resources_price->{'regularMarketPrice'} : '');;\
              }\
              elsif ($_api eq 'YahooJSON2')\
              {\
                $json_timestamp           = (exists($json_resources_price->{'regularMarketChange'}) && exists($json_resources_price->{'regularMarketChange'}{'regularMarketTime'}) ?\
                                             $json_resources_price->{'regularMarketChange'}{'regularMarketTime'} : '');;\
                $json_volume              = (exists($json_resources_price->{'regularMarketVolume'}) && exists($json_resources_price->{'regularMarketVolume'}{'raw'}) ?\
                                             $json_resources_price->{'regularMarketVolume'}{'raw'} : '');;\
                $json_open                = (exists($json_resources_price->{'regularMarketOpen'}) && exists($json_resources_price->{'regularMarketOpen'}{'raw'}) ?\
                                             $json_resources_price->{'regularMarketOpen'}{'raw'} : '');;\
                $json_high                = (exists($json_resources_price->{'regularMarketDayHigh'}) && exists($json_resources_price->{'regularMarketDayHigh'}{'raw'}) ?\
                                             $json_resources_price->{'regularMarketDayHigh'}{'raw'} : '');;\
                $json_low                 = (exists($json_resources_price->{'regularMarketDayLow'}) && exists($json_resources_price->{'regularMarketDayLow'}{'raw'}) ?\
                                             $json_resources_price->{'regularMarketDayLow'}{'raw'} : '');;\
                $json_close               = (exists($json_resources_price->{'regularMarketPreviousClose'}) && exists($json_resources_price->{'regularMarketPreviousClose'}{'raw'}) ?\
                                             $json_resources_price->{'regularMarketPreviousClose'}{'raw'} : '');;\
                $json_change              = (exists($json_resources_price->{'regularMarketChange'}) && exists($json_resources_price->{'regularMarketChange'}{'raw'}) ?\
                                             $json_resources_price->{'regularMarketChange'}{'raw'} : '');;\
                $json_changep             = (exists($json_resources_price->{'regularMarketChangePercent'}) && exists($json_resources_price->{'regularMarketChangePercent'}{'raw'}) ?\
                                             $json_resources_price->{'regularMarketChangePercent'}{'raw'} : '');;\
                $json_price               = (exists($json_resources_price->{'regularMarketPrice'}) && exists($json_resources_price->{'regularMarketPrice'}{'raw'}) ?\
                                             $json_resources_price->{'regularMarketPrice'}{'raw'} : '');;\
              }\
              \
              ## replace | by _\
              $json_longname = '' if (!defined($json_longname));;\
              $json_longname = decode_entities($json_longname) if ($json_longname ne '');;\
              $json_longname =~ s/\|/_/g;;\
              $json_shortname =~ s/\|/_/g;;\
              \
              ## convert to %\
              $json_changep = $json_changep * 100 if (($json_changep ne '') && ($_api eq 'YahooJSON2'));;\
\
              $stock_count = 1 if (!defined($stock_count));;\
              $stock_initvalue = 0 if (!defined($stock_initvalue));;\
\
              if (($json_currency eq "GBp") ||\
                  ($json_currency eq "GBX") ||\
                  ($json_currency eq "ZAc"))\
              {\
                $json_price = $json_price / 100 if ($json_price ne '');;\
                $json_open = $json_open / 100 if ($json_open ne '');;\
                $json_high = $json_high / 100 if ($json_high ne '');;\
                $json_low = $json_low / 100 if ($json_low ne '');;\
                $json_close = $json_close / 100 if ($json_close ne '');;\
                $json_change = $json_change / 100 if ($json_change ne '');;\
              }\
\
              if (($json_currency eq "GBp") ||\
                  ($json_currency eq "GBX"))\
              {\
                $json_currency = "GBP";;\
              }\
              elsif ($json_currency eq "ZAc")\
              {\
                $json_currency = "ZAR";;\
              }\
\
              $output .= '|'.$stock_symbol.'|'.$json_symbol.'|'.$json_shortname.'|'.$json_longname.'|'.$json_type.'|'.$json_currency.'|'.\
                             $json_exchangeSymbol.'|'.$json_exchangeName.'|'.$json_marketState.'|'.$json_timestamp.'|'.\
                             $json_volume.'|'.$json_open.'|'.$json_high.'|'.$json_low.'|'.$json_close.'|'.$json_change.'|'.\
                             $json_changep.'|'.$json_price.'|'.$json_delay.'|'.$stock_count.'|'.$stock_initvalue;;\
\
              sleep(10);;\
              last;;\
            }\
          }\
        }\
        elsif (($_api eq 'S-InvestorXETRA') || ($_api eq 'S-InvestorTRADEGATE'))\
        {\
          my $type = '';;          \
          my $exchangeSymbol;;\
          my $delay = '';;\
          my $marketState = '';;\
          \
          $exchangeSymbol = 'GER' if ($_api eq 'S-InvestorXETRA');;\
          $exchangeSymbol = 'TDG' if ($_api eq 'S-InvestorTRADEGATE');;\
                                        \
          eval\
          {\
            my $tree = HTML::TreeBuilder->new_from_url($url);;\
\
            my $lastvalue = $tree->look_down('_tag'=>'table');;\
\
            my $td1 = ($lastvalue->look_down('_tag'=>'td'))[1];;\
            my @child = $td1->content_list;;\
            my $symbol = $child[0];;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[3];;\
            @child = $td1->content_list;;\
            my $shortname = $child[0];;\
            my $longname = $shortname;;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[5];;\
            @child = $td1->content_list;;\
            my $exchangeName = $child[0];;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[7];;\
            @child = $td1->content_list;;\
            my $timestamp = substr($child[0], 0, 8).' '.substr($child[0], 9, 5);;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[9];;\
            @child = $td1->content_list;;\
            my $price = $child[0];;\
            $price =~ s/\.//g;;\
            $price =~ s/,/\./;;\
            my $encprice = encode_entities($price);;\
            my @splitprice = split ('&', $encprice);;\
            $price = $splitprice[0];;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[11];;\
            @child = $td1->content_list;;\
            my $currency = $child[0];;\
            $currency =~ s/Euro/EUR/;;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[13];;\
            @child = $td1->content_list;;\
            my $volume = $child[0];;\
\
            $lastvalue = ($tree->look_down('_tag'=>'table'))[2];;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[5];;\
            @child = $td1->content_list;;\
            my $open = $child[0];;\
            $open =~ s/\.//g;;\
            $open =~ s/,/\./;;\
            my $encopen = encode_entities($open);;\
            my @splitopen = split ('&', $encopen);;\
            $open = ($splitopen[0] eq '-') ? '' : $splitopen[0];;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[8];;\
            @child = $td1->content_list;;\
            my $high = $child[0];;\
            $high =~ s/\.//g;;\
            $high =~ s/,/\./;;\
            my $enchigh = encode_entities($high);;\
            my @splithigh = split ('&', $enchigh);;\
            $high = ($splithigh[0] eq '-') ? '' : $splithigh[0];;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[11];;\
            @child = $td1->content_list;;\
            my $low = $child[0];;\
            $low =~ s/\.//g;;\
            $low =~ s/,/\./;;\
            my $enclow = encode_entities($low);;\
            my @splitlow = split ('&', $enclow);;\
            $low = ($splitlow[0] eq '-') ? '' : $splitlow[0];;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[14];;\
            @child = $td1->content_list;;\
            my $change = $child[0];;\
            $change =~ s/\.//g;;\
            $change =~ s/,/\./;;\
            my $encchange = encode_entities($change);;\
            my @splitchange = split ('&', $encchange);;\
            $change = ($splitchange[0] eq '-') ? '' : $splitchange[0];;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[17];;\
            @child = $td1->content_list;;\
            my $changep = $child[0];;\
            $changep =~ s/[\.|%]//g;;\
            $changep =~ s/,/\./;;\
            $changep = '' if ($changep eq '-');;\
\
            $td1 = ($lastvalue->look_down('_tag'=>'td'))[35];;\
            @child = $td1->content_list;;\
            my $close = $child[0];;\
            $close =~ s/\.//g;;\
            $close =~ s/,/\./;;\
            my $encclose = encode_entities($close);;\
            my @splitclose = split ('&', $encclose);;\
            $close = ($splitclose[0] eq '-') ? '' : $splitclose[0];;\
\
            ## replace | by _\
            $shortname =~ s/\|/_/g;;\
            $longname =~ s/(\||&amp;;)/_/g;;\
\
            $stock_count = 1 if (!defined($stock_count));;\
            $stock_initvalue = 0 if (!defined($stock_initvalue));;\
\
            $output .= '|'.$stock_symbol.'|'.$symbol.'|'.$shortname.'|'.$longname.'|'.$type.'|'.$currency.'|'.\
                           $exchangeSymbol.'|'.$exchangeName.'|'.$marketState.'|'.$timestamp.'|'.\
                           $volume.'|'.$open.'|'.$high.'|'.$low.'|'.$close.'|'.$change.'|'.\
                           $changep.'|'.$price.'|'.$delay.'|'.$stock_count.'|'.$stock_initvalue;;\
\
            sleep(10);;\
          };;\
          if (!$@)\
          {\
            print $@;;\
            last;;\
          }\
        }\
        \
        sleep(10);;\
      }\
    }\
\
    $output = '|||||||||||||||||||||' if ($output eq '');; \
    \
    return $name.$output;;\
  }\
  \
  sub DOIF::endUpdate($)\
  {\
    my ($name, @output) = split("\\|", shift);;\
    my $hash = $::defs{$name};;\
    my $numValues = 21;;\
\
    #::Log3 $name, 1, $name.': '.Dumper(\@output);;\
\
    if (0 != (scalar(@output) % $numValues))\
    {\
      ::Log3 $name, 1, $name.': $numShares does not match the received output.';;\
    }\
    else\
    {    \
      my $totalValue = 0;;\
      my $totalInitValue = 0;;\
      my $totalCloseValue = 0;;\
      ## using $_stocks is not reliable\
      my @stocks = split(",", ::AttrVal($name, 'stocks', ''));;\
      \
      ::readingsBeginUpdate($hash);;\
      for (my $i = 0;; $i < (scalar(@output) / $numValues);; $i += 1)\
      {\
        my $pos = $i * $numValues;;\
        my $prefix = $output[$pos];;\
\
        ## remove any .\
        $prefix =~ s/\.//g;;\
\
        ## preserve some values if we do not get new ones (e.g. in case of weekend)\
        ::readingsBulkUpdate($hash, $prefix.'_symbol', $output[$pos + 1]);;\
        ::readingsBulkUpdate($hash, $prefix.'_shortname', $output[$pos + 2]);;\
        ::readingsBulkUpdate($hash, $prefix.'_longname', $output[$pos + 3]);;\
        ::readingsBulkUpdate($hash, $prefix.'_type', $output[$pos + 4]);;\
        ::readingsBulkUpdate($hash, $prefix.'_currency', $output[$pos + 5]);;\
        ::readingsBulkUpdate($hash, $prefix.'_exchangeSymbol', $output[$pos + 6]);;\
        ::readingsBulkUpdate($hash, $prefix.'_exchangeName', $output[$pos + 7]);;\
        ::readingsBulkUpdate($hash, $prefix.'_marketState', $output[$pos + 8]);;\
        ::readingsBulkUpdate($hash, $prefix.'_timestamp', looks_like_number($output[$pos + 9]) ? localtime($output[$pos + 9]) : $output[$pos + 9]);;\
        ::readingsBulkUpdate($hash, $prefix.'_volume', $output[$pos + 10]) if ($output[$pos + 10] ne '');;\
        ::readingsBulkUpdate($hash, $prefix.'_open', $output[$pos + 11]) if ($output[$pos + 11] ne '');;\
        ::readingsBulkUpdate($hash, $prefix.'_high', $output[$pos + 12]) if ($output[$pos + 12] ne '');;\
        ::readingsBulkUpdate($hash, $prefix.'_low', $output[$pos + 13]) if ($output[$pos + 13] ne '');;\
        ::readingsBulkUpdate($hash, $prefix.'_close', $output[$pos + 14]) if ($output[$pos + 14] ne '');;\
        ::readingsBulkUpdate($hash, $prefix.'_change', $output[$pos + 15]) if ($output[$pos + 15] ne '');;\
        ::readingsBulkUpdate($hash, $prefix.'_changep', $output[$pos + 16]) if ($output[$pos + 16] ne '');;\
        ::readingsBulkUpdate($hash, $prefix.'_price', $output[$pos + 17]);;\
        ::readingsBulkUpdate($hash, $prefix.'_delay', $output[$pos + 18]);;\
        ::readingsBulkUpdate($hash, $prefix.'_count', $output[$pos + 19]);;\
        ::readingsBulkUpdate($hash, $prefix.'_initvalue', $output[$pos + 20]);;\
      }\
      ::readingsEndUpdate($hash, 1);;\
      \
      if (scalar(@stocks) == 0)\
      {\
        #::Log3 $name, 1, $name.': stocks = '.$_stocks;;\
        #::Log3 $name, 1, $name.': stocks = '.::AttrVal($name, 'stocks', 'test');;;;\
        @stocks = split(",", ::AttrVal($name, 'stocks', ''));;\
      }\
      \
      foreach my $stock (@stocks)\
      {\
        my ($stock_symbol, $stock_symbol_alt, $stock_count, $stock_initvalue) = split(':', $stock);;\
        \
        ## remove any .\
        $stock_symbol =~ s/\.//g;;\
        \
        $stock_count = 1 if (!defined($stock_count));;\
        $stock_initvalue = 0 if (!defined($stock_initvalue));;\
        \
        $totalValue += ($stock_count * ReadingsVal($name, $stock_symbol.'_price', 0));;\
        $totalInitValue += $stock_initvalue;;\
        $totalCloseValue += ($stock_count * ReadingsVal($name, $stock_symbol.'_close', 0));;\
      }\
      \
      $totalValue = 1 if (0 == $totalValue);;\
      ::readingsBeginUpdate($hash);;\
      ::readingsBulkUpdate($hash, 'totalValue', $totalValue);;\
      ::readingsBulkUpdate($hash, 'totalInitValue', $totalInitValue);;\
      ::readingsBulkUpdate($hash, 'totalCloseValue', $totalCloseValue);;\
      ::readingsBulkUpdate($hash, 'numberOfLines', scalar(@stocks));;\
      ::readingsEndUpdate($hash, 1);;\
    }\
    \
    delete($_blockingcalls{'PID_UPDATE'.$name});;\
    \
    ::Log3 $name, 4, $name.': Blocking call finished to update shares.';;\
  }\
\
  sub DOIF::abortUpdate($)\
  {\
    my $name = shift;;\
\
    delete($_blockingcalls{'PID_UPDATE'.$name});;\
\
    ::Log3 $name, 1, $name.': Blocking call aborted (update).';;\
  }\
}\
init\
{\
  startUpdate("$SELF");;\
}\
## start in a raster of 30min\
{[08:00-22:30,+:30|12345];;startUpdate("$SELF")}
attr doif_Stocks userattr stocks api:YahooJSON1,YahooJSON2,S-InvestorXETRA,S-InvestorTRADEGATE
attr doif_Stocks api S-InvestorXETRA
attr doif_Stocks event-on-change-reading .*
attr doif_Stocks room FINANCE
attr doif_Stocks stocks US02079K3059:GOOG:100:8000,US5949181045:MSFT:100:40000
attr doif_Stocks uiTable {\
  package ui_Table;;\
\
  $SHOWNOSTATE = 1;;\
  $ATTRIBUTESFIRST = 1;;\
  \
  {\
    my $numberOfLines = ::ReadingsNum("$SELF", "numberOfLines", 0) + 1;;\
\
    ## Header\
    $TR{0} = "style='color:yellow;;text-align:left;;font-weight:bold;;font-size:18px;;border-bottom-style:solid;;border-color:#CCCCCC;;border-bottom-width:1px;;'";;\
    ## Footer\
    $TR{$numberOfLines} = "style='color:yellow;;text-align:left;;font-weight:bold;;font-size:18px;;border-top-style:solid;;border-color:#CCCCCC;;border-top-width:1px;;'";;\
    ## First 2 columns\
    $TD{0..$numberOfLines}{0..1} = "style='font-size:16px;;border-right-style:solid;;border-color:#CCCCCC;;border-right-width:1px;;'";;\
    ## Column 2 to 7\
    $TD{0..$numberOfLines}{2..7} = "align='right' style='font-size:16px;;border-right-style:solid;;border-color:#CCCCCC;;border-right-width:1px;;'";;\
    ## Column 8\
    $TD{0..$numberOfLines}{8} = "align='right' style='font-size:16px'";;\
  }\
  \
  sub totalValue\
  {\
    return ($_[0] * $_[1]);;\
  }\
  \
  sub getChangeColor\
  {\
    if ($_[0] >= 5.0)\
    {\
      return "<div style='color:darkgreen;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= 3.0)\
    {\
      return "<div style='color:green;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= 0.0)\
    {\
      return "<div style='color:lightgreen;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= -1.0)\
    {\
      return "<div style='color:orange;;'>$_[0]%</div>";;\
    }\
    else\
    {\
      return "<div style='color:red;;'>$_[0]%</div>";;\
    }\
  }\
  \
  sub getTotalChangeColor\
  {\
    if ($_[0] >= 5.0)\
    {\
      return "<div style='color:darkgreen;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= 3.0)\
    {\
      return "<div style='color:green;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= 0.0)\
    {\
      return "<div style='color:lightgreen;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= -1.0)\
    {\
      return "<div style='color:orange;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
    else\
    {\
      return "<div style='color:red;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
  }\
  \
  sub getTrendColor\
  {\
    if ($_[0] >= 1.0)\
    {\
      return "<div style='color:darkgreen;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= 0.0)\
    {\
      return "<div style='color:green;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= -1.0)\
    {\
      return "<div style='color:yellow;;'>$_[0]%</div>";;\
    }\
    else\
    {\
      return "<div style='color:red;;'>$_[0]%</div>";;\
    }\
  }\
  \
  sub getTotalTrendColor\
  {\
    if ($_[0] >= 1.0)\
    {\
      return "<div style='color:darkgreen;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= 0.0)\
    {\
      return "<div style='color:green;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
    elsif ($_[0] >= -1.0)\
    {\
      return "<div style='color:yellow;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
    else\
    {\
      return "<div style='color:red;;font-weight:bold;;'>$_[0]%</div>";;\
    }\
  }\
}\
\
DEF TPL_STOCK([$1:$2_symbol]|[$1:$2_shortname]|sprintf("%.2f",totalValue([$1:$2_count],[$1:$2_price]))."€"|sprintf("%.2f",(totalValue([$1:$2_count],[$1:$2_price])-[$1:$2_initvalue]))."€"|getChangeColor(sprintf("%.2f",100*((totalValue([$1:$2_count],[$1:$2_price]))-[$1:$2_initvalue])/(totalValue([$1:$2_count],[$1:$2_price]))))|getTrendColor(sprintf("%.2f",[$1:$2_changep]))|sprintf("%.2f",[$1:$2_price])."€"|[$1:$2_count])\
\
"Symbol"|"Name"|"Total Value"|"Change abs."|"Change rel."|"Trend"|"Value"|"Count"\
TPL_STOCK($SELF,US02079K3059)\
TPL_STOCK($SELF,US5949181045)\
"&nbsp;;"|"&nbsp;;"|sprintf("%.2f",[$SELF:totalValue])."€"|sprintf("%.2f",[$SELF:totalValue]-[$SELF:totalInitValue])."€"|getTotalChangeColor(sprintf("%.2f",([$SELF:totalValue]-[$SELF:totalInitValue])*100/[$SELF:totalValue]))|getTotalTrendColor(sprintf("%.2f",([$SELF:totalValue]-[$SELF:totalCloseValue])*100/[$SELF:totalValue]))|"&nbsp;;"|"&nbsp;;"\


Das Attribut stocks setzt sich wie folgt zusammen (mehrere Angaben müssen mit einem Komma getrennt werden):

<symbol>:<alternatives symbol oder leer>:<anzahl>:<initialwert aller aktien zusammengerechnet>

Das Symbol muss für Abfragen über die Yahoo API über Yahoo ermittelt werden. Für die beiden S-Invester APIs muss hingegen die ISIN verwendet werden!

Über das Attribut api kann die Art der Abfrage festgelegt werden.


Ein Update kann länger dauern, je nachdem wie viele Aktien man eingetragen hat. Jeder Aufruf wird um 10s verzögert, damit man nicht geblockt wird!

Eine Anzeige der Charts kann man exemplarisch hiermit machen:
defmod doif_Stocks_Charts DOIF ##
attr doif_Stocks_Charts room FINANCE
attr doif_Stocks_Charts uiTable {\
  package ui_Table;;\
}\
\
card([doif_Stocks:<prefix der Aktie>_price:col4w],"Name der Aktie",undef,0,100,0,120,"€",undef,"1","200,,,,,1")

Ausgehend vom Initialwert einer Einzelaktie setze ich bei mir den unteren Wert immer auf Initialwert - 20% und den oberen Wert auf den Initialwert + 20%. Dann kann man anhand der Farbe immer schön sehen, wohin die das Wertpapier, ausgehend von seinem Initialwert, entwickelt.

Damian

Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

betateilchen

-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

mumpitzstuff

Ich hab grad mitbekommen, das Yahoo seit gestern deutsche IP's blockt und die Abfragen nur noch 404 zurück liefern. Ich glaube ich muss erst mal eine zuverlässigere Datenquelle suchen.
Wenn das also momentan nicht funktioniert, dann liegt das nicht an meinem Code.

mumpitzstuff

Ich habe eine andere API gefunden, die hoffentlich länger funktioniert. Ich bin auch nicht sicher, ob nur meine IP geblockt wurde oder es ein generelles Problem ist.

mumpitzstuff


Damian

Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

mumpitzstuff

Verschiedene Bugs wurden behoben und 4 APIs wurden eingefügt, die über ein Attribut umschaltbar sind (2x Yahoo, 1x S-Investor XETRA, 1x S-Investor Tradegate).

carlos

Gefällt mir gut dein DOIF.
Aber wenn man mehr als 2 Aktien eingibt ist die Darstellung leider nicht mehr so schön.
Kann man den style entsprechend anpassen?

Gruß

Hubert
FHEM svn auf Intel NUC mit proxmox,1 UDOO, 3 Raspberry Pi, signalduino, nanoCUL, div. Homematic Komponenten, toom Baumarkt Funksteckdosen, einige sonoffs, hue, shelly

mumpitzstuff

Bei den markierten Stellen im Attribut uiTable muss bei 2 Aktien eine 3 stehen, bei 3 Aktien eine 4 usw., also immer Anzahl der Aktien + 1.

Per

Man könnte es mit Variablen versuchen, leider werden diese nicht immer genommen (siehe TV DOIF), ein System konnte ich nicht erkennen.

carlos

Ich habe es mal so versucht:

in startUpdate:

    my $length = @stocks;
    set_Reading("Anzahl", $length);

in uiTable

my $anzahl = ::ReadingsNum("$SELF","Anzahl",0) + 1;;
 
  ## Header
  $TR{0} = "style='color:blue;text-align:left;font-weight:bold;font-size:18px;border-bottom-style:solid;border-color:#CCCCCC;border-bottom-width:1px;'";
  ## Footer
  $TR{$anzahl} = "style='color:blue;text-align:left;font-weight:bold;font-size:18px;border-top-style:solid;border-color:#CCCCCC;border-top-width:1px;'";
  ## First 2 columns
  $TD{0..$anzahl}{0..1} = "style='font-size:16px;border-right-style:solid;border-color:#CCCCCC;border-right-width:1px;'";
  ## Column 2 to 7
  $TD{0..$anzahl}{2..7} = "align='right' style='font-size:16px;border-right-style:solid;border-color:#CCCCCC;border-right-width:1px;'";
  ## Column 8
  $TD{0..$anzahl}{8} = "align='right' style='font-size:16px'";
 

Scheint zu funktionieren.

Gruß

Hubert
FHEM svn auf Intel NUC mit proxmox,1 UDOO, 3 Raspberry Pi, signalduino, nanoCUL, div. Homematic Komponenten, toom Baumarkt Funksteckdosen, einige sonoffs, hue, shelly

mumpitzstuff

Vielen Dank! Ich schaue es mir an und nehme das mit auf, falls es auch bei mir funktioniert.

mumpitzstuff

Leider hat es bei mir nur bei dem einen DOIF funktioniert, bei dem anderen aber nicht. Dort musste ich mehrmals irgendwo Leerzeichen in der uiTable einfügen und speichern und dann auf einmal hat es funktioniert. Das ist das selbe Problem wie ich es beim Fernsehprogramm DOIF hatte und wie oben schon erwähnt wurde. Wenn es funktioniert dann ist es schön, aber leider tut es das nicht immer...

Per