Подробный гайд: полноценный 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 на динамическую загрузку:
- Steam Market API (через спец сеть, т.к. CORS/блокировки)
- CSFloat / Market.csgo / Buff163 API (публичные эндпоинты)
- Локальный кэш 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%). |
Как расширить скрипт
- Добавьте
--output jsonдля экспорта в ботов - Подключите
pandasдля анализа исторических контрактов - Добавьте фильтр по
float_rangeдля расчёта Factory New / Minimal Wear - Интегрируйте с
selenium/playwrightдля парсинга цен с маркетплейсов
Важно:
В этом скрипте прописаны не все скины для cs2. Вам необходимо его доработать до полноценной базы, как в Steam.