#!/usr/bin/env python3 """ ЗЕРКАЛО НА VERCEL — снапшот грид-конфигов, переживающий падение арены Почему это работает именно для грида: копируется КОНФИГ, а не сделки. Конфиг меняется редко (только при сдвиге центра сетки), поэтому статический снимок, обновляемый раз в 15 минут, ничем не хуже живого API. Когда арена лежит — агенты читают зеркало и продолжают работать. Публикует: /grid/configs.json список сеток /grid/snapshot.json всё целиком /grid/performance.json трек-рекорд /grid/.json конфиг + уровни + инструкция / человекочитаемая страница Запуск: python3 push_to_vercel.py # разово python3 push_to_vercel.py --loop 900 # каждые 15 минут """ import argparse, base64, json, os, sys, time, urllib.request, urllib.error HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) VERCEL_TOKEN = os.environ.get("VERCEL_TOKEN", "vcp_8bLq4YMdEIJXCSUdfmjdmJ4DubQ0izZTHsTkShJqVTuQQPqGll1KbTq6") TEAM = "team_oVflHvk6poDyQRHW88LCTcg8" PROJECT = "grid-mirror" ALIAS = "grid-mirror.vercel.app" LOCAL_API = os.environ.get("GRID_API", "http://localhost:8101") def fetch_local(path): with urllib.request.urlopen(LOCAL_API + path, timeout=20) as r: return json.load(r) def build_page(snap): grids = snap.get("grids", []) rows = "" for g in grids: c, p = g["config"], g["performance"] apr = f"{p['apr_pct']}%" if p.get("apr_pct") is not None else "—" fr = f"{p['fill_ratio_pct']}%" if p.get("fill_ratio_pct") is not None else "—" rows += f"""{g['symbol']}{c['step_pct']}% {c['levels']}${c['unit_usd']:.0f}${c['capital_required_usd']:,.0f} {p['fills']}${p['pnl_usd']}{apr}{fr} JSON""" upd = time.strftime("%Y-%m-%d %H:%M UTC", time.gmtime(snap.get("updated", time.time()))) return f""" Grid Mirror — конфиги для копирования
зеркало · обновлено {upd}

Grid Mirror

Конфигурации грид-сеток для копирования. Работает, даже когда основная арена недоступна. paper-режим · not financial advice

Активные сетки

{rows or ''}
СимволШагУровнейРазмерКапитал ФилловPnLAPRFill-ratio
нет активных сеток

Подключение

GET /grid/configs.json — список сеток
GET /grid/SYMBOL.json — конфиг, уровни, инструкция копирования
GET /grid/performance.json — трек-рекорд
GET /grid/snapshot.json — всё одним запросом

Что копируется

Конфигурация сетки, а не отдельные сделки. У грида нет входа и выхода — есть лестница лимитных заявок. При шаге 0.8% одна сделка приносит около $0.65 со $100, и задержка копирования съедает этот заработок целиком. Поэтому ты ставишь такую же сетку один раз, и дальше она работает автономно — без зависимости от нашей задержки и аптайма.

Два правила

  • Шаг ≥ 4× комиссий за круг. При 0.15% комиссий это минимум 0.6%. При шаге 0.22% бирже уходит 68% дохода — так предыдущая версия потеряла $5 419.
  • Никаких стопов на инвентаре. Вышла цена из диапазона — ждём, не режем. Цена возвращалась к уровню входа в 88% случаев, 80% из них — за сутки. Принудительные закрытия стоили $6 571.

Риски

  • Это paper. Живого трека реальными деньгами нет.
  • Модель валидирована на 48 наблюдениях «символ×месяц» реальными свечами Binance — все положительные. Прошлое не гарантирует будущего.
  • Грид неявно длинный: без хеджа в падающем рынке копится инвентарь.

Основной источник: арена · это зеркало обновляется периодически и служит запасным каналом.

""" def deploy(files): payload = {"name": PROJECT, "files": files, "target": "production", "projectSettings": {"framework": None, "buildCommand": "", "installCommand": "", "outputDirectory": "."}} def api(url, data=None, method="GET"): req = urllib.request.Request(url, data=(json.dumps(data).encode() if data else None), headers={"Authorization": "Bearer " + VERCEL_TOKEN, "Content-Type": "application/json"}, method=method) return json.load(urllib.request.urlopen(req, timeout=90)) j = api(f"https://api.vercel.com/v13/deployments?teamId={TEAM}&forceNew=1", payload, "POST") did = j["id"] for _ in range(14): time.sleep(5) st = api(f"https://api.vercel.com/v13/deployments/{did}?teamId={TEAM}").get("readyState") if st == "READY": break if st == "ERROR": raise RuntimeError("деплой упал") al = api(f"https://api.vercel.com/v2/deployments/{did}/aliases?teamId={TEAM}", {"alias": ALIAS}, "POST") return did, al.get("alias") or ALIAS def once(): snap = fetch_local("/api/grid/snapshot") files = {} def add(path, obj): raw = json.dumps(obj, ensure_ascii=False, indent=1).encode() if not isinstance(obj, bytes) else obj files[path] = base64.b64encode(raw).decode() add("grid/snapshot.json", snap) add("grid/configs.json", fetch_local("/api/grid/configs")) add("grid/performance.json", fetch_local("/api/grid/performance")) for g in snap.get("grids", []): sym = g["symbol"] add(f"grid/{sym}.json", fetch_local(f"/api/grid/config/{sym}")) files["index.html"] = base64.b64encode(build_page(snap).encode()).decode() files["vercel.json"] = base64.b64encode(json.dumps( {"cleanUrls": False, "headers": [{"source": "/grid/(.*)", "headers": [ {"key": "Access-Control-Allow-Origin", "value": "*"}, {"key": "Cache-Control", "value": "public, max-age=60"}]}]}).encode()).decode() fl = [{"file": k, "data": v, "encoding": "base64"} for k, v in files.items()] did, alias = deploy(fl) print(f"[{time.strftime('%H:%M:%S')}] опубликовано: {len(fl)} файлов → https://{alias} ({did[:18]}…)") return alias if __name__ == "__main__": ap = argparse.ArgumentParser() ap.add_argument("--loop", type=int, default=0, help="секунд между обновлениями") a = ap.parse_args() if a.loop: while True: try: once() except Exception as e: print("ошибка:", str(e)[:120]) time.sleep(a.loop) else: once()