How to Build a CS2 Inventory Profit and Loss Tracker
Updated
A CS2 inventory profit and loss tracker combines an external acquisition and sale ledger with repeatable market valuations. Store each purchase, sale, fee, and quantity change; use cs2.sh's
POST /v1/prices/latestfor current market values; and append valuation snapshots instead of overwriting them. Unrealized profit compares remaining cost with current value, while realized profit compares actual sale proceeds with the cost assigned to assets already sold.
Scope: This is a broad, high-level v0 guide, not a complete application. cs2.sh is designed as a flexible market-data layer for whatever you want to build on top of it, so adapt the implementation to your own product, strategy, and integrations.
cs2.sh provides item identity and market data. A Steam inventory integration, marketplace account export, or user-entered ledger must provide ownership, acquisition cost, sale proceeds, and individual asset attributes.
Separate the ledger from market value
The ledger records events that actually happened to the portfolio. Valuation records estimates calculated at a particular time. Keep them in separate tables so a changed pricing model cannot rewrite purchase history.
Minimum acquisition fields:
transaction_id, acquired_at, market_hash_name, variant_version
asset_id, quantity, unit_cost, acquisition_fees, currency
float_value, paint_seed, stickers, notesMinimum disposal fields:
transaction_id, disposed_at, asset_id or inventory lot
quantity, gross_proceeds, selling_fees, currencyStore the original transaction currency and the USD conversion rate used at the transaction time. API market prices are returned in USD, but a current USD value cannot reconstruct the historical exchange rate or payment cost of an acquisition.
This ledger is also the source of truth when an item leaves the visible Steam inventory because it was sold, transferred, consumed, or moved to another account. A current inventory snapshot alone cannot identify why ownership changed.
Resolve holdings to exact items
Match each holding to GET /v1/schema by exact market_hash_name. Store supported variant version codes separately from the base name. Re-sync the catalog when its generation_id changes.
Aggregate interchangeable items into quantity lots only when exact asset attributes do not change their value. Keep individual rows for skins whose float, fade, paint seed, stickers, StatTrak status, souvenir status, or variant can create a material premium.
Unresolved names belong in an exceptions table. Fuzzy-matching an unknown transaction to the nearest catalog name can assign its cost and profit to the wrong wear or variant.
Calculate a repeatable current value
Request distinct holdings in batches of at most 100 through POST /v1/prices/latest. For each item or variant, apply one documented valuation policy consistently.
Useful value columns include:
| Value | Input | Meaning |
|---|---|---|
| Cash listing value | Median or selected current cash-market ask | Comparable current sell listings before account-specific costs |
| Sell-now value | Highest credible current cash-market bid | Current generic buyer demand before selling and withdrawal costs |
| Conservative value | Liquidity-adjusted estimate | Current estimate moved toward credible buyer demand as evidence weakens |
Keep Steam Wallet value separate from cash value. Apply marketplace fees, deposit costs, withdrawal costs, and other account-specific adjustments in your valuation service because they are not returned by cs2.sh.
Join GET /v1/liquidity/items and record the liquidity bucket and estimated sale time with the estimate. Use GET /v1/market/buff/latest when an asset's float or fade belongs to a supported returned range. Rare paint seeds and applied stickers may still require individual appraisal.
The inventory valuation guide defines these choices in more detail. The important P&L rule is to select a policy version and retain it with every snapshot.
Calculate realized and unrealized profit separately
For open holdings:
remaining_cost = assigned acquisition cost + assigned acquisition fees
unrealized_profit = current_value - remaining_cost
unrealized_return = unrealized_profit / remaining_costFor completed sales:
net_sale_proceeds = gross_proceeds - selling fees - withdrawal costs
realized_profit = net_sale_proceeds - cost assigned to the sold assetsChoose a consistent method for assigning acquisition cost when several identical units were purchased at different prices. Specific asset identification is natural when the ledger retains Steam asset ids; aggregated commodity lots require an explicit lot policy. Accounting and tax treatment vary by jurisdiction, so the product should not label an internal performance method as tax reporting without separate review.
Do not count an open holding's unrealized change again when it is sold. Close its remaining cost and transfer the result into realized profit at the disposal time.
Append valuation snapshots
Create one immutable portfolio snapshot per valuation run:
valuation_id, valued_at, policy_version
market_hash_name, variant_version, quantity
unit_value, total_value, selected_sources
updated_at, collected_at, liquidity, confidence
remaining_cost, unrealized_profit, unpriced_reasonThe portfolio total should carry a coverage figure:
coverage = holdings with accepted valuations / total holdingsAlso show the value-weighted coverage when known values vary widely. Ten priced cases and one unpriced knife can produce high item coverage but low confidence in the total.
Missing data is not zero. Keep an unpriced item in holdings, retain its cost, and exclude its unknown current value from the known subtotal. Report the number and recorded cost of unpriced assets beside the total.
Build portfolio history from holdings at each time
The most reliable chart comes from the snapshots your application actually recorded. To backfill an earlier period, reconstruct the quantity held after every acquisition and disposal, then join each historical time bucket to only the assets held at that time.
POST /v1/prices/history supplies listing and buy-order OHLC from December 24, 2025. Completed-sale archives can provide longer reference series: CSFloat daily data begins in 2022, BUFF sales begin September 6, 2024, and Steam daily data begins April 26, 2013. Those histories describe generic items or supported variants, not the past premium of every exact float, paint seed, or sticker combination.
Keep valuation changes separate from cash flows. Buying a $500 skin increases inventory value and invested cost at the same time; it is not a $500 profit. Selling an item moves value out of holdings and into proceeds; the difference from assigned cost is the realized result.
Explain changes in the total
A daily or hourly report can attribute portfolio movement to:
- market price changes on holdings present in both snapshots;
- acquisitions and incoming transfers;
- sales and outgoing transfers;
- changed quantities;
- assets becoming priced or unpriced;
- valuation-policy or fee-model changes.
Do not place a policy change on the same line as market performance. If the selected source set, fair-value formula, fee model, or confidence rule changes, start a new policy_version and show the resulting valuation break explicitly.
Product failure behavior
- On partial API success, value returned items and attach the per-item error to affected holdings.
- On stale source data, keep the prior snapshot visible but do not record it as a new market observation.
- On a missing variant, leave the exact holding unpriced instead of falling back to the base item.
- On a temporary upstream failure, retain the last accepted value with its original timestamp.
- On an inventory-sync failure, do not infer that every missing asset was sold.
These rules let the tracker distinguish portfolio loss from missing evidence. That distinction matters more than forcing every dashboard run to produce one complete-looking number.
References: current prices, price history, BUFF ranges, CSFloat sales, liquidity, schema, and API missing-data behavior.