UNPKG

polynance_sdk

Version:

TypeScript SDK for prediction market aggregation supporting Polymarket, Limitless, and Truemarket

306 lines (268 loc) 9.07 kB
import axios, { AxiosInstance } from 'axios'; import { Protocol, PredictionMarketEvent, PredictionMarketComment, OrderBookSummary, FillEventData, PredictionMarket } from './types'; export interface PolynanceClientOptions { apiBaseUrl?: string; redisUrl?: string; sseBaseUrl?: string; } /** * SSE購読時に指定可能なハンドラーの型 */ export interface FillEventSSEHandlers { /** * SSE接続確立時のコールバック */ onOpen?: (ev: Event) => void; /** * SSEメッセージ受信時のコールバック(parse済み FillEventData が渡る) */ onMessage?: (data: FillEventData) => void; /** * SSE接続エラー時のコールバック */ onError?: (ev: Event) => void; } /** * SSE購読が返す購読インスタンスの型 */ export interface FillEventSubscription { /** * EventSource のインスタンス */ eventSource: EventSource; /** * SSE購読を終了(接続をClose)するためのメソッド */ close: () => void; /** * 直近の SSE メッセージ内容を取得するためのメソッド */ getLatestData: () => FillEventData | null; } /** * 予測市場のアグリゲータクライアント */ export class PolynanceClient { private apiClient: AxiosInstance; private sseBaseUrl: string; constructor(options?: PolynanceClientOptions) { const apiBaseUrl = options?.apiBaseUrl || 'http://57.180.216.102:9000'; this.sseBaseUrl = options?.sseBaseUrl || 'http://57.180.216.102:9000'; this.apiClient = axios.create({ baseURL: apiBaseUrl, timeout: 100000, headers: { 'Content-Type': 'application/json', }, }); } /** * 指定されたプロトコルとイベントIDに対応するイベント情報を取得 */ async getEvent(protocol: Protocol, eventId: string): Promise<PredictionMarketEvent> { try { const response = await this.apiClient.get(`/v1/events/${eventId}`, { params: { protocol }, }); return response.data; } catch (error) { this.handleError('getEvent', error); throw error; } } /** * 指定されたプロトコルとマーケットIDに対応するマーケット情報を取得 */ async getMarket(protocol: Protocol, marketId: string): Promise<PredictionMarket> { try { const response = await this.apiClient.get(`/v1/markets/${marketId}`, { params: { protocol }, }); return response.data; } catch (error) { this.handleError('getMarket', error); throw error; } } /** * 指定されたプロトコルの進行中のイベント一覧を取得 */ async getOngoingEvents(protocol: Protocol, page: number = 1, limit: number = 50): Promise<PredictionMarketEvent[]> { try { const response = await this.apiClient.get('/v1/ongoing-events', { params: { protocol, page, limit }, }); return response.data; } catch (error) { this.handleError('getOngoingEvents', error); throw error; } } /** * 指定されたプロトコルとイベントIDに対応するコメント一覧を取得 */ async getEventComments(protocol: Protocol, eventId: string): Promise<PredictionMarketComment[]> { try { const response = await this.apiClient.get(`/v1/events/${eventId}/comments`, { params: { protocol }, }); return response.data; } catch (error) { this.handleError('getEventComments', error); throw error; } } /** * 指定されたプロトコルとマーケットIDに対応するオーダーブック情報を取得 */ async getOrderbook(protocol: Protocol, marketId: string): Promise<Record<string, OrderBookSummary>> { try { const response = await this.apiClient.get(`/v1/markets/${marketId}/orderbook`, { params: { protocol }, }); return response.data; } catch (error) { this.handleError('getOrderbook', error); throw error; } } /** * Retrieves the filled order events for position tokens in a market for the last month. * Returns price and timestamp data for each execution in the orderbook. * * The response is a 2D array where the index corresponds to the position tokens * in the same order as they appear in the market response's position_tokens array. * For binary options, index 0 corresponds to "Yes" tokens. * * @param protocol - The protocol identifier * @param marketId - The market identifier * @returns A 2D array of fill event data, organized by position token index */ async getOrderBookFilledEvents(protocol: Protocol, marketId: string): Promise<FillEventData[][]> { try { const response = await this.apiClient.get(`/v1/markets/${marketId}/orderbook/filledevents`, { params: { protocol }, }); return response.data; } catch (error) { this.handleError('getOrderBookFilledEvents', error); throw error; } } /** * 指定されたプロトコルと ID(マーケットID等) に対応する Fill イベントを SSE で購読 * * @param protocol プロトコル名 * @param id マーケットID等 * @param handlers SSE購読時の各種コールバック (onOpen, onMessage, onError) * @returns SSE購読インスタンス (eventSource, close, getLatestData) */ subscribeFillEvents( protocol: Protocol, id: string, handlers?: FillEventSSEHandlers ): FillEventSubscription { const url = `${this.sseBaseUrl}/sse/fillevent?protocol=${protocol}&id=${id}`; const eventSource = new EventSource(url); let latestData: FillEventData | null = null; // SSE接続が開いたとき eventSource.onopen = (ev) => { if (handlers?.onOpen) { handlers.onOpen(ev); } }; // メッセージを受信したとき eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data) as FillEventData; latestData = data; // 最新データを更新 if (handlers?.onMessage) { handlers.onMessage(data); } } catch (error) { console.error('Error parsing SSE message:', error); } }; // エラーが発生したとき eventSource.onerror = (ev) => { if (handlers?.onError) { handlers.onError(ev); } else { console.error('SSE connection error:', ev); } }; const close = () => { eventSource.close(); }; return { eventSource, close, getLatestData: () => latestData, }; } /** * エラーハンドリング */ private handleError(method: string, error: any): void { if (axios.isAxiosError(error)) { console.error(`PolynanceClient.${method} error:`, error.response?.data || error.message); } else { console.error(`PolynanceClient.${method} error:`, error); } } } export interface Candle { time: number // UNIX open: number high: number low: number close: number volume: number } export function generateCandlestickData( fillEvents: FillEventData[], intervalMillis: number, fromTime: number, toTime: number ): Candle[] { // タイムスタンプを秒からミリ秒に変換する const convertedEvents = fillEvents.map(event => ({ ...event, timestamp: event.timestamp * 1000 // 秒からミリ秒へ変換 })); // 指定期間内の FillEventData のみに絞る const filteredEvents = convertedEvents.filter( (event) => event.timestamp >= fromTime && event.timestamp < toTime ); // タイムスタンプの昇順にソート filteredEvents.sort((a, b) => a.timestamp - b.timestamp); // timeBucket(ローソク足の開始時刻)毎のローソク足データを保持するマップ const candleMap = new Map<number, Candle>(); for (const event of filteredEvents) { // どのローソク足に属するか (interval の開始時刻) を計算 const bucketTimeMillis = Math.floor(event.timestamp / intervalMillis) * intervalMillis; // すでにローソク足データがあるかチェック const existingCandle = candleMap.get(bucketTimeMillis); if (!existingCandle) { // 新しくローソク足を作成 candleMap.set(bucketTimeMillis, { // TradingView が秒単位を想定している場合、ミリ秒→秒に変換しておく time: Math.floor(bucketTimeMillis / 1000), open: event.price, high: event.price, low: event.price, close: event.price, volume: event.volumeBase, }); } else { // 既存のローソク足を更新 existingCandle.high = Math.max(existingCandle.high, event.price); existingCandle.low = Math.min(existingCandle.low, event.price); existingCandle.close = event.price; existingCandle.volume += event.volumeBase; } } // Map を配列に変換し、time (秒) 昇順にソートして返却 const candles = Array.from(candleMap.values()).sort((a, b) => a.time - b.time); return candles; }