A Python module that makes it easy to download current prices of TSP from the official website and perform basic data analysis using pandas and matplotlib.
Project description
tsp-analytics
A Python module for retrieving the prices of Thrift Savings Plan (TSP) funds, calculating analytics, and visualizing historical performance.
Highlights
- Pulls official TSP fund price history from
tsp.govand caches the CSV locally. - Flexible current-price access (latest row, per-fund latest prices, historical as-of anchors).
- Optional completeness checks for as-of pricing (require all requested funds).
- Current price alerts for stale data and large daily moves.
- Analytics for returns, correlations, drawdowns, rolling metrics, risk stats, and rankings.
- Dashboard-ready outputs (dataframes, long/tidy tables, JSON-friendly dictionaries).
- Current price dashboard snapshot (prices, recency, trailing returns, risk stats).
- Built-in Matplotlib visualization helpers for price history, rankings, recency, and risk.
- Correlation pair summaries to spotlight the strongest fund relationships.
- Data-quality tooling: cache status, fund coverage, missing business days, and reports.
- Weighted portfolio analytics for value, drawdowns, and performance summaries.
- Monthly return tables (wide, long, or dict) for seasonality dashboards.
- Recent daily change windows with summary stats and heatmap-friendly outputs.
- Recent price window helpers for the latest N trading days (wide, long, or JSON-ready).
- Flexible fund name aliases (e.g.,
G,g-fund,L2050,Lifecycle 2050).
Installation
pip install tsp-analytics
For local development:
pip install -r requirements.txt
pip install -e .[dev]
If you prefer a single requirements file instead of extras:
pip install -r requirements-dev.txt
Cache Location
By default, the price history CSV is cached at ~/.cache/tsp/fund-price-history.csv.
To customize the cache directory, pass data_dir or set TSP_DATA_DIR:
from pathlib import Path
from tsp import TspAnalytics
prices = TspAnalytics(data_dir=Path.home() / ".cache" / "tsp")
export TSP_DATA_DIR="$HOME/.cache/tsp"
Offline or Manual Refresh
Disable automatic updates when you need offline access or want to control refresh timing:
from tsp import TspAnalytics
prices = TspAnalytics(auto_update=False)
prices.refresh()
Quickstart
from tsp import TspAnalytics, TspIndividualFund
prices = TspAnalytics()
latest_price = prices.get_price(TspIndividualFund.G_FUND)
latest_prices = prices.get_current_prices()
latest_snapshot = prices.get_current_price_snapshot_dict()
Analytics & Visualization Quickstart
from datetime import date
from tsp import TspAnalytics, TspIndividualFund
prices = TspAnalytics()
# Price history (rows with all selected fund prices missing are dropped)
history = prices.get_price_history(
funds=[TspIndividualFund.C_FUND, TspIndividualFund.S_FUND],
start_date=date(2024, 1, 1),
)
# Long-format data for Plotly/Seaborn dashboards
history_long = prices.get_price_history_long(
funds=[TspIndividualFund.C_FUND, TspIndividualFund.S_FUND],
)
# Recent trading-day window for dashboards
recent_prices = prices.get_recent_prices(days=10)
recent_prices_long = prices.get_recent_prices_long(days=10)
# Built-in chart helpers (Matplotlib)
prices.show_fund_price_chart(TspIndividualFund.C_FUND)
prices.show_current_prices_per_fund_chart()
Documentation
- Documentation index
- Getting started
- Usage guide
- Current prices & snapshots
- Analytics & visualization
- Visualization guide
- Portfolio analytics
- Examples & recipes
- Dashboards & reporting
- Configuration
- Data sources & caching
- API reference
- Troubleshooting
- Contributing guide
- Development guide
- Testing guide
- Architecture overview
Building Documentation
To build the Sphinx documentation locally:
pip install -r requirements-dev.txt
sphinx-build -b html docs/sphinx docs/sphinx/_build/html
At a Glance
Current Prices
from datetime import date
from tsp import TspAnalytics, TspIndividualFund
prices = TspAnalytics()
latest = prices.get_current_prices(fund=TspIndividualFund.G_FUND)
latest_per_fund = prices.get_current_prices(per_fund=True)
latest_dict = prices.get_current_prices_dict()
latest_per_fund_dict = prices.get_current_prices_dict(per_fund=True)
latest_report = prices.get_current_price_report()
latest_report_long = prices.get_current_price_report_long()
latest_report_as_of = prices.get_current_price_report(as_of=date(2024, 1, 2))
latest_report_as_of_long = prices.get_current_price_report_long(as_of=date(2024, 1, 2))
latest_changes = prices.get_current_price_changes()
latest_changes_as_of = prices.get_current_price_changes(as_of=date(2024, 1, 2))
latest_changes_dict = prices.get_current_price_changes_dict(as_of=date(2024, 1, 2))
latest_changes_per_fund = prices.get_current_price_changes_per_fund(as_of=date(2024, 1, 2))
snapshot = prices.get_current_price_snapshot()
snapshot_as_of = prices.get_current_price_snapshot(as_of=date(2024, 1, 2))
snapshot_dict = prices.get_current_price_snapshot_dict(as_of=date(2024, 1, 2))
as_of_prices = prices.get_current_prices(as_of=date(2024, 1, 2))
as_of_prices_complete = prices.get_current_prices(
as_of=date(2024, 1, 2),
funds=[TspIndividualFund.G_FUND, TspIndividualFund.C_FUND],
require_all_funds=True,
)
as_of_per_fund = prices.get_current_prices_per_fund(as_of=date(2024, 1, 2))
as_of_per_fund_dict = prices.get_current_prices_per_fund_dict(as_of=date(2024, 1, 2))
safe_per_fund = prices.get_current_prices_per_fund(
funds=[TspIndividualFund.G_FUND, TspIndividualFund.C_FUND],
allow_missing=True,
)
safe_per_fund_payload = prices.get_current_prices_per_fund_dict(
funds=[TspIndividualFund.G_FUND, TspIndividualFund.C_FUND],
allow_missing=True,
)
status = prices.get_current_price_status()
status_dict = prices.get_current_price_status_dict()
status_g = prices.get_current_price_status(fund=TspIndividualFund.G_FUND)
summary = prices.get_current_price_summary(stale_days=2)
summary_dict = prices.get_current_price_summary_dict(stale_days=2)
summary_as_of = prices.get_current_price_summary(as_of=date(2024, 1, 2), stale_days=2)
summary_as_of_dict = prices.get_current_price_summary_dict(
as_of=date(2024, 1, 2),
stale_days=2,
)
alerts = prices.get_current_price_alerts(stale_days=2, change_threshold=0.03)
alerts_dict = prices.get_current_price_alerts_dict(stale_days=2, change_threshold=0.03)
alerts_g = prices.get_current_price_alerts(fund="G", stale_days=2, change_threshold=0.03)
alert_summary = prices.get_current_price_alert_summary(stale_days=2, change_threshold=0.03)
alert_summary_dict = prices.get_current_price_alert_summary_dict(
stale_days=2,
change_threshold=0.03,
)
Tip: When requesting
as_ofprices for a single fund, the lookup skips rows where that fund has missing prices so you always get the most recent valid value.
Analytics
from datetime import date
from tsp import TspAnalytics, TspIndividualFund
prices = TspAnalytics()
returns = prices.get_daily_returns()
drawdown = prices.get_drawdown_series(fund=TspIndividualFund.C_FUND)
price_history = prices.get_price_history(start_date=date(2024, 1, 1))
risk_summary = prices.get_risk_return_summary()
performance_summary_dict = prices.get_performance_summary_dict()
risk_summary_dict = prices.get_risk_return_summary_dict()
dashboard = prices.get_current_price_dashboard(periods=[1, 5, 20])
price_summary = prices.get_price_summary()
price_recency = prices.get_price_recency()
correlation_pairs = prices.get_correlation_pairs(top_n=5)
current_overview = prices.get_current_fund_overview()
current_overview_as_of = prices.get_current_fund_overview(as_of=date(2024, 1, 2))
current_change_rank = prices.get_fund_rankings(metric="change_percent")
return_distribution = prices.get_return_distribution_summary(
fund=TspIndividualFund.C_FUND,
percentiles=[0.05, 0.5, 0.95],
)
price_stats = prices.get_price_statistics(start_date=date(2024, 1, 1))
return_stats = prices.get_return_statistics(end_date=date(2024, 3, 31))
price_stats_dict = prices.get_price_statistics_dict(fund=TspIndividualFund.G_FUND)
return_stats_dict = prices.get_return_statistics_dict(
fund=TspIndividualFund.G_FUND,
trading_days=252,
)
recent_changes = prices.get_recent_price_changes(days=5)
recent_change_summary = prices.get_recent_price_change_summary(days=5)
recent_changes_dict = prices.get_recent_price_changes_dict(days=5)
fund_report = prices.get_fund_analytics_report(
TspIndividualFund.C_FUND,
start_date=date(2024, 1, 1),
)
fund_report_dict = prices.get_fund_analytics_report_dict(
TspIndividualFund.C_FUND,
start_date=date(2024, 1, 1),
)
drawdown_summary = prices.get_drawdown_summary_dict(TspIndividualFund.C_FUND)
range_changes = prices.get_price_change_by_date_range_dict(
start_date=date(2024, 1, 1),
end_date=date(2024, 1, 31),
)
as_of_changes = prices.get_price_changes_as_of_per_fund(date(2024, 1, 3))
monthly_return_table = prices.get_monthly_return_table_long(TspIndividualFund.C_FUND)
rolling_volatility = prices.get_rolling_volatility(TspIndividualFund.C_FUND, window=63)
rolling_performance = prices.get_rolling_performance_summary(TspIndividualFund.C_FUND, window=63)
trailing_return = prices.get_trailing_returns(periods=20, fund=TspIndividualFund.C_FUND)
trailing_return_long = prices.get_trailing_returns_long(periods=[1, 5, 20, 63])
trailing_return_dict = prices.get_trailing_returns_dict(periods=[1, 5, 20, 63])
correlation_payload = prices.get_correlation_matrix_dict()
rolling_correlation_payload = prices.get_rolling_correlation_matrix_dict(window=63)
Visualization
from datetime import date
from tsp import TspAnalytics, TspIndividualFund
prices = TspAnalytics()
prices.show_fund_price_chart(TspIndividualFund.C_FUND)
prices.show_latest_price_changes_per_fund_chart()
prices.show_recent_price_change_heatmap(days=5)
prices.show_price_recency_chart()
prices.show_current_prices_per_fund_chart()
prices.show_price_history_chart(start_date=date(2024, 1, 1))
prices.show_return_histogram_chart(TspIndividualFund.C_FUND)
prices.show_correlation_heatmap()
prices.show_correlation_pairs_chart(top_n=5)
prices.show_rolling_performance_summary_chart(TspIndividualFund.C_FUND, window=63)
prices.show_fund_rankings_chart(metric="trailing_return", period=20, top_n=5)
prices.show_current_price_dashboard_metric_chart(metric="change_percent")
prices.show_current_price_alerts_chart(metric="change_percent", change_threshold=0.03)
prices.show_trailing_returns_chart(
periods=[1, 5, 20, 63],
funds=[TspIndividualFund.G_FUND, TspIndividualFund.C_FUND],
)
# Capture charts for custom dashboards or reports
fig, ax = prices.show_fund_price_chart(TspIndividualFund.C_FUND, show=False)
fig.savefig("c-fund.png", dpi=150, bbox_inches="tight")
# Current price bar chart anchored to a historical date
fig, ax = prices.show_current_prices_per_fund_chart(
as_of=date(2024, 1, 2),
sort_by="fund",
show=False,
)
Data Quality & Cache Status
from tsp import TspAnalytics
prices = TspAnalytics(auto_update=False)
status = prices.get_cache_status()
status_dict = prices.get_cache_status_dict()
report = prices.get_data_quality_report()
report_dict = prices.get_data_quality_report_dict()
Fund Metadata & Aliases
from tsp import TspAnalytics
prices = TspAnalytics(auto_update=False)
aliases = prices.get_fund_aliases()
metadata = prices.get_fund_metadata()
metadata_dict = prices.get_fund_metadata_dict()
Data Sources & Caching
The library downloads the official fund-price-history.csv file from tsp.gov and caches it
locally. The cache refreshes automatically after the most recent business day and the configured
update time (defaults to 7:00 PM local time). You can override the cache directory with the
TSP_DATA_DIR environment variable or the data_dir constructor argument:
from pathlib import Path
from tsp import TspAnalytics
prices = TspAnalytics(data_dir=Path.home() / ".cache" / "tsp")
If you need to run fully offline or inject a custom HTTP session, disable auto updates and load data explicitly:
from tsp import TspAnalytics
prices = TspAnalytics(auto_update=False)
prices.refresh() # or prices.load_csv(...), prices.load_csv_text(...)
For custom networking (proxies, retries, shared sessions), pass a pre-configured session.
The library ensures a CSV-friendly Accept header and a user-agent string are present:
import requests
from tsp import TspAnalytics
session = requests.Session()
session.headers.update({"User-Agent": "my-app/1.0"})
prices = TspAnalytics(session=session, request_timeout=15.0, max_retries=3, retry_backoff=0.5)
Downloads are validated to ensure the response looks like a CSV (including a Date header
and known fund columns). If the upstream response appears to be HTML or missing expected headers,
the client raises
a validation error and preserves the existing cache. See
docs/TROUBLESHOOTING.md for guidance.
To load a pandas dataframe directly, pass it into load_dataframe. The dataframe can include
either a Date column or a date-like index (named Date or a datetime index):
import pandas as pd
from tsp import TspAnalytics, TspIndividualFund
df = pd.DataFrame(
{
TspIndividualFund.G_FUND.value: [100.0, 101.5],
TspIndividualFund.C_FUND.value: [200.0, 202.0],
},
index=pd.to_datetime(["2024-01-02", "2024-01-03"]),
)
prices = TspAnalytics(auto_update=False)
prices.load_dataframe(df)
See docs/DATA_SOURCES.md for full details.
Analytics & Visualization Tips
- Use the long-format helpers (
get_price_history_long,get_daily_returns_long) when charting with Seaborn or Plotly. - Price history helpers drop rows where all selected fund prices are missing to avoid blank records in downstream charts and analytics.
- Use the
show_*helpers for quick Matplotlib visuals; they return(fig, ax)so you can save or embed charts without opening a window. - Use dictionary helpers (like
get_current_price_report_dict) for JSON-ready dashboard payloads.
Start with docs/ANALYTICS.md and docs/VISUALIZATION.md for step-by-step examples.
Contributing & Development
Interested in contributing? Start here:
- Contributing guide
- Contributor guide
- Development guide (architecture & tooling notes)
- Testing guide
- Troubleshooting
Quick setup:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
pip install -e .
pytest
When you add user-facing features, please update the relevant docs in docs/ and include
tests under tests/.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tsp_analytics-0.1.1.tar.gz.
File metadata
- Download URL: tsp_analytics-0.1.1.tar.gz
- Upload date:
- Size: 148.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f77aa4902eb619cdcd0ba9ae5184634a053e4c555c9df005bea3be37c83ceeed
|
|
| MD5 |
682f7a9ae17a1f34321c02737c08297b
|
|
| BLAKE2b-256 |
72e1afcfa1ae871341e7a63867de99598477a9075694dad5071e144523c798d5
|
File details
Details for the file tsp_analytics-0.1.1-py3-none-any.whl.
File metadata
- Download URL: tsp_analytics-0.1.1-py3-none-any.whl
- Upload date:
- Size: 82.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72b38156d99c578dcf090eea097f2b9d8fba68c98e9a16af5913afd6b4322cee
|
|
| MD5 |
8c18f4eaa642019dc5fbfa61eb2e1b9f
|
|
| BLAKE2b-256 |
a6bad81b8cebcfbd4e42a4228445ac6a902dd2078815529e4025562e824f0502
|