Инструментарий Quant Trader’s Toolkit — ссылки на полную серию:

Ваше ожидание окончено!

Вы когда-нибудь пытались получить необходимые фундаментальные данные?! Вы когда-нибудь хотели использовать квартальные фундаментальные данные, но обнаруживали, что они скрыты за платным доступом?! Что ж, сегодня вы получаете код Python для доступа к БЕСПЛАТНЫМ ФУНДАМЕНТАЛЬНЫМ ДАННЫМ из документов секонд-х, БЫСТРО И ЛЕГКО, благодаря вашим друзьям из The Quant Trading Room!

Данные — король

Данные станут основой любой стратегии, которую вы будете строить в качестве специалиста по количественному анализу. В частности, получение высококачественных данных, к которым другие люди не имеют доступа, в конечном итоге станет тем, где вы найдете свои самые прибыльные альфы. Как вы думаете, каковы ваши шансы найти преимущество, используя только данные OHLC, полученные от yfinance? Они низкие…. очень низкий. Однако не стоит паниковать! В этой серии «Инструментарий Quant Trader’s Toolkit» мы предоставим вам инструменты, которые позволят вам отделить себя от подражателей и найти преимущества, которые будут финансировать арендные платежи вашего сахарного ребенка ;).

Получить все публично торгуемые компании

Эта первая функция позволяет вам получить ключи центрального индекса (CIK) ВСЕХ публично торгуемых компаний, которые отвечают за представление отчетов в Комиссию по ценным бумагам и биржам.

def get_all_companies(headers):
    
    companyTickers = requests.get(
        "https://www.sec.gov/files/company_tickers.json",
        headers=headers)
    company_dct = companyTickers.json()
    cik_lst = [str(company_dct[x]['cik_str']).zfill(10) for x in company_dct]
    
    return cik_lst

Получить данные о подаче данных CIK

Эта функция позволяет вам взять один из CIK из списка, возвращенного последней функцией, и запросить данные из всех этих компаний 10-K (Годовой отчет) и 10-Q (Квартальный отчет), начиная с 2009 года. Словарь возвращается, который включает название компании, CIK и данные для отчетов 10-K и 10-Q как в отдельной, так и в объединенной форме для более полного представления об основных принципах компании.

def get_filing_data_by_cik(cik, headers):
    
    companyFacts = requests.get(
        f'https://data.sec.gov/api/xbrl/companyfacts/CIK{cik}.json', 
        headers=headers)
    cFacts = companyFacts.json()
    facts = cFacts['facts']
    
    date_dict = {'Q1': '03-30-', 
                 'Q2': '06-30-', 
                 'Q3': '09-30-', 
                 'FY': '12-31-'}

    Q10K10_tags_values = [(f"{t}_{u}", 
                           [(f"{date_dict[i['fp']]}{i['fy']}", i['val']) 
                            for i in facts[c][t]['units'][u] if i['form'] 
                            in ['10-Q','10-K']
                           ]
                          ) 
                          for c in facts.keys() 
                          for t in facts[c].keys() 
                          for u in facts[c][t]['units'].keys()
                         ]

    K10_tags_values = [(f"{t}_{u}", 
                        [(f"{date_dict[i['fp']]}{i['fy']}", i['val']) 
                         for i in facts[c][t]['units'][u] if i['form'] 
                         in ['10-K']
                        ]
                       ) 
                       for c in facts.keys() 
                       for t in facts[c].keys() 
                       for u in facts[c][t]['units'].keys()
                      ]

    Q10_tags_values = [(f"{t}_{u}", 
                        [(f"{date_dict[i['fp']]}{i['fy']}", i['val']) 
                         for i in facts[c][t]['units'][u] if i['form'] 
                         in ['10-Q']
                        ]
                       ) 
                       for c in facts.keys() 
                       for t in facts[c].keys() 
                       for u in facts[c][t]['units'].keys()
                      ]

    filing_data = {'name': cFacts['entityName'], 
                   'cik': cFacts['cik'], 
                   '10Q10K': {k:v for k,v in Q10K10_tags_values},
                   '10K': {k:v for k,v in K10_tags_values}, 
                   '10Q': {k:v for k,v in Q10_tags_values}
                  }
    
    return filing_data

Просмотр данных

Теперь, когда у вас есть словарь, содержащий все фундаментальные данные из отчетов 10-K и 10-Q, пришло время преобразовать эти данные в форму, на которую мы можем смотреть, не причиняя себе вреда. Просто заполните параметры этой функции данными о регистрации, которые вы только что собрали для конкретной интересующей вас компании, укажите, из какой формы вы хотите просмотреть данные, и включите в данные допуск для значений nan. Например, при извлечении каждого тега (например, EntityCommonStockSharesOutstanding_shares) из файла некоторые теги заполняются значениями nan и только несколькими значениями, отличными от nan. Если вас устраивает 50% значений nan в исходном теге, установите nan_tolerance=50. Если вы сейчас немного запутались, просто поставьте nan_tolerance=20 и назовите это хорошим. Только теги с данными менее 20% будут включены в ваш окончательный фрейм данных.

def convert_dict_to_df(filing_data, form, nan_tolerance=100):
    '''
    filing_data: dict
    form: lst 
          ['10K'], ['10Q'], or ['10K', '10Q']
    nan_tolerance: int 
                    a integer from 1 - 100
    '''
    form_df = pd.DataFrame()
    
    if form == ['10K']:
        
        tenk = filing_data['10K']

        for i in tqdm(tenk.keys()):
            
            if len(tenk[i]) > 0:
                
                df = pd.DataFrame(tenk[i], columns=['date', i])
                df.index = pd.to_datetime(df.date)
                df = df.drop(columns=['date'])
                df = df.resample('Y').last()
                length = len(df)
                percent_nan = df.isnull().sum() * 100 / length
                
                if percent_nan[0] < nan_tolerance:
                    
                    if form_df.empty:
                        form_df = df
                    else:
                        form_df = pd.merge(form_df, 
                                           df, 
                                           on=['date'], 
                                           how='outer')
        
    elif form == ['10Q']:
        
        tenq = filing_data['10Q']

        for i in tqdm(tenq.keys()):
            
            if len(tenq[i]) > 0:
                
                df = pd.DataFrame(tenq[i], columns=['date', i])
                df.index = pd.to_datetime(df.date)
                df = df.drop(columns=['date'])
                df = df.resample('Q').last()
                length = len(df)
                percent_nan = df.isnull().sum() * 100 / length
                
                if percent_nan[0] < nan_tolerance:
                    
                    if form_df.empty:
                        form_df = df
                    else:
                        form_df = pd.merge(form_df, 
                                           df, 
                                           on=['date'], 
                                           how='outer')
    
    elif form == ['10Q', '10K'] or form == ['10K', '10Q']:
        
        tenqk = filing_data['10Q10K']
        
        for i in tqdm(tenqk.keys()):
        
            if len(tenqk[i]) > 0:
                df = pd.DataFrame(tenqk[i], columns=['date', i])
                df.index = pd.to_datetime(df.date)
                df = df.drop(columns=['date'])
                df = df.resample('Q').last()
                length = len(df)
                percent_nan = df.isnull().sum() * 100 / length

                if length > 30 and percent_nan[0] < nan_tolerance :

                    if form_df.empty:
                        form_df = df
                    else:
                        form_df = pd.merge(form_df, 
                                           df, 
                                           on=['date'], 
                                           how='outer')
    
    else: 
        print("Invalid form.")
        print("Vaild inputs are ['10K'], ['10Q'], or ['10K', '10Q']")
        
    form_df.dropna(axis=1, how='all', inplace=True)
    form_df.dropna(axis=0, how='all', inplace=True)
    form_df = form_df.fillna(0)
    
    return form_df

Собираем все вместе

Пришло время применить вышеперечисленные функции! Во-первых, SEC требует, чтобы вы указали свой адрес электронной почты в качестве пользовательского агента при отправке запроса на получение их API. Затем мы получим все CIK, выберем один для использования, получим данные о регистрации этой компании и просмотрим данные во фрейме данных pandas.

from tqdm import tqdm
import requests
import pandas as pd
# View Dataframe as an interactive table in Jupyter Notebook
from itables import init_notebook_mode
init_notebook_mode(all_interactive=True)

headers = {'User-Agent': "[email protected]"}

cik_lst = get_all_companies(headers)

filing_data = get_filing_data_by_cik(cik_lst[1], headers)

df = convert_dict_to_df(filing_data, form=['10Q', '10K'], nan_tolerance=20)

df

Это все люди! Я оставлю вам несколько мудрых слов моего отца.

«Всегда помните первые два правила работы с данными. 1) Получайте удовольствие! и 2) Получайте удовольствие!»

Пожалуйста, не стесняйтесь комментировать любые вопросы, проблемы или темы, которые вы хотели бы осветить в будущих сообщениях!

Загляните на наш YouTube, где мы подробно разбираем код в наших статьях!

Если вы нашли этот или любой другой наш контент ценным, рассмотрите возможность купить нам кофе.

Получите полный доступ ко всем нашим статьям, став участником Medium!

** ЭТО СОДЕРЖАНИЕ НЕ ЯВЛЯЕТСЯ ФИНАНСОВОЙ КОНСУЛЬТАЦИЕЙ. ЭТО СТРОГО ОБРАЗОВАТЕЛЬНЫЙ **