[ML4T] 미국 주식 데이터 수집하기 - 주가, 거래량, 재무 데이터

목차

    0. 관련 글 목록

    [ML4T] Machine Learning for Trading: From Idea to Execution


    이 글은 퀀트 투자를 위한 머신러닝 딥러닝 알고리즘 트레이딩 2/e 2장의 내용을 바탕으로 작성되었습니다.

    https://github.com/stefan-jansen/machine-learning-for-trading 

     

    GitHub - stefan-jansen/machine-learning-for-trading: Code for Machine Learning for Algorithmic Trading, 2nd edition.

    Code for Machine Learning for Algorithmic Trading, 2nd edition. - GitHub - stefan-jansen/machine-learning-for-trading: Code for Machine Learning for Algorithmic Trading, 2nd edition.

    github.com


    1. 들어가며

    2장에서는 퀀트 투자에 필요한 미국 주식 데이터를 어떻게 구할 수 있는지에 대해 설명하고 있다. 크게 1) 고빈도 데이터, 2) 마켓 데이터, 3) 재무 데이터로 구분하여 설명하고 있는데, 고빈도 데이터는 개별 주문건 단위, 분단위 데이터로 일부 샘플만 무료로 제공된다. 틱 단위 혹은 분 단위의 high-frequency trading을 할 일은 없을 것 같아 이번 글에서는 간단하게 소개만 하고 넘어가려고 한다.


    2. 고빈도 데이터

    2.1. Nasdaq TotalView-ITCH

    - 나스닥에서 제공하는 데이터 프로토콜
    - 호가창을 재구성해볼 수 있음
    - 일별 샘플 데이터 제공

    2.2. AlgoSeek

    - 분 바(minutes bars) 데이터를 제공하여 ML 기반 트레이딩 전략을 설계하고 백테스트 할 수 있게 함
    - OHLCV(시가, 고가, 저가, 종가, 거래량), 매수-매도 호가 스프레드, 가격 상승.하강 움직임의 틱 수 정보 포함
    - 2013~2017년 나스닥 100 종목에 대한 분 바 데이터 샘플을 제공


    3. 마켓 데이터

    3.1 pandas

    3.1.1 HTML 테이블 읽기

    - read_html 함수로 웹사이트 상의 데이터를 가져올 수 있음
    - 예) 위키피디아에서 S&P 500 구성종목 정보 가져오기

    import pandas as pd
    
    sp_url = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'
    sp500_constituents = pd.read_html(sp_url, header=0)[0]
    sp500_constituents.info()
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 505 entries, 0 to 504
    Data columns (total 9 columns):
     #   Column                 Non-Null Count  Dtype 
    ---  ------                 --------------  ----- 
     0   Symbol                 505 non-null    object
     1   Security               505 non-null    object
     2   SEC filings            505 non-null    object
     3   GICS Sector            505 non-null    object
     4   GICS Sub-Industry      505 non-null    object
     5   Headquarters Location  505 non-null    object
     6   Date first added       457 non-null    object
     7   CIK                    505 non-null    int64 
     8   Founded                505 non-null    object
    dtypes: int64(1), object(8)
    memory usage: 35.6+ KB
    sp500_constituents.head()


    3.1.2 pandas-datareader

    pandas-datareader는 Quandl, World Bank, OECD, Yahoo Finance 등 다양한 데이터 소스에 접근할 수 있는 함수를 제공한다. Naver Finance를 통해 코스피, 코스닥 데이터도 가져올 수 있다.

    코랩을 통해 코드를 실행하고 싶다면 pandas_datareader를 최신버전으로 업데이트 시켜줘야 한다.

    pip install --upgrade pandas-datareader
    %matplotlib inline
    import os
    from datetime import datetime
    import pandas_datareader.data as web
    import matplotlib.pyplot as plt
    import mplfinance as mpf
    import seaborn as sns

    DataReader 함수에 ticker, data provider, 데이터 조회 시작일, 데이터 조회 종료일을 넣어주면 된다. 일자는 'yyyymmdd', 'yyyy-mm-dd', 'yyyy/mm/dd' 등 상식적으로 말이 되는 형태로 넣어주면 대충 다 알아서 처리해준다.

    web.DataReader(ticker, data_provider, start, end)

    마이크로소프트(MSFT)의 2020년 1월 1일 ~ 2021년 11월 6일까지의 주가 데이터를 야후 파이낸스에서 조회해보자.

    start = '20200101'
    end = '20211106'
    
    yahoo= web.DataReader('MSFT', 'yahoo', start=start, end=end)
    yahoo.info()
    
    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 467 entries, 2020-01-02 to 2021-11-05
    Data columns (total 6 columns):
     #   Column     Non-Null Count  Dtype  
    ---  ------     --------------  -----  
     0   High       467 non-null    float64
     1   Low        467 non-null    float64
     2   Open       467 non-null    float64
     3   Close      467 non-null    float64
     4   Volume     467 non-null    float64
     5   Adj Close  467 non-null    float64
    dtypes: float64(6)
    memory usage: 25.5 KB
    yahoo.head()

    yahoo.tail()

    일별 고가, 저가, 시가, 종가, 거래량, 수정종가(분할, 배당, 증자 등 주가에 영향을 줄만한 액션을 반영하여 계산된 종가)를 조회할 수 있고, 일반적으로 일별 주가데이터는 이런 형태로 제공되는 경우가 많다. 

    mplfinance 패키지를 이용하여 캔들차트를 그려볼 수도 있다.

    mpf.plot(yahoo.drop('Adj Close', axis=1), type='candle')
    plt.tight_layout()

    기간이 길어서 캔들이 잘 안보이는데 2021년 10월 1일 이후 데이터만 조회해서 다시 그려보면

    캔들차트가 그려지는 것을 확인할 수 있다. 여기서 검정색 캔들이 음봉이고, 흰색 캔들이 양봉이다. 2000조짜리 기업이 한달동안 20% 상승하는 웅장한 모습을 관찰할 수 있다.


    3.2 yfinance

    pandas_datareader를 이용하지 않고 yfinance 패키지를 이용하여 직접 데이터를 가져올 수도 있다. yfinance 패키지의 Ticker 메서드를 이용하여 야후 파이낸스 웹사이트의 정보를 가져오는 방식인데 Ticker로 가져온 정보를 객체(여기서는 ticker)에 할당한 후 info, history, action, dividends, splits, financials, balance_sheet, cashflow 등을 활용하여 일별주가, 분할/배당 정보, 재무 정보를 조회할 수 있다. 또한, sustatinability에서는 ESG 정보를 얻을 수 있고, recommendations에서는 애널리스트 추천 정보를 얻을 수 있다.

    import pandas as pd
    import yfinance as yf
    
    symbol = 'MSFT'
    ticker = yf.Ticker(symbol)

    3.2.1 info

    info는 종목에 대한 요약 설명, 직원 수, 시가총액, 거래량, P/E 비율, 배당금 등을 포함하여 시세에 대한 광범위한 정보가 포함된 딕셔너리를 반환한다.

    ticker.info
    
    {'52WeekChange': 0.5388067,
     'SandP52WeekChange': 0.3230616,
     'address1': 'One Microsoft Way',
     'algorithm': None,
     'annualHoldingsTurnover': None,
     'annualReportExpenseRatio': None,
     'ask': 336.16,
     'askSize': 1300,
     'averageDailyVolume10Day': 29406012,
     'averageVolume': 23353873,
     'averageVolume10days': 29406012,
     'beta': 0.862337,
     'beta3Year': None,
     'bid': 335.84,
     'bidSize': 1200,
     'bookValue': 20.242,
     'category': None,
     'circulatingSupply': None,
     'city': 'Redmond',
     'companyOfficers': [],
     'country': 'United States',
     'currency': 'USD',
     'currentPrice': 336.06,
     'currentRatio': 2.165,
     'dateShortInterest': 1634256000,
     'dayHigh': 338.78,
     'dayLow': 334.4217,
     'debtToEquity': 51.938,
     'dividendRate': 2.48,
     'dividendYield': 0.0074,
     'earningsGrowth': 0.489,
     'earningsQuarterlyGrowth': 0.476,
     'ebitda': 85745000448,
     'ebitdaMargins': 0.48648998,
     'enterpriseToEbitda': 28.824,
     'enterpriseToRevenue': 14.023,
     'enterpriseValue': 2471482884096,
     'exDividendDate': 1637107200,
     'exchange': 'NMS',
     'exchangeTimezoneName': 'America/New_York',
     'exchangeTimezoneShortName': 'EDT',
     'expireDate': None,
     'fiftyDayAverage': 304.81833,
     'fiftyTwoWeekHigh': 338.79,
     'fiftyTwoWeekLow': 208.16,
     'financialCurrency': 'USD',
     'fiveYearAverageReturn': None,
     'fiveYearAvgDividendYield': 1.45,
     'floatShares': 7500247224,
     'forwardEps': 10.51,
     'forwardPE': 31.97526,
     'freeCashflow': 49819750400,
     'fromCurrency': None,
     'fullTimeEmployees': 181000,
     'fundFamily': None,
     'fundInceptionDate': None,
     'gmtOffSetMilliseconds': '-14400000',
     'grossMargins': 0.68864995,
     'grossProfits': 115856000000,
     'heldPercentInsiders': 0.00079,
     'heldPercentInstitutions': 0.71957,
     'impliedSharesOutstanding': None,
     'industry': 'Software—Infrastructure',
     'isEsgPopulated': False,
     'lastCapGain': None,
     'lastDividendDate': 1629244800,
     'lastDividendValue': 0.56,
     'lastFiscalYearEnd': 1625011200,
     'lastMarket': None,
     'lastSplitDate': 1045526400,
     'lastSplitFactor': '2:1',
     'legalType': None,
     'logo_url': 'https://logo.clearbit.com/microsoft.com',
     'longBusinessSummary': 'Microsoft Corporation develops, licenses, and supports software, services, devices, and solutions worldwide. Its Productivity and Business Processes segment offers Office, Exchange, SharePoint, Microsoft Teams, Office 365 Security and Compliance, and Skype for Business, as well as related Client Access Licenses (CAL); Skype, Outlook.com, OneDrive, and LinkedIn; and Dynamics 365, a set of cloud-based and on-premises business solutions for organizations and enterprise divisions. Its Intelligent Cloud segment licenses SQL, Windows Servers, Visual Studio, System Center, and related CALs; GitHub that provides a collaboration platform and code hosting service for developers; and Azure, a cloud platform. It also offers support services and Microsoft consulting services to assist customers in developing, deploying, and managing Microsoft server and desktop solutions; and training and certification on Microsoft products. Its More Personal Computing segment provides Windows original equipment manufacturer (OEM) licensing and other non-volume licensing of the Windows operating system; Windows Commercial, such as volume licensing of the Windows operating system, Windows cloud services, and other Windows commercial offerings; patent licensing; Windows Internet of Things; and MSN advertising. It also offers Surface, PC accessories, PCs, tablets, gaming and entertainment consoles, and other devices; Gaming, including Xbox hardware, and Xbox content and services; video games and third-party video game royalties; and Search, including Bing and Microsoft advertising. It sells its products through OEMs, distributors, and resellers; and directly through digital marketplaces, online stores, and retail stores. It has collaborations with Dynatrace, Inc., Morgan Stanley, Micro Focus, WPP plc, ACI Worldwide, Inc., and iCIMS, Inc., as well as strategic relationships with Avaya Holdings Corp. and wejo Limited. Microsoft Corporation was founded in 1975 and is based in Redmond, Washington.',
     'longName': 'Microsoft Corporation',
     'market': 'us_market',
     'marketCap': 2523131543552,
     'maxAge': 1,
     'maxSupply': None,
     'messageBoardId': 'finmb_21835',
     'morningStarOverallRating': None,
     'morningStarRiskRating': None,
     'mostRecentQuarter': 1632960000,
     'navPrice': None,
     'netIncomeToCommon': 67882999808,
     'nextFiscalYearEnd': 1688083200,
     'numberOfAnalystOpinions': 38,
     'open': 338.51,
     'openInterest': None,
     'operatingCashflow': 81945001984,
     'operatingMargins': 0.42143002,
     'payoutRatio': 0.25059998,
     'pegRatio': 2.23,
     'phone': '425 882 8080',
     'preMarketPrice': None,
     'previousClose': 336.44,
     'priceHint': 2,
     'priceToBook': 16.602114,
     'priceToSalesTrailing12Months': 14.315559,
     'profitMargins': 0.38515,
     'quickRatio': 1.961,
     'quoteType': 'EQUITY',
     'recommendationKey': 'buy',
     'recommendationMean': 1.6,
     'regularMarketDayHigh': 338.78,
     'regularMarketDayLow': 334.4217,
     'regularMarketOpen': 338.51,
     'regularMarketPreviousClose': 336.44,
     'regularMarketPrice': 336.06,
     'regularMarketVolume': 22570098,
     'returnOnAssets': 0.14589,
     'returnOnEquity': 0.49303,
     'revenueGrowth': 0.22,
     'revenuePerShare': 23.395,
     'revenueQuarterlyGrowth': None,
     'sector': 'Technology',
     'sharesOutstanding': 7507979776,
     'sharesPercentSharesOut': 0.0064999997,
     'sharesShort': 48556022,
     'sharesShortPreviousMonthDate': 1631664000,
     'sharesShortPriorMonth': 44204519,
     'shortName': 'Microsoft Corporation',
     'shortPercentOfFloat': 0.0064999997,
     'shortRatio': 1.86,
     'startDate': None,
     'state': 'WA',
     'strikePrice': None,
     'symbol': 'MSFT',
     'targetHighPrice': 407,
     'targetLowPrice': 299.93,
     'targetMeanPrice': 357.27,
     'targetMedianPrice': 360,
     'threeYearAverageReturn': None,
     'toCurrency': None,
     'totalAssets': None,
     'totalCash': 130584002560,
     'totalCashPerShare': 17.393,
     'totalDebt': 78934999040,
     'totalRevenue': 176250994688,
     'tradeable': False,
     'trailingAnnualDividendRate': 2.3,
     'trailingAnnualDividendYield': 0.006836286,
     'trailingEps': 8.939,
     'trailingPE': 37.594807,
     'twoHundredDayAverage': 281.06702,
     'volume': 22570098,
     'volume24Hr': None,
     'volumeAllCurrencies': None,
     'website': 'http://www.microsoft.com',
     'yield': None,
     'ytdReturn': None,
     'zip': '98052-6399'}

    3.2.2 history

    history 메서드는 특정기간 동안의 OHLCV와 배당, 분할 정보를 가져올 때 사용한다. period와 interval을 정해주거나 start와 end를 정해준 다음 데이터를 조회하면 된다. period는 현재 시점으로부터 1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max기간의 데이터를 조회하도록 설정할 수 있다. period에 값을 넣지 않고, start와 end에만 값을 넣어주면 일별 데이터를 조회하고, interval 값을 넣어주면 start~end 기간 사이에서 정해진 간격의 데이터를 조회한다. start와 end에 날짜를 넣어줄 때는 'yyyy-mm-dd' 형태로 넣어줘야 한다.

    tickers : str, list
        List of tickers to download
    period : str
        Valid periods: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max
        Either Use period parameter or use start and end
    interval : str
        Valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo
        Intraday data cannot extend last 60 days
    start: str
        Download start date string (YYYY-MM-DD) or _datetime.
        Default is 1900-01-01
    end: str
        Download end date string (YYYY-MM-DD) or _datetime.
        Default is now
    group_by : str
        Group by 'ticker' or 'column' (default)
    prepost : bool
        Include Pre and Post market data in results?
        Default is False
    auto_adjust: bool
        Adjust all OHLC automatically? Default is False
    actions: bool
        Download dividend + stock splits data. Default is False
    threads: bool / int
        How many threads to use for mass downloading. Default is True
    proxy: str
        Optional. Proxy server URL scheme. Default is None
    rounding: bool
        Optional. Round values to 2 decimal places?
    show_errors: bool
        Optional. Doesn't print errors if True

    (1) 최근 5일의 1분 간격 주가 데이터 조회

    #### period와 interval로 데이터 조회
    data = ticker.history(period='5d',
                          interval='1m',
                          start=None,
                          end=None,
                          actions=True,
                          auto_adjust=True,
                          back_adjust=False)
    data.info()
    
    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 1945 entries, 2021-11-01 09:30:00-04:00 to 2021-11-05 15:59:00-04:00
    Data columns (total 7 columns):
     #   Column        Non-Null Count  Dtype  
    ---  ------        --------------  -----  
     0   Open          1945 non-null   float64
     1   High          1945 non-null   float64
     2   Low           1945 non-null   float64
     3   Close         1945 non-null   float64
     4   Volume        1945 non-null   int64  
     5   Dividends     1945 non-null   int64  
     6   Stock Splits  1945 non-null   int64  
    dtypes: float64(4), int64(3)
    memory usage: 121.6 KB

    (2) 2021-11-01~2021-11-06 기간의 일 간격 주가 데이터 조회

    #### start와 end로 데이터 조회
    data = ticker.history(start='2021-11-01',
                          end='2021-11-06',
                          actions=True,
                          auto_adjust=True,
                          back_adjust=False)
    data.info()
    
    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 5 entries, 2021-11-01 to 2021-11-05
    Data columns (total 7 columns):
     #   Column        Non-Null Count  Dtype  
    ---  ------        --------------  -----  
     0   Open          5 non-null      float64
     1   High          5 non-null      float64
     2   Low           5 non-null      float64
     3   Close         5 non-null      float64
     4   Volume        5 non-null      int64  
     5   Dividends     5 non-null      int64  
     6   Stock Splits  5 non-null      int64  
    dtypes: float64(4), int64(3)
    memory usage: 320.0 bytes

    (3) 2021-11-01~2021-11-06 기간의 1분 간격 주가 데이터 조회

    #### start, end, interval로 데이터 조회
    data = ticker.history(start='2021-11-01',
                          end='2021-11-06',
                          interval='1m',
                          actions=True,
                          auto_adjust=True,
                          back_adjust=False)
    data.info()
    
    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 1945 entries, 2021-11-01 09:30:00-04:00 to 2021-11-05 15:59:00-04:00
    Data columns (total 7 columns):
     #   Column        Non-Null Count  Dtype  
    ---  ------        --------------  -----  
     0   Open          1945 non-null   float64
     1   High          1945 non-null   float64
     2   Low           1945 non-null   float64
     3   Close         1945 non-null   float64
     4   Volume        1945 non-null   int64  
     5   Dividends     1945 non-null   int64  
     6   Stock Splits  1945 non-null   int64  
    dtypes: float64(4), int64(3)
    memory usage: 121.6 KB

    3.2.3 financials, quarterly_financials

    fianancials를 이용하면 연간 재무상태 요약, quarterly_financials을 이용하면 분기별 재무상태 요약 정보를 조회할 수 있다. 연간 요약은 결산일 기준 정보를 보여주고(기업마다 결산일이 다름. ex) 마이크로소프트는 6월, 애플은 9월, GM을 12월에 결산일이 있음), 분기별 요약은 현재 시점기준으로 분기결산이 완료된 시점까지 4분기의 정보를 보여준다.

    ticker.financials

    ticker.quarterly_financials

    재무정보 각 항목의 한글명을 매핑하고 숫자를 알아보기 쉽게 포맷을 설정하면

    import pandas as pd
    mapping = {
        'Research Development':'연구개발비', 
        'Effect Of Accounting Charges':'회계변경효과',
        'Income Before Tax':'법인세 차감전 당기순이익', 
        'Minority Interest':'소액주주지분', 
        'Net Income': '당기순이익',
        'Selling General Administrative':'판매비와 관리비', 
        'Gross Profit':'매출총이익', 
        'Ebit':'이자와 법인세 차감전 이익',
        'Operating Income':'영업이익', 
        'Other Operating Expenses':'기타영업비용', 
        'Interest Expense':'이자비용',
        'Extraordinary Items':'빈도가 낮고 특이한 사건 등으로 발생한 수익/손실', 
        'Non Recurring':'영업외손익', 
        'Other Items':'기타항목',
        'Income Tax Expense':'법인세비용', 
        'Total Revenue':'매출액', 
        'Total Operating Expenses':'영업비용',
        'Cost Of Revenue':'매출원가', 
        'Total Other Income Expense Net':'총 기타 순 수익/비용',
        'Discontinued Operations':'비계속적영업', 
        'Net Income From Continuing Ops':'계속영업순이익',
        'Net Income Applicable To Common Shares':'보통주적용순이익'
        }
    
    yearly_fin_info = ticker.financials
    yearly_fin_info.columns = yearly_fin_info.columns.astype(str)
    
    df_financials = pd.concat([pd.DataFrame.from_dict(mapping, orient='index', columns=['한글명']), 
                               yearly_fin_info], axis=1)
    df_financials.set_index('한글명').astype(float).fillna(0).style.format('${0:,.0f}')


    3.2.4 balance_sheet, quarterly_balance_sheet

    balance_sheet, quarterly_balance_sheet에는 연간/분기별 재무제표 정보가 들어있다.

    bs = ticker.balance_sheet
    bs.columns = bs.columns.astype(str)
    bs.fillna(0).style.format('${0:,.0f}')

    bs = ticker.quarterly_balance_sheet
    bs.columns = bs.columns.astype(str)
    bs.fillna(0).style.format('${0:,.0f}')


    3.2.5 cashflow, quarterly_cashflow

    cashflow, quarterly_cashflow에는 연간/분기별 현금흐름표 정보가 들어있다.

    cf = ticker.cashflow
    cf.columns = cf.columns.astype(str)
    cf.fillna(0).style.format('${0:,.0f}')

    cf = ticker.quarterly_cashflow
    cf.columns = cf.columns.astype(str)
    cf.fillna(0).style.format('${0:,.0f}')


    3.2.6 Sustainability: Envirionmental, Social and Governance(EGS)

    ticker.sustainability.reset_index()


    3.2.7 Analyst Recommendations

    ticker.recommendations.info()
    
    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 358 entries, 2012-03-16 08:19:00 to 2021-11-02 09:45:19
    Data columns (total 4 columns):
     #   Column      Non-Null Count  Dtype 
    ---  ------      --------------  ----- 
     0   Firm        358 non-null    object
     1   To Grade    358 non-null    object
     2   From Grade  358 non-null    object
     3   Action      358 non-null    object
    dtypes: object(4)
    memory usage: 14.0+ KB
    ticker.recommendations.tail(10)


    3.2.8 Upcoming Events

    ticker.calendar


    3.3 zipline with quandl

    zipline퀀토피안에서 운영하던 알고리즘 트레이딩 라이브러리이다. 하지만 2020년 10월 퀀토피안이 서비스를 종료하면서 더이상 업데이트 되지 않고 있다(참고 - 퀀토피안 서비스 종료가 알려주는 세 가지 교훈). 

    하지만 이 책의 저자인 Stefan Jansen이 zipline-reloaded라는 이름으로 라이브러리를 살려놓았고, 현재도 지속적으로 업데이트 되고 있는 중이다.

    설치는 콘다를 통해 가상환경을 만든 후 진행해야 한다(안그러면 디펜던시 문제 발생).

    !conda install -c ml4t -c conda-forge -c ranaroussi zipline-reloaded --yes

    코랩에서 실행해보고 싶다면 콘다를 설치하고 가상환경을 만든 후 위의 코드를 실행해주면 된다. 만약 사용하고 있는 인터넷망이 현대HCN 같은 회사에서 운영하는 지역 케이블이라면 해외망 다운로드 속도가 심각하게 느리기 때문에 무조건 코랩을 이용해야 한다. zipline 자체를 실행하는데는 문제가 없겠지만 퀀들 같은 데이터 소스에서 데이터를 받아올 때 해외망을 타기 때문이다.

    !which python # should return /usr/local/bin/python
    
    !python --version # 설치할 콘다의 버전을 결정하기 위함
    
    !echo $PYTHONPATH # /env/python
    
    ################################################################################
    # python path 초기화
    ################################################################################
    """
    Typically, it is a good idea to unset the PYTHONPATH variable 
    before installing Miniconda as it can cause problems if there are packages installed and 
    accessible via directories included in the PYTHONPATH 
    that are not compatible with the version of Python included with Miniconda.
    You can unset the PYTHONPATH variable with the following command. 
    
    This step is optional but if you don't unset this variable then 
    you will see a warning message after installing Miniconda.
    """
    %env PYTHONPATH=
    
    !echo $PYTHONPATH
    
    ################################################################################
    # 미니콘다 설치파일 다운로드 후 설치 & 패키지 설치 경로 지정
    ################################################################################
    ! wget https://repo.anaconda.com/miniconda/Miniconda3-py37_4.10.3-Linux-x86_64.sh
    ! chmod +x Miniconda3-py37_4.10.3-Linux-x86_64.sh
    ! bash ./Miniconda3-py37_4.10.3-Linux-x86_64.sh -b -f -p /usr/local
    import sys
    sys.path.append('/usr/local/lib/python3.7/site-packages/')
    
    !which conda  # should return /usr/local/bin/conda
    
    !conda --version
    
    !python --version
    
    ################################################################################
    # 가상환경 생성
    ################################################################################
    !conda create -n zipline python=3.7 --yes
    
    # 가상환경 실행시키고 가상환경 목록 확인
    !source activate zipline && conda env list
    
    ################################################################################
    # zipline 설치
    ################################################################################
    !conda install -c ml4t -c conda-forge -c ranaroussi zipline-reloaded --yes

    이제 나스닥에서 제공하는 데이터 플랫폼인 퀀들(Quandl)에서 데이터를 수집해보자. 우선 퀀들 웹사이트에 회원가입을 하고 API KEY를 받아와야 한다. 회원가입 후 로그인을 하고 우측 상단에 있는 파란색 사람 아이콘을 누르면 ACCOUNT SETTINGS가 나오는데 여기서 본인의 API KEY를 확인할 수 있다.

    zipline의 커맨드를 실행하기 전에는 zipline을 로드하는 과정이 필요하고 아래 커맨드를 통해 실행할 수 있다.

    %load_ext zipline

    발급받은 API KEY는 환경변수에 'QUANDL_API_KEY'라는 이름으로 넣어주면 된다.

    def get_localtime():
        from datetime import datetime
        from pytz import timezone
        lcltm = datetime.now(timezone('Asia/Seoul')).strftime('%Y/%m/%d %H:%M:%S')
        return lcltm
    
    # 퀀들 회원가입 후 발급받을 수 있는 API KEY 입력
    API_KEY = '발급받은 API KEY'
    
    print(f'START at {get_localtime()}')
    import os
    os.environ['QUANDL_API_KEY'] = API_KEY
    !zipline ingest -b quandl
    print(f'END at {get_localtime()}')
    START at 2021/11/07 15:58:18
    [2021-11-07 06:58:21.371336] INFO: zipline.data.bundles.core: Ingesting quandl.
    [2021-11-07 06:58:21.371602] INFO: zipline.data.bundles.quandl: Downloading WIKI metadata.
    Downloading WIKI Prices table from Quandl  [####################################]  100%          
    [2021-11-07 06:58:54.857327] INFO: zipline.data.bundles.quandl: Parsing raw data.
    [2021-11-07 06:59:34.094963] INFO: zipline.data.bundles.quandl: Generating asset metadata.
    Merging daily equity files:  [#-----------------------------------]  1731/usr/local/lib/python3.7/site-packages/zipline/data/bcolz_daily_bars.py:366: UserWarning: Ignoring 1 values because they are out of bounds for uint32:             open  ...  split_ratio
    2011-04-11  1.79  ...          1.0
    
    [1 rows x 7 columns]
      winsorise_uint32(raw_data, invalid_data_behavior, "volume", *OHLC)
    Merging daily equity files:  [####################################]      
    [2021-11-07 07:02:10.178701] INFO: zipline.data.bundles.quandl: Parsing split data.
    [2021-11-07 07:02:10.359589] INFO: zipline.data.bundles.quandl: Parsing dividend data.
    [2021-11-07 07:02:12.639905] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=67, ex_date=2017-11-09, amount=0.620
    [2021-11-07 07:02:12.640133] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=93, ex_date=2017-11-09, amount=0.240
    [2021-11-07 07:02:12.640255] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=161, ex_date=2017-11-09, amount=0.110
    [2021-11-07 07:02:12.640337] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=283, ex_date=2017-11-09, amount=0.415
    [2021-11-07 07:02:12.640416] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=298, ex_date=2017-11-09, amount=1.420
    [2021-11-07 07:02:12.640499] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=318, ex_date=2017-11-09, amount=0.330
    [2021-11-07 07:02:12.640583] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=434, ex_date=2017-11-09, amount=0.110
    [2021-11-07 07:02:12.640665] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=516, ex_date=1996-05-30, amount=0.310
    [2021-11-07 07:02:12.640747] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=524, ex_date=2017-11-09, amount=0.050
    [2021-11-07 07:02:12.640829] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=556, ex_date=2017-11-09, amount=0.075
    [2021-11-07 07:02:12.640910] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=578, ex_date=2017-11-09, amount=0.160
    [2021-11-07 07:02:12.640991] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=605, ex_date=2017-11-09, amount=0.040
    [2021-11-07 07:02:12.641078] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=666, ex_date=1990-03-26, amount=0.140
    [2021-11-07 07:02:12.641160] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=694, ex_date=1990-03-27, amount=0.100
    [2021-11-07 07:02:12.641251] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=723, ex_date=2017-11-09, amount=1.620
    [2021-11-07 07:02:12.641331] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=758, ex_date=2017-11-09, amount=0.500
    [2021-11-07 07:02:12.641422] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=788, ex_date=2017-11-09, amount=0.060
    [2021-11-07 07:02:12.641503] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=859, ex_date=1995-05-09, amount=0.100
    [2021-11-07 07:02:12.641584] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=904, ex_date=2017-11-09, amount=0.135
    [2021-11-07 07:02:12.641672] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=975, ex_date=2017-11-09, amount=0.030
    [2021-11-07 07:02:12.641752] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1057, ex_date=2017-11-09, amount=0.250
    [2021-11-07 07:02:12.641832] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1088, ex_date=1990-03-26, amount=0.240
    [2021-11-07 07:02:12.641913] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1091, ex_date=2017-11-09, amount=0.075
    [2021-11-07 07:02:12.641998] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1111, ex_date=1993-03-04, amount=0.070
    [2021-11-07 07:02:12.642090] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1172, ex_date=2017-11-09, amount=0.130
    [2021-11-07 07:02:12.642182] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1209, ex_date=2017-11-09, amount=0.010
    [2021-11-07 07:02:12.642262] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1322, ex_date=1995-05-25, amount=0.150
    [2021-11-07 07:02:12.642343] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1441, ex_date=2017-11-09, amount=1.500
    [2021-11-07 07:02:12.642423] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1525, ex_date=2017-11-09, amount=0.090
    [2021-11-07 07:02:12.642502] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1600, ex_date=2015-07-06, amount=16.500
    [2021-11-07 07:02:12.642584] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1642, ex_date=2017-11-09, amount=0.270
    [2021-11-07 07:02:12.642665] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1748, ex_date=2017-11-09, amount=0.740
    [2021-11-07 07:02:12.642745] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1876, ex_date=2017-11-09, amount=0.120
    [2021-11-07 07:02:12.642825] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1922, ex_date=2017-11-09, amount=0.040
    [2021-11-07 07:02:12.642906] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=1947, ex_date=1990-03-26, amount=0.150
    [2021-11-07 07:02:12.642988] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2098, ex_date=2017-11-09, amount=0.200
    [2021-11-07 07:02:12.643075] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2118, ex_date=2014-11-06, amount=0.050
    [2021-11-07 07:02:12.643156] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2120, ex_date=2017-11-09, amount=0.110
    [2021-11-07 07:02:12.643246] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2149, ex_date=2017-11-09, amount=0.330
    [2021-11-07 07:02:12.643327] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2204, ex_date=2017-11-09, amount=0.320
    [2021-11-07 07:02:12.643412] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2220, ex_date=2017-11-09, amount=0.660
    [2021-11-07 07:02:12.643492] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2281, ex_date=2017-11-09, amount=0.450
    [2021-11-07 07:02:12.643572] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2389, ex_date=2017-11-09, amount=0.140
    [2021-11-07 07:02:12.643653] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2441, ex_date=2017-11-09, amount=0.215
    [2021-11-07 07:02:12.643732] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2517, ex_date=2017-11-09, amount=0.080
    [2021-11-07 07:02:12.643812] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2582, ex_date=2017-11-09, amount=0.780
    [2021-11-07 07:02:12.643891] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2622, ex_date=2017-11-09, amount=0.390
    [2021-11-07 07:02:12.643971] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2662, ex_date=2015-01-14, amount=0.750
    [2021-11-07 07:02:12.644057] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2754, ex_date=2000-12-27, amount=0.250
    [2021-11-07 07:02:12.644138] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2754, ex_date=2009-09-11, amount=0.420
    [2021-11-07 07:02:12.644233] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2754, ex_date=2009-12-11, amount=0.420
    [2021-11-07 07:02:12.644315] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2754, ex_date=2010-03-11, amount=0.420
    [2021-11-07 07:02:12.644395] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2754, ex_date=2010-12-15, amount=0.180
    [2021-11-07 07:02:12.644481] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2766, ex_date=2017-11-09, amount=0.320
    [2021-11-07 07:02:12.644562] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2798, ex_date=2017-11-09, amount=0.065
    [2021-11-07 07:02:12.644643] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2817, ex_date=1992-03-03, amount=0.300
    [2021-11-07 07:02:12.644723] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2824, ex_date=2017-11-09, amount=0.120
    [2021-11-07 07:02:12.644803] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2843, ex_date=2017-11-09, amount=0.150
    [2021-11-07 07:02:12.644882] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2857, ex_date=2011-09-07, amount=0.410
    [2021-11-07 07:02:12.644962] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=2968, ex_date=1990-03-26, amount=0.100
    [2021-11-07 07:02:12.645048] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=3005, ex_date=1990-03-26, amount=0.070
    [2021-11-07 07:02:12.645128] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=3078, ex_date=2014-05-12, amount=0.060
    [2021-11-07 07:02:12.645217] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=3117, ex_date=2017-11-09, amount=0.430
    [2021-11-07 07:02:12.645299] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=3138, ex_date=2010-08-16, amount=0.060
    [2021-11-07 07:02:12.645380] WARNING: zipline.data.adjustments: Couldn't compute ratio for dividend sid=3145, ex_date=2017-11-09, amount=0.050
    [2021-11-07 07:02:12.645715] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=501, ex_date=2006-01-03, amount=41.560
    [2021-11-07 07:02:12.645821] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=1557, ex_date=2007-07-02, amount=88.530
    [2021-11-07 07:02:12.645897] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=1632, ex_date=2000-07-13, amount=181.000
    [2021-11-07 07:02:12.645969] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=1657, ex_date=2013-09-30, amount=21.355
    [2021-11-07 07:02:12.646050] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=1775, ex_date=1994-12-01, amount=76.000
    [2021-11-07 07:02:12.646129] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=1776, ex_date=1996-11-04, amount=36.708
    [2021-11-07 07:02:12.646221] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=2455, ex_date=2016-10-03, amount=25.611
    [2021-11-07 07:02:12.646305] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=2687, ex_date=2008-06-26, amount=10.000
    [2021-11-07 07:02:12.646386] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=2900, ex_date=2007-07-02, amount=88.530
    [2021-11-07 07:02:12.646467] WARNING: zipline.data.adjustments: Dividend ratio <= 0 for dividend sid=3088, ex_date=2015-04-27, amount=31.291
    END at 2021/11/07 16:02:14
    CPU times: user 3.64 s, sys: 486 ms, total: 4.13 s
    Wall time: 3min 56s

    warning이 잔뜩 뜨긴 하지만 4분 정도 걸려서 퀀들 데이터 로딩이 완료되었다.

    zipline을 사용할때는 initialize와 handle_data 함수를 항상 정의해줘야 한다. 한 건씩 데이터를 불러와서 처리하는 구조로 되어 있는 것 같은데 이 부분은 좀 더 구조를 살펴봐야 정확하게 파악할 수 있을 것 같다.

    %%zipline --start 2021-11-01 --end 2021-11-06 --data-frequency daily --no-benchmark
    from zipline.api import order_target, record, symbol
    import pandas as pd
    
    def initialize(context):
        context.i = 0
        context.assets = [symbol('MSFT'), symbol('AAPL'), symbol('AMZN')]
        
    def handle_data(context, data):
        df = data.history(context.assets, fields=['price', 'volume'], bar_count=1, frequency="1d")
        df = df.reset_index()
        
        if context.i == 0:
            df.columns = ['date', 'asset', 'price', 'volume']
            df.to_csv('stock_data.csv', index=False)
        else:
            df.to_csv('stock_data.csv', index=False, mode='a', header=None)
        context.i += 1

    2021-11-01 ~ 2021-11-06의 마이크로소프트, 애플, 아마존 데이터를 가져오려고 했는데 저장된 데이터를 열어보니

    df = pd.read_csv('stock_data.csv')
    df.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 15 entries, 0 to 14
    Data columns (total 4 columns):
     #   Column  Non-Null Count  Dtype  
    ---  ------  --------------  -----  
     0   date    15 non-null     object 
     1   asset   15 non-null     object 
     2   price   0 non-null      float64
     3   volume  15 non-null     float64
    dtypes: float64(2), object(2)
    memory usage: 608.0+ bytes

    가격 정보가 모두 비어있었다. 왜 그런가 했는데 zipline 웹사이트의 설명을 보니 퀀들이 2018년 초반까지의 데이터만 제공하고 있다고 한다.

    연도를 2017년으로 바꾼후 다시 커맨드를 실행해보면

    %%zipline --start 2017-11-01 --end 2017-11-06 --data-frequency daily --no-benchmark
    from zipline.api import order_target, record, symbol
    import pandas as pd
    
    def initialize(context):
        context.i = 0
        context.assets = [symbol('MSFT'), symbol('AAPL'), symbol('AMZN')]
        
    def handle_data(context, data):
        df = data.history(context.assets, fields=['price', 'volume'], bar_count=1, frequency="1d")
        df = df.reset_index()
        
        if context.i == 0:
            df.columns = ['date', 'asset', 'price', 'volume']
            df.to_csv('stock_data.csv', index=False)
        else:
            df.to_csv('stock_data.csv', index=False, mode='a', header=None)
        context.i += 1
    df = pd.read_csv('stock_data.csv')
    df

    데이터가 잘 들어와 있는 것을 확인할 수 있다.

    퀀들 같은 데이터 소스 이외에도 직접 데이터를 밀어 넣을 수도 있다고 하니 최근 데이터로 테스트를 해보고 싶다면 야후 파이낸스 같은 다른 데이터 소스에서 최근 데이터를 수집한 후 ingest 커맨드로 zipline에 사용할 수 있게 로드하는 작업이 필요해 보인다.


    4. 재무 데이터

    미국 증권거래위원회(SEC, Securities and Exchange Commision)는 상장 기업을 대상으로 3개의 분기 제무제표(10-Q)와 1개의 연간 보고서(10-K)를 포함한 여러 문서를 제출하게 하고 있다. 이 문서들은 EDGAR(Eletronic Data Gathering Analysis, and Retrieval) 시스템을 통해 공개된다. 위에서 설명한 yfinance 라이브러리를 통해서도 재무정보를 얻을 수 있지만 공식적인 데이터는 SEC가 제공하는 것이기 때문에 yfinance 데이터가 정확한 것인지 확인하는 목적에서라도 SEC가 제공하는 데이터를 확인해보는 과정이 필요하다.

    [NOTE] 2020 3Q 이후로 분기단위 보고서가 아닌 월단위 보고서를 제공하고 있다.

    SEC가 제공하는 Financial Statement and Notes(FSN)에서 재무정보를 추출할 수 있다.

    Data Downloads 부분을 보면 2020년 3분기까지는 분기단위이고 2020년 10월부터는 월단위로 FSN zip 파일을 제공하고 있다.


    먼저 필요한 라이브러리를 불러오고, 데이터를 저장할 경로와 데이터를 가져올 URL을 지정한다.

    from pathlib import Path
    from datetime import date
    import json
    from io import BytesIO
    from zipfile import ZipFile, BadZipFile
    from tqdm import tqdm
    import requests
    
    import pandas_datareader.data as web
    import pandas as pd
    
    # store data in this directory since we won't use it in other chapters
    data_path = Path('data') # perhaps set to external harddrive to accomodate large amount of data
    if not data_path.exists():
        data_path.mkdir()
        
    SEC_URL = 'https://www.sec.gov/'
    FSN_PATH = 'files/dera/data/financial-statement-and-notes-data-sets/'

    이번 글에서는 2020년 ~ 2021년 9월 까지의 데이터를 가져오려고 한다.

    filing_periods = [(d.year, d.quarter) for d in pd.date_range('2020', '2021-09-30', freq='Q')]
    filing_periods
    [(2020, 1), (2020, 2), (2020, 3), (2020, 4), (2021, 1), (2021, 2), (2021, 3)]

    원본 코드에서는 분기별로 폴더를 만들어 하위폴더 source에 파일들을 저장하는 것으로 되어 있었는데, 2020년 10월 부터는 월별로 폴더를 만들어 파일들을 저장하도록 코드를 수정하였다. 

    qtr_to_mth = {1:['01', '02', '03'], 2:['04', '05', '06'], 3:['07', '08', '09'], 4:['10', '11', '12']}
    for yr, qtr in tqdm(filing_periods):
       
        # define url and get file
        if str(yr)+'_'+str(qtr) >= '2020_4':
            filing_list = [f'{yr}_{mth}_notes.zip' for mth in qtr_to_mth[qtr]]
            for filing in filing_list:
                # set (and create) directory
                path = data_path / filing[:7] / 'source'
                if not path.exists():
                    path.mkdir(parents=True)
    
                url = SEC_URL + FSN_PATH + filing
                response = requests.get(url).content
            
                # decompress and save
                try:
                    with ZipFile(BytesIO(response)) as zip_file:
                        for file in zip_file.namelist():
                            local_file = path / file
                            if local_file.exists():
                                continue
                            with local_file.open('wb') as output:
                                for line in zip_file.open(file).readlines():
                                    output.write(line)
                except BadZipFile:
                    print(f'\nBad zip file: {yr} {qtr}\n')
                    continue
        else:
            # set (and create) directory
            path = data_path / f'{yr}_{qtr}' / 'source'
            if not path.exists():
                path.mkdir(parents=True)
    
            filing = f'{yr}q{qtr}_notes.zip'
            url = SEC_URL + FSN_PATH + filing
            response = requests.get(url).content
        
            # decompress and save
            try:
                with ZipFile(BytesIO(response)) as zip_file:
                    for file in zip_file.namelist():
                        local_file = path / file
                        if local_file.exists():
                            continue
                        with local_file.open('wb') as output:
                            for line in zip_file.open(file).readlines():
                                output.write(line)
            except BadZipFile:
                print(f'\nBad zip file: {yr} {qtr}\n')
                continue

    source 폴더에 저장된 파일을 parquet 타입으로 변환하는데 txt.tsv 파일에 잘못된 행이 있으면 제외하고 저장한다.

    for f in tqdm(sorted(list(data_path.glob('**/*.tsv')))):
        # set (and create) directory
        parquet_path = f.parent.parent / 'parquet'
        if not parquet_path.exists():
            parquet_path.mkdir(parents=True)    
    
        # write content to .parquet
        file_name = f.stem  + '.parquet'
        if not (parquet_path / file_name).exists():
            try:
                df = pd.read_csv(f, sep='\t', encoding='latin1', low_memory=False, error_bad_lines=False)
                df.to_parquet(parquet_path / file_name)
            except Exception as e:
                print(e, ' | ', f)
            # optional: uncomment to delete original .tsv
    #         else:
                # f.unlink

    이제 sub.parquet 파일 중 하나를 열어서 기업을 식별하기 위한 CIK(Central Index Key)를 조회한다. 마이크로소프트의 cik는 789019이다.

    sub = pd.read_parquet(data_path / '2020_1' / 'parquet' / 'sub.parquet')
    name = 'MICROSOFT CORP'
    corp_cik = sub[sub.name == name].cik.iat[0]
    corp_cik
    
    789019

    다른 폴더에 있는 sub.parquet 파일들에서 cik가 위에서 조회한 값이고 form이 10-Q(분기보고서), 10-K(연간보고서)인 행들을 조회한다.

    res = pd.DataFrame()
    for path in data_path.glob('**/sub.parquet'):
        print(path)
        sub = pd.read_parquet(path)
        sub = sub[(sub.cik.astype(int) == corp_cik) & (sub.form.isin(['10-Q', '10-K']))]
        res = pd.concat([res, sub])
    data/2020_1/parquet/sub.parquet
    data/2020_2/parquet/sub.parquet
    data/2020_3/parquet/sub.parquet
    data/2020_10/parquet/sub.parquet
    data/2020_11/parquet/sub.parquet
    data/2020_12/parquet/sub.parquet
    data/2021_01/parquet/sub.parquet
    data/2021_02/parquet/sub.parquet
    data/2021_03/parquet/sub.parquet
    data/2021_04/parquet/sub.parquet
    data/2021_05/parquet/sub.parquet
    data/2021_06/parquet/sub.parquet
    data/2021_07/parquet/sub.parquet
    data/2021_08/parquet/sub.parquet
    data/2021_09/parquet/sub.parquet

    res에는 5개의 10-Q, 2개의 10-K 보고서에 대한 정보가 포함되어 있다. (참고-마이크로소프의 결산일이 6월에 있기 때문에 10-K가 2개 조회됨)

    우리가 궁금해할만한 재무 데이터는 num.parquet 파일에 들어있다. num.parquet 파일에서 위에서 조회한 보고서 정보의 adsh(보고서 번호 인듯)에 해당하는 행만 추출해서 가져온다.

    df_nums = pd.DataFrame()
    for num in data_path.glob('**/num.parquet'):
        df_num = pd.read_parquet(num).drop('dimh', axis=1)
        df_num = df_num[df_num.adsh.isin(res.adsh)]
        print(len(df_num))
        df_nums = pd.concat([df_nums, df_num])
    
    df_nums.ddate = pd.to_datetime(df_nums.ddate, format='%Y%m%d')   
    df_nums.to_parquet(data_path / f'{res.name.iat[0].replace(" ", "_")}.parquet')
    df_nums.info()
    
    <class 'pandas.core.frame.DataFrame'>
    Int64Index: 9840 entries, 273588 to 1344933
    Data columns (total 15 columns):
     #   Column    Non-Null Count  Dtype         
    ---  ------    --------------  -----         
     0   adsh      9840 non-null   object        
     1   tag       9840 non-null   object        
     2   version   9840 non-null   object        
     3   ddate     9840 non-null   datetime64[ns]
     4   qtrs      9840 non-null   int64         
     5   uom       9840 non-null   object        
     6   iprx      9840 non-null   int64         
     7   value     9826 non-null   float64       
     8   footnote  35 non-null     object        
     9   footlen   9840 non-null   int64         
     10  dimn      9840 non-null   int64         
     11  coreg     0 non-null      object        
     12  durp      9840 non-null   float64       
     13  datp      9840 non-null   float64       
     14  dcml      9840 non-null   int64         
    dtypes: datetime64[ns](1), float64(3), int64(5), object(6)
    memory usage: 1.2+ MB

     

    재무정보의 명칭은 tag변수에 포함되어 있다. 조회하고자 하는 항목이 tag 변수에 어떤 값으로 들어가 있는지 확인한 후 해당하는 행에서 value 변수의 값을 보면 된다. 예를 들어, PER을 계산할 때 사용되는 EPS의 tag 값은 'EarningsPerShareDiluted'이다. 

    df_nums[df_nums.tag.isin(['EarningsPerShareDiluted'])]


    5. 글을 마치며

    이번 글에서는 미국 주식 데이터 수집에 대해 알아보았다. 미국 주식 데이터를 수집할 수 있는 다양한 데이터 소스가 있지만 가장 쉽게 사용할 수 있는 방법은 pandas-datareader나 yfinance 패키지를 활용하는 것이다. yfinance 패키지의 경우 재무데이터에도 접근할 수 있는데 SEC EDGAR에서 직접 데이터를 가져오는 것보다 조금 더 정제된 데이터를 바로 얻을 수 있다는 장점이 있다. zipline에서 사용하는 quandl 데이터 소스의 경우 2018년 초 이후로 데이터 업데이트가 되지 않고 있어 zipline에서 최근 데이터를 이용해 백테스팅을 하려면 직접 데이터를 밀어넣는 작업이 필요하다. 이 부분은 추후에 백테스팅을 본격적으로 하게 되면 새로운 글로 작성해보려고 한다.

    다음 글에서는 포트폴리오 최적화(5장)나 선형 모델(7장)에 관한 내용을 다루려고 하는데 선형 모델에 관한 내용을 먼저 다루게 될 가능성이 높을 것 같다(스터디 발표 준비도 해야해서...).