Stock Liquidity Analysis Skill

Analyze stock liquidity using bid-ask spreads, volume profiles, order book depth, market impact estimates, and turnover ratios via Yahoo Finance data.

How to use it

Claude Code
  1. Run the line below. It pulls the whole folder into ~/.claude/skills/stock-liquidity.
  2. Describe your job in plain words. Claude Code follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit himself65/finance-skills/plugins/market-analysis/skills/stock-liquidity#main ~/.claude/skills/stock-liquidity

For one project only, change the path to .claude/skills/stock-liquidity.

Claude (web or desktop app)
  1. On this page open ⋯ → Download .md.
  2. Save it as SKILL.md in a folder, zip the folder, then Customize → Skills → + → Create skill → Upload a skill.
  3. Pick the file and Save. Claude shows the name and description and runs a security scan.
  4. Check the skill is switched on.
  5. Start a new chat and describe your job in plain words. The AI follows the skill from there.
ChatGPT or another app
  1. ChatGPT: make a Project and paste it into Instructions.
  2. Neither? Paste it at the top of a new chat — it works for that chat.
Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Source of Stock Liquidity Analysis Skill

Show the full text498 lines
namedescription
stock-liquidity> Analyze stock liquidity using bid-ask spreads, volume profiles, order book depth, market impact estimates, and turnover ratios via Yahoo Finance data. Use this skill whenever the user asks about liquidity, trading costs, bid-ask spread, market depth, volume analysis, slippage, market impact, turnover ratio, or how easy/hard it is to trade a stock without moving the price. Triggers: "how liquid is AAPL", "bid-ask spread", "volume analysis", "order book depth", market impact of a large order", "turnover ratio", "slippage estimate", can I trade 100k shares without moving the price", "liquidity comparison", spread analysis", "ADTV", "Amihud illiquidity", "dollar volume", execution cost estimate", "liquidity score", penny stocks, small caps, or thinly traded securities.

Stock Liquidity Analysis Skill

Analyzes stock liquidity across multiple dimensions — bid-ask spreads, volume patterns, order book depth, estimated market impact, and turnover ratios — using data from Yahoo Finance via yfinance.

Liquidity matters because it determines the real cost of trading. The quoted price is not what you actually pay — spreads, slippage, and market impact all eat into returns, especially for larger positions or less liquid names.

Important: This is for research and educational purposes only. Not financial advice. yfinance is not affiliated with Yahoo, Inc.


Step 1: Ensure Dependencies Are Available

Current environment status:

!`python3 -c "exec('try:\n import yfinance, pandas, numpy\n print(f\'yfinance={yfinance.__version__} pandas={pandas.__version__} numpy={numpy.__version__}\')\nexcept Exception:\n print(\'DEPS_MISSING\')')"`

If DEPS_MISSING, install required packages:

import subprocess, sys
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "yfinance", "pandas", "numpy"])

If already installed, skip and proceed.


Step 2: Route to the Correct Sub-Skill

Classify the user's request and jump to the matching section. If the user asks for a general liquidity assessment without specifying a particular metric, run Sub-Skill A (Liquidity Dashboard) which computes all key metrics together.

User Request Route To Examples
General liquidity check, "how liquid is X" Sub-Skill A: Liquidity Dashboard "how liquid is AAPL", "liquidity analysis for TSLA", "is this stock liquid enough"
Bid-ask spread, trading costs, effective spread Sub-Skill B: Spread Analysis "bid-ask spread for AMD", "what's the spread on NVDA options", "trading cost estimate"
Volume, ADTV, dollar volume, volume profile Sub-Skill C: Volume Analysis "volume analysis MSFT", "average daily volume", "volume profile for SPY"
Order book depth, market depth, level 2 Sub-Skill D: Order Book Depth "order book depth for AAPL", "market depth", "show me the book"
Market impact, slippage, execution cost for large orders Sub-Skill E: Market Impact "how much would 50k shares move the price", "slippage estimate", "market impact of $1M order"
Turnover ratio, trading activity relative to float Sub-Skill F: Turnover Ratio "turnover ratio for GME", "float turnover", "how actively traded is this"
Compare liquidity across multiple stocks Sub-Skill A (multi-ticker mode) "compare liquidity AAPL vs TSLA", "which is more liquid AMD or INTC"
Defaults
Parameter Default
Lookback period 3mo (3 months)
Data interval 1d (daily)
Market impact model Square-root model
Intraday interval (when needed) 5m

Sub-Skill A: Liquidity Dashboard

Goal: Produce a comprehensive liquidity snapshot combining all key metrics for one or more tickers.

A1: Fetch data and compute all metrics
import yfinance as yf
import pandas as pd
import numpy as np

def liquidity_dashboard(ticker_symbol, period="3mo"):
    ticker = yf.Ticker(ticker_symbol)
    info = ticker.info
    hist = ticker.history(period=period)

    if hist.empty:
        return None

    # --- Spread metrics (from current quote) ---
    bid = info.get("bid", None)
    ask = info.get("ask", None)
    current_price = info.get("currentPrice") or info.get("regularMarketPrice") or hist["Close"].iloc[-1]

    spread = None
    spread_pct = None
    if bid and ask and bid > 0 and ask > 0:
        spread = round(ask - bid, 4)
        midpoint = (ask + bid) / 2
        spread_pct = round((spread / midpoint) * 100, 4)

    # --- Volume metrics ---
    avg_volume = hist["Volume"].mean()
    median_volume = hist["Volume"].median()
    avg_dollar_volume = (hist["Close"] * hist["Volume"]).mean()
    volume_std = hist["Volume"].std()
    volume_cv = volume_std / avg_volume if avg_volume > 0 else None  # coefficient of variation

    # --- Turnover ratio ---
    shares_outstanding = info.get("sharesOutstanding", None)
    float_shares = info.get("floatShares", None)
    base_shares = float_shares or shares_outstanding
    turnover_ratio = round(avg_volume / base_shares, 6) if base_shares else None

    # --- Amihud illiquidity ratio ---
    # Average of |daily return| / daily dollar volume
    returns = hist["Close"].pct_change().dropna()
    dollar_volume = (hist["Close"] * hist["Volume"]).iloc[1:]  # align with returns
    amihud_values = returns.abs() / dollar_volume
    amihud = amihud_values[amihud_values.replace([np.inf, -np.inf], np.nan).notna()].mean()

    # --- Market impact estimate (square-root model) ---
    # For a hypothetical order of 1% of ADV
    adv = avg_volume
    order_size = adv * 0.01
    daily_volatility = returns.std()
    sigma = daily_volatility
    participation_rate = order_size / adv if adv > 0 else 0
    impact_bps = sigma * np.sqrt(participation_rate) * 10000  # in basis points

    return {
        "ticker": ticker_symbol,
        "current_price": round(current_price, 2),
        "bid": bid,
        "ask": ask,
        "spread": spread,
        "spread_pct": spread_pct,
        "avg_daily_volume": int(avg_volume),
        "median_daily_volume": int(median_volume),
        "avg_dollar_volume": round(avg_dollar_volume, 0),
        "volume_cv": round(volume_cv, 3) if volume_cv else None,
        "shares_outstanding": shares_outstanding,
        "float_shares": float_shares,
        "turnover_ratio": turnover_ratio,
        "amihud_illiquidity": round(amihud * 1e9, 4) if not np.isnan(amihud) else None,
        "daily_volatility": round(daily_volatility * 100, 2),
        "impact_1pct_adv_bps": round(impact_bps, 2),
        "observations": len(hist),
    }
A2: Interpret and present

Present as a summary card. For the Amihud illiquidity ratio, multiply by 1e9 for readability (standard convention).

Liquidity grade (use these rough thresholds for US equities):

Grade Avg Dollar Volume Spread (%) Amihud (×10⁹)
Very High > $500M/day < 0.03% < 0.01
High $50M–$500M/day 0.03–0.10% 0.01–0.1
Moderate $5M–$50M/day 0.10–0.50% 0.1–1.0
Low $500K–$5M/day 0.50–2.00% 1.0–10
Very Low < $500K/day > 2.00% > 10

When comparing multiple tickers, show a side-by-side table and highlight which is more liquid and why.


Sub-Skill B: Spread Analysis

Goal: Detailed bid-ask spread analysis including current spread, historical context from options data, and effective spread estimates.

B1: Current spread from quote
import yfinance as yf

def spread_analysis(ticker_symbol):
    ticker = yf.Ticker(ticker_symbol)
    info = ticker.info

    bid = info.get("bid", 0)
    ask = info.get("ask", 0)
    bid_size = info.get("bidSize", None)
    ask_size = info.get("askSize", None)
    current_price = info.get("currentPrice") or info.get("regularMarketPrice", 0)

    result = {"bid": bid, "ask": ask, "bid_size": bid_size, "ask_size": ask_size}

    if bid > 0 and ask > 0:
        midpoint = (bid + ask) / 2
        result["absolute_spread"] = round(ask - bid, 4)
        result["relative_spread_pct"] = round((ask - bid) / midpoint * 100, 4)
        result["relative_spread_bps"] = round((ask - bid) / midpoint * 10000, 2)
    return result
B2: Options spread context

Options data from yfinance includes bid/ask for each strike, which gives a sense of derivatives liquidity. Use the nearest expiration, extract near-the-money calls and puts, and compute spread and spread percentage for each.

See references/liquidity_reference.md § "Options Spread Analysis" for the full code template.

B3: Present results

Show:

  • Current quoted spread (absolute, relative %, basis points)
  • Bid/ask sizes if available
  • Near-the-money options spreads for context
  • How the spread compares to typical ranges for this market cap tier

Sub-Skill C: Volume Analysis

Goal: Analyze trading volume patterns — averages, trends, relative volume, and dollar volume.

C1: Compute volume metrics
import yfinance as yf
import pandas as pd
import numpy as np

def volume_analysis(ticker_symbol, period="3mo"):
    ticker = yf.Ticker(ticker_symbol)
    hist = ticker.history(period=period)

    if hist.empty:
        return None

    vol = hist["Volume"]
    close = hist["Close"]
    dollar_vol = vol * close

    # Relative volume (today vs average)
    rvol = vol.iloc[-1] / vol.mean() if vol.mean() > 0 else None

    # Volume trend (linear regression slope over the period)
    x = np.arange(len(vol))
    slope, _ = np.polyfit(x, vol.values, 1) if len(vol) > 1 else (0, 0)
    trend_pct = (slope * len(vol)) / vol.mean() * 100  # % change over period

    # Volume profile by day of week
    hist_copy = hist.copy()
    hist_copy["DayOfWeek"] = hist_copy.index.dayofweek
    day_names = {0: "Mon", 1: "Tue", 2: "Wed", 3: "Thu", 4: "Fri"}
    vol_by_day = hist_copy.groupby("DayOfWeek")["Volume"].mean()
    vol_by_day.index = vol_by_day.index.map(day_names)

    # High/low volume days
    high_vol_days = hist.nlargest(5, "Volume")[["Close", "Volume"]]
    low_vol_days = hist.nsmallest(5, "Volume")[["Close", "Volume"]]

    return {
        "avg_volume": int(vol.mean()),
        "median_volume": int(vol.median()),
        "avg_dollar_volume": round(dollar_vol.mean(), 0),
        "current_volume": int(vol.iloc[-1]),
        "relative_volume": round(rvol, 2) if rvol else None,
        "volume_trend_pct": round(trend_pct, 1),
        "volume_by_day": vol_by_day.to_dict(),
        "high_vol_days": high_vol_days,
        "low_vol_days": low_vol_days,
        "max_volume": int(vol.max()),
        "min_volume": int(vol.min()),
    }
C2: Present results

Show:

  • Average daily volume (shares and dollar) with median for comparison
  • Relative volume (RVOL) — today's volume vs. the average. RVOL > 1.5 is elevated; RVOL < 0.5 is unusually quiet
  • Volume trend — is trading activity increasing or declining?
  • Day-of-week pattern (if meaningful variation exists)
  • Top 5 highest-volume days with context (earnings? news?)

Sub-Skill D: Order Book Depth

Goal: Estimate order book depth using available bid/ask data from the equity quote and options chain.

Yahoo Finance does not provide full Level 2 / order book data. Be upfront about this limitation. What we can do:

  1. Equity quote: bid, ask, bid size, ask size (top of book only)
  2. Options chain: bid/ask and open interest across strikes give a proxy for derivatives depth
  3. Intraday volume distribution: how volume is distributed within the day suggests how deep the continuous market is
D1: Gather available depth data

Collect three data points:

  1. Top of book — bid, ask, bidSize, askSize from ticker.info
  2. Intraday volume distribution — 5-min bars over the last 5 days, grouped by time-of-day and normalized to percentage of daily volume
  3. Options open interest — total call/put OI and volume from the nearest expiration as a derivatives depth proxy

See references/liquidity_reference.md § "Order Book Depth Proxy" for the full code template.

D2: Present results

Show:

  • Top of book: current bid/ask with sizes
  • Intraday volume shape: where volume concentrates (open/close vs. midday)
  • Options depth: total open interest and volume as a proxy for derivatives liquidity
  • Honest limitation: "Yahoo Finance provides top-of-book only. For full Level 2 depth, a direct market data feed (e.g., NYSE OpenBook, NASDAQ TotalView) is needed."

Sub-Skill E: Market Impact

Goal: Estimate how much a given order size would move the price, using the square-root market impact model.

The standard model in practice is: Impact (%) = σ × √(Q / V) where σ is daily volatility, Q is order size in shares, and V is average daily volume. This is a simplified version of the Almgren-Chriss framework used by institutional traders.

E1: Compute market impact estimate
import yfinance as yf
import numpy as np

def market_impact(ticker_symbol, order_shares=None, order_dollars=None, period="3mo"):
    ticker = yf.Ticker(ticker_symbol)
    hist = ticker.history(period=period)
    info = ticker.info

    if hist.empty:
        return None

    current_price = info.get("currentPrice") or hist["Close"].iloc[-1]
    avg_volume = hist["Volume"].mean()
    daily_volatility = hist["Close"].pct_change().dropna().std()

    # Determine order size in shares
    if order_dollars and not order_shares:
        order_shares = order_dollars / current_price
    elif not order_shares:
        # Default: estimate for various sizes
        order_shares = avg_volume * 0.01  # 1% of ADV

    participation_rate = order_shares / avg_volume if avg_volume > 0 else 0
    pct_adv = (order_shares / avg_volume * 100) if avg_volume > 0 else 0

    # Square-root impact model
    impact_pct = daily_volatility * np.sqrt(participation_rate) * 100
    impact_bps = impact_pct * 100
    impact_dollars = impact_pct / 100 * current_price * order_shares

    # Generate impact curve for multiple order sizes
    sizes = [0.001, 0.005, 0.01, 0.02, 0.05, 0.10, 0.20, 0.50]  # as fraction of ADV
    curve = []
    for s in sizes:
        q = avg_volume * s
        imp = daily_volatility * np.sqrt(s) * 100
        curve.append({
            "pct_adv": round(s * 100, 1),
            "shares": int(q),
            "dollars": round(q * current_price, 0),
            "impact_bps": round(imp * 100, 1),
            "impact_dollars_per_share": round(imp / 100 * current_price, 4),
        })

    return {
        "ticker": ticker_symbol,
        "current_price": round(current_price, 2),
        "avg_daily_volume": int(avg_volume),
        "daily_volatility_pct": round(daily_volatility * 100, 2),
        "order_shares": int(order_shares),
        "order_dollars": round(order_shares * current_price, 0),
        "pct_of_adv": round(pct_adv, 2),
        "estimated_impact_bps": round(impact_bps, 1),
        "estimated_impact_pct": round(impact_pct, 4),
        "estimated_impact_total_dollars": round(impact_dollars, 2),
        "impact_curve": curve,
    }
E2: Present results

Show:

  • The estimated impact for the user's specific order size
  • An impact curve table showing how cost scales with order size
  • Context: "This uses the square-root market impact model, a standard institutional estimate. Actual impact depends on execution strategy (VWAP, TWAP, etc.), time of day, and current market conditions."
  • If impact > 50 bps, flag that the order is large relative to liquidity and suggest the user consider algorithmic execution or splitting the order across days

Sub-Skill F: Turnover Ratio

Goal: Measure how actively a stock trades relative to its shares outstanding and free float.

F1: Compute turnover metrics
import yfinance as yf
import pandas as pd
import numpy as np

def turnover_analysis(ticker_symbol, period="3mo"):
    ticker = yf.Ticker(ticker_symbol)
    hist = ticker.history(period=period)
    info = ticker.info

    if hist.empty:
        return None

    avg_volume = hist["Volume"].mean()
    shares_outstanding = info.get("sharesOutstanding")
    float_shares = info.get("floatShares")

    result = {
        "avg_daily_volume": int(avg_volume),
        "shares_outstanding": shares_outstanding,
        "float_shares": float_shares,
    }

    if shares_outstanding:
        daily_turnover = avg_volume / shares_outstanding
        result["daily_turnover_ratio"] = round(daily_turnover, 6)
        result["annualized_turnover"] = round(daily_turnover * 252, 2)
        result["days_to_trade_float"] = round(
            (float_shares or shares_outstanding) / avg_volume, 1
        ) if avg_volume > 0 else None

    if float_shares:
        float_turnover = avg_volume / float_shares
        result["float_turnover_daily"] = round(float_turnover, 6)
        result["float_turnover_annualized"] = round(float_turnover * 252, 2)

    # Turnover trend
    vol = hist["Volume"]
    base = float_shares or shares_outstanding
    if base:
        hist_copy = hist.copy()
        hist_copy["turnover"] = hist_copy["Volume"] / base
        recent_turnover = hist_copy["turnover"].tail(20).mean()
        older_turnover = hist_copy["turnover"].head(20).mean()
        if older_turnover > 0:
            result["turnover_trend_pct"] = round(
                (recent_turnover - older_turnover) / older_turnover * 100, 1
            )

    return result
F2: Present results

Show:

  • Daily and annualized turnover ratios (vs. outstanding and float)
  • "Days to trade the float" — how many days at average volume to turn over the entire free float
  • Turnover trend — is the stock becoming more or less actively traded?
  • Context:
Turnover (Annualized) Interpretation
> 500% Extremely active — likely speculative or momentum-driven
100–500% Actively traded
30–100% Moderate activity
< 30% Thinly traded — likely institutional buy-and-hold or neglected

Step 3: Respond to the User

After running the appropriate sub-skill:

Always include
  • The lookback period used for historical metrics
  • The data timestamp — spreads and quotes are snapshots, not real-time
  • Any tickers that returned empty data (invalid symbol, delisted, etc.)
Always caveat
  • Yahoo Finance quote data has a 15-minute delay for most exchanges — spreads shown may not reflect the current live market
  • Full order book (Level 2) data is not available through Yahoo Finance
  • Market impact estimates are models, not guarantees — actual execution costs depend on strategy, timing, and market conditions
  • Liquidity can change rapidly — a stock that's liquid today may not be tomorrow (especially around events, halts, or during extended hours)
Practical guidance (mention when relevant)
  • Position sizing: If estimated impact exceeds 25 bps, the position may be too large for the stock's liquidity
  • Small/micro-cap warning: Stocks with < $1M daily dollar volume require careful execution
  • Spread costs compound: A 0.10% spread on a round-trip (buy + sell) costs 0.20% — this adds up for active strategies
  • Illiquidity premium: Less liquid stocks historically earn higher returns as compensation — but the transaction costs can eat this premium

Important: Never recommend specific trades. Present liquidity data and let the user make their own decisions.


Reference Files

  • references/liquidity_reference.md — Detailed formulas, extended code templates, metric interpretation guides, and academic references for all liquidity measures

Read the reference file when you need exact formulas, edge case handling, or deeper background on liquidity metrics.

1---
2name: stock-liquidity
3description: >
4 Analyze stock liquidity using bid-ask spreads, volume profiles, order book depth,
5 market impact estimates, and turnover ratios via Yahoo Finance data.
6 Use this skill whenever the user asks about liquidity, trading costs, bid-ask spread,
7 market depth, volume analysis, slippage, market impact, turnover ratio, or how
8 easy/hard it is to trade a stock without moving the price.
9 Triggers: "how liquid is AAPL", "bid-ask spread", "volume analysis", "order book depth",
10 "market impact of a large order", "turnover ratio", "slippage estimate",
11 "can I trade 100k shares without moving the price", "liquidity comparison",
12 "spread analysis", "ADTV", "Amihud illiquidity", "dollar volume",
13 "execution cost estimate", "liquidity score", penny stocks, small caps,
14 or thinly traded securities.
15---
16 
17# Stock Liquidity Analysis Skill
18 
19Analyzes stock liquidity across multiple dimensions — bid-ask spreads, volume patterns, order book depth, estimated market impact, and turnover ratios — using data from Yahoo Finance via [yfinance](https://github.com/ranaroussi/yfinance).
20 
21Liquidity matters because it determines the real cost of trading. The quoted price is not what you actually pay — spreads, slippage, and market impact all eat into returns, especially for larger positions or less liquid names.
22 
23**Important**: This is for research and educational purposes only. Not financial advice. yfinance is not affiliated with Yahoo, Inc.
24 
25---
26 
27## Step 1: Ensure Dependencies Are Available
28 
29**Current environment status:**
30 
31```
32!`python3 -c "exec('try:\n import yfinance, pandas, numpy\n print(f\'yfinance={yfinance.__version__} pandas={pandas.__version__} numpy={numpy.__version__}\')\nexcept Exception:\n print(\'DEPS_MISSING\')')"`
33```
34 
35If `DEPS_MISSING`, install required packages:
36 
37```python
38import subprocess, sys
39subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "yfinance", "pandas", "numpy"])
40```
41 
42If already installed, skip and proceed.
43 
44---
45 
46## Step 2: Route to the Correct Sub-Skill
47 
48Classify the user's request and jump to the matching section. If the user asks for a general liquidity assessment without specifying a particular metric, run **Sub-Skill A** (Liquidity Dashboard) which computes all key metrics together.
49 
50| User Request | Route To | Examples |
51|---|---|---|
52| General liquidity check, "how liquid is X" | **Sub-Skill A: Liquidity Dashboard** | "how liquid is AAPL", "liquidity analysis for TSLA", "is this stock liquid enough" |
53| Bid-ask spread, trading costs, effective spread | **Sub-Skill B: Spread Analysis** | "bid-ask spread for AMD", "what's the spread on NVDA options", "trading cost estimate" |
54| Volume, ADTV, dollar volume, volume profile | **Sub-Skill C: Volume Analysis** | "volume analysis MSFT", "average daily volume", "volume profile for SPY" |
55| Order book depth, market depth, level 2 | **Sub-Skill D: Order Book Depth** | "order book depth for AAPL", "market depth", "show me the book" |
56| Market impact, slippage, execution cost for large orders | **Sub-Skill E: Market Impact** | "how much would 50k shares move the price", "slippage estimate", "market impact of $1M order" |
57| Turnover ratio, trading activity relative to float | **Sub-Skill F: Turnover Ratio** | "turnover ratio for GME", "float turnover", "how actively traded is this" |
58| Compare liquidity across multiple stocks | **Sub-Skill A** (multi-ticker mode) | "compare liquidity AAPL vs TSLA", "which is more liquid AMD or INTC" |
59 
60### Defaults
61 
62| Parameter | Default |
63|---|---|
64| Lookback period | `3mo` (3 months) |
65| Data interval | `1d` (daily) |
66| Market impact model | Square-root model |
67| Intraday interval (when needed) | `5m` |
68 
69---
70 
71## Sub-Skill A: Liquidity Dashboard
72 
73**Goal**: Produce a comprehensive liquidity snapshot combining all key metrics for one or more tickers.
74 
75### A1: Fetch data and compute all metrics
76 
77```python
78import yfinance as yf
79import pandas as pd
80import numpy as np
81 
82def liquidity_dashboard(ticker_symbol, period="3mo"):
83 ticker = yf.Ticker(ticker_symbol)
84 info = ticker.info
85 hist = ticker.history(period=period)
86 
87 if hist.empty:
88 return None
89 
90 # --- Spread metrics (from current quote) ---
91 bid = info.get("bid", None)
92 ask = info.get("ask", None)
93 current_price = info.get("currentPrice") or info.get("regularMarketPrice") or hist["Close"].iloc[-1]
94 
95 spread = None
96 spread_pct = None
97 if bid and ask and bid > 0 and ask > 0:
98 spread = round(ask - bid, 4)
99 midpoint = (ask + bid) / 2
100 spread_pct = round((spread / midpoint) * 100, 4)
101 
102 # --- Volume metrics ---
103 avg_volume = hist["Volume"].mean()
104 median_volume = hist["Volume"].median()
105 avg_dollar_volume = (hist["Close"] * hist["Volume"]).mean()
106 volume_std = hist["Volume"].std()
107 volume_cv = volume_std / avg_volume if avg_volume > 0 else None # coefficient of variation
108 
109 # --- Turnover ratio ---
110 shares_outstanding = info.get("sharesOutstanding", None)
111 float_shares = info.get("floatShares", None)
112 base_shares = float_shares or shares_outstanding
113 turnover_ratio = round(avg_volume / base_shares, 6) if base_shares else None
114 
115 # --- Amihud illiquidity ratio ---
116 # Average of |daily return| / daily dollar volume
117 returns = hist["Close"].pct_change().dropna()
118 dollar_volume = (hist["Close"] * hist["Volume"]).iloc[1:] # align with returns
119 amihud_values = returns.abs() / dollar_volume
120 amihud = amihud_values[amihud_values.replace([np.inf, -np.inf], np.nan).notna()].mean()
121 
122 # --- Market impact estimate (square-root model) ---
123 # For a hypothetical order of 1% of ADV
124 adv = avg_volume
125 order_size = adv * 0.01
126 daily_volatility = returns.std()
127 sigma = daily_volatility
128 participation_rate = order_size / adv if adv > 0 else 0
129 impact_bps = sigma * np.sqrt(participation_rate) * 10000 # in basis points
130 
131 return {
132 "ticker": ticker_symbol,
133 "current_price": round(current_price, 2),
134 "bid": bid,
135 "ask": ask,
136 "spread": spread,
137 "spread_pct": spread_pct,
138 "avg_daily_volume": int(avg_volume),
139 "median_daily_volume": int(median_volume),
140 "avg_dollar_volume": round(avg_dollar_volume, 0),
141 "volume_cv": round(volume_cv, 3) if volume_cv else None,
142 "shares_outstanding": shares_outstanding,
143 "float_shares": float_shares,
144 "turnover_ratio": turnover_ratio,
145 "amihud_illiquidity": round(amihud * 1e9, 4) if not np.isnan(amihud) else None,
146 "daily_volatility": round(daily_volatility * 100, 2),
147 "impact_1pct_adv_bps": round(impact_bps, 2),
148 "observations": len(hist),
149 }
150```
151 
152### A2: Interpret and present
153 
154Present as a summary card. For the Amihud illiquidity ratio, multiply by 1e9 for readability (standard convention).
155 
156**Liquidity grade** (use these rough thresholds for US equities):
157 
158| Grade | Avg Dollar Volume | Spread (%) | Amihud (×10⁹) |
159|---|---|---|---|
160| Very High | > $500M/day | < 0.03% | < 0.01 |
161| High | $50M–$500M/day | 0.03–0.10% | 0.01–0.1 |
162| Moderate | $5M–$50M/day | 0.10–0.50% | 0.1–1.0 |
163| Low | $500K–$5M/day | 0.50–2.00% | 1.0–10 |
164| Very Low | < $500K/day | > 2.00% | > 10 |
165 
166When comparing multiple tickers, show a side-by-side table and highlight which is more liquid and why.
167 
168---
169 
170## Sub-Skill B: Spread Analysis
171 
172**Goal**: Detailed bid-ask spread analysis including current spread, historical context from options data, and effective spread estimates.
173 
174### B1: Current spread from quote
175 
176```python
177import yfinance as yf
178 
179def spread_analysis(ticker_symbol):
180 ticker = yf.Ticker(ticker_symbol)
181 info = ticker.info
182 
183 bid = info.get("bid", 0)
184 ask = info.get("ask", 0)
185 bid_size = info.get("bidSize", None)
186 ask_size = info.get("askSize", None)
187 current_price = info.get("currentPrice") or info.get("regularMarketPrice", 0)
188 
189 result = {"bid": bid, "ask": ask, "bid_size": bid_size, "ask_size": ask_size}
190 
191 if bid > 0 and ask > 0:
192 midpoint = (bid + ask) / 2
193 result["absolute_spread"] = round(ask - bid, 4)
194 result["relative_spread_pct"] = round((ask - bid) / midpoint * 100, 4)
195 result["relative_spread_bps"] = round((ask - bid) / midpoint * 10000, 2)
196 return result
197```
198 
199### B2: Options spread context
200 
201Options data from yfinance includes bid/ask for each strike, which gives a sense of derivatives liquidity. Use the nearest expiration, extract near-the-money calls and puts, and compute spread and spread percentage for each.
202 
203See `references/liquidity_reference.md` § "Options Spread Analysis" for the full code template.
204 
205### B3: Present results
206 
207Show:
208- Current quoted spread (absolute, relative %, basis points)
209- Bid/ask sizes if available
210- Near-the-money options spreads for context
211- How the spread compares to typical ranges for this market cap tier
212 
213---
214 
215## Sub-Skill C: Volume Analysis
216 
217**Goal**: Analyze trading volume patterns — averages, trends, relative volume, and dollar volume.
218 
219### C1: Compute volume metrics
220 
221```python
222import yfinance as yf
223import pandas as pd
224import numpy as np
225 
226def volume_analysis(ticker_symbol, period="3mo"):
227 ticker = yf.Ticker(ticker_symbol)
228 hist = ticker.history(period=period)
229 
230 if hist.empty:
231 return None
232 
233 vol = hist["Volume"]
234 close = hist["Close"]
235 dollar_vol = vol * close
236 
237 # Relative volume (today vs average)
238 rvol = vol.iloc[-1] / vol.mean() if vol.mean() > 0 else None
239 
240 # Volume trend (linear regression slope over the period)
241 x = np.arange(len(vol))
242 slope, _ = np.polyfit(x, vol.values, 1) if len(vol) > 1 else (0, 0)
243 trend_pct = (slope * len(vol)) / vol.mean() * 100 # % change over period
244 
245 # Volume profile by day of week
246 hist_copy = hist.copy()
247 hist_copy["DayOfWeek"] = hist_copy.index.dayofweek
248 day_names = {0: "Mon", 1: "Tue", 2: "Wed", 3: "Thu", 4: "Fri"}
249 vol_by_day = hist_copy.groupby("DayOfWeek")["Volume"].mean()
250 vol_by_day.index = vol_by_day.index.map(day_names)
251 
252 # High/low volume days
253 high_vol_days = hist.nlargest(5, "Volume")[["Close", "Volume"]]
254 low_vol_days = hist.nsmallest(5, "Volume")[["Close", "Volume"]]
255 
256 return {
257 "avg_volume": int(vol.mean()),
258 "median_volume": int(vol.median()),
259 "avg_dollar_volume": round(dollar_vol.mean(), 0),
260 "current_volume": int(vol.iloc[-1]),
261 "relative_volume": round(rvol, 2) if rvol else None,
262 "volume_trend_pct": round(trend_pct, 1),
263 "volume_by_day": vol_by_day.to_dict(),
264 "high_vol_days": high_vol_days,
265 "low_vol_days": low_vol_days,
266 "max_volume": int(vol.max()),
267 "min_volume": int(vol.min()),
268 }
269```
270 
271### C2: Present results
272 
273Show:
274- Average daily volume (shares and dollar) with median for comparison
275- Relative volume (RVOL) — today's volume vs. the average. RVOL > 1.5 is elevated; RVOL < 0.5 is unusually quiet
276- Volume trend — is trading activity increasing or declining?
277- Day-of-week pattern (if meaningful variation exists)
278- Top 5 highest-volume days with context (earnings? news?)
279 
280---
281 
282## Sub-Skill D: Order Book Depth
283 
284**Goal**: Estimate order book depth using available bid/ask data from the equity quote and options chain.
285 
286Yahoo Finance does not provide full Level 2 / order book data. Be upfront about this limitation. What we can do:
287 
2881. **Equity quote**: bid, ask, bid size, ask size (top of book only)
2892. **Options chain**: bid/ask and open interest across strikes give a proxy for derivatives depth
2903. **Intraday volume distribution**: how volume is distributed within the day suggests how deep the continuous market is
291 
292### D1: Gather available depth data
293 
294Collect three data points:
295 
2961. **Top of book** — bid, ask, bidSize, askSize from `ticker.info`
2972. **Intraday volume distribution** — 5-min bars over the last 5 days, grouped by time-of-day and normalized to percentage of daily volume
2983. **Options open interest** — total call/put OI and volume from the nearest expiration as a derivatives depth proxy
299 
300See `references/liquidity_reference.md` § "Order Book Depth Proxy" for the full code template.
301 
302### D2: Present results
303 
304Show:
305- **Top of book**: current bid/ask with sizes
306- **Intraday volume shape**: where volume concentrates (open/close vs. midday)
307- **Options depth**: total open interest and volume as a proxy for derivatives liquidity
308- **Honest limitation**: "Yahoo Finance provides top-of-book only. For full Level 2 depth, a direct market data feed (e.g., NYSE OpenBook, NASDAQ TotalView) is needed."
309 
310---
311 
312## Sub-Skill E: Market Impact
313 
314**Goal**: Estimate how much a given order size would move the price, using the square-root market impact model.
315 
316The standard model in practice is: **Impact (%) = σ × √(Q / V)** where σ is daily volatility, Q is order size in shares, and V is average daily volume. This is a simplified version of the Almgren-Chriss framework used by institutional traders.
317 
318### E1: Compute market impact estimate
319 
320```python
321import yfinance as yf
322import numpy as np
323 
324def market_impact(ticker_symbol, order_shares=None, order_dollars=None, period="3mo"):
325 ticker = yf.Ticker(ticker_symbol)
326 hist = ticker.history(period=period)
327 info = ticker.info
328 
329 if hist.empty:
330 return None
331 
332 current_price = info.get("currentPrice") or hist["Close"].iloc[-1]
333 avg_volume = hist["Volume"].mean()
334 daily_volatility = hist["Close"].pct_change().dropna().std()
335 
336 # Determine order size in shares
337 if order_dollars and not order_shares:
338 order_shares = order_dollars / current_price
339 elif not order_shares:
340 # Default: estimate for various sizes
341 order_shares = avg_volume * 0.01 # 1% of ADV
342 
343 participation_rate = order_shares / avg_volume if avg_volume > 0 else 0
344 pct_adv = (order_shares / avg_volume * 100) if avg_volume > 0 else 0
345 
346 # Square-root impact model
347 impact_pct = daily_volatility * np.sqrt(participation_rate) * 100
348 impact_bps = impact_pct * 100
349 impact_dollars = impact_pct / 100 * current_price * order_shares
350 
351 # Generate impact curve for multiple order sizes
352 sizes = [0.001, 0.005, 0.01, 0.02, 0.05, 0.10, 0.20, 0.50] # as fraction of ADV
353 curve = []
354 for s in sizes:
355 q = avg_volume * s
356 imp = daily_volatility * np.sqrt(s) * 100
357 curve.append({
358 "pct_adv": round(s * 100, 1),
359 "shares": int(q),
360 "dollars": round(q * current_price, 0),
361 "impact_bps": round(imp * 100, 1),
362 "impact_dollars_per_share": round(imp / 100 * current_price, 4),
363 })
364 
365 return {
366 "ticker": ticker_symbol,
367 "current_price": round(current_price, 2),
368 "avg_daily_volume": int(avg_volume),
369 "daily_volatility_pct": round(daily_volatility * 100, 2),
370 "order_shares": int(order_shares),
371 "order_dollars": round(order_shares * current_price, 0),
372 "pct_of_adv": round(pct_adv, 2),
373 "estimated_impact_bps": round(impact_bps, 1),
374 "estimated_impact_pct": round(impact_pct, 4),
375 "estimated_impact_total_dollars": round(impact_dollars, 2),
376 "impact_curve": curve,
377 }
378```
379 
380### E2: Present results
381 
382Show:
383- The estimated impact for the user's specific order size
384- An impact curve table showing how cost scales with order size
385- Context: "This uses the square-root market impact model, a standard institutional estimate. Actual impact depends on execution strategy (VWAP, TWAP, etc.), time of day, and current market conditions."
386- If impact > 50 bps, flag that the order is large relative to liquidity and suggest the user consider algorithmic execution or splitting the order across days
387 
388---
389 
390## Sub-Skill F: Turnover Ratio
391 
392**Goal**: Measure how actively a stock trades relative to its shares outstanding and free float.
393 
394### F1: Compute turnover metrics
395 
396```python
397import yfinance as yf
398import pandas as pd
399import numpy as np
400 
401def turnover_analysis(ticker_symbol, period="3mo"):
402 ticker = yf.Ticker(ticker_symbol)
403 hist = ticker.history(period=period)
404 info = ticker.info
405 
406 if hist.empty:
407 return None
408 
409 avg_volume = hist["Volume"].mean()
410 shares_outstanding = info.get("sharesOutstanding")
411 float_shares = info.get("floatShares")
412 
413 result = {
414 "avg_daily_volume": int(avg_volume),
415 "shares_outstanding": shares_outstanding,
416 "float_shares": float_shares,
417 }
418 
419 if shares_outstanding:
420 daily_turnover = avg_volume / shares_outstanding
421 result["daily_turnover_ratio"] = round(daily_turnover, 6)
422 result["annualized_turnover"] = round(daily_turnover * 252, 2)
423 result["days_to_trade_float"] = round(
424 (float_shares or shares_outstanding) / avg_volume, 1
425 ) if avg_volume > 0 else None
426 
427 if float_shares:
428 float_turnover = avg_volume / float_shares
429 result["float_turnover_daily"] = round(float_turnover, 6)
430 result["float_turnover_annualized"] = round(float_turnover * 252, 2)
431 
432 # Turnover trend
433 vol = hist["Volume"]
434 base = float_shares or shares_outstanding
435 if base:
436 hist_copy = hist.copy()
437 hist_copy["turnover"] = hist_copy["Volume"] / base
438 recent_turnover = hist_copy["turnover"].tail(20).mean()
439 older_turnover = hist_copy["turnover"].head(20).mean()
440 if older_turnover > 0:
441 result["turnover_trend_pct"] = round(
442 (recent_turnover - older_turnover) / older_turnover * 100, 1
443 )
444 
445 return result
446```
447 
448### F2: Present results
449 
450Show:
451- Daily and annualized turnover ratios (vs. outstanding and float)
452- "Days to trade the float" — how many days at average volume to turn over the entire free float
453- Turnover trend — is the stock becoming more or less actively traded?
454- Context:
455 
456| Turnover (Annualized) | Interpretation |
457|---|---|
458| > 500% | Extremely active — likely speculative or momentum-driven |
459| 100–500% | Actively traded |
460| 30–100% | Moderate activity |
461| < 30% | Thinly traded — likely institutional buy-and-hold or neglected |
462 
463---
464 
465## Step 3: Respond to the User
466 
467After running the appropriate sub-skill:
468 
469### Always include
470 
471- The **lookback period** used for historical metrics
472- The **data timestamp** — spreads and quotes are snapshots, not real-time
473- Any tickers that returned **empty data** (invalid symbol, delisted, etc.)
474 
475### Always caveat
476 
477- Yahoo Finance quote data has a **15-minute delay** for most exchanges — spreads shown may not reflect the current live market
478- Full order book (Level 2) data is **not available** through Yahoo Finance
479- Market impact estimates are **models, not guarantees** — actual execution costs depend on strategy, timing, and market conditions
480- Liquidity can **change rapidly** — a stock that's liquid today may not be tomorrow (especially around events, halts, or during extended hours)
481 
482### Practical guidance (mention when relevant)
483 
484- **Position sizing**: If estimated impact exceeds 25 bps, the position may be too large for the stock's liquidity
485- **Small/micro-cap warning**: Stocks with < $1M daily dollar volume require careful execution
486- **Spread costs compound**: A 0.10% spread on a round-trip (buy + sell) costs 0.20% — this adds up for active strategies
487- **Illiquidity premium**: Less liquid stocks historically earn higher returns as compensation — but the transaction costs can eat this premium
488 
489**Important**: Never recommend specific trades. Present liquidity data and let the user make their own decisions.
490 
491---
492 
493## Reference Files
494 
495- `references/liquidity_reference.md` — Detailed formulas, extended code templates, metric interpretation guides, and academic references for all liquidity measures
496 
497Read the reference file when you need exact formulas, edge case handling, or deeper background on liquidity metrics.
498 

Discussion