Building a Go Yahoo Finance Library with Claude Code #6
The sixth post in a series about building a Go version of the Yahoo Finance library based on Python’s yfinance, utilizing Claude Code.
ยท 10 min read
Creating CLAUDE.md (Finally) #
I asked Claude Code to develop the remaining Phases, but it proceeded with an unexpected task. The current working repository is structured as follows:
yfinance-main
ใด yfinance
ใด go-yfinance
ใด ...So far, I had downloaded the ranaroussi/yfinance repository locally. Inside it, I created the go-yfinance repository to implement functions by checking and comparing them one by one with yfinance.
However, Claude Code suddenly asked to retrieve the list of yfinance APIs from the Web. It hadn’t done this when developing previous phases. I realized this wouldn’t work, so I decided to define the working method in CLAUDE.md. Yes, my previous decision to just let it code out of laziness seemed to result in it suddenly changing its usual workflow.
๐จ๐ปโ๐ป: Please write the policies we’ve adopted so far during development into CLAUDE.md. Configure it so that it only operates within this project.
The written CLAUDE.md includes the content below. When work starts in go-yfinance, CLAUDE.md is automatically loaded, and all development guidelines are applied automatically.
Project Identity & Reference Principles
- Goal: A project to port the Python
yfinancelibrary to the Go language. - Reference Principles (CRITICAL):
- Do NOT search the web for feature implementation; directly analyze and reference the local Python
yfinancesource code. - Maintain consistency with the Python version regarding method names, parameters, return types, etc.
- Do NOT search the web for feature implementation; directly analyze and reference the local Python
Development Workflow
- Observe the following steps when developing new features:
- Preparation: Check
STATUS.mdand sync with themainbranch. - Development:
- Create
phase{N}/{feature-name}branch. - Adhere to existing code patterns (
pkg/ticker,pkg/models). - Unit tests must be written.
- Create
- Documentation: Add comments to
doc.gofiles and updateSTATUS.md.
- Preparation: Check
API Consistency
- Strictly manage method mapping between Python
yfinanceand Gogo-yfinance.- Naming: Convert Python’s snake_case to Go’s PascalCase (e.g.,
major_holdersโMajorHolders). - Mapping Table: Adhere to specifically stated mapping rules like
InsiderRosterHolders,AnalystPriceTargets.
- Naming: Convert Python’s snake_case to Go’s PascalCase (e.g.,
Definition of Done
- Perform the following before merging into the main branch:
- Pass tests (
go test) and build (go build). - Verify API correspondence with the Python version.
- Generate Documentation: Execute
make docsto generate and verifydocs/API.mdlocally (Do not rely on CI/CD). - Commit and push including the generated documents.
- Pass tests (
Code Style & Tools
- Comments: Include
gomarkdoccompatible comments and Examples. - Backward Compatibility: Maintain Deprecated Aliases when method names change to ensure compatibility.
- Makefile: Use
build,test,docs,lintcommands.
Issue Response
- Includes guidelines for CycleTLS panic handling and Git environment variable settings.
Trusting Completely, But With a Touch of Interference #
After writing CLAUDE.md, I thought Claude Code would handle things well on its own. However, just like the sudden attempt to fetch APIs from the Web, I felt that continuous checking was necessary, even though I had created the guideline document.

As mentioned in a post by Kim Wooyeong, simply writing down all guidelines in Claude Code doesn’t mean you can delegate everything. I definitely felt the quality improve when I continued to give feedback and proceeded with additional verification and questions at each step.
Should We Strictly Follow Go Function Naming Rules? #
For example, in Claude Code’s report stating that Phase 7 implementation was complete, the Python yfinance API mapping result was as follows:
| Python | Go |
|---|---|
yf.Tickers(symbols) | multi.NewTickers(symbols) |
tickers.tickers[symbol] | tickers.Get(symbol) |
tickers.history() | tickers.History(params) |
yf.download(symbols) | multi.Download(symbols, params) |
The part I questioned here was writing tickers.tickers[symbol] as tickers.Get(symbol). I had requested strict adherence to yfinance principles, but Claude Code created the function with the name Get. So, I asked Claude Code:
๐จ๐ปโ๐ป: I want to strictly follow yfinance’s function naming rules. Is writing tickers.tickers[symbol] as tickers.Get(symbol) a good approach?
๐ค: In the Go implementation, I created it as a Get(symbol) method, which is more idiomatic in Go. To expose it as a public field like Python, you have Option 1: keep the current method style using tickers.Get(“AAPL”), or Option 2: expose a public map field using tickers.Tickers[“AAPL”]. To exactly match the Python API, we could expose the Tickers field publicly.
I suspected it was created this way to satisfy Go’s implementation philosophy. However, as I thought when first starting this library conversion: It is important to follow Python yfinance’s principles as much as possible, but there will definitely be cases where we must implement differently due to the language’s characteristics.
So I asked if other features were also maintaining this Go style. If other methods were strictly following yfinance principles, I would need to change this part, but if other parts were also satisfying Go style, I considered the implementation direction to be consistent.
The following is the analysis result of all methods developed up to Phase 7:
| Python yfinance | Go go-yfinance | Match Status |
|---|---|---|
| Core | - | - |
Ticker(symbol) | ticker.New(symbol) | โ Go Style |
ticker.ticker | t.Symbol() | โ |
ticker.history() | t.History(params) | โ |
ticker.info | t.Info() | โ |
ticker.fast_info | t.FastInfo() | โ |
| Quote | - | - |
ticker.quote (internal) | t.Quote() | โ |
| Options | - | - |
ticker.options | t.Options() | โ |
ticker.option_chain(date) | t.OptionChain(date) | โ |
| Financials | - | - |
ticker.get_income_stmt(freq) | t.IncomeStatement(freq) | โ |
ticker.get_balance_sheet(freq) | t.BalanceSheet(freq) | โ |
ticker.get_cashflow(freq) | t.CashFlow(freq) | โ |
| Analysis | - | - |
ticker.get_recommendations() | t.Recommendations() | โ |
ticker.get_analyst_price_targets() | t.AnalystPriceTargets() | โ |
ticker.get_earnings_estimate() | t.EarningsEstimate() | โ |
ticker.get_revenue_estimate() | t.RevenueEstimate() | โ |
ticker.get_eps_trend() | t.EPSTrend() | โ |
ticker.get_eps_revisions() | t.EPSRevisions() | โ |
ticker.get_earnings_history() | t.EarningsHistory() | โ |
ticker.get_growth_estimates() | t.GrowthEstimates() | โ |
| Holders | - | - |
ticker.get_major_holders() | t.MajorHolders() | โ |
ticker.get_institutional_holders() | t.InstitutionalHolders() | โ |
ticker.get_mutualfund_holders() | t.MutualFundHolders() | โ |
ticker.get_insider_transactions() | t.InsiderTransactions() | โ |
ticker.get_insider_roster_holders() | t.InsiderRosterHolders() | โ |
ticker.get_insider_purchases() | t.InsiderPurchases() | โ |
| Calendar | - | - |
ticker.get_calendar() | t.Calendar() | โ |
| History | - | - |
ticker.dividends | t.Dividends() | โ |
ticker.splits | t.Splits() | โ |
ticker.actions | t.Actions() | โ |
| Multi-ticker | - | - |
yf.Tickers(symbols) | multi.NewTickers(symbols) | โ Go Style |
tickers.tickers[symbol] | tickers.Get(symbol) | โ Go Style |
tickers.history() | tickers.History(params) | โ |
yf.download() | multi.Download() | โ |
| Search | - | - |
yf.Search(query) | search.Search(query) | โ |
| Screener | - | - |
yf.Screener.get_screeners() | screener.Screen() | โ |
Excluding the unimplemented functions in Phases 8-9, I could confirm that all implemented content was mapped consistently.
Additionally, as each step proceeded, I made sure to check again whether the documentation was written correctly (though it might seem unnecessary). This is partly because I can’t delegate 100% of the work to Claude Code, but also because I believe it is a manager’s duty to verify the work done.
Is it Okay if Package Names are Different? #
During the implementation of Phase 8, the package for Websocket was named live. Consequently, the part called as yf.WebSocket() in Python changed to live.New() in Go. I asked for the reason behind this implementation.
๐จ๐ปโ๐ป: Is there a reason why the Websocket-related package name is ’live’?
It turned out that Python yfinance’s filename is live.py, and the classes WebSocket and AsyncWebSocket are also inside the live module, so it was matched identically.
# Python yfinance
from yfinance.live import WebSocketAlthough there were alternative names suggested by Claude Code such as stream, websocket, and realtime, I judged that keeping it identical to Python was more correct. And actually, when using it in Go, I thought it would be usable without confusion due to the consistency with Python yfinance.
import "github.com/wnjoon/go-yfinance/pkg/live"
ws, _ := live.New()
ws.Subscribe([]string{"AAPL"})
ws.Listen(handler)Implementation Results of Phase 6-9 #
Phase 6: Search & Lookup #
Code Implementation
- Search Package (
pkg/search/search.go):Search,SearchWithParams,Quotes,Newsmethods - Screener Package (
pkg/screener/screener.go):Screen,ScreenWithQuery,DayGainers,DayLosers,MostActivesmethods - Models (
pkg/models/search.go,pkg/models/screener.go): All data structures
Testing
- All tests passed (
go test ./pkg/search/... ./pkg/screener/... -v)
Documentation
pkg/search/doc.go,pkg/screener/doc.go- Package documentationpkg/models/doc.go- Added Search/Screener typesSTATUS.md- Updated Phase 6 completion statusdocs/API.md- Automatically generated viamake docs
Python yfinance API Consistency
| Python yfinance | Go Implementation |
|---|---|
yf.search(query) | search.Search(query) |
Screener().set_predefined_body(name) | screener.Screen(name, params) |
Screener().set_body(query) | screener.ScreenWithQuery(query, params) |
| Query Operators (EQ, GT, LT, AND, OR, etc.) | Identical to models.OpEQ, models.OpGT, etc. |
| Predefined screeners (day_gainers, etc.) | Identical to models.ScreenerDayGainers, etc. |
Phase 7: Multi-ticker & Batch #
New Files
pkg/models/multi.go:DownloadParams,MultiTickerResultmodelspkg/multi/multi.go:Tickersstruct,Downloadfunctionpkg/multi/multi_test.go: Unit tests
Key Features
multi.NewTickers(symbols): Create Tickers with symbol slicemulti.NewTickersFromString(str): Create Tickers from string (space/comma separated)tickers.Get(symbol): Access individual tickertickers.History(params): Download full ticker historymulti.Download(symbols, params): Batch download as an independent function- Parallel processing support via Worker pool (
Threadsparameter)
Python yfinance API Mapping
| Python | Go |
|---|---|
yf.Tickers(symbols) | multi.NewTickers(symbols) |
tickers.tickers[symbol] | tickers.Get(symbol) |
tickers.history() | tickers.History(params) |
yf.download(symbols) | multi.Download(symbols, params) |
Phase 8: Real-time WebSocket #
New Files
pkg/models/live.go:PricingDatastruct (33 fields),MarketStatetypepkg/live/proto.go: Protobuf decoder (Base64 + protobuf parsing)pkg/live/websocket.go: WebSocket clientpkg/live/doc.go: Package documentationpkg/live/live_test.go: Unit tests
Key Features
live.New(): Create WebSocket clientws.Subscribe(symbols): Subscribe to symbolsws.Unsubscribe(symbols): Unsubscribews.Listen(handler): Blocking listeningws.ListenAsync(handler): Asynchronous listeningws.Close(): Close connection- Auto heartbeat (resubscribe every 15 seconds)
- Auto reconnect
Python yfinance API Mapping
| Python | Go |
|---|---|
yf.WebSocket() | live.New() |
ws.subscribe(symbols) | ws.Subscribe(symbols) |
ws.listen(handler) | ws.Listen(handler) |
ws.close() | ws.Close() |
Phase 9: Python yfinance Advanced Features #
Added Features
- News Feed (
pkg/ticker/news.go)ticker.News(count, tab): Fetch news articlesticker.GetNews(): Fetch news with default parameters- Includes ad filtering
- Cache Layer (
pkg/cache/cache.go)- Thread-safe TTL-based in-memory cache
- Auto-cleanup of expired items
- Global cache instance
- Timezone Utilities (
pkg/utils/timezone.go)- Mapping of 50+ global exchange timezones
GetTimezone(),IsValidTimezone(),ConvertToTimezone()MarketIsOpen(): Basic market hour check
Summary #
In this way, I analyzed yfinance and asked Claude Code to create a Go library with the same functions. In terms of duration, it took about 3 days, but in actual working hours, it took roughly 6-7 hours. I probably could have worked much faster if I had written CLAUDE.md more meticulously or hadn’t requested redundant verification continuously.
Initially, I started this side project because there was no yfinance library in Go while building an Investment Strategy Agent in a course. I could have just followed the course using Python, thinking of it as studying Python, but since Google ADK supports Go, I thought, “Is there any reason not to try using Go?” and that led me here.
Although I borrowed the ideas and logic from open source, I left an inquiry in the Discussion section of ranaroussi/yfinance just in case.

I also asked Gemini a few questions out of curiosity regarding potential issues.
๐จ๐ปโ๐ป: I made a library for Go by referencing the logic and structure of yfinance used in Python. Although yfinance is open source, I sent an email asking if it’s okay to implement and create a library like this, but I haven’t received a reply yet. Could there be any problems in this case?
Here is a summary of Gemini’s response:
yfinancelikely follows a Permissive license like Apache License 2.0 (or MIT depending on the version). Converting Python code to Go is a form of porting. Open source licenses generally allow code modification, distribution, and porting to other languages.- The logic of fetching data from Yahoo Finance or the function structure itself is closer to an ‘idea’ or ‘function’ rather than ’expression,’ which is subject to copyright protection. Therefore, if you didn’t just copy and paste the Python code but re-implemented it to fit the characteristics of the Go language, the possibility of copyright infringement is low.
- To respect the original author and follow license regulations, it is customary and safe to state in the GitHub README, etc., that “This project is inspired by the Python yfinance library” and provide a link to the original.
- Yahoo Finance currently does not officially provide a free API.
yfinanceor user-created Go libraries likely use methods of crawling web pages or calling unofficial internal API endpoints. However, Yahoo’s Terms of Service generally prohibit automated data collection (scraping). - Yahoo may block IPs or change structures at any time, and if you use this commercially or cause excessive traffic, you may face legal sanctions.
Finally, it suggested clearly stating in the README.md that it is an “Unofficial library implemented in Go referencing the logic of ranaroussi/yfinance,” and giving notice that “This is not an official API of Yahoo Finance, and data collection may be blocked at any time.”
So, I decided to add the following content to README.md.
## Credits
This project is a Go implementation inspired by the Python library [yfinance](https://github.com/ranaroussi/yfinance).
Special thanks to [Ran Aroussi](https://github.com/ranaroussi) and all contributors of the original project for their excellent work.
...
## Disclaimer
> **Please read this carefully before using this library.**
1. **Unofficial API**: This library is **not affiliated with, endorsed by, or connected to Yahoo! Finance**. It wraps unofficial API endpoints intended for web browser consumption.
2. **Terms of Service**: Use of this library must comply with [Yahoo!'s Terms of Service](https://policies.yahoo.com/us/en/yahoo/terms/index.htm). Users are solely responsible for ensuring their usage is compliant.
3. **Risk of Blocking**: Since this library relies on unofficial methods, Yahoo! Finance may change their API structure or block IP addresses making excessive requests at any time without notice.
4. **No Warranty**: This software is provided "as is", without warranty of any kind, express or implied. The authors shall not be held liable for any damages or legal issues arising from the use of this software.Next time, I plan to actually register it as a Go library. I’m thinking of letting Claude Code handle this part as well.