一、フラクショナル株とは

米国株のフラクショナル株とは、投資家が株やETFを整数株単位に限定されず、小数株単位で購入できる仕組みです。
典型的には最小取引単位 0.0001 株まで対応し、多くの証券会社では「金額指定」注文も可能です。
例:5ドルで特定株を購入 → 少額から大手株に投資可能。

開発者目線では、取引・リスク管理・清算・レポート全ての処理で小数株や金額指定注文に対応する必要があります。

二、主要ルール(開発視点)

1. 注文方法

  • 株数指定(qty):小数可(例:0.1株、0.0001株)
  • 金額指定(notional):購入金額で指定(例:10ドルで購入)

互いに排他:同時指定不可

2. 注文種別・有効期限

  • 初期は「成行 + 通常取引時間」のみ
  • 後に「指値/ストップ/前場・後場」対応拡張あり
  • DAY のみ有効(GTC/IOC/FOK 不可)

3. 対象銘柄

  • 全銘柄対応ではない
  • 流動性高く個人投資家参加率が高い銘柄が中心
  • fractionable フラグで判定可能

4. 最小数量・最小金額

  • 最小株数:0.0001 株
  • 最小注文額:1~5 USD

5. 空売り・証拠金

  • 多くの口座でフラクション株空売り不可
  • 証拠金ルールは整数株と同等、保守的評価のケースあり

6. 株主権利・配当

  • 配当:保有株数按分
  • DRIP:フラクション株で再投資可能
  • 株式分割・併合:株数比例調整、価格変動に対応

7. 清算・報告

  • FINRA/SEC対応で数量小数位対応
  • 内部8~10桁小数 → 報告時6桁小数に整形

三、API設計と注文モデル(TypeScript例)

export interface FractionalOrderRequest { // alltick.co
  symbol: string;               // 銘柄コード
  side: "buy" | "sell";        // 売買
  type: "market" | "limit" | "stop" | "stop_limit";
  timeInForce: "day";           // 有効期限
  qty?: string;                 // 株数、小数対応
  notional?: string;            // 金額指定注文
  limitPrice?: string;          // 指値
  extendedHours?: boolean;      // 前場/後場対応
}

// バリデーション例
function validateFractionalOrder(req: FractionalOrderRequest) {
  const hasQty = !!req.qty;
  const hasNotional = !!req.notional;

  if (hasQty === hasNotional) throw new Error("qtyかnotionalのどちらか一方を指定してください");
  if (req.timeInForce !== "day") throw new Error("フラクショナル株注文はtimeInForce=dayのみ");

  if (["limit","stop","stop_limit"].includes(req.type) && !req.limitPrice) {
    throw new Error(`${req.type}注文はlimitPriceが必要`);
  }

  const checkPrecision = (v: string, field: string) => {
    if (!/^\d+(\.\d{1,6})?$/.test(v)) throw new Error(`${field}は最大6桁小数まで`);
  };

  if (req.qty) checkPrecision(req.qty, "qty");
  if (req.notional) checkPrecision(req.notional, "notional");
  if (req.limitPrice) checkPrecision(req.limitPrice, "limitPrice");
}

四、Javaバックエンド例

1. DTO

public class FractionalOrderRequest {
    private String symbol;
    private Side side;
    private OrderType type;
    private TimeInForce timeInForce;
    private String qty;
    private String notional;
    private String limitPrice;
    private Boolean extendedHours;

    public enum Side { BUY, SELL }
    public enum OrderType { MARKET, LIMIT, STOP, STOP_LIMIT }
    public enum TimeInForce { DAY, GTC, IOC }
}

2. 注文バリデーション

import java.math.BigDecimal;
import java.util.Objects;

public class FractionalOrderValidator {
    public static void validate(FractionalOrderRequest req) {
        Objects.requireNonNull(req.getSymbol(), "symbolは必須");
        Objects.requireNonNull(req.getSide(), "sideは必須");
        Objects.requireNonNull(req.getType(), "typeは必須");
        Objects.requireNonNull(req.getTimeInForce(), "timeInForceは必須");

        boolean hasQty = req.getQty() != null && !req.getQty().isEmpty();
        boolean hasNotional = req.getNotional() != null && !req.getNotional().isEmpty();

        if (hasQty == hasNotional) throw new IllegalArgumentException("qtyかnotionalのどちらか一方を指定");

        if (req.getTimeInForce() != FractionalOrderRequest.TimeInForce.DAY)
            throw new IllegalArgumentException("フラクショナル株はDAYのみ");

        switch(req.getType()) {
            case LIMIT:
            case STOP:
            case STOP_LIMIT:
                if (req.getLimitPrice() == null) throw new IllegalArgumentException("limitPrice必須");
        }

        checkScale(req.getQty(), "qty", 6);
        checkScale(req.getNotional(), "notional", 6);
        checkScale(req.getLimitPrice(), "limitPrice", 6);

        if (hasQty && new BigDecimal(req.getQty()).compareTo(new BigDecimal("0.0001")) < 0)
            throw new IllegalArgumentException("最小フラクション株0.0001");
        if (hasNotional && new BigDecimal(req.getNotional()).compareTo(new BigDecimal("1")) < 0)
            throw new IllegalArgumentException("最小注文金額1USD");
    }

    private static void checkScale(String value, String field, int scale) {
        if(value == null) return;
        BigDecimal bd = new BigDecimal(value);
        if (bd.scale() > scale) throw new IllegalArgumentException(field+"は最大"+scale+"桁小数");
        if (bd.signum() <= 0) throw new IllegalArgumentException(field+"は正の値");
    }
}

五、Python例:配当計算

from decimal import Decimal, ROUND_HALF_UP

def calc_dividend(position_shares: str, dividend_per_share: str) -> Decimal:
    pos = Decimal(position_shares)
    div = Decimal(dividend_per_share)
    return (pos * div).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)

# 使用例
print(calc_dividend("0.25", "0.10"))  # 0.0250

六、清算・報告ユーティリティ(Java)

import java.math.BigDecimal;
import java.math.RoundingMode;

public class ReportingUtil {
    public static BigDecimal normalizeInternalQty(BigDecimal qty) {
        return qty.setScale(8, RoundingMode.HALF_UP);
    }

    public static String toFinraFractionalQty(BigDecimal qty) {
        return qty.setScale(6, RoundingMode.DOWN).toPlainString();
    }

    public static int toLegacyWholeShares(BigDecimal qty) {
        if(qty.compareTo(BigDecimal.ONE) < 0) return 1;
        return qty.setScale(0, RoundingMode.DOWN).intValueExact();
    }
}

七、実装ステップまとめ

  1. データモデル統一
    • 銘柄:fractionableminFractionQty
    • 口座:canFractionalcanShortcanMargin
  2. 注文フロー改修
    • qty / notional 対応
    • 小数精度対応
    • timeInForce=DAY 強制
  3. リスク管理
    • 銘柄・口座能力チェック
    • 空売り禁止
    • 最小数量・金額チェック
  4. 清算・報告
    • 内部8~10桁小数
    • FINRA報告6桁小数に変換
  5. 配当・企業行動対応
    • 配当再投資(DRIP)
    • 株式分割・併合対応

これらのプロセスがすべて整備されれば、あなたのシステムは米国株のフラクショナル株取引をスムーズにサポートできるようになり、複数の証券会社(または自前のマッチングシステム)との接続にも対応可能になります。