29 December 2017

Не хочешь депозит 6,5? Расчет нормы доходности акций и полной доходности с помощью Moex API и парсера дивидендов

PHPAPIООP

Немного о тексте ниже




Самый ленивый портфельный инвестор обычно поступает так: идет к финансовому управляющему, они вместе составляют профиль инвестора и на основании этого профиля они собирают портфель из активов, которые соответствуют тем показателям риска\доходности, которые портфельный инвестор готов принять.

Если инвестор очень долгосрочный и портфель составлен правильно, то он может покупать бумаги в любое время и по любой цене, 10-тилетний временной промежуток сгладит разницу за счет див.выплат (конечно мы должны искать ценные бумаги с постоянным денежным потоком).
Рассмотрим ситуацию, в которой вам надо найти ценные бумаги (далее я буду подразумевать конкретный тип бумаг — акции, с облигациями все понятно, там купон), которые приносит в виде дивидендов денежный поток, удовлетворяющий вашему финансовому плану. Самый простой пример — найти акцию, денежный поток которой превышает значение инфляции, т.е. 4% (по данным Росстата)

Давайте рассмотрим второй фактор из этой пары риск/доходность — собственно доходность

Доходность акции это показатель, который позволяет оценить, какую прибыль можно получить с момента приобретения ценной бумаги. Доходность может быть не только положительной, но и отрицательной – в том случае, когда акция принесла инвестору убыток. В общем виде доходность акции рассчитывает по следующей простой формуле:

$V = (\frac{D}{P})\times 100\%$


Агрегаторы


Сразу приведу пару ресурсов, которые уже содержат в себе те данные, которые нам нужны. Там приведены данные по доходностям ценных бумаг и факторы, на которые надо обратить внимание при их покупке. Сам я тоже периодически заглядываю на эти ресурсы:Доход.ру и Инвестиционный раздел Смартлаба.

Вы можете спокойно пользоваться ими, я же поставил себе задачу автоматизировать некий процесс + иметь возможность работать с данными по дивидендным выплатам за некий возможный период времени.

Пишем парсер


Перебрав несколько вариантов, я остановился на phpQuery, хотя до этого попытался реализовать парсер на Simple HTML DOM. Результат обрабатывался очень долго, поэтому пришлось сменить. Данные парсим с сайта investfunds.ru

Код парсера
public function getDivsFromCode($code){ //код эмитента - номер эмитента на сайте, откуда берутся данные.
     include_once './phpQuery.php';

     if ( $curl = curl_init () ) //инициализация сеанса
       {
        curl_setopt ($curl, CURLOPT_URL, 'http://stocks.investfunds.ru/stocks/'.$code.'/dividend/');//указываем адрес страницы
        curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt ($curl, CURLOPT_POST, true);
        curl_setopt ($curl, CURLOPT_HEADER, 0);
        $result = curl_exec ($curl);//выполнение запроса
        curl_close ($curl);//закрытие сеанса
      }
    $html = phpQuery::newDocumentHTML($result);
    phpQuery::selectDocument($html);
    $a = [1,3,5];            //номера столбцов из которых надо извлечь данные(год, дата выплаты и сумма)
    
    $emitent_string = htmlentities(pq('h1')->text(),ENT_COMPAT,'UTF-8');
    
    if ($emitent_string === ""){
        $divs['emitent'] = 'Нет эмитента';
        return $divs;
    }
    
    $re = [' акция привилегированная, Дивиденды', ' акция обыкновенная, Дивиденды',' акция обыкновенная',' акция привилегированная'];
    $emi = str_replace($re,'',$emitent_string);
    
    $divs['emitent'] = $emi;
    $divs['code'] = $code;

    /*находим количество лет, когда выплачивали дивы*/
    $i = pq('[cellpadding="0"] tr td:nth-child(1)');
    $l = substr_count($i,'td style');
    /*помечаем, что эмитент не выплачивал дивы*/
    if ($l === 0){
    $divs['data']= 'No divs';
    return $divs;
    }
        
    /******играемся с регулярными выражениями и css************************/
    for ($i =3;$i<$l+3;$i++){
        foreach ($a as $j){
            switch ($j) {
                case 1:
                    $divs['data'][$i-2]['year'] = str_replace(' ','',htmlentities(pq("[cellpadding='0'] tr:nth-child($i) td:nth-child($j)")->text(),ENT_COMPAT,'UTF-8'));
                    break;
                case 3:
                    $divs['data'][$i-2]['date'] = str_replace(' ','',htmlentities(pq("[cellpadding='0'] tr:nth-child($i) td:nth-child($j)")->text(),ENT_COMPAT,'UTF-8'));
                    break;
                case 5:
                    $string = htmlentities(pq("[cellpadding='0'] tr:nth-child($i) td:nth-child($j)")->text(),ENT_SUBSTITUTE,'UTF-8');
                    $re = '/[ \s]|[руб]/';
                    $divs['data'][$i-2]['price'] = (float)preg_replace($re,'',$string);
                    break;
                default:
                    break;
            }
           
        }
     }
     
           /*возвращаем массив, который содержит в себе название эмитента, год выплаты, дату выплаты и сумму выплаты */
    return $divs; 
    }


результатом работы функции будет массив с данными по одному эмитенту. Далее, я импортирую результат в companies.json

импорт
$divs = $emitent->getDivsFromCode($i);
if ($divs['emitent'] != 'Нет эмитента'){
        array_push($json_div, $divs);
    }
}
file_put_contents('companies_part1.json',json_encode($json_div,JSON_UNESCAPED_UNICODE));


Пример массива
[{"emitent":"АВТОВАЗ, (RU0009071187, AVAZ)",
   "code":3,
   "data":
            {"1": {"year":"2007","date":"16.05.2008","price":0.29},
              "2": {"year":"2006","date":"06.04.2007","price":0.1003},
              "3": {"year":"2005","date":"07.04.2006","price":0.057}
            }
  }]


Минусы парсера: сначала я думал, что придется спарсить около 1000 страниц, потому что после 800 мне стали попадаться только доп.эмиссии. Но потом, не найдя одного из эмитентов, я решил продолжить парсинг и выяснилось, что даже и после 5к страниц может что-то попадаться. Решение — переписать парсер с использованием multicurl для скорости обработки. До этого у меня руки не дошли (начал читать), но по правильному, стоило бы поступить именно так. Может кто-то из читателей мне поможет. Опять же, базу с дивами можно обновлять раз в пол года. Этого хватит, если вы оперируете годовыми доходностями.

Высчитываем норму доходности и полную доходность акции с момента покупки


Формулу я уже привел выше. Для автоматизации я написал скрипт, который использует исходные данные, вводимые вручную и считает требуемые показатели.

$name = 'GAZP';         //Тикер эмитента
$year_buy = 2015;      //Год покупки актива
$buy_price = 130;        //Цена покупки актива
$year_last_div = 2016;  //Год последних выплаченных диви
$v_need = 4;           //требуемая доходность

Для удобства я написал небольшую функцию. которая по тикеру находит данные эмитента в json

Поиск по тикеру
public function getDivsFromName($name){
         $file = file_get_contents('companies.json');
        $array = json_decode($file,true);
        foreach ($array as $emitent) {
            if ((stristr($emitent['emitent'],$name))&&(!stristr($emitent['emitent'],$name.'-'))){
                return $emitent;                
            }
        }
    }


Теперь мы можем посчитать интересующие нас данные. Для получения текущей цены (переменная $today_price) я использую функцию, в которой я писал в своем предыдущем посте и которая использует API Московской биржи. Все формулы взяты из вот этой статьи.

$emitent = new emitent();

$v_today;           //Текущая норма прибыли;
$v_need = $v;   //Требуемая норма доходности;

$div = $emitent->getDivsFromName($name);
$sum = $emitent->getSumDivs($year_buy, $div);
$last_div = $emitent->getSumDivs($year_last_div, $div,1);
//Считаем доходность c момента покупки
/*r = (D + (P1 - P0))/P0 * 100%
 * P0 - цена покупки
 * P1 - цена продажи
 * D - сумма дивидендов
 *  */
$today_price = $emitent->getPrice($name, '.json');
$r = ($sum + ($today_price-$buy_price))/$buy_price*100;
$v_today = $last_div/$today_price*100;
$P = ($last_div/$v_need)*100; //целевая цена покупки

Теперь немного анализа получившихся результатов. Для примера я беру две крупных компании, входящих INDEX MSCI RUSSIA

Газпром


и Алроса (Допустим, мы ее брали по 60р. в 2015 году.)



Как видите, несмотря на то, что цена акции газпрома практически не отличается от цены покупки, мы все равно имеем доходность 12% за счет дивидендов. У Алросы дела обстоят еще лучше. Можем пересчитать для доходности ставок по депозитам с сайта банки.ру из списка банков top-50. Сейчас банки дают нам 6,5%





Соответственно, так вы можете, варьируя нужную норму доходности, понять удовлетворяет ли текущая цена акции вашим запросам и вашим финансовым целям.

Замечу, что как и в прошлой статье, это лишь нахождение одного из факторов, влияющих на принятие решения.

Многие инвесторы делают вышеописанные расчеты в Excel. Этот инструмент предоставляет множество формул для расчетов прибыли, денежных потоков и т.д. Но я ставил целью автоматизировать сбор данных по дивидендам и автоматизированную обработку этих данных.
Статья в этот раз включает меньше теории и больше кода потому, что теория очень обширна, а привязать данную задачу к общей теории построения портфеля — надо писать цикл статей. Да и формат Хабра не подразумевает написание чисто инвестиционных статей, тем более сейчас на просторах сети их огромное множество.

Предыдущая статья
Спасибо за внимание!
Tags:инвестиции для новичковакциирасчетдивидендыдоходностьмосковская биржа
Hubs: PHP API ООP
+10
7k 64
Comments 10