CS2 market data API: volume, liquidity and 90-day averages
Most CS2 price APIs return one number. The Skinstrack market data API returns volume, depth, a liquidity score, and 7 to 90 day aggregates across 34+ marketplaces in one response. This guide shows how to load it into pandas and build signals on it.
On this page
What counts as market data beyond price
A single current price is the bare minimum. Useful market data has at least four dimensions: best bid and best ask, volume, liquidity, and a time-windowed reference price. The Skinstrack API returns all four in the same payload.
Best bid and best ask
Volume and quantity
Liquidity score
Time-window aggregates
Pulling market data in one call
Hit GET /paid/items with avg=true and median=true. You receive every item the providers list, plus aggregates inline.
curl -H "X-API-KEY: $SKINSTRACK_KEY" \
"https://api.skinstrack.com/v1/paid/items?providers=csfloat,buff,skinport&avg=true&median=true"How 34 marketplaces become one row
For each item, Skinstrack normalizes per-marketplace listings into the same shape: provider, price, count, volume, updated_at. That uniformity is the whole point: you write one function that works against every marketplace, instead of 34 adapters.
{
"market_hash_name": "AK-47 | Redline (Field-Tested)",
"slug": "ak-47-redline-field-tested",
"liquidity": 85,
"quantity": 15000,
"prices": [
{ "provider": "csfloat", "price": 12.50, "count": 150, "volume": 500 },
{ "provider": "buff", "price": 11.92, "count": 482, "volume": 920 },
{ "provider": "skinport", "price": 13.10, "count": 44, "volume": 110 }
],
"average": { "7d": 12.35, "14d": 12.20, "30d": 12.10, "60d": 11.95, "90d": 11.85 },
"median": { "7d": 12.40, "14d": 12.25, "30d": 12.05, "60d": 11.95, "90d": 11.90 }
}Reading the liquidity score
The liquidity score is the most useful single field on the response for anyone running a strategy. It tells you whether the item you are modeling actually trades. The convention to start with:
80-100
50-79
20-49
0-19
The comparison tool uses the same score to filter cross-market opportunities. Eyeballing the tool with a liquidity slider is a fast way to calibrate where your strategy threshold belongs.
Loading the universe into pandas
For research, the right starting point is a flat dataframe with one row per item and columns for the fields you actually use. Everything else compounds from here.
import os, requests, pandas as pd
def load_universe(providers=('csfloat', 'buff', 'skinport')):
res = requests.get(
'https://api.skinstrack.com/v1/paid/items',
headers={'X-API-KEY': os.environ['SKINSTRACK_KEY']},
params={'providers': ','.join(providers), 'avg': 'true', 'median': 'true'},
)
res.raise_for_status()
rows = []
for item in res.json():
avg = item.get('average', {}) or {}
med = item.get('median', {}) or {}
best_bid = min((p['price'] for p in item['prices']), default=None)
best_ask = max((p['price'] for p in item['prices']), default=None)
rows.append({
'slug': item['slug'],
'name': item['market_hash_name'],
'rarity': item.get('rarity_name'),
'wear': item.get('wear_name'),
'liquidity': item.get('liquidity', 0),
'quantity': item.get('quantity', 0),
'best_bid': best_bid,
'best_ask': best_ask,
'spread': (best_ask - best_bid) if best_bid and best_ask else None,
'avg_7d': avg.get('7d'),
'avg_30d': avg.get('30d'),
'avg_90d': avg.get('90d'),
'med_7d': med.get('7d'),
'med_30d': med.get('30d'),
'med_90d': med.get('90d'),
})
return pd.DataFrame(rows)
df = load_universe()
print(df.describe())Three quick signal sketches
These are not strategies. They are screens that produce candidate lists you then evaluate by hand. Tighten the thresholds before you put money behind them.
# Three quick signal sketches off the same dataframe.
# 1. Momentum: 7d average is meaningfully above 30d average,
# suggesting demand is accelerating.
momentum = df[
(df['avg_7d'].notna())
& (df['avg_30d'].notna())
& (df['avg_7d'] / df['avg_30d'] - 1 > 0.05)
& (df['liquidity'] >= 50)
]
# 2. Mean reversion: current best ask is far below the 30d average
# on a liquid item.
mean_reversion = df[
(df['best_ask'].notna())
& (df['avg_30d'].notna())
& (df['best_ask'] / df['avg_30d'] - 1 < -0.10)
& (df['liquidity'] >= 60)
]
# 3. Volume / quantity spike: current quantity is dramatically
# above what the price band would predict. Often a sell signal
# because supply is flooding in.
import numpy as np
df['price_bucket'] = pd.cut(df['avg_30d'].fillna(0), bins=[0,5,25,100,500,np.inf])
expected = df.groupby('price_bucket', observed=True)['quantity'].transform('median')
supply_spike = df[df['quantity'] > expected * 2.5]Building your own time series
The API ships 7d to 90d aggregates, which is enough for the signals above. For per-day or per-hour granularity, snapshot the API on a schedule and append.
# Incremental sync pattern: every 6 hours, fetch the snapshot
# and append a row per item with a load_ts. Build your own
# historical series from successive snapshots.
from datetime import datetime, timezone
import sqlite3
def sync(db_path='skins.db'):
df = load_universe()
df['load_ts'] = datetime.now(timezone.utc).isoformat()
with sqlite3.connect(db_path) as conn:
df.to_sql('skin_snapshots', conn, if_exists='append', index=False)Backtesting caveats
Aggregates, not ticks
Build your own series
Survivorship bias
Cross-market fees skew returns
Pull market data with one API key
Growth tier covers a full universe snapshot every few hours for a month of research.
Questions
Does the CS2 market data API expose a full price history endpoint?
No raw tick stream. The API exposes the current cross-marketplace snapshot, plus 7d, 14d, 30d, 60d and 90d average and median price aggregates per item when you pass avg=true and median=true on /paid/items. For per-day or per-hour granularity, store successive snapshots on your side.
What does the Skinstrack liquidity score measure?
A 0-100 metric that combines recent trading volume, listing depth and price stability across the marketplaces an item is listed on. Higher means tighter spreads and faster fills. It is computed by Skinstrack and is not available through any single marketplace API.
Which marketplaces does the market data API cover?
34 marketplaces are normalized into one response, including Steam, Buff163, CSFloat, Skinport, DMarket, WaxPeer, Gamerpay and CSGOEmpire. The full list is in the API docs at /api-docs.
Can I use this data for algorithmic trading?
Yes for signal generation, alerting, and analytics. No for direct execution. The Skinstrack API does not place buy or sell orders. You execute on each marketplace through its own UI or its own API. See the CS2 arbitrage bot guide for a working architecture.
How do I backtest a strategy on CS2 skins?
Snapshot the universe on a fixed cadence (every 6 hours is a reasonable starting point), persist each snapshot with a load timestamp, and replay your signal logic over that history. Make sure to include items that have since delisted to avoid survivorship bias.
How fresh is the data?
Each price in the response carries its own updated_at timestamp from the marketplace it was pulled from. Most providers refresh within a few minutes. For signal logic, drop offers older than your chosen freshness window before computing best bid and best ask.
Keep reading