3. 시스템 설계 및 엔지니어링 목표

지난 포스팅 리뷰

FMP에서 사용한 API 요약

API 구분 Endpoint (Base URL) 주요 파라미터 데이터 용도
일별 종가 데이터 /historical-price-eod/full symbol 상장 이후 전체 주가 이력 수집 (수익률 계산 및 백테스팅용)
현금흐름표 /cash-flow-statement limit=5, period=annual 최근 5년치 연간 현금흐름 분석 (영업현금흐름, FCF 확인용)
재무상태표 /balance-sheet-statement limit=5, period=annual 최근 5년치 연간 자산/부채/자본 확인 (PBR, 부채비율 계산용)

위의 표에 나와있는 api들을 이용하여 입력에 대해 출력을 뽑아낼 수 있는 데이터를 저장하려고 했다.

시스템 설계

단순한 CRUD 시스템이라고 생각이 되었고, Spring서버와 pg(PostgreSQL)를 사용한다.
흥미를 가지고 진행하기위해, 먼저 빠르게 PoC를 쳐내고, 이후에 최적화나 설계를 변경하는 방식으로 하려한다.

fmp API 상세

FMP api 명세 링크에서 지원하는 API 명세서를 확인할 수 있다.
필요한 api는 아래와 같다.

  1. Ticker의 리스트 (APPL, TSLA ... 등 주식의 id의 리스트)
  2. Ticker에 대응하는 5개년치 재무제표, 현금 흐름표
  3. Ticker에 대응하는 5개년치 가격 정보

명세

  1. Ticker의 리스트 (APPL, TSLA ... 등 주식의 id의 리스트)
    • 해당 명세는 찾을 수가 없었다. Ticker를 찾기 위해 위키피디아를 크롤링하였다.
    • S&P 500 외에도, 나스닥 100, 다우존스의 리스트를 크롤링하였다.
  2. 재무제표, 현금흐름표의 경우 걸려있는 링크와 같이 찾을 수 있었다.
  3. 마지막으로 가격정보 또한 링크와 같이 찾을 수 있었다.

예외처리

tickers_curr.json의 일부 데이터

  [
    { "ticker": "A", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AAPL", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ABBV", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ABNB", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ABT", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ACGL", "country": "Bermuda", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ACN", "country": "Ireland", "currency": "EUR", "usd_to_local_ratio": 0.8525 },
    { "ticker": "ADBE", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ADI", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ADM", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ADP", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ADSK", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AEE", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AEP", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AES", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AFL", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AIG", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AIZ", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AJG", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AKAM", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ALB", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ALGN", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ALL", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "ALLE", "country": "Ireland", "currency": "EUR", "usd_to_local_ratio": 0.8525 },
    { "ticker": "AMAT", "country": "USA", "currency": "USD", "usd_to_local_ratio": 1.0 },
    { "ticker": "AMCR", "country": "Switzerland", "currency": "CHF", "usd_to_local_ratio": 0.8842 }
    ]

데이터 스키마 설계

두가지의 테이블을 구상하였다.

PG에서 DOUBLE과 DECIMAL의 연산 비교

항목 DOUBLE PRECISION DECIMAL / NUMERIC
연산 속도 매우 빠름 (기준점) 약 10~50배 느림
저장 효율 매우 좋음 (고정 8자) 보통 (숫자가 클수록 커짐)
정확도 근사치 (소수점 오차 가능성) 완벽한 정확도

스키마 설계

이런이런 요소들 넣음
인덱스는 PK만
필터링 조건 = 컬럼

스키마 DDL

  -- 기존 테이블 초기화
DROP TABLE IF EXISTS market_prices CASCADE;
DROP TABLE IF EXISTS company_fundamentals CASCADE;

-- 1. Market Data (일봉 데이터 + 데일리 밸류에이션)
CREATE TABLE market_prices (
ticker VARCHAR(10),
trade_date DATE,
open_price DOUBLE PRECISION,
high_price DOUBLE PRECISION,
low_price DOUBLE PRECISION,
close_price DOUBLE PRECISION NOT NULL,
volume BIGINT,

per DOUBLE PRECISION,
pbr DOUBLE PRECISION,
psr DOUBLE PRECISION,
fcf_yield DOUBLE PRECISION,

updated_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (ticker, trade_date)
);

-- 2. Fundamentals
CREATE TABLE company_fundamentals (
ticker VARCHAR(10),
report_period DATE,
filing_date DATE NOT NULL,

revenue BIGINT,
operating_income BIGINT,
net_income BIGINT,
eps DOUBLE PRECISION,
shares_outstanding BIGINT,

fcf BIGINT,
total_assets BIGINT,
total_debt BIGINT,
total_equity BIGINT,

per DOUBLE PRECISION,
pbr DOUBLE PRECISION,
psr DOUBLE PRECISION,
market_cap BIGINT,

roe DOUBLE PRECISION,
roic DOUBLE PRECISION,
opm DOUBLE PRECISION,
debt_to_equity DOUBLE PRECISION,

revenue_growth DOUBLE PRECISION,
eps_growth DOUBLE PRECISION,
fcf_yield DOUBLE PRECISION,

consecutive_growth_years INT,

updated_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (ticker, report_period)
);
CREATE INDEX idx_market_prices_covering ON market_prices (ticker, trade_date);

예외 처리

API 설계

goal

non-goal

명세