Простая стратегия выбора акций с 6 ключевыми финансовыми показателями

Часть 1

В этой статье мы рассмотрим шесть ключевых финансовых параметров для выбора акций. Более подробно эта стратегия описана в видео на YouTube Выбор акций как профессионал | Пример Intuit.

Отказ от ответственности: Информация, представленная здесь, предназначена только для информационных целей и не предназначена для личных финансовых, инвестиционных или других советов.

Ключевые метрики:

  • Операционная маржа — определяет прибыль, которую бизнес получает с каждого доллара продаж после оплаты переменных затрат, таких как заработная плата и сырье.
  • Дивидендная доходность — показывает ежегодную выплату дивидендов фирмы по отношению к цене ее акций.
  • Дивидендное покрытие — измеряет количество дивидендных выплат, которые компания может выплатить своим акционерам.
  • Долг/EBITDA — измеряет сумму дохода, полученного и доступного для погашения долга до учета процентов, налогов, износа и амортизационных отчислений.
  • Коэффициент P/E — измеряет текущую цену акции по отношению к прибыли на акцию (EPS)
  • Мультипликатор PEG — оценивает стоимость акции, а также учитывает ожидаемый рост прибыли компании.

Предпосылка

  1. Выберите акцию для оценки, например, мы выберем Intuit Inc. (INTU) в качестве примера
  2. Подберите список акций для сравнения. Сравнение акций на основе релевантных характеристик, таких как отрасль, сектор и другие, имеет решающее значение. Для этого вы можете использовать «Stock Screener» от Yahoo Finance. Вот мои критерии для INTU:
Скринер акций от Yahoo Finance

Для того, чтобы список акций не превышал 20 акций, я добавил Exchange и ограничил его Large и Mega Caps. Вы можете выбрать свои собственные критерии, но имейте в виду, что yfinance API может ограничивать избыточные запросы.

3. Скопируйте список символов. К сожалению, в Yahoo Finance нет способа загрузить список символов в файл. Вы можете скопировать и вставить таблицу в Excel, чтобы получить список символов. Этот список будет помещен в файл свойств, как описано в разделе «Загрузить свойства».

Код доступен в виде записной книжки Jupyter на сайте GitHub.

Библиотеки Python

Необходимые библиотеки Python:

  • yfinance — для доступа к данным финансового рынка
  • jproperties — парсер и запись файлов свойств для Python
  • pandas — использовать фрейм данных

Импорт библиотек

# Read stocks
import yfinance as yf

import pandas as pd

# To read external property file
from jproperties import Properties

Свойства загрузки

configs = Properties()

with open(‘config/yf_stock_picker_p1.properties’, ‘rb’) as config_file:
configs.load(config_file)

SYMBOLS = configs.get(‘SYMBOLS’).data.split(‘,’)
TICKER = configs.get(‘TICKER’).data
METRICS = configs.get(‘METRICS’).data.split(‘,’)
WEIGHTS = configs.get(‘WEIGHTS’).data.split(‘,’)

# Convert weights to integers
WEIGHTS = [int(i) for i in WEIGHTS]

  • SYMBOLS — список символов из Stock Screener (шаг 3 Prerequisite)
  • TICKER — символ для оценки (шаг 1 из Prerequisite)
  • МЕТРИКИ — шесть ключевых метрик
  • WEIGHTS — весовые коэффициенты, присвоенные каждой метрике

Пример файла свойств:# Related stocks
SYMBOLS = INTU,CDNS,WDAY,ROP,TEAM,ADSK,DDOG,ANSS,ZM,PTC,\
BSY,GRAB,SSNC,APP,AZPN,MANH,ZI,NICE

# Stock on focus
TICKER = INTU

# Metric names
METRICS = Operating Margin,Dividend Yield,Dividend Cover,Debt/EBITDA,P/E ratio,PEG ratio

# Equals weight of 1 for each metric
WEIGHTS = 1,1,1,1,1,1

Определение фреймов данных для сбора метрик

# DF to collect key metrics
key_metrics_df = pd.DataFrame()
# DF to collect exclusions
key_metrics_ex_df = pd.DataFrame()
# Initialize list with empty dictionaries. This collects raw metrics data for each metric
raw_metrics = [{} for sub in range(len(METRICS))]

Сбор ключевых метрик

for symbol in SYMBOLS:
ticker = yf.Ticker(symbol)
# Calculate Operating Margin and convert it to percentage
raw_metrics[0][symbol] = round((compute_om(ticker) * 100), 2)

# Dividend Yield -> Check to see whether the dividend yield exists for the symbol
if ‘dividendYield’ in ticker.info:
raw_metrics[1][symbol] = round((ticker.info[‘dividendYield’] * 100), 2)
else:
new_row = {‘Symbol’:symbol, ‘Metric’:METRICS[1], ‘Reason’:’Missing Dividend Yield’}
key_metrics_ex_df = pd.concat([key_metrics_ex_df, pd.DataFrame([new_row])], ignore_index=True)

# Dividend Cover -> Check to see whether the dividend rate exists for the symbol
if ‘dividendRate’ in ticker.info:
raw_metrics[2][symbol] = round((ticker.info[‘trailingEps’]/ticker.info[‘dividendRate’]), 2)
else:
new_row = {‘Symbol’:symbol, ‘Metric’:METRICS[2], ‘Reason’:’Missing Dividend Rate’}
key_metrics_ex_df = pd.concat([key_metrics_ex_df, pd.DataFrame([new_row])], ignore_index=True)

# Debt/EBITDA; check it whether total debt exists in income statement
if ‘Total Debt’ in ticker.balance_sheet.index:
debt_to_ebitda = round((ticker.balance_sheet.loc[‘Total Debt’][0]/ticker.income_stmt.loc[‘EBITDA’][0]), 2)
# We only take into ccount with positive ratios or else they will impact rankings
if debt_to_ebitda >= 0:
raw_metrics[3][symbol] = debt_to_ebitda
else:
new_row = {‘Symbol’:symbol, ‘Metric’:METRICS[3], ‘Reason’:’Negative Debt/EBITDA ratio’}
key_metrics_ex_df = pd.concat([key_metrics_ex_df, pd.DataFrame([new_row])], ignore_index=True)
else:
new_row = {‘Symbol’:symbol, ‘Metric’:METRICS[3], ‘Reason’:’Missing Total Debt’}
key_metrics_ex_df = pd.concat([key_metrics_ex_df, pd.DataFrame([new_row])], ignore_index=True)

# Fwd P/E -> Check to see whether the Fwd P/E exists for the symbol
if ‘forwardPE’ in ticker.info:
fwd_pe = ticker.info[‘forwardPE’]
if fwd_pe >= 0:
raw_metrics[4][symbol] = round(fwd_pe, 2)
else:
new_row = {‘Symbol’:symbol, ‘Metric’:METRICS[4], ‘Reason’:’Negaive Forward P/E ratio’}
key_metrics_ex_df = pd.concat([key_metrics_ex_df, pd.DataFrame([new_row])], ignore_index=True)
else:
new_row = {‘Symbol’:symbol, ‘Metric’:METRICS[4], ‘Reason’:’Missing Forward P/E ratio’}
key_metrics_ex_df = pd.concat([key_metrics_ex_df, pd.DataFrame([new_row])], ignore_index=True)

# PEG Ration -> Check to see whether the PEG ration exists for the symbol
if ‘pegRatio’ in ticker.info:
peg_ratio = ticker.info[‘pegRatio’]
if peg_ratio >= 0:
raw_metrics[5][symbol] = peg_ratio
else:
new_row = {‘Symbol’:symbol, ‘Metric’:METRICS[5], ‘Reason’:’Negaive PEG ratio’}
key_metrics_ex_df = pd.concat([key_metrics_ex_df, pd.DataFrame([new_row])], ignore_index=True)
else:
new_row = {‘Symbol’:symbol, ‘Metric’:METRICS[5], ‘Reason’:’Missing PEG Ratio’}
key_metrics_ex_df = pd.concat([key_metrics_ex_df, pd.DataFrame([new_row])], ignore_index=True)

Примечания:

  1. raw_metrics — массив метрик; позиция 0 содержит метрики операционной маржи, позиция 1 — дивидендная доходность и т.д.
  2. key_metrics_ex_df — это DataFrame, который собирает исключения, такие как отсутствующие дивиденды, отрицательные коэффициенты для Forward PE или PEG и т.д. Чтобы не влиять на ранжирование, были устранены отрицательные форвардные мультипликаторы PE и PEG.

Установите ключевые метрики

for pos in range(len(METRICS)):
if (pos >= 3):
# Ranking is on ascending order (lower the better)
new_row = get_key_metrics_row(raw_metrics[pos], METRICS[pos], False)
else:
new_row = get_key_metrics_row(raw_metrics[pos], METRICS[pos])
key_metrics_df = pd.concat([key_metrics_df, pd.DataFrame([new_row])], ignore_index=True)

Примечания:

  1. get_key_metrics_row — служебный метод (код см. в записной книжке Jupyter с GitHub)
  2. pos ≥ 3 — загребание по последним 3 ключевым метрикам (долг/EBITDA, коэффициент P/E и мультипликатор PEG) отличается от первых ключевых метрик

Ниже приведен пример выходных данных для INTU после установки ключевых метрик:

Ключевые метрики для INTU — ранжирование доступно по всем ключевым метрикам
Ключевые метрики для WDAY — Отсутствует рейтинг для дивидендных метрик

Примечание: ‘Дивидендная доходность» доступна только для 4 акций (включая INTU), поэтому сравнение проводилось с этими 4, а не с 18 акциями, используемыми для «Операционной маржи».

Список (усеченный) исключений

Оценка для каждого показателя

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

Пример таблицы ранжирования по пяти элементам

Элемент с наивысшей оценкой получает максимальное значение 5, в то время как элемент с наименьшей оценкой получает минимально возможный балл 1. Если мы нормализуем его до 1, вы получите значения 1 для предметов с самым высоким рангом и 0,2 для предметов с самым низким рангом соответственно. Используя вышеупомянутую таблицу с 5 акциями:

Примеры оценок для пяти акций

Вот код python для этого:score = []
for index, row in key_metrics_df.iterrows():
if row[‘Ranking’] == ‘NA’:
score.append(0.0)
continue
rank, total = tuple(map(int, row[‘Ranking’].split(‘/’)))
score.append(round(((total — rank) + 1)/total,2))
key_metrics_df[‘Score’] = score

Примечание: Если для акции не было найдено ранжирование, присваивалась оценка 0.

Оценка для INTU — ненулевые значения баллов для всех ключевых метрик
Score for WDAY — нулевые значения баллов для дивидендных метрик

Итоговый счет

# Normalize the score out of 10
ticker_score = round(((((key_metrics_df[‘Score’] * WEIGHTS).sum())/sum(WEIGHTS)) * 10), 2)
print(f’Score for {TICKER} is {ticker_score} out of 10′)

INTU (по состоянию на 29.09.2023) с весовыми коэффициентами 1 для всех ключевых метрик
INTU (по состоянию на 29.09.2023) с использованием весовых коэффициентов 2 для «операционной маржи» и коэффициента PEG и сохранения 1 для остальных
WDAY (по состоянию на 30.09.2023) с весовыми коэффициентами 1 для всех ключевых метрик

Заключение

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

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

Еще одним аспектом, который следует учитывать, является абсолютный характер рейтинга, который игнорирует размер расхождений между двумя акциями. Например, даже если коэффициенты P/E двух акций очень низкие, одна из них все равно будет иметь более высокий рейтинг, чем другая.

Часть 2

Часть 1 фокусируется на конкретной акции в сравнении с группой акций, которые похожи на нее. Ранжирование внутри выбранной группы акций является основной темой Части 2.

Предварительное условие Части 1 #1 (запас в фокусе) больше не требуется. Тем не менее, часть 2 по-прежнему требует предварительных требований #2 и #3.

Никаких изменений в библиотеках Python, выбранных для части 1.

Код доступен в виде записной книжки Jupyter на сайте GitHub.

Свойства загрузки

configs = Properties()

with open(‘config/yf_stock_picker_p2.properties’, ‘rb’) as config_file:
configs.load(config_file)

SYMBOLS = configs.get(‘SYMBOLS’).data.split(‘,’)
METRICS = configs.get(‘METRICS’).data.split(‘,’)
WEIGHTS = configs.get(‘WEIGHTS’).data.split(‘,’)

# Convert weights to integers
WEIGHTS = [int(i) for i in WEIGHTS]

За исключением свойства TICKER, которое не требуется, остальные свойства остаются неизменными.

  • SYMBOLS — список символов из Stock Screener (шаг 3 Пререквизита — Часть 1)
  • METRICS — шесть ключевых метрик (без изменений)
  • WEIGHTS — веса, присвоенные каждой метрике (без изменений)

Пример файла свойств:# Related ticker symbols
SYMBOLS = INTU,CDNS,WDAY,ROP,TEAM,ADSK,DDOG,ANSS,ZM,PTC,\
BSY,GRAB,SSNC,APP,AZPN,MANH,ZI,NICE

# Metric names
METRICS = Operating Margin,Dividend Yield,Dividend Cover,Debt/EBITDA,P/E ratio,PEG ratio

# Equals weight of 1 for each metric
WEIGHTS = 1,1,1,1,1,1

Заметка: Раздел «Сбор ключевых метрик» опущен, так как в части 2 эта логика не изменена.

Заполнение ключевых метрик

Чтобы заполнить значения ключевых метрик для акций, перечисленных в параметре «СИМВОЛЫ», мы повторно используем данные, которые были собраны на шаге «Сбор важных показателей».# DF to collect value key metrics for all the tickers
key_metrics_value_df = pd.DataFrame()

for idx in range(len(METRICS)):
key_metrics_value_df[METRICS[idx]] = raw_metrics[idx]
key_metrics_value_df

Значения ключевых метрик для акций, перечисленных в параметре SYMBOLS

Чтобы уменьшить влияние на ранжирование, отсутствующие дивиденды и отрицательные коэффициенты для «Форвардного PE» или PEG помечаются как «NaN».

Вычисление баллов для каждой метрики

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

Примечание: в разделе «Оценка по каждому показателю» в части 1 представлена дополнительная информация о том, как определяются баллы.# DF to hold key metrics scores
key_metrics_score_df = pd.DataFrame()

# Set the index to SYMBOLS
key_metrics_score_df.index = SYMBOLS

# Loop through raw data we collected under Collect Key Metrics section
for idx in range(len(raw_metrics)):
if (idx >= 3):
# Ranking is on ascending order (lower the better)
ranking_list = get_ranking_list(raw_metrics[idx], False)
else:
# Ranking is on descending order (higher the better)
ranking_list = get_ranking_list(raw_metrics[idx], True)

# List of scores for a metric
score = []
# Loop through the ranking list to calculate the score
for ranking in ranking_list:
if ranking == ‘NA’:
score.append(0.0)
continue
rank, total = tuple(map(int, ranking.split(‘/’)))
score.append(round(((total — rank) + 1)/total,2))

# Append the Score to the metric name
col_name = f'{METRICS[idx]} Score’
key_metrics_score_df[col_name] = score
key_metrics_score_df

Баллы по каждой акции по каждому ключевому показателю

Вычислить общий балл

Следующий шаг — сложить баллы метрик, чтобы получить общую оценку.# List of total scores for each stock
total_score = []
for symbol in SYMBOLS:
# Sum all scores across key metrics * weights and normalize the score out of 10
total_score.append(round(((((key_metrics_score_df.loc[symbol] * WEIGHTS).sum())/sum(WEIGHTS)) * 10), 2))
key_metrics_score_df[‘Total Score’] = total_score
key_metrics_score_df

Суммарные баллы, рассчитанные для каждой акции

Ранжирование с использованием общего балла

Мы должны иметь возможность ранжировать на основе оценки, поскольку мы знаем общие баллы для каждой акции.# Each score in this list has the format: score/total, e.g. 4/17
tot_score_ranking = get_ranking_list(key_metrics_score_df[‘Total Score’], True)
# Only interested in the score part
ranking = [x.split(‘/’)[0] for x in tot_score_ranking]
key_metrics_score_df[‘Ranking’] = ranking
key_metrics_score_df

Ранжирование добавлено к каждой акции в свойстве SYMBOLS

Заключение

Как следует из названия статьи, представлен простой подход к выбору акций с использованием шести важнейших показателей. Эта стратегия наносит ущерб компаниям, у которых нет дивидендов или отрицательная прибыль. Тем не менее, важно иметь в виду, что как новая, так и уже существующая компания может пройти через это и пережить кратковременную отрицательную прибыль.

Абсолютный характер рейтинга, который не учитывает размер различий между двумя акциями, является еще одним соображением. Например, даже если две акции имеют чрезвычайно низкие коэффициенты P/E, одна из них все равно будет оценена выше, чем другая.

Источник