iT邦幫忙

2022 iThome 鐵人賽

DAY 27
1
Software Development

從 Node.js 開發者到量化交易者:打造屬於自己的投資系統系列 第 27

Day 27 - 程式交易系統:以富果交易 API 為例

  • 分享至 

  • xImage
  •  

前一天我們以 TradingView 為例,示範如何對交易策略進行績效回測,不過一個成功的交易策略仍必須經由市場驗證才知道是否可行。今天我們就要實作程式交易系統,透過富果交易 API 進行下單委託,開啟進入程式交易的大門。

富果交易 API

我們在前面的篇章已經介紹過 富果股市 API,並使用 行情 API 完成即時行情監控系統。富果也提供 交易 API 並提供 Node.js SDK 與 Nest Framework 的支援,因此對於 Node.js 開發者建構臺股交易系統是首選。

富果交易 API 是由富果技術團隊與玉山證券合作開發的程式交易 API。使用者完成 玉山證券富果帳戶 開戶程序後,只要簽署「API 服務申請同意書」並線上提出申請,就可以在 Windows、Mac 及 Linux 平台上使用富果提供的 SDK 進行程式交易。

https://ithelp.ithome.com.tw/upload/images/20220927/20150150xeTIfRzYGv.png

請參考富果交易 API 事前準備 的流程,申請使用交易 API 服務、進行模擬測試後,就可以開通正式環境交易權限,然後我們就可以進行後續的實作。

Trader 應用伺服器概觀

我們要整合富果交易 API 的下單委託以及帳務查詢功能,因此我們會建立一個「Trader」應用伺服器(Application Server)。為了理解之後實作的內容,我們先描繪出 Trader 應用伺服器的系統環境圖:

https://ithelp.ithome.com.tw/upload/images/20220927/20150150tt4mXsVk56.png

在 Trader 應用伺服器中,主要包含以下元件:

  • Trader API:提供 REST 風格的 HTTP API,使用者可以向 Trader 應用程式發送請求,進行下單委託、並查詢成交明細、帳戶庫存、交割資訊等。
  • Trader Service:整合富果交易 SDK 並處理 Trader API 請求,當收到委託及成交回報時,透過 LINE Notify 請求發送推播訊息。

使用者透過 Trader 應用伺服器進行下單委託,資料處理流程如下:

  • ① User → Trader API:使用者向 Trader API 發送請求,進行下單委託。
  • ② Trader API → Trader Service:Trader Service 處理使用者向 Trader API 發送的請求。
  • ③ Trader Service → FugleTrade:Trader Service 向富果交易系統請求下單委託。
  • ④ FugleTrade → E.SUN Securities:富果交易系統向玉山證券驗章並轉送下單委託。
  • ⑤ E.SUN Securities → Exchange:玉山證券向交易所發出下單委託電文。
  • ⑥ Exchange → E.SUN Securities:玉山證券收到交易所的委託或成交回報電文。
  • ⑦ E.SUN Securities → FugleTrade:FugleTrade 接收來自交易所及玉山證券的委託或成交回報。
  • ⑧ FugleTrade → Trader Service:Trader Service 與富果交易系統 Streamer 連線,接收委託及成交回報。
  • ⑨ Trader Service → LINE Notify:當收到委託或成交回報時,Trader Service 會向 LINE Notify 請求發送推播訊息。
  • ⑩ LINE Notify → User:LINE Notify 收到推播請求後,會將訊息發送給使用者。

建立 Trader 應用程式

暸解 Trader 應用伺服器概觀後,我們開始進行實作。首先我們要建立一個新的 Nest Application 來實作程式交易系統。打開終端機,在我們的專案目錄下使用 Nest CLI 建立名為 trader 的 Nest application:

$ nest g app trader

執行完成後,Nest CLI 會在 apps 目錄下新增 trader 應用程式。我們需要將 trader 的應用程式結構稍作調整。Nest CLI 預設在 apps/trader/src 目錄下會建立 main.tstrader.module.tstrader.controller.tstrader.controller.spec.tstrader.service.ts 檔案。我們保留 main.tstrader.module.ts 檔案,並將 trader.module.ts 更名為 app.module.ts,然後開啟該檔案,將 TraderModule 更名為 AppModule,作為 trader 應用程式的根模組:

import { Module } from '@nestjs/common';

@Module({})
export class AppModule {}

接著開啟 apps/trader/src/main.ts 檔案,將匯入的模組名稱改為 AppModule

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

為了使用富果交易 API,我們安裝富果提供的 Node.js 交易 SDK 以及 Nest Module:

$ npm install --save @fugle/trade @fugle/trade-nest

安裝完成後,我們在專案目錄下開啟 .env 檔案,加入以下環境變數:

FUGLE_TRADE_API_URL=
FUGLE_TRADE_CERT_PATH=
FUGLE_TRADE_API_KEY=
FUGLE_TRADE_API_SECRET=
FUGLE_TRADE_AID=
FUGLE_TRADE_PASSWORD=
FUGLE_TRADE_CERT_PASS=
LINE_NOTIFY_ACCESS_TOKEN=

這些環境變數代表的意義如下:

  • FUGLE_TRADE_API_URL:富果交易 API URL。
  • FUGLE_TRADE_API_KEY:富果交易 API 私鑰。
  • FUGLE_TRADE_API_SECRET:富果交易 API 私鑰。
  • FUGLE_TRADE_AID:證券帳號。
  • FUGLE_TRADE_PASSWORD:證券帳號密碼。
  • FUGLE_TRADE_CERT_PATH:憑證路徑。
  • FUGLE_TRADE_CERT_PASS:憑證密碼。
  • LINE_NOTIFY_ACCESS_TOKEN:LINE Notify Access Token。

這些環境變數的值,如 FUGLE_TRADE_API_URLFUGLE_TRADE_API_KEYFUGLE_TRADE_API_SECRET 可以從 玉山證券富果交易 API 網站下載設定檔取得,並且匯出憑證檔案。

開啟在 apps/trader/src/app.module.ts 檔案,在 AppModule 匯入 ConfigModuleLineNotifyModuleFugleTradeModule

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { FugleTradeModule } from '@fugle/trade-nest';
import { LineNotifyModule } from 'nest-line-notify';

@Module({
  imports: [
    ConfigModule.forRoot(),
    FugleTradeModule.forRoot({
      config: {
        apiUrl: process.env.FUGLE_TRADE_API_URL,
        certPath: process.env.FUGLE_TRADE_CERT_PATH,
        apiKey: process.env.FUGLE_TRADE_API_KEY,
        apiSecret: process.env.FUGLE_TRADE_API_SECRET,
        aid: process.env.FUGLE_TRADE_AID,
        password: process.env.FUGLE_TRADE_PASSWORD,
        certPass: process.env.FUGLE_TRADE_CERT_PASS,
      },
    }),
    LineNotifyModule.forRoot({
      accessToken: process.env.LINE_NOTIFY_ACCESS_TOKEN,
    }),
  ],
})
export class AppModule {}

實作 Trader Service

我們先使用 Nest CLI 建立 TraderModule

$ nest g module trader -p trader

執行後,Nest CLI 會在 apps/trader/src/trader 目錄下建立 trader.module.ts 檔案,並且將 TraderModule 加入至 AppModuleimports 設定。

然後再使用 Nest CLI 建立 TraderService

$ nest g service trader -p trader --no-spec

執行命令後,Nest CLI 會在 apps/trader/src/trader 目錄下新增 trader.service.ts 檔案,並且將 TraderService 加入至 TraderModuleproviders 設定。

我們要在 TraderService 整合富果交易 SDK 的功能。開啟 apps/trader/src/trader/trader.service.ts 檔案,在 TraderService 實作以下方法:

  • getOrders():取得當日所有委託單。
  • placeOrder():下委託單。
  • replaceOrder():修改委託單。
  • cancelOrder():取消委託單。
  • getTransactions():取得交易明細。
  • getInventories():取得帳戶庫存。
  • getSettlements():取得交割資訊。
  • onConnect():當 Streamer 連線建立時的處理。
  • onDisconnect():當 Streamer 連線關閉時的處理。
  • onOrder():當委託確認時的處理。
  • onTrade():當執行委託時的處理。
  • onError():當 Streamer 發生錯誤時的處理。

實作 TraderService 程式碼如下:

import { Injectable, Logger, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { InjectFugleTrade, Streamer } from '@fugle/trade-nest';
import { InjectLineNotify, LineNotify } from 'nest-line-notify';
import { FugleTrade, Order, OrderPayload } from '@fugle/trade';
import { PlaceOrderDto } from './dto/place-order.dto';
import { ReplaceOrderDto } from './dto/replace-order.dto';
import { GetTransactionsDto } from './dto/get-transactions.dto';
import { getOrderSideName, getOrderTypeName, getTradeTypeName, getPriceTypeName } from './utils';

@Injectable()
export class TraderService {
  private readonly logger = new Logger(TraderService.name);

  constructor(
    @InjectFugleTrade() private readonly fugle: FugleTrade,
    @InjectLineNotify() private readonly lineNotify: LineNotify,
  ) { }

  async getOrders() {
    return this.fugle.getOrders()
      .then(orders => orders.map(order => order.payload))
      .catch(err => {
        throw new InternalServerErrorException(err.message);
      });
  }

  async placeOrder(placeOrderDto: PlaceOrderDto) {
    const payload = placeOrderDto as OrderPayload;
    const order = new Order(payload);

    return this.fugle.placeOrder(order)
      .catch(err => {
        throw new InternalServerErrorException(err.message);
      });
  }

  async replaceOrder(id: string, replaceOrderDto: ReplaceOrderDto) {
    const orders = await this.fugle.getOrders();

    const order = orders.find(order =>
      [order.payload.ordNo, order.payload.preOrdNo].includes(id)
    );

    if (!order) throw new NotFoundException('order not found');

    return this.fugle.replaceOrder(order, replaceOrderDto)
      .catch(err => {
        throw new InternalServerErrorException(err.message);
      });
  }

  async cancelOrder(id: string) {
    const orders = await this.fugle.getOrders();

    const order = orders.find(order =>
      [order.payload.ordNo, order.payload.preOrdNo].includes(id)
    );

    if (!order) throw new NotFoundException('order not found');

    return this.fugle.cancelOrder(order)
      .catch(err => {
        throw new InternalServerErrorException(err.message);
      });
  }

  async getTransactions(getTransactionsDto: GetTransactionsDto) {
    const { range } = getTransactionsDto;

    return this.fugle.getTransactions(range)
      .catch(err => {
        throw new InternalServerErrorException(err.message);
      });
  }

  async getInventories() {
    return this.fugle.getInventories()
      .catch(err => {
        throw new InternalServerErrorException(err.message);
      });
  }

  async getSettlements() {
    return this.fugle.getSettlements()
      .catch(err => {
        throw new InternalServerErrorException(err.message);
      });
  }

  @Streamer.OnConnect()
  async onConnect() {
    this.logger.log('Streamer.onConnect');
  }

  @Streamer.OnDisconnect()
  async onDisconnect() {
    this.logger.log('Streamer.onDisconnect');
    this.fugle.streamer.connect();
  }

  @Streamer.OnOrder()
  async onOrder(data) {
    this.logger.log(`Streamer.OnOrder ${JSON.stringify(data)}`);

    const { action, stockNo, buySell, bsFlag, trade, odPrice, orgQty, afterQty, apCode, priceFlag } = data;
    const actionName = action === 'M' ? '改量' : action === 'C' ? '刪單' : action === 'R' ? '改價' : '委託';
    const side = getOrderSideName(buySell);
    const orderType = getOrderTypeName(bsFlag) || '';
    const tradeType = getTradeTypeName(trade);
    const isOddLot = apCode === Order.ApCode.Odd || apCode === Order.ApCode.Emg || apCode === Order.ApCode.IntradayOdd;

    const price = (() => {
      const price = Number(odPrice);
      if (action === 'R') return '';
      if (apCode === Order.ApCode.AfterMarket) return '收盤價';
      return (price === 0) ? getPriceTypeName(priceFlag) : price;
    })();

    const priceUnit = (action === 'R' || Number(odPrice) === 0) ? '' : '元';
    const size = action === 'O' ? Number(orgQty) : Number(afterQty);
    const sizeUnit: string = isOddLot ? '股' : '張';

    const info = (() => {
      const actions = {
        '刪單': '已刪單',
        '改量': `已改為 ${size} ${sizeUnit}`,
        '改價': `已改為 ${Number(odPrice)} 元`,
        '委託': `${size} ${sizeUnit}`,
      };
      return actions[actionName];
    })();

    const message = [
      '',
      `<<委託回報>>`,
      `${stockNo}:${price} ${priceUnit} ${orderType} ${tradeType} ${side} ${info}`,
    ].join('\n');

    await this.lineNotify.send({ message })
      .catch((err) => this.logger.error(err.message, err.stack));
  }

  @Streamer.OnTrade()
  async onTrade(data) {
    this.logger.log(`Streamer.OnTrade ${JSON.stringify(data)}`);

    const { stockNo, buySell, trade, matPrice, matQty } = data;
    const side = getOrderSideName(buySell);
    const tradeType = getTradeTypeName(trade);
    const price: string | number = Number(matPrice);
    const priceUnit: string = price === 0 ? '' : '元';
    const size = Number(matQty);
    const sizeUnit = '股';

    const message = [
      '',
      `<<成交回報>>`,
      `${stockNo}:${price} ${priceUnit} ${tradeType} ${side} ${size} ${sizeUnit} 已成交`,
    ].join('\n');

    await this.lineNotify.send({ message })
      .catch((err) => this.logger.error(err.message, err.stack));
  }

  @Streamer.OnError()
  async onError(err) {
    this.logger.error(err.message, err.stack);
    this.fugle.streamer.disconnect();
  }
}

我們還需要實作 PlaceOrderDtoReplaceOrderDto 以及 GetTransactionsDto,分別表示新增委託單、修改委託單以及取得成交明細的資料傳輸物件(Data Transfer Object)。

apps/trader/src/trader 目錄下建立 dto 資料夾,並在目錄下新增 place-order.dto.ts 檔案, 實作 PlaceOrderDto 表示新增委託單的資料傳輸物件:

import { IsString, IsNumber, IsEnum } from 'class-validator';
import { Order } from '@fugle/trade';

export class PlaceOrderDto {
  @IsString()
  stockNo: string;

  @IsEnum(Order.Side)
  buySell: string;

  @IsNumber()
  price?: number

  @IsNumber()
  quantity: number;

  @IsEnum(Order.ApCode)
  apCode: string;

  @IsEnum(Order.PriceFlag)
  priceFlag: string;

  @IsEnum(Order.BsFlag)
  bsFlag: string;

  @IsEnum(Order.Trade)
  trade: string;
}

然後,在 apps/trader/src/trader/dto 目錄下建立 replace-order.dto.ts 檔案, 實作 ReplaceOrderDto 表示修改委託單的資料傳輸物件:

import { IsNumber } from 'class-validator';

export class ReplaceOrderDto {
  @IsNumber()
  price: number;

  @IsNumber()
  quantity: number;
}

接著,在 apps/trader/src/trader/dto 目錄下建立 get-transactions.dto.ts 檔案, 實作 GetTransactionsDto 表示取得交易明細的資料傳輸物件,目前富果行情 API 可以查詢日內、3 日內、1 個月內以及 3 個月內的交易紀錄:

import { IsIn } from 'class-validator';

type Range = '0d' | '3d' | '1m' | '3m';

export class GetTransactionsDto {
  @IsIn(['0d', '3d', '1m', '3m'])
  range: Range;
}

收到委託及成交回報時,我們會透過 LINE Notify 推播通知訊息,因此需要將收到的委託或成交回報資訊轉換成文字訊息。在 apps/trader/src/trader 目錄下建立 utils 資料夾,並在該資料夾下新增 get-names.util.ts 檔案,開啟檔案並實作 getOrderSideName()getOrderTypeName()getTradeTypeName()getPriceTypeName() 等工具函式:

import { Order } from '@fugle/trade';

export function getOrderSideName(side: string) {
  const names = {
    [Order.Side.Buy]: '買進',
    [Order.Side.Sell]: '賣出',
  }
  return names[side];
}

export function getOrderTypeName(bsFlag: string) {
  const names = {
    [Order.BsFlag.ROD]: 'ROD',
    [Order.BsFlag.IOC]: 'IOC',
    [Order.BsFlag.FOK]: 'FOK',
  }
  return names[bsFlag];
}

export function getTradeTypeName(trade: string) {
  const names = {
    [Order.Trade.Cash]: '現股',
    [Order.Trade.DayTrading]: '信用當沖',
    [Order.Trade.DayTradingSell]: '現股當沖',
    [Order.Trade.Margin]: '融資',
    [Order.Trade.Short]: '融券',
  }
  return names[trade];
}

export function getPriceTypeName(priceFlag: string) {
  const names = {
    [Order.PriceFlag.Limit]: '限價',
    [Order.PriceFlag.Flat]: '平盤價',
    [Order.PriceFlag.LimitDown]: '跌停價',
    [Order.PriceFlag.LimitUp]: '漲停價',
    [Order.PriceFlag.Market]: '市價價',
  }
  return names[priceFlag];
}

完成上述資料傳輸物件以及工具函式後,TraderService 就可以正常運作了。

TraderService 中,我們使用了 @Streamer 的裝飾器處理與 Streamer 的連線,Streamer 是一個向 Fugle 客戶端提供即時數據的應用程式,當我們想要主動接收委託或成交回報,就需要連接 Streamer。當 TraderService 收到 Stramer 的委託及成交回報後,就會透過 LINE Notify 向使用者推播回報通知。

實作 Trader API

實作 Trader Service 後,接下來要完成 Trader API。請打開終端機,使用 Nest CLI 建立 TraderController

$ nest g controller trader -p trader --no-spec

執行後,Nest CLI 會在 apps/trader/src/trader 目錄下建立 trader.controller.ts 檔案,開啟該檔案在 TraderController 實作以下方法:

  • getOrders():取得當日所有委託單。
  • placeOrder():新增委託單。
  • replaceOrder():修改委託單。
  • cancelOrder():取消委託單。
  • getTransactions():取得交易明細。
  • getInventories():取得帳戶庫存。
  • getSettlements():取得交割資訊。

實作 TraderController 程式碼如下:

import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
import { TraderService } from './trader.service';
import { PlaceOrderDto } from './dto/place-order.dto';
import { ReplaceOrderDto } from './dto/replace-order.dto';
import { GetTransactionsDto } from './dto/get-transactions.dto';

@Controller('trader')
export class TraderController {
  constructor(private readonly traderService: TraderService) {}

  @Get('/orders')
  async getOrders() {
    return this.traderService.getOrders();
  }

  @Post('/orders')
  async placeOrder(@Body() placeOrderDto: PlaceOrderDto) {
    return this.traderService.placeOrder(placeOrderDto);
  }

  @Put('/orders/:id')
  async replaceOrder(@Param('id') id: string, @Body() replaceOrderDto: ReplaceOrderDto) {
    return this.traderService.replaceOrder(id, replaceOrderDto);
  }

  @Delete('/orders/:id')
  async cancelOrder(@Param('id') id: string) {
    return this.traderService.cancelOrder(id);
  }

  @Get('/transactions')
  async getTransactions(@Query() getTransactionsDto: GetTransactionsDto) {
    return this.traderService.getTransactions(getTransactionsDto);
  }

  @Get('/inventories')
  async getInventories() {
    return this.traderService.getInventories();
  }

  @Get('/settlements')
  async getSettlements() {
    return this.traderService.getSettlements();
  }
}

為了讓每個路由都通過 ValidationPipe 進行驗證程序,我們在 apps/trader/src/mian.ts 加入 useGlobalPipes() 方法使其作用為全域範圍:

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );
  
  await app.listen(3000);
}
bootstrap();

最後,為了確保交易命令是我們本人執行,因此需要設定「白名單」限制特定 IP 存取 Trader API。請安裝以下套件:

$ npm install --save nestjs-ip-filter

安裝完成後,在專案目錄下的 .env 檔案加入 ALLOWED_IPS 環境變數,將每個接受的 IP 位址以逗號 , 分隔:

ALLOWED_IPS=

然後開啟 apps/trader/src/app.module.ts 檔案,在 AppModule 加入 IpFilter Module:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule';
import { MongooseModule } from '@nestjs/mongoose';
import { FugleTradeModule } from '@fugle/trade-nest';
import { IpFilter } from 'nestjs-ip-filter';
import { LineNotifyModule } from 'nest-line-notify';
import { TraderModule } from './trader/trader.module';

@Module({
  imports: [
    ConfigModule.forRoot(),
    ScheduleModule.forRoot(),
    MongooseModule.forRoot(process.env.MONGODB_URI),
    FugleTradeModule.forRoot({
      config: {
        apiUrl: process.env.FUGLE_TRADE_API_URL,
        certPath: process.env.FUGLE_TRADE_CERT_PATH,
        apiKey: process.env.FUGLE_TRADE_API_KEY,
        apiSecret: process.env.FUGLE_TRADE_API_SECRET,
        aid: process.env.FUGLE_TRADE_AID,
        password: process.env.FUGLE_TRADE_PASSWORD,
        certPass: process.env.FUGLE_TRADE_CERT_PASS,
      },
    }),
    LineNotifyModule.forRoot({
      accessToken: process.env.LINE_NOTIFY_ACCESS_TOKEN,
    }),
    IpFilter.register({
      whitelist: String(process.env.ALLOWED_IPS).split(','),
    }),
    TraderModule,
  ],
})
export class AppModule {}

IpFilter Module 設定完成後,當使用者向 Trader 發送請求,trader 應用程式會去檢查請求來源位址。只有在白名單上的 IP 位址,才可以存取 Trader API。

測試 Trader API

完成 Trader API 後,我們就可以對其進行測試。打開終端機,啟動 Trader 應用伺服器:

$ npm start trader

然後在終端機透過 curl 指令來測試以下 API endpoints:

  • GET /trader/orders:取得當日所有委託單。
  • POST /trader/orders:新增委託單。
  • PUT /trader/orders/:id:修改委託單。
  • DELETE /trader/orders/:id:取消委託單。
  • GET /trader/transactions:取得交易明細。
  • GET /trader/inventories:取得帳戶庫存。
  • GET /trader/settlements:取得交割資訊。

取得當日所有委託單

使用以下 curl 指令取得所有委託單:

$ curl --request GET \
    --url http://localhost:3000/trader/orders 

新增委託單

使用以下 curl 指令新增委託單,以 500 元 ROD 現股,買進 1 張台積電(2330)為例:

$ curl --request POST \
    --url http://localhost:3000/trader/orders \
    --header 'Content-Type: application/json' \
    --data '{"stockNo": "2330","buySell": "B","price": 500,"quantity": 1,"apCode": "1","priceFlag": "0","bsFlag": "R","trade": "0"}'

修改委託單

使用以下 curl 指令修改委託單,以改價 499 為例:

$ curl --request PUT \
    --url http://localhost:3001/trader/orders/<ORDER_ID> \
    --header 'Content-Type: application/json' \
    --data '{"price": 499}'

取消委託單

使用以下 curl 指令取消委託單:

$ curl --request DELETE \
    --url http://localhost:3001/trader/orders/<ORDER_ID> \
    --header 'Content-Type: application/json'

取得交易明細

使用以下 curl 指令取得交易明細,以近 3 個月為例:

$ curl --request GET \
    --url http://localhost:3000/trader/transactions?range=3m 

取得帳戶庫存

使用以下 curl 指令取得帳戶庫存:

$ curl --request GET \
    --url http://localhost:3000/trader/inventories 

取得交割資訊

使用以下 curl 指令取得交割資訊:

$ curl --request GET \
    --url http://localhost:3000/trader/settlements 

LINE Notify 訊息推播

我們在 TraderService 實作了委託或成交回報的處理,當我們收到委託或成交回報後,就會收到 LINE Notify 推播訊息的通知。

https://ithelp.ithome.com.tw/upload/images/20220927/20150150PRYs7j6kR4.png

至此,我們已經在 Trader 應用伺服器上整合了富果行情 API,完成程式交易系統。使用者可以透過請求 Trader API,進行下單委託、帳務查詢等功能。

本日小結

  • 暸解如何申請富果股市 API,並完成富果交易 API 的開通流程。
  • 瞭解 Trader 應用伺服器的元件組成以及資料處理流程。
  • 建立 Trader 應用程式,使用富果交易 API 進行程式交易。
  • 實作 Trader Service,整合富果交易 SDK 服務。
  • 實作 Trader Service 連接 FugleTrade Streamer 接收委託或成交回報,並透過 LINE Notify 推播訊息。
  • 實作 Trader API,使用者可以新增、查詢、修改、刪除委託單,以及查詢成交明細、帳戶庫存、交割資訊。

Node.js 量化投資全攻略:從資料收集到自動化交易系統建構實戰
本系列文已正式出版為《Node.js 量化投資全攻略:從資料收集到自動化交易系統建構實戰》。本書新增了全新內容和實用範例,為你提供更深入的學習體驗!歡迎參考選購,開始你的量化投資之旅!
天瓏網路書店連結:https://www.tenlong.com.tw/products/9786263336070


上一篇
Day 26 - 測試交易策略:TradingView 策略測試器
下一篇
Day 28 - 自動化下單:富果行情與交易 API 的整合應用
系列文
從 Node.js 開發者到量化交易者:打造屬於自己的投資系統31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言