Подробный гайд: полноценный Python-скрипт калькулятора Trade Up для CS2 (Counter-Strike 2)

Python-скрипт для анализа контрактов Trade Up в CS2 (Counter-Strike 2). Точный расчёт вероятностей, EV, ROI и Monte-Carlo симуляция для оценки прибыли.

2026.05.19                  


Подробный гайд: полноценный Python-скрипт калькулятора Trade Up для CS2 (Counter-Strike 2)Подробный гайд: полноценный Python-скрипт калькулятора Trade Up для CS2 (Counter-Strike 2) Вот полноценный Python-скрипт калькулятора Trade Up для CS2.

Он включает:

  • Точный расчёт вероятностей по коллекциям
  • Математическое ожидание (EV) и ROI
  • Monte-Carlo симуляцию для оценки дисперсии
  • Валидацию правил контракта (10 скинов, одинаковая редкость, одинаковый StatTrak)
  • Готовую структуру для подключения реальных цен через API
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CS2 Trade Up Contract Calculator
Расчёт вероятностей, EV, ROI и Monte-Carlo симуляция контрактов обмена.
"""

import random
import argparse
from dataclasses import dataclass
from typing import List, Dict, Tuple
from collections import Counter

@dataclass
class Skin:
    name: str
    collection: str
    rarity: str
    price: float
    stattrak: bool = False
    float_val: float = 0.0  # Опционально, для справки

RARITY_ORDER = [
    "Consumer Grade", "Industrial Grade", "Mil-Spec",
    "Restricted", "Classified", "Covert"
]

# База возможных исходов: коллекция -> редкость -> список скинов следующего тира
# В продакшене загружайте из JSON/API Steam/CSGOMarket
OUTCOME_DB: Dict[str, Dict[str, List[Skin]]] = {
    "The Safehouse Collection": {
        "Restricted": [
            Skin("Glock-18 | Ground Water", "The Safehouse Collection", "Restricted", 4.20),
            Skin("MAC-10 | Urban DDPAT", "The Safehouse Collection", "Restricted", 3.85),
            Skin("UMP-45 | Scorched", "The Safehouse Collection", "Restricted", 5.10),
        ]
    },
    "The Cache Collection": {
        "Restricted": [
            Skin("M4A1-S | Atomic Alloy", "The Cache Collection", "Restricted", 12.50),
            Skin("AWP | Worm God", "The Cache Collection", "Restricted", 18.90),
            Skin("AK-47 | Red Laminate", "The Cache Collection", "Restricted", 22.30),
        ]
    },
    "The Cobblestone Collection": {
        "Classified": [
            Skin("M4A4 | 龍王 Dragon King", "The Cobblestone Collection", "Classified", 85.0),
            Skin("AK-47 | Fire Serpent", "The Cobblestone Collection", "Classified", 320.0),
            Skin("USP-S | Kill Confirmed", "The Cobblestone Collection", "Classified", 45.5),
        ]
    }
}

def validate_inputs(skins: List[Skin]) -> str:
    if len(skins) != 10:
        return " Требуется ровно 10 скинов."
    rarities = {s.rarity for s in skins}
    if len(rarities) > 1:
        return " Все скины должны быть одной редкости."
    if skins[0].stattrak != any(s.stattrak != skins[0].stattrak for s in skins):
        return " Все скины должны иметь одинаковый StatTrak статус."
    current_rarity = skins[0].rarity
    if current_rarity not in RARITY_ORDER or RARITY_ORDER.index(current_rarity) >= len(RARITY_ORDER) - 1:
        return " Невозможно улучшить скины максимальной редкости."
    return ""

def calculate_trade_up(input_skins: List[Skin], outcome_db: Dict) -> Dict:
    err = validate_inputs(input_skins)
    if err:
        raise ValueError(err)

    current_rarity = input_skins[0].rarity
    target_rarity = RARITY_ORDER[RARITY_ORDER.index(current_rarity) + 1]

    # Считаем, сколько скинов из каждой коллекции
    coll_counts = Counter(s.collection for s in input_skins)

    # Собираем пул возможных исходов
    raw_outcomes = []
    for coll, count in coll_counts.items():
        if coll in outcome_db and target_rarity in outcome_db[coll]:
            for skin in outcome_db[coll][target_rarity]:
                raw_outcomes.append({"skin": skin, "prob": count / 10.0})

    # Агрегируем по уникальным скинам (если пересекаются коллекции)
    aggregated = {}
    for o in raw_outcomes:
        key = (o["skin"].name, o["skin"].stattrak)
        if key not in aggregated:
            aggregated[key] = {"skin": o["skin"], "prob": 0.0}
        aggregated[key]["prob"] += o["prob"]

    outcomes = list(aggregated.values())
    # Нормализация на случай погрешностей float
    total_prob = sum(o["prob"] for o in outcomes)
    for o in outcomes:
        o["prob"] /= total_prob

    total_cost = sum(s.price for s in input_skins)
    ev = sum(o["skin"].price * o["prob"] for o in outcomes)
    profit = ev - total_cost
    roi = (profit / total_cost) * 100 if total_cost > 0 else 0.0

    return {
        "target_rarity": target_rarity,
        "outcomes": outcomes,
        "total_cost": total_cost,
        "ev": ev,
        "profit": profit,
        "roi": roi
    }

def monte_carlo_sim(calc_result: Dict, trials: int = 100_000) -> Dict:
    outcomes = calc_result["outcomes"]
    total_cost = calc_result["total_cost"]

    # Подготовка для быстрого выбора
    names = [o["skin"].name for o in outcomes]
    prices = [o["skin"].price for o in outcomes]
    probs = [o["prob"] for o in outcomes]

    rng = random.Random(42)  # Детерминированный для воспроизводимости
    results = []
    for _ in range(trials):
        idx = rng.choices(range(len(names)), weights=probs, k=1)[0]
        results.append(prices[idx] - total_cost)

    results.sort()
    return {
        "trials": trials,
        "min_profit": results[0],
        "max_profit": results[-1],
        "avg_profit": sum(results) / trials,
        "median_profit": results[len(results)//2],
        "win_rate": sum(1 for p in results if p > 0) / trials * 100,
        "distribution_10p": results[int(trials * 0.1)],
        "distribution_90p": results[int(trials * 0.9)]
    }

def print_report(calc: Dict, sim: Dict = None):
    print("\n" + "="*50)
    print(" ОТЧЁТ TRADE UP CONTRACT")
    print("="*50)
    print(f" Целевая редкость: {calc['target_rarity']}")
    print(f" Стоимость контракта: ${calc['total_cost']:.2f}")
    print(f" Мат. ожидание (EV): ${calc['ev']:.2f}")
    print(f" Прогноз прибыли/убытка: ${calc['profit']:.2f}")
    print(f" ROI: {calc['roi']:.2f}%")

    print("\n Вероятности исходов:")
    for o in sorted(calc["outcomes"], key=lambda x: x["prob"], reverse=True):
        print(f"  • {o['skin'].name:<30} | {o['prob']*100:5.1f}% | ${o['skin'].price:.2f}")

    if sim:
        print("\n Monte-Carlo Симуляция ({} trials):".format(sim["trials"]))
        print(f"  Win Rate: {sim['win_rate']:.1f}%")
        print(f"  Мин. результат: ${sim['min_profit']:.2f}")
        print(f"  Макс. результат: ${sim['max_profit']:.2f}")
        print(f"  Средний: ${sim['avg_profit']:.2f} | Медиана: ${sim['median_profit']:.2f}")
        print(f"  10% квантиль: ${sim['distribution_10p']:.2f} | 90%: ${sim['distribution_90p']:.2f}")
    print("="*50 + "\n")

def parse_skins_from_args(args: List[str]) -> List[Skin]:
    """Парсинг формата: name,collection,rarity,price[,stattrak]"""
    skins = []
    for entry in args:
        parts = entry.split(",")
        if len(parts) < 4:
            raise ValueError(f"Неверный формат: {entry}. Ожидается: name,collection,rarity,price[,stattrak]")
        skins.append(Skin(
            name=parts[0].strip(),
            collection=parts[1].strip(),
            rarity=parts[2].strip(),
            price=float(parts[3].strip()),
            stattrak=len(parts) > 4 and parts[4].strip().lower() in ("true", "1", "yes")
        ))
    return skins

def main():
    parser = argparse.ArgumentParser(description="CS2 Trade Up Calculator")
    parser.add_argument("--skins", nargs="+", required=True,
                        help="10 скинов в формате: name,collection,rarity,price[,stattrak]")
    parser.add_argument("--trials", type=int, default=100000, help="Кол-во симуляций")
    args = parser.parse_args()

    try:
        input_skins = parse_skins_from_args(args.skins)
        calc = calculate_trade_up(input_skins, OUTCOME_DB)
        sim = monte_carlo_sim(calc, args.trials)
        print_report(calc, sim)
    except Exception as e:
        print(f" Ошибка: {e}")

if __name__ == "__main__":
    main()

Как использовать

python tradeup_calc.py --skins \
  "P250|Sand Dune,The Safehouse Collection,Mil-Spec,1.2" \
  "PP-Bizon|Sand Dashed,The Safehouse Collection,Mil-Spec,0.9" \
  "MAG-7|Sand Dune,The Safehouse Collection,Mil-Spec,1.5" \
  "Sawed-Off|Sand Dune,The Safehouse Collection,Mil-Spec,0.8" \
  "G3SG1|Safari Mesh,The Safehouse Collection,Mil-Spec,1.1" \
  "Nova|Safari Mesh,The Safehouse Collection,Mil-Spec,1.0" \
  "SG 553|Safari Mesh,The Safehouse Collection,Mil-Spec,1.3" \
  "Negev|Palm,The Safehouse Collection,Mil-Spec,0.7" \
  "Dual Berettas|Moon in Libra,The Safehouse Collection,Mil-Spec,1.4" \
  "Five-SeveN|Forest Leaves,The Safehouse Collection,Mil-Spec,1.2"

Подключение реальных цен

Для продакшена замените OUTCOME_DB на динамическую загрузку:

  1. Steam Market API (через спец сеть, т.к. CORS/блокировки)
  2. CSFloat / Market.csgo / Buff163 API (публичные эндпоинты)
  3. Локальный кэш JSON с автообновлением раз в 15 минут

Пример структуры для загрузки:

import requests, json

def load_live_prices():
    # Пример эндпоинта (замените на рабочий)
    res = requests.get("https://api.csgofloat.com/v1/prices")
    return {skin["name"]: skin["price"] for skin in res.json()}

Важные нюансы CS2 Trade Up

Фактор Влияние
StatTrak Остаётся неизменным. Контракт с ST даёт только ST-исход.
Float Усредняется: float_out = avg(float_inputs) * 0.99 + random(0, 0.01). Не влияет на вероятность, но влияет на цену.
Пересечение коллекций Если два выходных скина из разных коллекций имеют одинаковое имя, их вероятности суммируются.
Волатильность рынка EV считается на момент расчёта. Реальная прибыль зависит от цен в момент продажи.
Налоги/комиссии В расчёт не включены. Добавьте price * 0.87 для учёта комиссии Steam (13%).

Как расширить скрипт

  1. Добавьте --output json для экспорта в ботов
  2. Подключите pandas для анализа исторических контрактов
  3. Добавьте фильтр по float_range для расчёта Factory New / Minimal Wear
  4. Интегрируйте с selenium/playwright для парсинга цен с маркетплейсов

Важно:

В этом скрипте прописаны не все скины для cs2. Вам необходимо его доработать до полноценной базы, как в Steam.