vault_report.post

Documentation for eth_defi.vault_report.post Python module.

Assemble the monthly vault report blog post HTML.

The post body is Ghost-compatible HTML:

  • Tables, the podcast list and the table of contents placeholder are wrapped in <!--kg-card-begin: html--> comments, which Ghost imports as raw HTML cards, keeping their markup intact

  • Charts are Ghost image cards

  • The post has no editor callouts. The parts the editor writes (the month’s highlight, the report content updates, community news and commentary, the feature image) are listed in the editor workflow of README-vault-report.md.

Evergreen sections (About the report, Next steps) are copied from the previous report post, so edits made in Ghost carry over month to month. The Partners section is rendered from PARTNERS, so partners are named by organisation, not by X handle.

The section order, headings, introductions and notes follow the Writing rules in README-blog-post-outline.md: keep them when changing the templates below, and update the rules when a decision changes.

The templates are data, not code: BEST_SECTIONS defines each ranked vault group once, and SECTION_TEMPLATES the order and text of every data section. build_post_html() walks the templates and includes a section only when its table or one of its charts was generated, so a missing input (no prices, a failed benchmark download, an empty group) silently removes a section instead of leaving an empty heading in the post.

build_post_html() is called twice per run by eth_defi.vault_report.report: once with bundle-relative image paths for post.html and the local preview, and once with the uploaded Ghost image URLs for the draft. It is a pure function of PostContext, so both renderings have the same text.

Module Attributes

REPORT_SLUG_PREFIX

Slug prefix of the monthly report posts.

VAULTS_URL

Vault pages on the website, linked from the section introductions

GLOSSARY_URL

Glossary on the website, linked from the section introductions

TABLE_OF_CONTENTS

Ghost theme placeholder that the theme fills with a table of contents of the post headings, as in the earlier reports

PARTNERS

Report partners as (organisation name, link), thanked in the Partners section.

DEFAULT_EVERGREEN_SECTIONS

Evergreen sections used when there is no previous post to copy them from, by heading id.

NEW_VAULTS_CHART

Chart key of the new vaults performance chart in PostContext.charts

BEST_SECTIONS

Best-performing vault groups, each with a table and a performance chart; SECTION_TEMPLATES sets their display order.

BEST_TEMPLATES

Post sections of the best-performing vault groups, by key

SECTION_TEMPLATES

Data sections in display order.

Functions

build_post_html(context)

Render the report post body.

build_preview_html(title, post_html[, ...])

Wrap the post body into a standalone HTML page for local preview.

extract_section_html(post_html, heading_id)

Extract a <h2> section, up to the next <h2>, from post HTML.

make_month_label(data_end_at)

Create a human-readable report month.

make_report_slug(data_end_at)

Create the post URL slug.

make_report_title(month_label)

Create the post title.

read_changelog_entries(changelog_path, since)

Read new integration entries from the changelog after a date.

render_partners_section([partners])

Render the Partners section, thanking each partner by name with a link.

Classes

BestSection

One ranked vault group of the post: its table, performance chart and section.

PostContext

Everything needed to render the post body.

SectionTemplate

Static content of one data section of the post.

REPORT_SLUG_PREFIX = 'the-best-performing-stablecoin-vaults'

Slug prefix of the monthly report posts. The Content API finds the previous report by this prefix, so changing it breaks the month-to-month continuity of the evergreen sections and the link to the previous report.

VAULTS_URL = 'https://tradingstrategy.ai/vaults'

Vault pages on the website, linked from the section introductions

GLOSSARY_URL = 'https://tradingstrategy.ai/glossary'

Glossary on the website, linked from the section introductions

TABLE_OF_CONTENTS = '<!--kg-card-begin: html-->\n<div id="table-of-contents"></div>\n<!--kg-card-end: html-->'

Ghost theme placeholder that the theme fills with a table of contents of the post headings, as in the earlier reports

PARTNERS = (('Arbitrum DAO', 'https://x.com/@arbitrumdao_gov'), ('Envio', 'https://x.com/@envio_indexer'), ('dRPC', 'https://x.com/@dRPCorg'), ('IPOR', 'https://x.com/@ipor_io'), ('Goldsky', 'https://x.com/@goldskyio'), ('IceCreamSwap', 'https://x.com/@icecream_swap'), ('GRVT', 'https://x.com/@grvt_io'), ('Orderly Network', 'https://x.com/@OrderlyNetwork'), ('Lagoon Finance', 'https://x.com/@lagoon_finance'), ('Webacy', 'https://x.com/mywebacy'), ('Quants Spaces', 'https://quants.space/'))

Report partners as (organisation name, link), thanked in the Partners section. Maintained here rather than copied from the previous post, so the names stay organisation names, not X handles; edit this list when the partners change.

render_partners_section(partners=(('Arbitrum DAO', 'https://x.com/@arbitrumdao_gov'), ('Envio', 'https://x.com/@envio_indexer'), ('dRPC', 'https://x.com/@dRPCorg'), ('IPOR', 'https://x.com/@ipor_io'), ('Goldsky', 'https://x.com/@goldskyio'), ('IceCreamSwap', 'https://x.com/@icecream_swap'), ('GRVT', 'https://x.com/@grvt_io'), ('Orderly Network', 'https://x.com/@OrderlyNetwork'), ('Lagoon Finance', 'https://x.com/@lagoon_finance'), ('Webacy', 'https://x.com/mywebacy'), ('Quants Spaces', 'https://quants.space/')))

Render the Partners section, thanking each partner by name with a link.

Rendered from PARTNERS every month rather than copied from the previous post like the other evergreen sections, because earlier posts named partners by X handle; the writing rules name them by organisation.

Parameters

partners (tuple[tuple[str, str], ...]) – (organisation name, link) pairs.

Returns

Section HTML.

Return type

str

DEFAULT_EVERGREEN_SECTIONS = {'about-the-report': '<h2 id="about-the-report">About the report</h2><p>In this post, we examine the performance of <a href="https://tradingstrategy.ai/glossary/decentralised-finance" rel="noreferrer">DeFi</a> vaults. Vaults can be considered "self-custodial investment strategies" in traditional finance: smart contracts that let users deposit funds from their cryptocurrency wallets and execute a predefined strategy with investors\' money.</p>', 'next-steps': '<h2 id="next-steps">Next steps</h2><p>Visit our <a href="https://tradingstrategy.ai/vaults">vaults page</a> for real-time dashboards. If you have any questions, <a href="https://tradingstrategy.ai/community">contact us on Discord, email or Twitter</a>.</p>'}

Evergreen sections used when there is no previous post to copy them from, by heading id.

Normally build_post_html() copies these sections from the previous report, so the editor’s wording in Ghost carries over. The defaults cover a run without the Ghost Content API (local previews, tests) or a previous post that lacks the heading; the editor can then fix the text in Ghost once and later reports inherit it.

class SectionTemplate

Bases: object

Static content of one data section of the post.

build_post_html() renders a section as its heading, introduction, criteria notes, charts and table, in that order. A section is included when its table or any of its charts exists. A grouping heading, see group, is included when any of its subsections is, so the post never shows a heading with nothing under it.

The writing rules give every section an introduction paragraph that links to the matching tradingstrategy.ai page or glossary entry, except The best-performing vaults, which goes straight to its subsections; test_every_section_introduces_itself_with_website_links enforces it.

key: str

Section key: the table key in PostContext.tables and the section notes key

heading_id: str

Heading anchor id

heading: str

Heading text

intro: str

Introduction paragraph HTML, or empty

charts: tuple[tuple[str, str], ...]

(chart key in PostContext.charts, alt text) pairs shown above the table

level: int

Heading level, 2 for sections and 3 for subsections

group: bool

A heading that groups the subsections after it: included when any of them is

__init__(key, heading_id, heading, intro='', charts=(), level=2, group=False)
Parameters
Return type

None

class BestSection

Bases: object

One ranked vault group of the post: its table, performance chart and section.

The single definition of a best-performing section. The tables, the chart selection, the chart panel, the criteria notes and the post section are all derived from it, see BEST_SECTIONS. Keeping them in one place means a new vault group, or a changed ranking metric, cannot leave the table, the chart and the investability check candidates (eth_defi.vault_report.report.collect_top_lists()) out of step.

Tables rank by the one-month return like the website’s live ranking, while the charts rank by the steadier three-month return, see chart_metric, so the two lists of a section may differ.

key: str

Section key, e.g. lending: the table and criteria notes key

group: str

Vault group, see eth_defi.vault_report.sections.classify_vault()

heading: str

Section heading

heading_id: str

Heading anchor id

subject: str

The ranked vaults in chart titles and alt texts, e.g. the best-performing lending vaults

benchmark: str

What the chart compares the vaults against

metric: str

Table ranking column

measure: Literal['equity', 'sharpe']

Chart lines: equity curves, or the rolling sharpe ratio ranked by metric

ranked_by: str

How the chart subtitle describes the ranking, e.g. funds by 3M return

Live page on the website, without https://, for the chart footer

intro: str

Introduction paragraph HTML, with links to the website

property chart_key: str

Key of the performance chart in PostContext.charts.

property chart_metric: str

Column that picks and ranks the chart vaults: the steadier three-month return, or the Sharpe ratio.

property template: eth_defi.vault_report.post.SectionTemplate

The post section of this group.

__init__(key, group, heading, heading_id, subject, benchmark='their benchmarks', metric='one_month_cagr_best', measure='equity', ranked_by='by 3M return', link='tradingstrategy.ai/vaults', intro='')
Parameters
  • key (str) –

  • group (str) –

  • heading (str) –

  • heading_id (str) –

  • subject (str) –

  • benchmark (str) –

  • metric (str) –

  • measure (Literal['equity', 'sharpe']) –

  • ranked_by (str) –

  • link (str) –

  • intro (str) –

Return type

None

NEW_VAULTS_CHART = 'new_performance'

Chart key of the new vaults performance chart in PostContext.charts

BEST_SECTIONS = (BestSection(key='lending', group='lending', heading='Lending vaults', heading_id='best-performing-lending-vaults', subject='the best-performing lending vaults', benchmark='their benchmarks', metric='one_month_cagr_best', measure='equity', ranked_by='by 3M return', link='tradingstrategy.ai/vaults', intro='<p>Vaults that supply stablecoins to <a href="https://tradingstrategy.ai/glossary/lending-protocol">lending protocols</a> such as Morpho and Euler, and earn the interest borrowers pay. See all <a href="https://tradingstrategy.ai/vaults/strategies/lending">lending vaults</a>.</p>'), BestSection(key='rwa', group='rwa', heading='Real-world asset (RWA) vaults', heading_id='best-performing-rwa-vaults', subject='the best-performing RWA vaults', benchmark='their benchmarks', metric='one_month_cagr_best', measure='equity', ranked_by='by 3M return', link='tradingstrategy.ai/vaults', intro='<p>Vaults that invest in, lend against or finance <a href="https://tradingstrategy.ai/glossary/rwa">real-world assets</a>, such as private credit, trade finance and royalties. See all <a href="https://tradingstrategy.ai/vaults/strategies/rwa">RWA vaults</a>.</p><p>Real-world asset vaults are a new category and may still contain misclassifications, as we are refining the different RWA categories.</p>'), BestSection(key='perp_dex', group='perp_dex', heading='Perpetual futures DEX vaults by return', heading_id='best-performing-perp-dex-vaults', subject='the best-performing perp DEX vaults', benchmark='BTC and ETH', metric='one_month_cagr_best', measure='equity', ranked_by='by 3M return', link='tradingstrategy.ai/vaults', intro='<p>Vaults on <a href="https://tradingstrategy.ai/glossary/perpetual-future">perpetual futures</a> DEXes such as Hyperliquid, GRVT and Lighter, which are discretionary, directional algorithmic or market making vaults. See all <a href="https://tradingstrategy.ai/vaults/strategies/perpetual-futures">perpetual futures vaults</a>.</p>'), BestSection(key='perp_dex_sharpe', group='perp_dex', heading='Perpetual futures DEX vaults by Sharpe ratio', heading_id='best-performing-perp-dex-vaults-by-sharpe', subject='perp DEX vaults with the best Sharpe ratio', benchmark='BTC and ETH', metric='three_months_sharpe_best', measure='sharpe', ranked_by='by 3M Sharpe ratio', link='tradingstrategy.ai/vaults', intro='<p>The same vaults ranked by their three-month <a href="https://tradingstrategy.ai/glossary/sharpe">Sharpe ratio</a>, the return per unit of volatility. The ranking rewards steady returns over high but volatile ones; see <a href="https://tradingstrategy.ai/glossary/risk-adjusted-return">risk-adjusted return</a>.</p>'), BestSection(key='amm', group='amm', heading='AMM pools', heading_id='best-performing-amm-pools', subject='the best-performing AMM pools', benchmark='their benchmarks', metric='one_month_cagr_best', measure='equity', ranked_by='by 3M return', link='tradingstrategy.ai/vaults', intro='<p><a href="https://tradingstrategy.ai/glossary/amm">AMM</a> liquidity pools, such as GMX GM and GLV pools and YieldBasis pools, earn trading fees, but their returns also move with the prices of the pooled crypto assets. See all <a href="https://tradingstrategy.ai/vaults/strategies/amm">AMM vaults</a>.</p>'), BestSection(key='tokenised_funds', group='tokenised_fund', heading='Tokenised funds', heading_id='best-performing-tokenised-funds', subject='the best-performing tokenised funds', benchmark='their benchmarks', metric='one_month_cagr_best', measure='equity', ranked_by='by 3M return', link='tradingstrategy.ai/vaults/funds', intro='<p>The <a href="https://tradingstrategy.ai/glossary/tokenised-fund">tokenised funds</a> with the best returns. Their yields follow money market, treasury and credit rates, and many are open only to qualified or institutional investors. See all <a href="https://tradingstrategy.ai/vaults/funds">tokenised funds</a>.</p><p>Often there is no clear distinction between a vault and a tokenised fund. From the technical point of view, both use exactly the same smart contract technology with whitelisting. For the report, we define a project as a tokenised fund if we have found evidence that they have self-declared themselves as a tokenised fund.</p>'))

Best-performing vault groups, each with a table and a performance chart; SECTION_TEMPLATES sets their display order. Perpetual futures DEX vaults appear twice, ranked by return and by Sharpe ratio, because a raw return ranking of trading vaults favours the most volatile ones.

BEST_TEMPLATES = {'amm': SectionTemplate(key='amm', heading_id='best-performing-amm-pools', heading='AMM pools', intro='<p><a href="https://tradingstrategy.ai/glossary/amm">AMM</a> liquidity pools, such as GMX GM and GLV pools and YieldBasis pools, earn trading fees, but their returns also move with the prices of the pooled crypto assets. See all <a href="https://tradingstrategy.ai/vaults/strategies/amm">AMM vaults</a>.</p>', charts=(('amm_performance', '90-day performance of the best-performing AMM pools against their benchmarks'),), level=3, group=False), 'lending': SectionTemplate(key='lending', heading_id='best-performing-lending-vaults', heading='Lending vaults', intro='<p>Vaults that supply stablecoins to <a href="https://tradingstrategy.ai/glossary/lending-protocol">lending protocols</a> such as Morpho and Euler, and earn the interest borrowers pay. See all <a href="https://tradingstrategy.ai/vaults/strategies/lending">lending vaults</a>.</p>', charts=(('lending_performance', '90-day performance of the best-performing lending vaults against their benchmarks'),), level=3, group=False), 'perp_dex': SectionTemplate(key='perp_dex', heading_id='best-performing-perp-dex-vaults', heading='Perpetual futures DEX vaults by return', intro='<p>Vaults on <a href="https://tradingstrategy.ai/glossary/perpetual-future">perpetual futures</a> DEXes such as Hyperliquid, GRVT and Lighter, which are discretionary, directional algorithmic or market making vaults. See all <a href="https://tradingstrategy.ai/vaults/strategies/perpetual-futures">perpetual futures vaults</a>.</p>', charts=(('perp_dex_performance', '90-day performance of the best-performing perp DEX vaults against BTC and ETH'),), level=3, group=False), 'perp_dex_sharpe': SectionTemplate(key='perp_dex_sharpe', heading_id='best-performing-perp-dex-vaults-by-sharpe', heading='Perpetual futures DEX vaults by Sharpe ratio', intro='<p>The same vaults ranked by their three-month <a href="https://tradingstrategy.ai/glossary/sharpe">Sharpe ratio</a>, the return per unit of volatility. The ranking rewards steady returns over high but volatile ones; see <a href="https://tradingstrategy.ai/glossary/risk-adjusted-return">risk-adjusted return</a>.</p>', charts=(('perp_dex_sharpe_performance', '90-day performance of perp DEX vaults with the best Sharpe ratio against BTC and ETH'),), level=3, group=False), 'rwa': SectionTemplate(key='rwa', heading_id='best-performing-rwa-vaults', heading='Real-world asset (RWA) vaults', intro='<p>Vaults that invest in, lend against or finance <a href="https://tradingstrategy.ai/glossary/rwa">real-world assets</a>, such as private credit, trade finance and royalties. See all <a href="https://tradingstrategy.ai/vaults/strategies/rwa">RWA vaults</a>.</p><p>Real-world asset vaults are a new category and may still contain misclassifications, as we are refining the different RWA categories.</p>', charts=(('rwa_performance', '90-day performance of the best-performing RWA vaults against their benchmarks'),), level=3, group=False), 'tokenised_funds': SectionTemplate(key='tokenised_funds', heading_id='best-performing-tokenised-funds', heading='Tokenised funds', intro='<p>The <a href="https://tradingstrategy.ai/glossary/tokenised-fund">tokenised funds</a> with the best returns. Their yields follow money market, treasury and credit rates, and many are open only to qualified or institutional investors. See all <a href="https://tradingstrategy.ai/vaults/funds">tokenised funds</a>.</p><p>Often there is no clear distinction between a vault and a tokenised fund. From the technical point of view, both use exactly the same smart contract technology with whitelisting. For the report, we define a project as a tokenised fund if we have found evidence that they have self-declared themselves as a tokenised fund.</p>', charts=(('tokenised_funds_performance', '90-day performance of the best-performing tokenised funds against their benchmarks'),), level=3, group=False)}

Post sections of the best-performing vault groups, by key

SECTION_TEMPLATES = (SectionTemplate(key='best', heading_id='the-best-performing-vaults', heading='The best-performing vaults', intro='', charts=(), level=2, group=True), SectionTemplate(key='new', heading_id='best-performing-new-vaults', heading='New vaults', intro='<p>The best-performing vaults launched recently. Their short history makes their returns less reliable, so treat them as vaults to watch. See all <a href="https://tradingstrategy.ai/vaults/new-vaults">new vaults</a>.</p>', charts=(('new_performance', 'Performance of the best-performing new vaults since their launch against their benchmarks'),), level=3, group=False), SectionTemplate(key='lending', heading_id='best-performing-lending-vaults', heading='Lending vaults', intro='<p>Vaults that supply stablecoins to <a href="https://tradingstrategy.ai/glossary/lending-protocol">lending protocols</a> such as Morpho and Euler, and earn the interest borrowers pay. See all <a href="https://tradingstrategy.ai/vaults/strategies/lending">lending vaults</a>.</p>', charts=(('lending_performance', '90-day performance of the best-performing lending vaults against their benchmarks'),), level=3, group=False), SectionTemplate(key='rwa', heading_id='best-performing-rwa-vaults', heading='Real-world asset (RWA) vaults', intro='<p>Vaults that invest in, lend against or finance <a href="https://tradingstrategy.ai/glossary/rwa">real-world assets</a>, such as private credit, trade finance and royalties. See all <a href="https://tradingstrategy.ai/vaults/strategies/rwa">RWA vaults</a>.</p><p>Real-world asset vaults are a new category and may still contain misclassifications, as we are refining the different RWA categories.</p>', charts=(('rwa_performance', '90-day performance of the best-performing RWA vaults against their benchmarks'),), level=3, group=False), SectionTemplate(key='perp_dex', heading_id='best-performing-perp-dex-vaults', heading='Perpetual futures DEX vaults by return', intro='<p>Vaults on <a href="https://tradingstrategy.ai/glossary/perpetual-future">perpetual futures</a> DEXes such as Hyperliquid, GRVT and Lighter, which are discretionary, directional algorithmic or market making vaults. See all <a href="https://tradingstrategy.ai/vaults/strategies/perpetual-futures">perpetual futures vaults</a>.</p>', charts=(('perp_dex_performance', '90-day performance of the best-performing perp DEX vaults against BTC and ETH'),), level=3, group=False), SectionTemplate(key='perp_dex_sharpe', heading_id='best-performing-perp-dex-vaults-by-sharpe', heading='Perpetual futures DEX vaults by Sharpe ratio', intro='<p>The same vaults ranked by their three-month <a href="https://tradingstrategy.ai/glossary/sharpe">Sharpe ratio</a>, the return per unit of volatility. The ranking rewards steady returns over high but volatile ones; see <a href="https://tradingstrategy.ai/glossary/risk-adjusted-return">risk-adjusted return</a>.</p>', charts=(('perp_dex_sharpe_performance', '90-day performance of perp DEX vaults with the best Sharpe ratio against BTC and ETH'),), level=3, group=False), SectionTemplate(key='amm', heading_id='best-performing-amm-pools', heading='AMM pools', intro='<p><a href="https://tradingstrategy.ai/glossary/amm">AMM</a> liquidity pools, such as GMX GM and GLV pools and YieldBasis pools, earn trading fees, but their returns also move with the prices of the pooled crypto assets. See all <a href="https://tradingstrategy.ai/vaults/strategies/amm">AMM vaults</a>.</p>', charts=(('amm_performance', '90-day performance of the best-performing AMM pools against their benchmarks'),), level=3, group=False), SectionTemplate(key='by_chain', heading_id='best-performing-vaults-on-each-chain', heading='Vaults on each chain', intro='<p>The best-performing vaults on each blockchain, for investors who stay on one chain. The <a href="https://tradingstrategy.ai/vaults/chains">blockchains page</a> lists the vaults of every chain.</p>', charts=(('by_chain_best', 'The two best-performing vaults on each chain'),), level=3, group=False), SectionTemplate(key='tokenised_funds', heading_id='best-performing-tokenised-funds', heading='Tokenised funds', intro='<p>The <a href="https://tradingstrategy.ai/glossary/tokenised-fund">tokenised funds</a> with the best returns. Their yields follow money market, treasury and credit rates, and many are open only to qualified or institutional investors. See all <a href="https://tradingstrategy.ai/vaults/funds">tokenised funds</a>.</p><p>Often there is no clear distinction between a vault and a tokenised fund. From the technical point of view, both use exactly the same smart contract technology with whitelisting. For the report, we define a project as a tokenised fund if we have found evidence that they have self-declared themselves as a tokenised fund.</p>', charts=(('tokenised_funds_performance', '90-day performance of the best-performing tokenised funds against their benchmarks'),), level=3, group=False), SectionTemplate(key='average_yields', heading_id='yield-by-chain-and-protocol', heading='Yield by chain and protocol', intro='<p>To better understand market competition between blockchains and protocols, we also compare projects by best returns, not just highest TVL.</p><p>In each chart, a small dot is a vault, and the large dot is the <a href="https://tradingstrategy.ai/glossary/total-value-locked-tvl">TVL</a>-weighted average annualised three-month return, compared with the 3-month US Treasury bill as the <a href="https://tradingstrategy.ai/glossary/risk-free-rate">risk-free rate</a>.</p>', charts=(), level=2, group=True), SectionTemplate(key='protocol_yields', heading_id='average-yield-by-protocol-high-tvl', heading='Yield by protocol, high TVL', intro='<p>The largest vault protocols by TVL, which hold most of the stablecoin deposits. See all <a href="https://tradingstrategy.ai/vaults/protocols">vault protocols</a>.</p>', charts=(('protocol_yields', 'Stablecoin vault yield of the largest protocols against the US Treasury bill'),), level=3, group=False), SectionTemplate(key='protocol_high_yields', heading_id='average-yield-by-protocol-high-yield', heading='Yield by protocol, high yield', intro='<p>The protocols paying the highest average yield. High yields usually come with higher risk, so check where the yield comes from before investing. The <a href="https://tradingstrategy.ai/vaults/yield-protocol">vault yield by protocol</a> chart compares the yields of every protocol.</p>', charts=(('protocol_high_yields', 'Stablecoin vault yield of the highest-yielding protocols against the US Treasury bill'),), level=3, group=False), SectionTemplate(key='chain_yields', heading_id='average-yield-by-blockchain', heading='Yield by blockchain', intro='<p>Which blockchains pay the most on stablecoins. Browse the vaults of each chain on the <a href="https://tradingstrategy.ai/vaults/chains">blockchains page</a>.</p>', charts=(('chain_yields', 'Stablecoin vault yield on the largest blockchains against the US Treasury bill'),), level=3, group=False), SectionTemplate(key='risk_return', heading_id='risk-and-return', heading='Risk and return', intro='<p>Higher returns usually come with higher <a href="https://tradingstrategy.ai/glossary/volatility">volatility</a>. Vaults above and to the left of the crowd offer better returns for their risk. The <a href="https://tradingstrategy.ai/vaults/yield-risk">vault yield and risk</a> chart compares the returns of all vaults with their risk ratings.</p>', charts=(('risk_return', 'Volatility risk and return of stablecoin vaults by strategy'),), level=2, group=False), SectionTemplate(key='tvl', heading_id='vaults-and-tokenised-funds-tvl', heading='Vaults and tokenised funds TVL', intro='<p>How much money stablecoin vaults and tokenised funds hold, by protocol, blockchain and fund, over the last 12 months. The <a href="https://tradingstrategy.ai/vaults/historical-tvl-stablecoin">stablecoin TVL</a> chart has the full history.</p>', charts=(), level=2, group=True), SectionTemplate(key='protocol_tvl', heading_id='stablecoin-tvl-by-defi-vault-protocol', heading='Stablecoin TVL by DeFi vault protocol', intro='<p>Where the stablecoins deposited in DeFi vaults are, by protocol, over the last 12 months. Tokenised funds are shown separately below. The <a href="https://tradingstrategy.ai/vaults/historical-tvl-protocol">live chart</a> has the full history.</p>', charts=(('protocol_tvl', 'Stablecoin TVL by DeFi vault protocol'),), level=3, group=False), SectionTemplate(key='chain_tvl', heading_id='stablecoin-tvl-by-blockchain', heading='Stablecoin TVL by blockchain', intro='<p>The same deposits by the blockchain they are on. The <a href="https://tradingstrategy.ai/vaults/historical-tvl-chain">live chart</a> has the full history.</p>', charts=(('chain_tvl', 'Stablecoin TVL by blockchain'),), level=3, group=False), SectionTemplate(key='fund_nav', heading_id='stablecoin-nav-by-tokenised-fund', heading='Stablecoin NAV by tokenised fund', intro='<p>Tokenised funds bring money market, treasury and credit funds onchain. The chart shows their net asset value (NAV) by fund over the last 12 months. See the <a href="https://tradingstrategy.ai/vaults/funds">tokenised funds page</a> for the live data.</p>', charts=(('fund_nav', 'Stablecoin NAV by tokenised fund'),), level=3, group=False), SectionTemplate(key='flows', heading_id='inflows-and-outflows', heading='Inflows and outflows', intro='<p>Where the money moved: the vaults and blockchains whose total value locked grew or shrank the most over the last 30 days. A TVL change includes deposits, redemptions and the returns the vaults earned. Browse all vaults and their current TVL on the <a href="https://tradingstrategy.ai/vaults">vaults page</a>.</p>', charts=(), level=2, group=True), SectionTemplate(key='tvl_changes', heading_id='inflows-and-outflows-by-vault', heading='Inflows and outflows by vault', intro='<p>The vaults with the largest TVL increases and decreases, in dollars. The <a href="https://tradingstrategy.ai/vaults/high-tvl">high TVL vaults</a> page ranks the vaults with at least $2M TVL.</p>', charts=(('tvl_changes', 'The largest vault TVL increases and decreases over the last 30 days'),), level=3, group=False), SectionTemplate(key='chain_tvl_changes', heading_id='inflows-and-outflows-by-blockchain', heading='Inflows and outflows by blockchain', intro='<p>The net TVL change of all vaults on each blockchain, for the blockchains with the largest increases and decreases. The <a href="https://tradingstrategy.ai/vaults/chains">blockchains page</a> shows the TVL of every chain.</p>', charts=(('chain_tvl_changes', 'The largest net TVL increases and decreases by blockchain over the last 30 days'),), level=3, group=False))

Data sections in display order. Follows the Writing rules in README-blog-post-outline.md: the best-performing vaults first, with no introduction or caption, then yield by chain and protocol, risk and return, vaults and tokenised funds TVL, and inflows and outflows last, with its by-vault and by-blockchain subsections. Every other section opens with an introduction linking to tradingstrategy.ai.

class PostContext

Bases: object

Everything needed to render the post body.

Built by eth_defi.vault_report.report.generate_monthly_vault_report() with bundle-relative image paths, and copied by eth_defi.vault_report.report.publish_report_draft() with the uploaded Ghost image URLs. Only charts and podcast_images differ between the two.

month_label: str

E.g. September 2026

stats: list[str]

Summary statistics bullet points for the report content updates section, plain text

tables: dict[str, str]

Section key -> rendered HTML table, see eth_defi.vault_report.sections.render_section_table(), which escapes the vault data. Sections without vaults are absent.

charts: dict[str, str]

Chart key -> image URL or relative path. Charts that were not rendered are absent.

criteria_notes: dict[str, list[str]]

Section or chart key -> bullet points describing the selection criteria; only what the chart and table do not show

previous: Optional[eth_defi.vault_report.ghost.GhostPost]

Previous report post with its HTML body, from the Ghost Content API, if found. Source of the evergreen sections and of the “previous report” link.

podcasts: list[eth_defi.vault_report.podcasts.PodcastEpisode]

Latest podcast episodes, newest first; the section is left out when empty

podcast_images: dict[str, str]

Podcast guest logos and service icons -> image URL or relative path, see eth_defi.vault_report.podcasts.render_podcast_episodes()

__init__(month_label, stats, tables, charts, criteria_notes, previous=None, podcasts=<factory>, podcast_images=<factory>)
Parameters
Return type

None

make_month_label(data_end_at)

Create a human-readable report month.

Parameters

data_end_at (datetime.datetime) – Report data date.

Returns

E.g. September 2026.

Return type

str

make_report_title(month_label)

Create the post title.

Parameters

month_label (str) – E.g. September 2026.

Returns

Post title.

Return type

str

make_report_slug(data_end_at)

Create the post URL slug.

Parameters

data_end_at (datetime.datetime) – Report data date.

Returns

E.g. the-best-performing-stablecoin-vaults-september-2026.

Return type

str

extract_section_html(post_html, heading_id)

Extract a <h2> section, up to the next <h2>, from post HTML.

Used to carry the evergreen sections (About the report, Next steps) over from the previous report, so wording the editor improved in Ghost is not lost the next month. The copied HTML is cleaned of what Ghost and older posts leave behind: ?ref=...ghost.io tracking parameters, vault links at their legacy /trading-view/ paths and empty paragraphs.

A regular expression is enough here, rather than an HTML parser, because Ghost renders headings with the id as their first attribute and a section ends at the next <h2 or at the end of the post (Next steps is the last section).

Parameters
  • post_html (str) – Full post HTML from the Ghost API.

  • heading_id (str) – The id attribute of the section <h2>.

Returns

Section HTML including the heading, or None if not found.

Return type

Optional[str]

read_changelog_entries(changelog_path, since, keywords=('vault', 'protocol'))

Read new integration entries from the changelog after a date.

Offers the editor a starting point for the report content updates section. Only feat: Add ... entries are considered, as refactors and performance work are not interesting to report readers. The command line script reads entries since the previous report’s publication date and passes them to report.json; the post itself never lists them, because the editor picks and rewrites the ones worth telling readers about.

Parameters
  • changelog_path (pathlib.Path) – Path to CHANGELOG.md, entries formatted as - feat: Add something (YYYY-MM-DD).

  • since (datetime.date) – Include entries dated after this day.

  • keywords (tuple[str, ...]) – Include only entries mentioning one of these words, case-insensitive.

Returns

Entry texts without the feat: prefix, in changelog order (newest first).

Return type

list[str]

build_post_html(context)

Render the report post body.

The post follows the structure in the Writing rules of README-blog-post-outline.md:

  1. Opening paragraph and the Ghost table of contents placeholder

  2. About the report, copied from the previous post

  3. Report content updates: a link to the previous report and the generated statistics; the editor writes the month’s integrations under the opening sentence

  4. DeFi vault community news: an introduction only, the editor writes the news

  5. Latest podcasts, when episodes were read

  6. The data sections of SECTION_TEMPLATES, each only when it has content

  7. Partners, rendered from PARTNERS, and Next steps, copied from the previous post

The post has no editor callouts or placeholders: the sections the editor completes are listed in the editor workflow of README-vault-report.md instead.

Tables and the podcast list are wrapped in <!--kg-card-begin: html--> comments so Ghost imports each as one raw HTML card and keeps its markup, see eth_defi.vault_report.ghost.GhostAdminClient.create_or_update_draft(). Charts become Ghost image cards.

Vault data reaches the HTML already escaped by the table renderer; the criteria notes and the templates are trusted HTML from this repository.

Parameters

context (eth_defi.vault_report.post.PostContext) – Report content. Image references may be bundle-relative paths or uploaded URLs; the text is the same either way.

Returns

Ghost-compatible post HTML.

Return type

str

build_preview_html(title, post_html, feature_image=None)

Wrap the post body into a standalone HTML page for local preview.

Lets the operator and reviewers read the generated post from the bundle with a browser, e.g. with GHOST_DRAFT=false or when the Ghost site is private. The minimal light stylesheet only makes tables and images readable; it does not reproduce the blog’s dark theme, so check the final look in the Ghost editor.

Parameters
  • title (str) – Post title.

  • post_html (str) – Output of build_post_html().

  • feature_image (Optional[str]) – Feature image path or URL, shown above the title as Ghost themes do.

Returns

HTML document.

Return type

str