How to Build a CS2 Skin Price Prediction Model
Updated
A CS2 skin price prediction model needs one precisely defined target, a point-in-time feature table, chronological evaluation, and a baseline simple enough to expose whether the model learned anything useful. Use cs2.sh's
POST /v1/prices/historyto forecast listing or buy-order prices, and use completed-sale archives when the target is a sale price. Do not train one unlabeled series from asks, bids, and completed sales as if they represented the same event.
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.
The first useful output is a table with item, variant, source, prediction time, horizon, predicted value, baseline value, confidence, and the data cutoff used to produce it. That is enough to evaluate forecasts without turning them into automatic trading decisions.
Choose one prediction target
"Predict the price" is not a model specification. Select the exact field, source, interval, and horizon before building features.
| Target | Suitable data | What the target means |
|---|---|---|
| Next-hour ask close | POST /v1/prices/history, one source at 1h |
Lowest sell listing at the final observation in the future bucket |
| Next-day bid close | POST /v1/prices/history, one source at 1d |
Highest generic buy order at the final observation in the future bucket |
| Next-day CSFloat sale price | POST /v1/archive/csfloat |
Arithmetic average of CSFloat completed-sale prices that day |
| Next-day Steam sale price | POST /v1/archive/steam |
Steam's median sale price in the daily bucket |
| Next BUFF sale | POST /v1/archive/buff |
BUFF sale price, with the day's total supply as a feature |
| Future sale volume | CSFloat or Steam archive | Number of completed sales or purchases in the future bucket |
Keep separate models or output heads for different meanings. A listing-price model can update intraday but does not predict a completed sale. A completed-sale model has a stronger sale interpretation but a lower-frequency target.
Steam targets also represent Steam Wallet value. Do not combine an unadjusted Steam forecast with cash-market forecasts under one cash-price label.
Build a point-in-time dataset
Request items in batches of at most 100 and normalize each endpoint into rows keyed by item, variant, source, and time. Retain the original timing fields instead of replacing them with the job's fetch time.
For OHLC history:
bucketis the UTC interval boundary;open_timeandclose_timeare the first and last real observations inside it;sample_countis the number of observations represented;- ask and bid objects can be absent when that source has no data in the bucket.
A feature calculated from a bucket becomes available only after its close_time. If the model predicts the next bucket, its row must contain data whose observation time is no later than the prediction cutoff.
OHLC coverage begins December 24, 2025. Request ranges are limited to 14 days at 5m, 90 days at 30m, 365 days at 1h, and unlimited at 1d. Backfill adjacent ranges and deduplicate on item, variant, source, interval, and bucket.
Add features that exist before the forecast
Useful feature families include:
| Feature family | Examples |
|---|---|
| Price movement | Trailing returns, moving averages, distance from trailing median, rolling high-low range |
| Sell listings and buy orders | Ask-bid difference, ask and bid changes, active-order counts where supported |
| Cross-market state | Difference between one source and the median non-stale cash-market ask or bid |
| Completed sales | Trailing CSFloat average sale price and sale count, trailing Steam median and purchase count, trailing median of BUFF sales |
| Supply and activity | Historical total_supply and approximate hourly_volume from archive/history |
| Identity | Category, rarity, collection, wear, float limits, and stable variant code from the schema |
| Data quality | Observation age, missing-source count, and sample_count |
Calculate every rolling statistic with trailing data only. A centered moving average includes future observations and makes historical evaluation invalid.
The current liquidity endpoint is a daily snapshot, not a historical liquidity series. It can describe a forecast produced now, but it cannot be joined backward to old training rows as though today's classification were known at the time. For historical liquidity features, derive trailing activity from the archive data available before each row.
Keep variants and missing data explicit
Doppler, Gamma Doppler, and supported Case Hardened variants have separate markets. Train and evaluate on the nested variant series with its stable version; do not fill a missing Phase 2 value with the base Doppler price.
Missing data is not zero. Add a missing indicator when that distinction helps the model, but keep the numeric value null until the training pipeline applies an explicit policy. Long forward-fills can make an illiquid item appear stable when it simply had no new observations.
Items and sources have different price scales and activity. Log prices or returns can make errors more comparable across cheap cases and expensive knives, but the final output should still be converted back to the exact target's USD meaning.
Split data chronologically
Randomly mixing rows from the same time series between training and test sets leaks adjacent market conditions. Use chronological windows:
training window -> validation window -> untouched test windowTune features and parameters on the validation window, then evaluate once on the later test window. A second item-held-out test shows whether the model generalizes beyond skins it saw during training.
Compare the model with simple baselines:
- last observed target value;
- trailing median;
- trailing moving average;
- no-change return.
Report mean absolute error in USD, a scale-aware percentage or log error, direction accuracy when direction matters, and coverage after missing rows are removed. Break results down by item, source, horizon, and activity level. An overall average can hide a model that works on liquid cases and fails on thin variants.
Evaluate forecasts as decisions separately
A lower prediction error does not automatically create a profitable strategy. If the intended action is buying or selling, run a separate backtest that applies the bid-ask difference, marketplace fees, fixed costs, delayed observations, and a later eligible price.
Do not score a predicted ask against a completed-sale result or assume the predicted price was available for the desired quantity. The backtesting guide covers point-in-time decisions and fill assumptions.
Serve forecasts with their evidence
Store each forecast as an immutable record:
model_version, feature_version, generated_at, data_cutoff
market_hash_name, variant_version, source, target, horizon
predicted_value, baseline_value, confidence
latest_observation_time, missing_feature_countSuppress or lower confidence when the current source is stale, required features are missing, the item falls outside the training distribution, or recent errors for that segment exceed your limit. Keep the observed market value and the model prediction in separate fields.
A prediction is an estimate conditioned on retained market history. It cannot anticipate an unobserved game update, marketplace policy change, or other new event. Product copy should state the target, horizon, data cutoff, and historical error instead of presenting a forecast as a guaranteed future price.
References: OHLC price history, long-term archive, BUFF sale history, CSFloat completed sales, Steam sale history, liquidity, schema, and historical coverage.