Building a Go Yahoo Finance Library with Claude Code #2

The second post in a series about building a Go version of the Yahoo Finance library based on Python’s yfinance, utilizing Claude Code.

Author Avatar

wonjoon

  ยท  9 min read

Before implementing the practical level, let’s test authentication bypass using CycleTLS. #

As written in the previous post ‘Building a Go Yahoo Finance Library with Claude Code #1’, the Go-based yfinance library proposed by Claude Code consists of three major phases.

MVP (Minimum Viable Product)
- Phase 0 + Phase 1 = ~1,100 lines
- Basic price lookup available

Practical Level
- Phase 0~4 = ~2,300 lines
- Most investment analysis available

Python yfinance Parity
- Phase 0~7 = ~3,500 lines
- Almost all features available

Since we implemented the MVP stage last time, I intended to request the implementation of the practical stage now. However, before that, I needed to confirm whether the bypass process in Yahoo Finance authentication using CycleTLS was working correctly. The sample code implemented in Phase 1 only queried a single ticker, Apple (AAPL). I felt it was necessary to test querying a larger volume to ensure stability.

So, I asked Gemini the following:


๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป: To verify if calls to Yahoo Finance using CycleTLS are being successfully bypassed, what would be the minimum number of simultaneous ticker queries I should try?

๐Ÿค–: … Start with 10 concurrent requests and a total of 50 queries. If all succeed, increase the concurrency to 20 or 30 to find the threshold where 429 errors occur.


Gemini suggested adjusting the number of concurrent requests to avoid situations where Yahoo Finance might consider the traffic as a DDoS attack or abnormal activity. It seemed to perceive the code I was writing as a single application.

Since my goal is to implement a library that can call Yahoo Finance in Go, I believed I only needed to implement the function of the tool itself correctly. In other words, avoiding errors from Yahoo Finance is a control issue for the application that uses this library, and I simply needed to implement the calling method correctly. I just needed to add the function to bypass calls to Yahoo Finance using CycleTLS.

So, I decided to slightly disregard Gemini’s well-crafted advice and tried calling up to 110 tickers simultaneously using goroutines. I thought this would be enough to verify if the bypass was working normally through concurrent requests without overloading the system. The results were as follows. No errors occurred, and it operated at a fairly decent speed even on a local environment with modest performance (M3 Air, 16GB RAM).

[Parallel] Batch call completed in 3.332168875s for 110 symbols (avg: 30.292444ms per symbol)

As mentioned above, verifying how many requests trigger a 429 error from Yahoo Finance was not within my scope. Therefore, I did not perform further testing. However, in the future, it might not be a bad idea to verify this by calling 110 tickers multiple times with short time intervals.

Establishing the Implementation Plan #

The “Practical Level” category is detailed into three specific phases.

Phase 2: Options (Option Data)

  • API Endpoint: query2.finance.yahoo.com/v7/finance/options/{symbol}
  • Implementation Items
    • Options(): Option chain lookup (Call/Put)
    • ExpirationDates(): List of expiration dates
    • Option Model Structs: Call, Put, OptionChain
  • Expected Methods
    • t.Options(): All option chains
    • t.OptionsAtExpiry(date): Option chain for a specific expiration date
    • t.ExpirationDates(): List of expiration dates

Phase 3: Financials (Financial Statements)

  • API Endpoint: query2.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/{symbol}
  • Implementation Items
    • IncomeStatement(): Income Statement
    • BalanceSheet(): Balance Sheet
    • CashFlow(): Cash Flow Statement
    • Annual / Quarterly support
  • Expected Methods
    • t.IncomeStatement(annual bool)
    • t.BalanceSheet(annual bool)
    • t.CashFlow(annual bool)
    • t.Financials(): All financial statements

Phase 4: Analysis (Analysis Data)

  • API Endpoint: Uses analysis modules from QuoteSummary API
    • recommendationTrend, earningsTrend, earningsHistory, etc.
  • Implementation Items
    • Recommendations(): Analyst recommendations
    • PriceTargets(): Price targets
    • EarningsEstimate(): Earnings estimates
    • RevenueEstimate(): Revenue estimates
    • GrowthEstimates(): Growth rate estimates
  • Expected Methods
    • t.Recommendations(): Buy/Sell/Hold recommendations
    • t.PriceTargets(): Price targets (Current/Mean/High/Low)
    • t.EarningsEstimate(): EPS estimates
    • t.RevenueEstimate(): Revenue estimates
    • t.EarningsDates(): Earnings announcement dates

Since these phases do not have dependencies on each other, I requested development in sequential order.

  1. Phase 2 (Options): Relatively simple with a single API endpoint.
  2. Phase 3 (Financials): Parsing the Timeseries API is complex but it’s a core feature.
  3. Phase 4 (Analysis): Reusing Info code by extending the QuoteSummary module.

Also, when I requested the MVP development in the last post, I hadn’t explicitly specified testing requirements. Consequently, Claude Code only wrote simple tests. This time, I requested thorough testing to ensure each function is implemented correctly.


๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป: … Please explicitly mark the progress in STATUS.md, and for each function, perform thorough testing to ensure the implementation is complete and accurate as you proceed.


TODO List #

I asked Claude Code to carefully identify the list of items to implement for each Phase, group the functions by topic, write them in the TODO list in advance, and process/check them step by step. Below is the TODO list drafted and managed by Claude Code for each Phase.

Phase 2: Options

  • Create phase2/options branch from main
  • Implement Option models (option.go)
  • Implement Options methods in ticker package
  • Write unit tests for Options
  • Test with live API data
  • Merge to main and update STATUS.md

Phase 3: Financials

  • Create phase3/financials branch from main
  • Research Yahoo Finance Financials API structure
  • Implement Financial models
  • Implement Financials methods in ticker package
  • Write unit tests for Financials
  • Test with live API data
  • Merge to main and update STATUS.md

Implementation Results #

Phase 2: Options #

Implementation Details

ItemFileDescription
Option Modelspkg/models/option.goOption, OptionQuote, OptionChain structs
Options Methodspkg/ticker/options.goOptions(), OptionChain(), Strikes(), etc.
Unit Testspkg/ticker/options_test.goCache and struct tests

Usage Example

t, _ := ticker.New("AAPL")
defer t.Close()

// List of expiration dates
expirations, _ := t.Options()

// Option chain (nearest expiration)
chain, _ := t.OptionChain("")

// Option chain for a specific date
chain, _ := t.OptionChain("2025-12-26")

// List of strike prices
strikes, _ := t.Strikes()

Issues Encountered

During development, the following issue occurred:


๐Ÿค–: The Underlying in options.go must now be of type *models.OptionQuote. Checking the code, it is already using &result.Quote, and since the Quote type has been changed to OptionQuote, it is automatically compatible. I need to update the test file and modify cmd/test_options/main.go as well.


As features were added, a new type needed to be applied to parts where an existing type was used. Claude Code correctly identified this and made the necessary modifications.

  • pkg/models/option.go: Added OptionQuote type
    • OptionChain.Underlying changed to *OptionQuote type
    • OptionChainResponse.Quote also changed to OptionQuote type
  • pkg/ticker/options.go: Automatic compatibility
    • No changes needed as &result.Quote returns *models.OptionQuote
  • cmd/test_options/main.go: Deleted as it was a temporary file after testing

Live Test Results

  • Underlying Price: $271.84
  • Calls: 99
  • Puts: 91

Phase 3: Financials #

Newly Added Files

  • pkg/models/financials.go: Financials data models
  • pkg/ticker/financials.go: Financials API methods
  • pkg/ticker/financials_test.go: Unit tests

Modified Files

  • internal/endpoints/endpoints.go: Added Financials key constants
  • pkg/ticker/ticker.go: Added financialsCache field

Implemented API Methods

MethodDescription
IncomeStatement(freq)Income Statement (annual/quarterly)
BalanceSheet(freq)Balance Sheet (annual/quarterly)
CashFlow(freq)Cash Flow (annual/quarterly)
FinancialsJSON()Raw JSON for debugging

Live Test Results (AAPL)

  • Income Statement: 5 periods, 26 fields
  • Total Revenue: $416.16B
  • Net Income: $112.01B
  • Diluted EPS: $7.46
  • Balance Sheet: 4 periods, 34 fields
  • Total Assets: $359.24B
  • Total Debt: $98.66B
  • Cash Flow: 5 periods, 38 fields
  • Operating Cash Flow: $111.48B
  • Free Cash Flow: $98.77B

Phase 4: Analysis #

Newly Added Files

  • pkg/models/analysis.go: Analysis data models (116 lines)
  • pkg/ticker/analysis.go: Analysis API methods (664 lines)
  • pkg/ticker/analysis_test.go: Unit tests (196 lines)

Modified Files

  • pkg/ticker/ticker.go: Added analysisCache field

Implemented API Methods

MethodDescriptionSource Module
Recommendations()Analyst recommendationsrecommendationTrend
PriceTarget()Price targetsfinancialData
EarningsEstimates()Earnings estimatesearningsTrend
RevenueEstimates()Revenue estimatesearningsTrend
EPSTrend()EPS trendearningsTrend
EPSRevisions()EPS revisionsearningsTrend
EarningsHistory()Earnings historyearningsHistory
GrowthEstimates()Growth estimatesindustryTrend, sectorTrend, indexTrend

Live Test Results (AAPL)

  • Recommendations: 4 periods (StrongBuy=5, Buy=24, Hold=15, Sell=1, StrongSell=3)
  • Price Targets: Current=$272.19, Mean=$287.71, High=$350, Low=$215
  • Earnings Estimates: 4 periods (0q, +1q, 0y, +1y)
  • Revenue Estimates: 4 periods with growth rates
  • Earnings History: 4 quarters with surprise %

Changes vs Plan

Original Plan

  • Analyst Recommendations โœ…
  • Price Targets โœ…
  • Earnings Estimates โœ…
  • Revenue Estimates โœ…
  • EPS Trend/Revisions โœ…
  • Growth Estimates โœ…

Additional Implementations

  • EarningsHistory(): Provides actual results vs past estimates (surprise %)
  • Caching System: Fetches earningsTrend data once and reuses it across multiple methods

Structural Changes

  • Initially expected to use separate API endpoints, but implemented by combining multiple modules of the quoteSummary API.
  • Unified function name getFloat โ†’ getFloat64 (to avoid conflict with info.go).

Intermediate Verification with yfinance #

I asked Claude Code to verify if all the phases developed so far are implemented identically to the features and methods provided by the existing Python-based yfinance.


๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป: Please double-check thoroughly that all Phases implemented so far accurately replicate all features provided by the existing Python-based yfinance. ultrathink. You may proceed with tests if necessary.


Verification Plan #

This is the TODO list set up by Claude Code. It plans to test each Phase, run Go Tests, and finally analyze any discrepancies.

  • Compare Phase 0 (Foundation): HTTP Client, Auth, Errors, Config
  • Compare Phase 1 (Core Data): Quote, History, Info
  • Compare Phase 2 (Options): Options chain and expiration
  • Compare Phase 3 (Financials): Income, Balance, CashFlow
  • Compare Phase 4 (Analysis): Recommendations, Price Targets, Estimates
  • Run Go tests to verify all implementations
  • Report findings and identify any gaps

Verification Results #

Phase 0: Foundation (Completed)

Python yfinanceGo ImplementationStatus
curl_cffi TLS fingerprintCycleTLS (Chrome JA3)โœ…
Cookie/Crumb authAuthManager (Basic + CSRF)โœ…
Proxy settingsConfig.SetProxy()โœ…
Timeout settingsConfig.SetTimeout()โœ…
Error handlingYFError with codesโœ…

Phase 1: Core Data (Completed)

Python MethodGo MethodStatus
ticker.infoInfo()โœ…
ticker.fast_infoFastInfo()โœ…
ticker.history()History()โœ…
ticker.dividendsDividends()โœ…
ticker.splitsSplits()โœ…
ticker.actionsActions()โœ…
Quote dataQuote()โœ…
History metadataGetHistoryMetadata()โœ…

Phase 2: Options (Completed)

Python MethodGo MethodStatus
ticker.optionsOptions()โœ…
ticker.option_chain()OptionChain()โœ…
Expiration datesOptionChainAtExpiry()โœ…
Strike pricesStrikes()โœ…

Phase 3: Financials (Completed)

Python MethodGo MethodStatus
ticker.income_stmtIncomeStatement("annual")โœ…
ticker.quarterly_income_stmtIncomeStatement("quarterly")โœ…
ticker.balance_sheetBalanceSheet("annual")โœ…
ticker.quarterly_balance_sheetBalanceSheet("quarterly")โœ…
ticker.cash_flowCashFlow("annual")โœ…
ticker.quarterly_cash_flowCashFlow("quarterly")โœ…
  • Note: Python’s ttm_income_stmt and ttm_cash_flow (trailing 12 months) are supported in Go via the freq="trailing" parameter.

Phase 4: Analysis (Completed)

Python MethodGo MethodStatus
ticker.recommendationsRecommendations()โœ…
ticker.analyst_price_targetsPriceTarget()โœ…
ticker.earnings_estimateEarningsEstimates()โœ…
ticker.revenue_estimateRevenueEstimates()โœ…
ticker.eps_trendEPSTrend()โœ…
ticker.eps_revisionsEPSRevisions()โœ…
ticker.earnings_historyEarningsHistory()โœ…
ticker.growth_estimatesGrowthEstimates()โœ…

Unimplemented Features (Scheduled for Phase 5 and beyond)

Python FeaturePhase
major_holdersPhase 5
institutional_holdersPhase 5
mutualfund_holdersPhase 5
insider_transactionsPhase 5
insider_purchasesPhase 5
insider_roster_holdersPhase 5
calendarPhase 5
sustainabilityPhase 9
isinPhase 9
newsPhase 9
earnings_datesPhase 9
upgrades_downgradesExpandable in Phase 4
sec_filingsPhase 9
shares / shares_fullPhase 5
capital_gainsExpandable in Phase 1
funds_dataPhase 9
live() WebSocketPhase 8

Evaluation #

  • All core features (Quote, History, Options, Financials, Analysis) are perfectly implemented.
  • Uses the same API endpoints as Python.
  • Caching strategy applied consistently.
  • Appropriate error handling and type safety.
  • All 38 unit tests passed.

Areas for Improvement #

  • upgrades_downgrades: Fetched from _quote data in Python’s quote.py โ†’ Can be added to Phase 4.
  • capital_gains: Data already being received in History API, can add a separate method.
  • TTM (Trailing Twelve Months) Financials: Currently supported via freq="trailing".