Исходный код, разобранный в статье, опубликован в этом репозитории

Экосистема языка Pine Script содержит огромное количество инструментов технического анализа. Однако, сам Trading View представлен не везде. Например, региональные фондовые биржи развивающихся рынков, которых на TradingView вообще нет — ни данных, ни торговли.
-
MSE (Монголия) — нет
-
UZSE (Узбекистан) — нет
-
DSE (Дакка, Бангладеш) — нет
-
GSE (Гана) — нет
-
SGBV (Алжир) — нет
-
BSE (Ботсвана) — нет
-
ESE (Эсватини) — нет
-
MERJ (Сейшелы) — нет
Почему просто не использовать их сайты?
Когда вместо понятного графика цены я вижу сырой aggregate trades с возможностью скачать в Excel только месяц (с учётом 6 часового рабочего дня и постоянными выходными), я чувствую себя слепым. Кнопка регистрации ведёт в никуда.

Помимо графика цены, чтобы не ограничиваться стандартным Excel, хотелось бы применить весь спектр наработок Open Source, чтобы не изобретать велосипед.

Собираем свечи из выполненных сделок
Первый скрипт скачивает страницы с сделками. Можно было бы использовать Excel, но там не указаны минуты
async function main() { const buildUrl = (p: number) => `https://uzse.uz/trade\_results?begin=${begin}&end=${end}&mkt_id=${mktId}&page=${p}&search_key=${symbol}`; const browser = await chromium.launch({ headless: true }); const page = await browser.newPage(); const firstHtml = await fetchPage(page, buildUrl(1)); fs.writeFileSync(path.join(TMP_DIR, "trades_page_1.html"), firstHtml, "utf8"); const totalPages = getLastPage(firstHtml); console.log(`Total pages: ${totalPages}`); for (let p = 2; p <= totalPages; p++) { const html = await fetchPage(page, buildUrl(p)); fs.writeFileSync(path.join(TMP_DIR, `trades_page_${p}.html`), html, "utf8"); console.log(`Downloaded page ${p}/${totalPages}`); } await browser.close(); console.log(`Done. HTML saved to ${TMP_DIR}`);}
Второй скрипт импортирует в MongoDb
function parseHtmlTable(html: string, pageIndex: number) { const rows: object[] = []; const trRegex = /<tr[\s\S]*?<\/tr>/gi; const tdRegex = /<td[^>]*>([\s\S]*?)<\/td>/gi; const tagRegex = /<[^>]+>/g; let rowIndex = 0; const urlKey = extractUrlKey(html); let trMatch: RegExpExecArray | null; while ((trMatch = trRegex.exec(html)) !== null) { const rowHtml = trMatch[0]; const cells: string[] = []; let tdMatch: RegExpExecArray | null; while ((tdMatch = tdRegex.exec(rowHtml)) !== null) { cells.push(tdMatch[1].replace(tagRegex, " ").replace(/\s+/g, " ").trim()); } if (cells.length < 10) continue; const symbolParts = cells[2].split(/\s+/).filter(Boolean); const volumeParts = cells[9].split(/\s+/).filter(Boolean); const time = parseRuDate(cells[0]); const symbol = symbolParts[0] ?? ""; const tradePrice = parseNumber(cells[7]); const quantity = parseNumber(cells[8]); const volume = parseNumber(volumeParts[volumeParts.length - 1] ?? ""); const hash = crypto .createHash("sha1") .update(`${symbol}|${time?.toISOString()}|${tradePrice}|${quantity}|${volume}|${pageIndex}|${rowIndex}|${urlKey}`) .digest("hex"); rowIndex++; rows.push({ time, symbol, issuer: cells[3], securityType: cells[4], market: cells[5], platform: cells[6], tradePrice, quantity, volume, hash }); } return rows.filter((r: any) => r.time !== null);}
Видим график

Торги не велись неделю с 12.08.2023 по 21.08.2023. Выглядит страшно, уточняем Claude что произошло в этот промежуток времени. Получаем аргументированный ответ со ссылками
Причина обвала: допэмиссия с увеличением уставного капитала в 3 раза
На годовом общем собрании акционеров 26 мая 2023 года было принято решение об увеличении уставного капитала банка с 107,77 млрд сумов до 323,32 млрд сумов — то есть в ~3 раза — за счёт нераспределённой прибыли. Выпуск новых акций был зарегистрирован регулятором 7 августа 2023 года.
Размещение акций проходило по закрытой подписке среди существующих акционеров, включённых в реестр, сформированный на 10-й день с момента государственной регистрации выпуска — то есть реестр фиксировался примерно 17 августа 2023 года.
Прибыль не выплачивается деньгами, а конвертируется в акции.
Смотрим теханализ
Я взял первый попавшийся индикатор. Вроде бы, продаём на цене красной медвежей линии и покупаем на зелёной бычей. Важно, что заработала сложная визуализация.
Так же можно посмотреть историю. Видно: цена несколько раз уходила далеко выше SMA(20). Тот же паттерн: перед этим несколько дней без торгов. Выглядит как single-price auction day.
Как подключить свечи
Файл ./modules/editor.module.ts
import { addExchangeSchema } from "backtest-kit";import { CandleModel } from "../schema/Candle.schema";import "../config/setup";addExchangeSchema({ exchangeName: "mongo-exchange", getCandles: async (symbol, interval, since, limit) => { const candles = await CandleModel.find( { symbol, interval, timestamp: { $gte: since.getTime() } }, { timestamp: 1, open: 1, high: 1, low: 1, close: 1, volume: 1, _id: 0 } ) .sort({ timestamp: 1 }) .limit(limit) .lean(); return candles.map(({ timestamp, open, high, low, close, volume }) => ({ timestamp, open, high, low, close, volume, })); },});
Как запустить
Через npm start
{ "name": "backtest-kit-project", "version": "1.0.0", "description": "Backtest Kit trading bot project", "main": "index.js", "homepage": "https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html", "scripts": { "start": "node ./node_modules/@backtest-kit/cli/build/index.mjs --editor" }, "keywords": [], "author": "", "license": "ISC", "type": "commonjs", "dependencies": { "@backtest-kit/cli": "^7.1.0", "@backtest-kit/graph": "^7.1.0", "@backtest-kit/pinets": "^7.1.0", "@backtest-kit/ui": "^7.1.0", "agent-swarm-kit": "^2.5.1", "backtest-kit": "^7.1.0", "functools-kit": "^2.2.0", "garch": "^1.2.3", "get-moment-stamp": "^1.1.2", "mongoose": "^8.23.0", "ollama": "^0.6.3", "playwright": "^1.59.1", "volume-anomaly": "^1.2.3" }}
Что интересно
-
Участники рынка не могут нанять программиста Pine Script
Его как сложно найти на рынке, так и некуда пристроить: нет инфраструктуры Trading View. Написать визуализацию индикаторов технически сложная задача, которую ИИ не решит, нужен человек
-
Новостной сентимент не действует на цену акций
Индикатор калибруется на исторических данных. Исторические данные невалидны, если изменился сентимент рынка. Новости Узбекистана не медийны
-
Политика партии — инвестиции
Единственный источник новостей который влияет на сентимент это президент республики. Он назвал общей ответственностью всего международного сообщества помощь таким регионам путём инвестиций в их экономику и развитие.
Спасибо за внимание!
Тут можно почитать про влияние новостного сентимента на рынок
ссылка на оригинал статьи https://habr.com/ru/articles/1026010/