Свой скринер
Скринер — небольшая библиотека на Rust или модуль на TypeScript, собранные в WebAssembly. Терминал запускает плагин в песочнице: своей сети, файлов и часов у него нет. Данные он получает от терминала — тикеры, списки инструментов, HTTPS-запросы к API бирж из разрешённого списка, облачную историю — и возвращает строки таблицы. Клик по строке открывает стакан её инструмента.
Всё для разработки собрано в открытом SDK
space-screener-sdk:
утилита командной строки st, библиотека для Rust, шаблоны проектов и
шесть примеров. Страница техническая: нужны командная строка и базовое
знание Rust или TypeScript. Не хотите писать код сами — поручите это
Claude: Скринер с помощью Claude.
Что понадобится
Заголовок раздела «Что понадобится»- Space Terminal 0.104.71 или новее, запущенный на этом компьютере,
с подключёнными биржами. Сервер Локальный API должен быть включён
(по умолчанию так и есть, см. Локальный API): через
него
stставит плагин в терминал. - Rust 1.91 или новее с
cargo— установка на rustup.rs, обновление —rustup update. Нужен и для TypeScript:stставится черезcargo. - Node.js 18 или новее — только для скринеров на TypeScript.
Установка st
Заголовок раздела «Установка st»cargo install --git https://github.com/Space-Terminal/space-screener-sdk stst --versionЦель сборки wasm32-unknown-unknown st добавит сам через rustup
при первой сборке. Для TypeScript st при первой сборке один раз
запустит npm install в проекте и скачает сборщики extism-js и
binaryen, проверив их контрольные суммы.
Новый проект
Заголовок раздела «Новый проект»st init my-screener --id you.my-screenercd my-screener--id — имя скринера, уникальное во всём каталоге: латиница в нижнем
регистре, цифры, ., _ и -, от 3 до 64 символов, начинается и
заканчивается буквой или цифрой. Принято писать автор.название.
Без --id получится local.<папка>. Имена install и sync
заняты. Для TypeScript добавьте --lang ts.
Что появится в папке:
| Файл | Что в нём |
|---|---|
manifest.yaml | Описание скринера: имя, колонки, параметры, адреса, частота обновления |
src/lib.rs | Код на Rust. Шаблон — рабочий скринер «самые сильные движения за сутки» по подключённым фьючерсным биржам |
Cargo.toml | Проект Rust, уже настроенный на сборку в wasm |
src/index.ts, package.json, tsconfig.json | То же для TypeScript (--lang ts) |
pdk/ | Только TypeScript: библиотеку плагина пишет и обновляет st — не правьте её |
Манифест
Заголовок раздела «Манифест»Фрагмент manifest.yaml, который создаёт st init:
abi: 1id: you.my-screenerversion: 0.1.0name: {ru: "Мой скринер", en: "My screener"}lang: rustmin_terminal: 0.104.71http: [] # адреса для HTTPS-запросов, например [fapi.binance.com]timer_ms: 10000 # как часто вызывать on_timer, мсcolumns: - {key: symbol, type: symbol, title: {ru: "Тикер", en: "Symbol"}} - {key: exchange, type: exchange, title: {ru: "Биржа", en: "Exchange"}} - {key: change, type: percent, title: {ru: "Изм. 24ч", en: "24h change"}, sort: desc} - {key: volume, type: usd, title: {ru: "Объём 24ч", en: "24h volume"}}params: - {key: min_volume, type: number, title: {ru: "Мин. объём, $", en: "Min volume, $"}, default: 1000000, min: 0}| Поле | Обязательное | Что задаёт |
|---|---|---|
abi | да | Версия контракта с терминалом, всегда 1 |
id | да | Имя скринера — см. выше |
version | да | Версия вида 1.2.0. Для каталога — без хвоста +… |
name | да | Название на русском и английском |
lang | да | rust или ts |
min_terminal | да | Минимальная версия терминала. st init пока подставляет 0.104.70 — поставьте 0.104.71: плагины появились в этой версии |
http | нет | Точные адреса, к которым плагин ходит по HTTPS: fapi.binance.com, без https://, без IP-адресов и localhost. К остальным запрос не пройдёт |
timer_ms | нет | Пауза между вызовами on_timer: от 250 мс до часа, по умолчанию 1000 |
columns | да | Колонки таблицы |
params | нет | Параметры, которые пользователь меняет в окне параметров — шестерёнка в шапке панели |
limits | нет | memory_mb — до 64, cpu_ms_per_call — от 50 до 1000 мс процессора на вызов, по умолчанию 250 |
history | нет | Доступ к облачной истории: cluster — кластеры, replay — запись ленты и стакана |
signals | нет | Сигналы сервера Space: activity — активность инструментов, density — плотности, prints — серии одинаковых сделок. Терминал отдаёт только density. Нужен min_terminal: 0.104.72 — см. Сигналы Space |
description | для каталога | Текст карточки на русском и английском, до 2000 символов |
categories | для каталога | От одной до трёх категорий — таблица ниже |
source | нет | Ссылка https:// на исходный код |
Колонка — {key, type, title, sort, width, show_if}. key — имя
ячейки в строках, title — заголовок на двух языках, sort: desc или
asc у первой такой колонки задаёт сортировку по умолчанию, width —
ширина в пикселях, от 1 до 2000. Ключ колонки не может повторяться.
show_if — ключ параметра типа bool: пока он выключен, терминал
прячет колонку, включили — колонка вернулась, без переустановки. Так
делается флажок «показывать колонку» в окне параметров:
columns: - {key: chg5, type: percent, title: {ru: "OI 5м", en: "OI 5m"}, show_if: col_chg5}params: - {key: col_chg5, type: bool, title: {ru: "Колонка OI 5м", en: "OI 5m column"}, default: true}Если show_if ссылается не на bool-параметр, манифест не пройдёт
проверку (invalid_manifest у columns[i].show_if). Терминал 0.104.73
и новее прячет такие колонки, более старые показывают их всегда — новый
min_terminal не нужен. Ячейки скрытой колонки строки могут присылать
и дальше. Кроме того, пользователь сам скрывает любые колонки в окне
параметров — это настройка панели, плагину она не передаётся.
| Тип колонки | Что показывает |
|---|---|
text | Текст |
number, integer | Число, целое число |
percent | Проценты — значение уже в процентах: 1.5 — это 1,5 % |
usd, price | Сумма в долларах, цена |
time | Время — миллисекунды с 1970 года |
duration | Длительность в миллисекундах |
countdown | Обратный отсчёт до момента — миллисекунды с 1970 года |
symbol, exchange | Тикер, биржа |
exchanges | Несколько бирж со значками спота и фьючерсов |
bool | Да или нет |
Параметр — {key, type, title, default, min, max, options}. Типы:
number, integer, bool, text (до 256 символов) и select —
выбор из options. default обязателен и должен подходить под тип и
границы.
Категории для каталога — значения categories и как они называются
на витрине:
| Значение | На витрине |
|---|---|
volume | Объём |
open-interest | Открытый интерес |
funding | Фандинг |
spread | Спред |
movers | Движения цены |
listings | Листинги |
orderbook | Стакан |
other | Другое |
Скринер на Rust — структура с методом on_timer, который терминал
вызывает раз в timer_ms: собрать данные, посчитать, отдать строки.
Упрощённый шаблон — в полном ошибка одной биржи не останавливает
остальные, а строки сортируются и обрезаются по параметру:
use space_screener::prelude::*;
#[derive(Default)]struct MyScreener;
impl Screener for MyScreener { fn on_timer(&mut self, _now_ms: i64) -> ScreenerResult { let min_volume = params().f64_or("min_volume", 1_000_000.0); let mut rows = Vec::new(); for ex in exchanges()? { if !ex.connected || ex.market != Market::Futures { continue; } for t in tickers(&ex.exchange, ex.market)?.tickers { if t.volume_quote < min_volume { continue; } rows.push( Row::new(format!("{}:{}", ex.exchange, t.symbol)) .market_ref(&MarketRef::new(&ex.exchange, ex.market, &t.symbol)) .cell("symbol", t.symbol.as_str()) .cell("exchange", ex.exchange.as_str()) .cell("change", Cell::signed(t.change_pct)) .cell("volume", t.volume_quote), ); } } replace_rows(rows)?; Ok(()) }}
export_screener!(MyScreener);- Данные.
exchanges()— биржи, которые подключил пользователь;tickers()— суточные тикеры терминала;symbols()— список инструментов;http()иhttp_batch()— HTTPS-запросы к адресам изhttpманифеста,http_batch()отправляет много запросов параллельно. - Строки.
Row::new(ключ)— ключ строки уникален и стабилен;.cell(колонка, значение)— ячейки, ключи должны быть колонками манифеста;Cell::signed()красит значение в зелёный или красный по знаку.replace_rows()заменяет всю таблицу,emit_rows()обновляет строки по ключу. - Порядок и избранное.
.rank(n)— порядок строк внутри данных плагина: строки с большимrankстоят выше при любой сортировке — например, закреплённые плагином строки сверху, чёрный список внизу. Избранное тикеров делает терминал: он сам рисует ★ в каждом скринере и держит избранные тикеры выше всех остальных строк, так что своя колонка избранного плагину не нужна. Фильтры Биржи и Секторы терминал тоже применяет сам — по бирже, рынку и тикеру строки. - Клик. Строка с
.market_ref(…)по клику сама открывает стакан своего инструмента — писать для этого код не нужно. Открыть стакан или стакан с графиком, заменить открытые панелью стаканы или добавить колонку — выбирает пользователь в блоке Клик по тикеру окна параметров. Стрелки ↑/↓ в панели кликают выбранную строку: плагин сon_clickполучает такой же вызов, как от левого клика мышью вне колонки, —columnпустой, без модификаторов. - Статус и сигналы.
set_status()— короткая строка статуса в подсказке у точки в шапке панели.alert()— всплывающее сообщение и запись в колокольчике, не чаще 6 в минуту. - Параметры и память.
params()— текущие значения параметров. Состояние в памяти пропадает при перезапуске плагина; то, что должно его пережить, сохраняйте черезkv_set()— до 1 МиБ. - Лог.
info!,warn!иerror!пишут в лог плагина — его показываетst logs.
Полный список функций, пример открытого интереса по восьми биржам и все тонкости — в SKILL.md и ABI.md, готовые проекты — в examples: открытый интерес по восьми биржам, самые сильные движения, ставки фандинга, спред одной пары между биржами, плотности из сигналов Space и всплески объёма на TypeScript.
На TypeScript функции те же: exchanges(), tickers(),
replaceRows(), setStatus(), параметры — num("min_volume", 1000000).
Две особенности: код на верхнем уровне модуля выполняется один раз при
сборке, поэтому обращайтесь к терминалу только внутри init и
on_timer; TypeScript-плагин работает заметно медленнее такого же на
Rust.
Сигналы Space
Заголовок раздела «Сигналы Space»Сервер Space сам считает сигналы по всем биржам: крупные плотности в стаканах, активность инструментов и серии одинаковых сделок. Терминал держит к нему живое подключение, а плагин получает эти сигналы от терминала — без своих запросов в сеть. Так устроен скринер Space Density; на тех же сигналах можно сделать свой.
- Манифест. Перечислите нужные источники в
signals, напримерsignals: [density], и поставьтеmin_terminal: 0.104.72— более старый терминал такой плагин не поставит. - Данные.
SignalFeed::new(SignalSource::Density)держит текущий набор сигналов;poll()в каждомon_timerподтягивает изменения с прошлого вызова,iter()перебирает сигналы,connected()говорит, свежие ли данные. Числа в сигналах — для отображения. - Доступность источников. Терминал отдаёт
density. Источникиactivityиprintsон не отдаёт —signalsотвечает ошибкойunavailable, покажите пользователю статус черезset_status(). Если источник не перечислен вsignalsманифеста, ошибка будет другая —not_permitted. - Клик. Свой обработчик клика — метод
on_clickиexport_screener!(MyScreener, on_click). В нём доступны ещё два действия, одно на клик:open_markets(&[…])открывает до 16 стаканов разом,open_market_with_level(&market, &SmartLevel::new(id))открывает стакан и ставит умный уровень на цене плотности с этимid— точную цену и метрики терминал берёт из своих данных.
Готовый пример — examples/density, формат сигналов и все поля — в разделе v1.1 ABI.md.
Сборка и проверка
Заголовок раздела «Сборка и проверка»st build # собрать screener.wasm и проверить его по правилам терминалаst validate # проверить манифест и уже собранный модульst build проверяет модуль так же, как это сделает терминал, и
печатает все ошибки. Строка catalog: ready в выводе st validate
значит, что заполнены поля для каталога.
Запуск в терминале
Заголовок раздела «Запуск в терминале»- Запустите терминал и подключите биржи, данные которых нужны скринеру.
- В папке проекта выполните
st dev. Утилита соберёт плагин, поставит его в терминал, и терминал сам откроет вкладку с его панелью.st devостаётся работать: при каждом сохранении файла плагин пересобирается и переставляется, лог плагина идёт в консоль. Флаг--no-open— не открывать панель. - Проверьте таблицу в панели и кликните строку — должен открыться стакан.
- В соседнем окне консоли:
st rows— строки так, как их видит терминал;st logs -f— лог плагина;st list— все установленные плагины и их статус.
Плагины, поставленные через st dev, видны в списке выбора панели
Скринеры под разделителем Space Market
вместе с каталожными. Скринер из
каталога с тем же именем терминал поверх вашей версии не ставит.
st находит терминал по файлу screeners/local_api_token в папке
данных терминала и берёт порт из его настроек, по умолчанию 5055.
Папка данных — ~/Library/Application Support/Space Terminal на macOS,
%APPDATA%\Space Terminal на Windows, ~/.local/share/Space Terminal
на Linux. Другую папку задаёт переменная SPACE_TERMINAL_DIR, другой
порт — флаг --port или переменная SPACE_TERMINAL_PORT.
Отдельной команды удаления в st нет. Удалить свой плагин из
терминала можно запросом к локальному API, например на macOS:
curl -X DELETE \ -H "Authorization: Bearer $(cat "$HOME/Library/Application Support/Space Terminal/screeners/local_api_token")" \ http://127.0.0.1:5055/api/v1/screeners/you.my-screenerЗапись и тесты
Заголовок раздела «Запись и тесты»Проверять каждую правку в живом терминале долго. Запишите данные один раз и прогоняйте плагин на записи без терминала:
st record --seconds 120 # плагин должен работать в открытой панелиst test # прогнать все записи и сравнить результатst record перезапускает плагин в терминале и сохраняет всё, что
терминал ему отвечал, в recordings/<время>.json; длительность — от 5
до 600 секунд. st test прогоняет каждую запись и сравнивает строки,
всплывающие сообщения, статус и клики с файлом
recordings/<запись>.expected.json — первый прогон его создаёт. После
намеренной правки обновите эталон: st test --update. Тест падает и
тогда, когда плагин вернул ошибку, упал или вышел за лимит процессора.
Для публикации запись должна быть не больше 4 МиБ — если больше,
st record предупредит, сколько секунд записывать.
| Что | Лимит |
|---|---|
| Память | До 64 МБ |
| Процессор на вызов | cpu_ms_per_call: по умолчанию 250 мс, от 50 до 1000. Ожидание сети не считается |
| Процессор в среднем | Не больше 30 секунд за любые 60 секунд — половина ядра |
| Время одного вызова | До 600 секунд вместе с ожиданием сети |
| Нарушения | 3 нарушения за последние 10 вызовов — плагин останавливается со статусом превышен лимит |
| HTTP | Тайм-аут от 1 до 10 секунд, ответ до 8 МиБ. К fapi.binance.com — не чаще 5 запросов в секунду |
| Таблица | До 10 000 строк, строка — до 16 КиБ |
Сохранённые данные (kv) | До 1 МиБ |
| Всплывающие сообщения | Не чаще 6 в минуту |
Модуль screener.wasm | До 10 МиБ; функций — до 10 000, одна функция — до 256 КиБ кода |
После сбоя терминал перезапускает плагин сам; память при этом
теряется, kv сохраняется.
Частые ошибки
Заголовок раздела «Частые ошибки»| Сообщение | Что делать |
|---|---|
cannot read the terminal token | Терминал ещё ни разу не запускался на этом компьютере, или SPACE_TERMINAL_DIR указывает не туда |
Space Terminal is not reachable | Терминал не запущен, выключен Локальный API или сменён порт — укажите его флагом --port |
unauthorized: the terminal rejected the token | Токен взят из папки другого терминала — проверьте SPACE_TERMINAL_DIR |
this Space Terminal build has no screener API | Терминал старше 0.104.71 — обновите его |
forbidden_import: wasi_snapshot_preview1::… | Код обращается к файлам, сети, потокам или системному времени напрямую. Собирайте через st build, данные берите у терминала |
паника time not implemented | Системного времени в плагине нет — берите now_ms из on_timer или now_ms() |
host_not_allowed | Адреса нет в http манифеста — добавьте его точное имя |
| ответ 301 или 302 с пустым телом | Переадресации не выполняются — обращайтесь сразу по конечному адресу |
invalid_wasm: function N: … KiB of code | Одна функция слишком большая — обычно огромный match или таблица прямо в коде. Вынесите данные в static, разбейте функцию |
| статус превышен лимит | Разбирайте из ответов бирж только нужные поля, отдавайте меньше строк, поднимите cpu_ms_per_call, реже вызывайте on_timer |
not_in_click | Открыть стакан из кода можно только внутри on_click |
st test: N not recorded | Код запрашивает данные, которых нет в записи, — запишите заново |
Полная таблица — в разделе «Pitfalls» SKILL.md.
Что дальше
Заголовок раздела «Что дальше»- Публикация в каталог — как отдать скринер другим.
- Скринер с помощью Claude
- Space Market — как скринер выглядит у пользователя.