Menjalankan bot trading otomatis langsung ke pasar prediksi terdesentralisasi seperti Polymarket membawa risiko finansial instan jika terjadi bug logika atau regresi kode. Kesalahan kecil seperti tipe data tidak valid saat menghitung probabilitas cuaca dapat memicu order on-chain yang menguras likuiditas wallet. Artikel ini membedah implementasi pipeline CI GitHub Actions untuk bot trading berbasis TypeScript (merujuk pada arsitektur bot cuaca Polymarket) dengan memvalidasi kode via static check dan integrasi dry-run menggunakan mock server lokal.

Arsitektur CI Pipeline Bot Trading

Pipeline Continuous Integration (CI) untuk bot trading harus bertindak sebagai gerbang deterministik sebelum kode dideploy ke server VPS atau execution runner. Tiga tahap utama yang wajib dieksekusi adalah:

  • Static Code Analysis: Linting dengan ESLint dan static type checking dengan TypeScript compiler (tsc --noEmit) untuk mendeteksi inferensi tipe salah, pemanggilan method tidak aman, dan dead code.
  • Mock Services: Server HTTP lokal ringan menggunakan Node.js stdlib (node:http) untuk meniru respon API Polymarket (CLOB/Gamma API) dan feed cuaca eksternal.
  • Dry-Run Execution: Menjalankan script bot dalam mode simulasi, memastikan bot menerima feed data, melakukan kalkulasi spread/probabilitas, membuat struktur transaksi order, lalu memverifikasi bahwa wallet balance tidak berkurang dan tidak ada network call yang keluar ke endpoint on-chain sebenarnya.

1. Setup Linting dan Typecheck Ketat

Bot trading tidak boleh mentoleransi tipe any implisit karena berpotensi merusak validasi payload transaksi. Konfigurasi tsconfig.json harus mengaktifkan mode strict, dan script package.json harus memisahkan tahapan verifikasi.

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Tambahkan target eksekusi pada package.json:

{
  "scripts": {
    "lint": "eslint src/**/*.ts",
    "typecheck": "tsc --noEmit",
    "test:dry-run": "ts-node test/dry-run.ts"
  }
}

2. Implementasi Mock Server API Polymarket & Feed Cuaca

Alih-alih menambahkan dependency mock server besar, gunakan native node:http untuk menangani endpoint Polymarket Orderbook dan feed cuaca. Pendekatan ini cepat dan meminimalkan footprint CI runner.

import http from 'node:http';

export function startMockServer(port = 8080): http.Server {
  const server = http.createServer((req, res) => {
    res.setHeader('Content-Type', 'application/json');

    // Mock API Cuaca: Prediksi Suhu
    if (req.url === '/weather?city=london') {
      res.writeHead(200);
      return res.end(JSON.stringify({ city: 'London', temp_c: 24.5, condition: 'Sunny' }));
    }

    // Mock Polymarket Gamma API: Metadata Market
    if (req.url?.startsWith('/markets')) {
      res.writeHead(200);
      return res.end(JSON.stringify([
        {
          id: 'market-weather-london-2026',
          question: 'Will London temp exceed 24C?',
          tokens: [{ token_id: 'tok_yes', price: 0.45 }, { token_id: 'tok_no', price: 0.55 }]
        }
      ]));
    }

    // Mock Polymarket CLOB: Submit Order
    if (req.url === '/order' && req.method === 'POST') {
      res.writeHead(200);
      return res.end(JSON.stringify({ status: 'OK', orderID: 'mock_tx_9981' }));
    }

    res.writeHead(404);
    res.end(JSON.stringify({ error: 'Endpoint not found' }));
  });

  return server.listen(port);
}

3. Skrip Validasi Dry-Run dan Assertion Tanpa Transaksi On-Chain

Logika trading diuji dengan mengarahkan environment variable ke port mock server. Script memastikan kalkulasi order terpicu, namun signing on-chain diblokir menggunakan flag mode dry-run atau mock wallet provider.

import assert from 'node:assert';
import { startMockServer } from './mock-server';
import { WeatherTradingEngine } from '../src/engine';

async function runDryRunValidation() {
  const server = startMockServer(8080);
  const initialVirtualBalance = 1000;

  try {
    const bot = new WeatherTradingEngine({
      apiUrl: 'http://localhost:8080',
      weatherApiUrl: 'http://localhost:8080/weather?city=london',
      dryRun: true,
      virtualBalance: initialVirtualBalance,
      privateKey: '0x0000000000000000000000000000000000000000000000000000000000000001'
    });

    // Jalankan siklus eksekusi trading bot sekali
    const executionResult = await bot.tick();

    // Assertions
    assert.strictEqual(executionResult.decision, 'BUY_YES', 'Bot gagal mendeteksi sinyal cuaca');
    assert.strictEqual(executionResult.isLiveTx, false, 'Dry run mengeksekusi on-chain tx secara ilegal');
    assert.strictEqual(bot.getVirtualBalance(), initialVirtualBalance - executionResult.cost, 'Kalkulasi balance bot tidak presisi');
    
    console.log('Dry-run lulus: Sinyal valid, kalkulasi balance presisi, nol transaksi on-chain.');
  } finally {
    server.close();
  }
}

runDryRunValidation().catch((err) => {
  console.error('Dry-run gagal:', err);
  process.exit(1);
});

4. Workflow GitHub Actions

File workflow menyatukan proses dependensi, linting, typecheck, dan eksekusi dry-run dalam satu job cepat.

name: Bot Validation & Dry-Run

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install Dependencies
        run: npm ci

      - name: Run Linting
        run: npm run lint

      - name: Run Static Typecheck
        run: npm run typecheck

      - name: Execute Dry-Run Trading Pipeline
        run: npm run test:dry-run
        env:
          NODE_ENV: test
          DRY_RUN: 'true'

Trade-offs dan Tips Debugging

  • Trade-off Mocking vs Integration Test: Mocking endpoint via node:http tidak menguji kompatibilitas signature EIP-712 yang digunakan pada Polymarket CLOB. Untuk memverifikasi format signature cryptographic, sertakan test assertion terpisah menggunakan public mock key tanpa menghubungkan RPC live.
  • Slippage & Timing Out: Di lingkungan mock, latency mendekati 0ms. Selalu lakukan assertion bahwa kode penentu harga menangani edge-case limit timeout atau order rejection dari server target.
  • Security Boundary: Jangan pernah menyimpan private key asli di repository secrets untuk proses CI linting dan dry-run. Gunakan dummy seed atau throw error otomatis jika variable environment mendeteksi address wallet produksi.