Анализ данных формы SEC EDGAR XML с дочерними узлами с использованием BeautifulSoup

Я пытаюсь очистить отдельные фонды из формы SEC N-PORT-P/A, используя красивый суп и xml. Типичная отправка, описанная ниже и [ссылка здесь][1], выглядит так:

<edgarSubmission xmlns = "http://www.sec.gov/edgar/nport" xmlns:com = "http://www.sec.gov/edgar/common" xmlns:ncom = "http://www.sec.gov/edgar/nportcommon" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance">
<headerData>
<submissionType>NPORT-P/A</submissionType>
<isConfidential>false</isConfidential>
<accessionNumber>0001145549-23-004025</accessionNumber>
<filerInfo>
<filer>
<issuerCredentials>
<cik>0001618627</cik>
<ccc>XXXXXXXX</ccc>
</issuerCredentials>
</filer>
<seriesClassInfo>
<seriesId>S000048029</seriesId>
<classId>C000151492</classId>
</seriesClassInfo>
</filerInfo>
</headerData>
    <formData>
        <genInfo>
        ...
        </genInfo>
        <fundInfo>
        ...
        </fundInfo>
        <invstOrSecs>
            <invstOrSec>
                <name>ARROW BIDCO LLC</name>
                <lei>549300YHZN08M0H3O128</lei>
                <title>Arrow Bidco LLC</title>
                <cusip>042728AA3</cusip>
                <identifiers>
                    <isin value = "US042728AA35"/>
                </identifiers>
                <balance>115000.000000000000</balance>
                <units>PA</units>
                <curCd>USD</curCd>
                <valUSD>114754.170000000000</valUSD>
                <pctVal>0.3967552449</pctVal>
                <payoffProfile>Long</payoffProfile>
                <assetCat>DBT</assetCat>
                <issuerCat>CORP</issuerCat>
                <invCountry>US</invCountry>
                <isRestrictedSec>N</isRestrictedSec>
                <fairValLevel>2</fairValLevel>
                <debtSec>
                    <maturityDt>2024-03-15</maturityDt>
                    <couponKind>Fixed</couponKind>
                    <annualizedRt>9.500000000000</annualizedRt>
                    <isDefault>N</isDefault>
                    <areIntrstPmntsInArrs>N</areIntrstPmntsInArrs>
                    <isPaidKind>N</isPaidKind>
                </debtSec>
                <securityLending>
                    <isCashCollateral>N</isCashCollateral>
                    <isNonCashCollateral>N</isNonCashCollateral>
                    <isLoanByFund>N</isLoanByFund>
                </securityLending>
            </invstOrSec>

Arrow Bidco LLC является облигацией в портфеле, и некоторые ее характеристики включены в заявку (CUSIP, CIK, баланс, срок погашения и т. д.). Я ищу лучший способ перебрать каждую отдельную ценную бумагу (investOrSec) и собрать характеристики каждой ценной бумаги в фрейме данных. Код, который я сейчас использую:

import numpy as np
import pandas as pd
import requests
from bs4 import BeautifulSoup

header = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.75 Safari/537.36", "X-Requested-With": "XMLHttpRequest"}

n_port_file = requests.get("https://www.sec.gov/Archives/edgar/data/1618627/000114554923004968/primary_doc.xml", headers=header, verify=False)
n_port_file_xml = n_port_file.content
soup = BeautifulSoup(n_port_file_xml,'xml')

names = soup.find_all('name')
lei = soup.find_all('lei')
title = soup.find_all('title')
cusip = soup.find_all('cusip')
....
maturityDt = soup.find_all('maturityDt')
couponKind = soup.find_all('couponKind')
annualizedRt = soup.find_all('annualizedRt')

Затем итерация по каждому списку для создания фрейма данных на основе значений в каждой строке.

fixed_income_data = []
for i in range(0,len(names)):
    rows = [names[i].get_text(),lei[i].get_text(),
        title[i].get_text(),cusip[i].get_text(),
        balance[i].get_text(),units[i].get_text(),
        pctVal[i].get_text(),payoffProfile[i].get_text(),
        assetCat[i].get_text(),issuerCat[i].get_text(),
        invCountry[i].get_text(),couponKind[i].get_text()
        ]
    fixed_income_data.append(rows)

fixed_income_df = pd.DataFrame(equity_data,columns = ['name',
                         'lei',
                         'title',
                         'cusip',
                         'balance',
                         'units',
                         'pctVal',
                         'payoffProfile',
                         'assetCat',
                         'issuerCat',
                         'invCountry'
                         'maturityDt',
                         'couponKind',
                         'annualizedRt'
                         ], dtype = float)

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

Кроме того, для портфелей, содержащих как ценные бумаги с фиксированным доходом, так и долевые ценные бумаги, долевые ценные бумаги не возвращают информацию для дочернего объекта DebtSecs. Есть ли способ перебирать эти данные, одновременно очищая их самым простым способом? Даже добавление «NaN» для дочерних элементов DebtSec, на которые не ссылаются ценные бумаги, было бы правильным ответом. Любая помощь приветствуется! [1]: https://www.sec.gov/Archives/edgar/data/1618627/000114554923004968/primary_doc.xml

Если я что-то не упустил, EDGAR API поддерживает только финансовые данные (10-Q, 10-K, 8-K и т. д.), а не портфельные активы (13-F, N-PORT и т. д.). На их веб-сайте: «В настоящее время в API-интерфейсы входят история отправки файлов и данные XBRL из финансовой отчетности (формы 10-Q, 10-K, 8-K, 20-F, 40-F, 6-K и их варианты)».

therdawg 06.02.2023 21:00
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
1
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Чтобы облегчить себе задачу, так как это файл XML, вы должны использовать синтаксический анализатор xml и xpath. Учитывая, что вы хотите создать фрейм данных, наиболее подходящим инструментом будет метод pandas read_xml().

Поскольку XML является вложенным, вам нужно будет создать два разных фрейма данных и соединить их (возможно, у других будет лучшее представление о том, как к этому подойти). И, наконец, хотя read_xml() может читать прямо из URL-адреса, в этом случае EDGAR требует использования пользовательского агента, то есть вам также необходимо использовать библиотеку requests.

Итак, все вместе:

#import required libraries
import pandas as pd
import requests

url = 'https://www.sec.gov/Archives/edgar/data/1618627/000114554923004968/primary_doc.xml'
#set headers with a user-agent
headers = {"User-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"}    
req =  requests.get(url, headers=headers)

#define the columns you want to drop (based on the data in your question)
to_drop = ['identifiers', 'curCd','valUSD','isRestrictedSec','fairValLevel','debtSec','securityLending']

#the filing uses namespaces (too complicated to get into here), so you need to define that as well
namespaces = {"nport": "http://www.sec.gov/edgar/nport"}

#create the first df, for the securities which are debt instruments
invest = pd.read_xml(req.text,xpath = "//nport:invstOrSec[.//nport:debtSec]",namespaces=namespaces).drop(to_drop, axis=1)

#crete the 2nd df, for the debt details:
debt = pd.read_xml(req.text,xpath = "//nport:debtSec",namespaces=namespaces).iloc[:,0:3]

#finally, concatenate the two into one df:
pd.concat([invest, debt], axis=1)

Это должно вывести ваши 126 долговых ценных бумаг (извините за форматирование):

lei     title   cusip   balance     units   pctVal  payoffProfile   assetCat    issuerCat   invCountry  maturityDt  couponKind  annualizedRt
0   ARROW BIDCO LLC     549300YHZN08M0H3O128    Arrow Bidco LLC     042728AA3   115000.00   PA  0.396755    Long    DBT     CORP    US  2024-03-15  Fixed   9.50000
1   CD&R SMOKEY BUYER INC   NaN     CD&R Smokey Buyer Inc   12510CAA9   165000.00   PA  0.505585    Long    DBT     CORP    US  2025-07-15  Fixed   6.75000

Затем вы можете поиграть с окончательным df, добавить или удалить столбцы и т. д.

Это красиво - спасибо! Да, я пришел к выводу, что с EDGAR вообще не приятно работать... 13-F HMTL не так уж и страшен, но заявка N-PORT построена плохо. Отметив это правильно, так как изначально это выглядит именно так, как я просил! Потребуется некоторое время, чтобы поиграть с ним и попытаться построить аналогичный DF для портфеля только акций, используя методы, описанные выше. Еще раз высоко ценим!

therdawg 06.02.2023 22:32

@therdawg Рад, что это сработало для вас! Чтобы не сильно вас угнетать, но подождите, пока вам не придется возиться с файлами XBRL :)

Jack Fleeting 06.02.2023 22:33

Это будет следующим шагом — я раньше занимался XBRL и знаю, насколько это сложно, но это мост, который я перейду, когда доберусь до него. Если я могу задать один дополнительный вопрос, как мне получить идентификаторы? В идеале это то, что я хотел бы иметь, но фрейм данных будет заполнять NaN, если форма не включает идентификатор. Я предполагаю, что для этого потребуется третий фрейм данных с использованием пространств имен, которые вы указали в своем первоначальном ответе? Извиняюсь за отсутствие ясности с моей стороны.

therdawg 06.02.2023 22:57

@therdawg Это может быть сложнее, потому что у некоторых <identifiers> есть <isin> ребенок, а у других нет. Это может стать слишком сложным для read_xml(), и тогда вам придется прибегнуть к другим инструментам. В любом случае, если это важно, вы должны опубликовать его как отдельный вопрос в соответствии с политикой SO.

Jack Fleeting 07.02.2023 00:22

Другие вопросы по теме