PluginBench
Skill
Pass
Audit score 90

finlab

koreal6803/finlab-ai

Comprehensive quantitative trading package for global stock markets with backtesting, factor analysis, and strategy development.

What is finlab?

FinLab is a Python-based quantitative trading framework supporting multiple markets (TW, US, KR, JP, HK) for both equities and ETFs. Use it when developing trading strategies, backtesting portfolios, analyzing factors, or working with stock market data across different regions.

  • Fetch and manipulate market data (price, volume, fundamentals, valuations) across multiple global markets
  • Develop trading strategies using condition-based stock selection and factor ranking
  • Backtest strategies with configurable parameters (fees, taxes, resampling frequency)
  • Analyze factor performance and construct universes (S&P 500, NASDAQ 100, dollar-volume-top-N)
  • Generate detailed backtest reports with equity curves, monthly returns, drawdowns, and trade lists
  • Support US-market specifics including filing-date-aligned quarterly fundamentals and ETF rotation strategies

How to install finlab

npx skills add https://github.com/koreal6803/finlab-ai --skill finlab
Prerequisites
  • Python 3.10 or higher
  • uv package manager (https://docs.astral.sh/uv/)
  • FinLab account (free tier: 500 MB/day; VIP: 5000 MB/day)
  • Browser login via finlab.login() or environment variables for headless/cron environments
Claude Code
Cursor
Windsurf
Cline

How to use finlab

  1. 1.Install uv package manager and verify with `uv --version`
  2. 2.Install FinLab via `uv pip install "finlab>=2.0.0"` or use `uv run --with "finlab" python3 script.py` for zero-setup
  3. 3.Log in with `python -m finlab login` (opens browser) or set FINLAB_REFRESH_TOKEN / FINLAB_SESSION_ID / FINLAB_API_KEY environment variables for headless machines
  4. 4.Set your market once per session with `data.set_market('us')` for US equities, `data.set_market('us_fund')` for US ETFs, or other codes (TW, KR, JP, HK)
  5. 5.Fetch data using `data.get("<TABLE>:<COLUMN>")` (e.g., `close = data.get("price:收盤價")`) and technical indicators via `data.indicator("RSI", timeperiod=14)`
  6. 6.Define trading conditions by combining boolean expressions (e.g., `cond = close.rise(10) & pb.rank(axis=1, pct=True) < 0.3`)
  7. 7.Select stocks using `.is_smallest()`, `.is_largest()`, or other ranking methods
  8. 8.Backtest with `sim(position, resample="M", upload=False)` and inspect results via `report.metrics` or `report.get_stats()`

Use cases

Good for
  • Build and backtest a momentum strategy combining price trends, volume filters, and valuation metrics
  • Rotate between sector ETFs or leveraged funds based on technical or fundamental signals
  • Screen stocks using multi-factor conditions (P/B ratio, ROE, revenue growth) and rank by lowest valuation
  • Analyze historical performance of a factor (e.g., dividend yield) across different market conditions
  • Construct a universe of top dollar-volume stocks and backtest mean-reversion strategies
Who it's for
  • Quantitative traders and strategy developers
  • Financial analysts building systematic trading systems
  • Researchers backtesting factor-based investment ideas
  • Portfolio managers evaluating sector rotation or ETF strategies

finlab FAQ

What markets does FinLab support?

FinLab supports Taiwan (TW, default), US (equities and ETFs/funds), South Korea (KR), Japan (JP), Hong Kong (HK), Taiwan emerging (rotc), and Taiwan convertible bonds (tw_cb). Set the active market once per session with `data.set_market(<code>)`.

Do I need to install FinLab locally?

No. You can run code directly in FinLab Studio (https://studio.finlab.finance), a hosted Jupyter environment with FinLab preinstalled and your account already logged in. Alternatively, use `uv run --with "finlab" python3 script.py` for zero-setup local execution.

How do I log in on a headless machine or in cron?

On a machine with a browser, run `python -m finlab token --env` and copy the printed environment variables (FINLAB_REFRESH_TOKEN, FINLAB_SESSION_ID, FINLAB_API_KEY) to your headless machine. Do not use the deprecated FINLAB_API_TOKEN.

What is the difference between US and US_FUND markets?

US is for single-name equities (stocks, S&P 500, NASDAQ 100); US_FUND is for ETFs and mutual funds. Use `data.set_market('us')` for stocks and `data.set_market('us_fund')` for ETF/fund backtesting with USFundMarket defaults.

How do I avoid lookahead bias in US backtests?

Use filing-date-aligned quarterly fundamentals (no `.shift()` workaround needed), avoid live-only tables (analyst consensus, DCF, ratios), construct universes with rolling-window filters, and exclude survivorship bias. See us-market.md for the full checklist.

Full instructions (SKILL.md)

Source of truth, from koreal6803/finlab-ai.


name: finlab description: Comprehensive guide for FinLab quantitative trading package across global stock markets (TW, US, KR, JP, HK; both single-name equities and ETFs/funds). Use when working with trading strategies, backtesting, stock data, FinLabDataFrame, factor analysis, stock selection, or when the user mentions FinLab, trading, quant trading, US equity, S&P 500 / NASDAQ 100, SPY / QQQ, sector or leveraged ETFs, ETF rotation, 美股, or stock market analysis. Includes data access, strategy development, backtesting workflows, best practices, and US-market specifics (data availability map, filing-date-aligned quarterly fundamentals, US universe construction, USMarket vs. USFundMarket defaults, and ETF backtesting). compatibility: Requires Python 3.10+ and uv package manager (https://docs.astral.sh/uv/)

FinLab Quantitative Trading Package

Prerequisites

Before running any FinLab code, verify these in order:

  1. uv is installed (Python package manager):

    uv --version
    

    If uv is not installed, tell the user to install it.

    After installing, ensure uv is on PATH:

    source $HOME/.local/bin/env 2>/dev/null  # Add uv to current shell
    
  2. FinLab is installed via uv (requires >= 2.0.0):

    uv python install 3.12  # Ensure Python is available (skip if already installed)
    uv pip install --system "finlab>=2.0.0" 2>/dev/null || uv pip install "finlab>=2.0.0"
    

    Or use uv run for zero-setup execution (recommended for one-off scripts):

    uv run --with "finlab" python3 script.py
    

    uv run --with auto-creates a temporary environment with dependencies — no venv management needed.

    Prefer zero-install? Run notebooks directly in FinLab Studio — a hosted Jupyter environment with finlab preinstalled and your account already logged in.

  3. Logged in to FinLab (required - data access fails without it):

    Desktop (has a browser): log in once, then just import:

    python -m finlab login   # opens the FinLab (Firebase) browser login; credentials are cached locally
    
    import finlab            # cached credentials are picked up automatically
    finlab.login()           # optional: reuses cached credentials, or starts the browser login if none
    

    finlab.login() opens a FinLab browser login (Firebase auth). Without a TTY it prints a login URL that can be opened on any device.

    Headless / cron / Docker: on a machine with a browser, log in and run python -m finlab token --env, then set the printed FINLAB_REFRESH_TOKEN, FINLAB_SESSION_ID and FINLAB_API_KEY environment variables on the headless machine.

    Google Colab: run finlab.login() in a cell.

    Do not use FINLAB_API_TOKEN or finlab.login('<api_token>') — the legacy API-token login is deprecated (python -m finlab migrate shows the migration guide).

Language

Respond in the user's language. If user writes in Chinese, respond in Chinese. If in English, respond in English.

Market Support

FinLab supports TW (default), US, KR, JP, HK, plus Taiwan emerging (rotc) and Taiwan convertible bonds (tw_cb). Pick the market once per session with data.set_market(<code>); generic dataset names like price:收盤價 or monthly_revenue:當月營收 resolve to the active market's tables, so strategy code is written the same way across markets. data.set_market('rotc') (v2.0.9) enables 興櫃 (TW emerging) — use it when you need pre-listing price action or revenue factors that don't exist in the main TSE/OTC catalog.

The rest of this file plus dataframe-reference.md, backtesting-reference.md, best-practices.md, factor-analysis-reference.md, and machine-learning-reference.md are market-agnostic — the APIs behave the same across markets.

For US-market work — whether single-name equities (data.set_market('us')) or ETFs/funds (data.set_market('us_fund')) — read us-market.md first. Queries that should trigger it include: US equity, S&P 500, NASDAQ 100, 美股, SPY / QQQ, sector SPDRs, leveraged / inverse ETFs, ETF rotation, us_price:*, us_fund_price:*, data.us_universe(...), or us_income_statement:* / us_cash_flow:* / us_balance_sheet:*. It documents:

  • Which US data tables are safe for backtesting versus current-snapshot-only (analyst consensus, ratios, DCF are live-only — do not use them historically)
  • Filing-date-aligned quarterly fundamentals (key_date == filing_date) — no .shift() workaround needed
  • Report API names on US (creturn / daily_creturn / get_stats(); no get_equity())
  • US backtest defaults for both markets: USMarket (fee_ratio=0, tax_ratio=0, trade_at_price='close') and USFundMarket for ETF/fund backtests
  • How data.set_market(...) is the session-scope switch (there is no market= kwarg on data.get())
  • Dollar-volume-top-N universe construction (works back to 2016), S&P 500 / NASDAQ 100 membership via data.us_universe(index='S&P 500' | 'NASDAQ 100') with its 2022-11 history-start caveat, quality gates, and sector-exclusion rationale
  • Lookahead-bias checklist specific to US data (rolling-window universe filters, survivorship avoidance)
  • ETF / sector-rotation backtesting via USFundMarket and us_fund_price:*

Other-market queries can skip that file.

Account Tiers & Usage

Tiers

TierDaily Limit
Free500 MB
VIP5000 MB

Check the current plan and quota with python -m finlab status.

Usage Reset

  • Resets daily at 8:00 AM UTC+8
  • When limit exceeded, user must wait for reset or upgrade to VIP at finlab.finance

Quick Start Example

from finlab import data
from finlab.backtest import sim

# 1. Fetch data
close = data.get("price:收盤價")
vol = data.get("price:成交股數")
pb = data.get("price_earning_ratio:股價淨值比")

# 2. Create conditions
cond1 = close.rise(10)  # Rising last 10 days
cond2 = vol.average(20) > 1000*1000  # High liquidity
cond3 = pb.rank(axis=1, pct=True) < 0.3  # Low P/B ratio

# 3. Combine conditions and select stocks
position = cond1 & cond2 & cond3
position = pb[position].is_smallest(10)  # Top 10 lowest P/B

# 4. Backtest
report = sim(position, resample="M", upload=False)

# 5. Print metrics - Two equivalent ways:

# Option A: Using metrics object
print(report.metrics.annual_return())
print(report.metrics.sharpe_ratio())
print(report.metrics.max_drawdown())

# Option B: Using get_stats() dictionary (different key names!)
stats = report.get_stats()
print(f"CAGR: {stats['cagr']:.2%}")
print(f"Sharpe: {stats['monthly_sharpe']:.2f}")
print(f"MDD: {stats['max_drawdown']:.2%}")

# Benchmark stats (finlab >= 2.0.17): same keys via report.get_benchmark_stats()

# 6. Write the FinLab-generated HTML report (REQUIRED — do not hand-roll your own HTML)
report.to_html("report.html")
print("Open report.html to inspect equity curve, monthly returns, drawdown, and trade list.")

Core Workflow: 5-Step Strategy Development

Step 1: Fetch Data

Use data.get("<TABLE>:<COLUMN>") to retrieve data:

from finlab import data

# Price data
close = data.get("price:收盤價")
volume = data.get("price:成交股數")

# Financial statements
roe = data.get("fundamental_features:ROE稅後")
revenue = data.get("monthly_revenue:當月營收")

# Valuation
pe = data.get("price_earning_ratio:本益比")
pb = data.get("price_earning_ratio:股價淨值比")

# Institutional trading
foreign_buy = data.get("institutional_investors_trading_summary:外陸資買賣超股數(不含外資自營商)")

# Technical indicators
rsi = data.indicator("RSI", timeperiod=14)
macd, macd_signal, macd_hist = data.indicator("MACD", fastperiod=12, slowperiod=26, signalperiod=9)

Filter by market/category using data.universe():

# Limit to specific industry
with data.universe(market='TSE_OTC', category=['水泥工業']):
    price = data.get('price:收盤價')

# Set globally
data.set_universe(market='TSE_OTC', category='半導體')

Use data.search('keyword', market='<market>') to discover available datasets. Supported markets: tw, us, kr, jp, hk. Use keywords in the dataset's native language (e.g. data.search('營收', market='tw'), data.search('revenue', market='us')).

Step 2: Create Factors & Conditions

Use FinLabDataFrame methods to create boolean conditions:

# Trend
rising = close.rise(10)  # Rising vs 10 days ago
sustained_rise = rising.sustain(3)  # Rising for 3 consecutive days

# Moving averages
sma60 = close.average(60)
above_sma = close > sma60

# Ranking
top_market_value = data.get('etl:market_value').is_largest(50)
low_pe = pe.rank(axis=1, pct=True) < 0.2  # Bottom 20% by P/E

# Industry ranking
industry_top = roe.industry_rank() > 0.8  # Top 20% within industry

See dataframe-reference.md for all FinLabDataFrame methods.

Step 3: Construct Position DataFrame

Combine conditions with & (AND), | (OR), ~ (NOT):

# Simple position: hold stocks meeting all conditions
position = cond1 & cond2 & cond3

# Limit number of stocks
position = factor[condition].is_smallest(10)  # Hold top 10

# Entry/exit signals with hold_until
entries = close > close.average(20)
exits = close < close.average(60)
position = entries.hold_until(exits, nstocks_limit=10, rank=-pb)

Important: Position DataFrame should have:

  • Index: DatetimeIndex (dates)
  • Columns: Stock IDs (e.g., '2330', '1101')
  • Values: Boolean (True = hold) or numeric (position size)

Step 4: Backtest

from finlab.backtest import sim

# Basic backtest
report = sim(position, resample="M")

# With risk management
report = sim(
    position,
    resample="M",
    stop_loss=0.08,
    take_profit=0.15,
    trail_stop=0.05,
    position_limit=1/3,
    fee_ratio=1.425/1000/3,
    tax_ratio=3/1000,
    trade_at_price='open',
    upload=False
)

# Extract metrics - Two ways:
# Option A: Using metrics object
print(f"Annual Return: {report.metrics.annual_return():.2%}")
print(f"Sharpe Ratio: {report.metrics.sharpe_ratio():.2f}")
print(f"Max Drawdown: {report.metrics.max_drawdown():.2%}")

# Option B: Using get_stats() dictionary (note: different key names!)
stats = report.get_stats()
print(f"CAGR: {stats['cagr']:.2%}")           # 'cagr' not 'annual_return'
print(f"Sharpe: {stats['monthly_sharpe']:.2f}") # 'monthly_sharpe' not 'sharpe_ratio'
print(f"MDD: {stats['max_drawdown']:.2%}")     # same name

# Benchmark comparison (finlab >= 2.0.17): same ffn keys, computed on the
# market benchmark over the backtest period — no need to recompute from
# market.get_benchmark()
bench = report.get_benchmark_stats()
print(f"Benchmark CAGR: {bench['cagr']:.2%} | MDD: {bench['max_drawdown']:.2%}")

See backtesting-reference.md for complete sim() API.

Step 4.5: Deliver the FinLab HTML Report (REQUIRED)

Follow each backtest the user will review with one HTML file — the one FinLab generates:

report = sim(position, resample="M", upload=False)
report.to_html("report.html")            # the FinLab-generated file is the deliverable

The canonical deliverable is the file generated by report.to_html() — do not hand-roll a separate report (custom HTML pages, Plotly summaries, dashboards, markdown files) unless the user explicitly asks. To summarize results, print a short terminal summary and point to the FinLab report. Exception: in batch runs (parameter sweeps, screening many variants), skip per-run HTML and write it only for the final strategy the user will review.

Pick a descriptive filename when running more than one strategy in the same session (e.g. momentum_top10.html, value_lowpb.html) so the user can compare without overwriting. After writing, tell the user the path so they can open it. Use report.to_terminal() only as a supplement for non-GUI terminals; it does not replace the HTML.

See the "report.to_html() — the canonical deliverable" section of backtesting-reference.md for details on what the file contains.

Step 5: Execute Orders (Optional)

Convert backtest results to live trading:

from finlab.online.order_executor import Position, OrderExecutor
from finlab.online.sinopac_account import SinopacAccount

# 1. Convert report to position
position = Position.from_report(report, fund=1000000)

# 2. Connect broker account
acc = SinopacAccount()

# 3. Create executor and preview orders
executor = OrderExecutor(position, account=acc)
executor.create_orders(view_only=True)  # Preview first

# 4. Execute orders (when ready)
executor.create_orders()

See trading-reference.md for complete broker setup and OrderExecutor API.

Reference Files

FileContent
backtesting-reference.mdsim() 參數、stop-loss、rebalancing
trading-reference.md券商設定、OrderExecutor、Position
factor-examples.md60+ 策略範例
dataframe-reference.mdFinLabDataFrame 方法
factor-analysis-reference.mdIC、Shapley、因子分析
best-practices.md常見錯誤、lookahead bias
machine-learning-reference.mdML 特徵工程
us-market.mdUS market specifics: data map, quarterly alignment, defaults, universe construction

What's New (since v1.5.8)

Short version pointers for features added in recent releases. Each reference file tags the exact API with (vX.Y.Z).

v2.0.15 (2026-07-18)

  • df.sector(by=...): sector accessor now accepts a custom classification — dict / pd.Series (stock_id → group) or a time-varying pd.DataFrame; unlisted stocks are excluded. Works with all sector.* methods — see dataframe-reference.md
  • df.sector.map(mapping): broadcast group-level scalars (e.g. sector weights) to full DataFrame shape for factor composition — see dataframe-reference.md
  • df.weight.by_group(weights, by, default): allocate capital across sectors/groups — normalize holdings so each group's total equals its share; under-allocation stays in cash — see dataframe-reference.md

v2.0.12 (2026-06-01)

  • sim() / hold_until(): trail_stop_activation — require a minimum unrealized gain before trail_stop arms. See backtesting-reference.md and dataframe-reference.md
  • report.to_html(path, title=...): standalone HTML now sets browser-tab title + FinLab favicon; pass title to disambiguate multi-strategy report folders — see backtesting-reference.md
  • Dashboard settings modal: language / light-dark theme / candle color scheme (default, east-red, west-green) consolidated into one panel

v2.0.9 (2026-05-27)

  • data.set_market("rotc"): 興櫃 is now a first-class market code; price:收盤價 / monthly_revenue:* / etc. resolve to the rotc_ catalog and sim() uses ROTCMarket defaults
  • data.search(market="rotc"): scoped to the emerging-market catalog only

v2.0.1 (2026-04-26)

  • python -m finlab cloud (CLI): deploy strategies to the finlab-auto-update Cloud Functions runtime with daily Asia/Taipei scheduling — deploy, get, list, run, logs, schedule set/delete, delete, status. See trading-reference.md
  • sim() peak RSS ~800 MB lower on full-market monthly strategies (was ~2.0–2.2 GiB → ~1.29 GiB); enables s-tier cloud workers that previously OOM'd

v2.0.0 (2026-04-04) — major release

  • finlab.exceptions: structured error hierarchy (FinlabError, DataError, BacktestError, ...) — see backtesting-reference.md
  • data.get(lazy=True) / data.gets(..., lazy=True): batch fetch + deferred compute; data.override() / DataContext for scoped global state
  • df.cs / df.sector / df.weight accessors; rolling().std/var/skew/kurt/median — see dataframe-reference.md
  • PositionStreamMixin for realtime position streaming — see trading-reference.md
  • from finlab import FinlabDataFrame top-level export
  • backtest.sim() refactored into 5 testable stages; eval() removed from optimize.combinations

v1.5.13 (2026-03-22)

  • universe(index=...) / us_universe(index=...): filter US stocks by S&P 500 / NASDAQ 100
  • New market code TW_CB (TW convertible bonds)

v1.5.11 (2026-03-11)

  • data.get_role() / data.is_vip(): query user quota tier
  • Report migration to canonical Firestore flow (transparent to users)

v1.5.9

  • finlab.schemas: typed PositionEntry, OrderEntry, PortfolioData contracts
  • OrderExecutor.generate_orders(as_entries, quantity_type) and generate_order_entries()
  • PortfolioSyncManager.get_data_typed() / set_data_typed()
  • data.get() 80% quota usage warning
  • sim() uses market-specific default fee_ratio / tax_ratio (no longer hardcoded TW values)

v1.5.8 (baseline)

  • verify_strategy(): automated lookahead-bias detector
  • report.to_terminal(): ASCII report for non-Jupyter runs
  • Overall strategy execution 3.4x faster

Financial Data Alignment

Never use reindex() or reindex_like(), including on final positions. FinLabDataFrame automatically reshapes dates and stock columns during arithmetic, comparisons and boolean operations: sales_to_price = eps_sales / close. Do not manually align or bypass this behavior with pandas/NumPy. Calculate quarterly shifts and rolling windows before combining with daily data; set rebalance schedules with sim(position, resample=...). Use index_str_to_date() only to inspect dates. On errors, inspect types and consult dataframe-reference.md.

Prevent Lookahead Bias

Critical: Avoid using future data to make past decisions:

# ✅ GOOD: Use shift(1) to get previous value
prev_close = close.shift(1)

# ❌ BAD: Don't use iloc[-2] (can cause lookahead)
# prev_close = close.iloc[-2]  # WRONG

# ✅ GOOD: Leave index as-is even with strings like "2025Q1"
# FinLabDataFrame operators align dates and stock columns automatically

# ❌ BAD: Don't manually assign to df.index
# df.index = new_index  # FORBIDDEN

See best-practices.md for more anti-patterns.

Performance Defaults

Pass lazy=True by default; drop to eager pandas only when debugging. data.get(..., lazy=True) and data.gets(..., lazy=True) (v2.0.0) return lazy FinlabDataFrames that defer the compute graph until a terminal call materializes it — chained ops avoid redundant passes (single-CPU). Omit lazy=True when you need to print/inspect intermediate values interactively.

# ✅ Default: fetch lazy directly
price, volume, pe = data.gets(
    'price:收盤價', 'price:成交股數', 'price_earning_ratio:本益比',
    lazy=True,
)

# ✅ Debug: eager pandas for row-level inspection
close = data.get('price:收盤價')
print(close.loc['2024-01-15', '2330'])

Feedback

Direct users to open an issue on GitHub: https://github.com/koreal6803/finlab-ai/issues

Notes

  • Some data columns use Chinese names — this is expected, use them as-is in data.get() calls
  • Data frequency varies: daily (price), monthly (revenue), quarterly (financial statements)
  • sim() defaults to upload=None: local runs do not upload; FinLab Studio / cloud schedules (which set FINLAB_STRATEGY_NAME / FINLAB_FORCED_STRATEGY_NAME) do. Pass upload=False to guarantee no upload, upload=True to force one

Related skills

More from koreal6803/finlab-ai and the wider catalog.

40404-page-generator logo

404-page-generator

kostja94/marketing-skills

When the user wants to create, optimize, or audit 404 error pages. Also use when the user mentions "404 page," "404 error," "error page," "page not found," "broken link page," "404 design," "custom 404," "404 redirect," "404 page UX," or "404 recovery." For sitewide page planning, use website-structure.

768 installsAudited
ABabout-page-generator logo

about-page-generator

kostja94/marketing-skills

When the user wants to create, optimize, or audit About page content. Also use when the user mentions "about page," "about us," "company story," "our team," "about section," "company overview," "brand story," "team page," or "who we are." For sitewide page planning, use website-structure.

787 installsAudited
AFaffiliate-marketing logo

affiliate-marketing

kostja94/marketing-skills

When the user wants to plan, implement, or optimize affiliate marketing strategy. Also use when the user mentions "affiliate marketing," "affiliate program strategy," "CPS model," "affiliate recruitment," "commission structure," "affiliate partners," "affiliate network," "affiliate tracking," "affiliate commission," or "partner marketing." For affiliate page, use affiliate-page-generator.

900 installsAudited
AFaffiliate-page-generator logo

affiliate-page-generator

kostja94/marketing-skills

When the user wants to create, optimize, or audit affiliate program page content. Also use when the user mentions "affiliate program," "affiliate page," "partner program," "referral program page," "affiliate landing," "partner landing," "commission page," or "affiliate signup." For affiliate program strategy, use affiliate-marketing.

803 installsAudited
AIai-traffic-tracking logo

ai-traffic-tracking

kostja94/marketing-skills

When the user wants to track AI search traffic in GA4 or GSC. Also use when the user mentions "AI traffic," "ChatGPT referral," "Perplexity traffic," "AI Overviews," "GA4 AI sources," "AI search analytics," "track AI referrals," "AI search traffic," "Claude traffic," or "how to track AI traffic." For AI SEO strategy, use generative-engine-optimization.

790 installs
ALalternatives-page-generator logo

alternatives-page-generator

kostja94/marketing-skills

When the user wants to create, optimize, or audit alternatives or comparison content (page or blog article). Also use when the user mentions "alternatives page," "alternatives listicle," "X alternatives," "competitor comparison," "vs page," "compare page," "best alternatives to X," "switch from X," "competitor brand traffic," "brand keyword ads," or "intercept competitor search." For competitor research, use competitor-research.

769 installsAudited