跳轉到

下單(單策略)

執行交易策略

目前交易系統僅支援 finlab>=0.3.0.dev1 版本。在下單前,請先確認安裝了新版的 Package 喔! 首先,在下一個交易日開盤前,執行策略:

from finlab import backtest

# write your own strategy

report = backtest.sim(...)

計算股票張數

接下來,顯示最近的部位

print(report.current_trades)
stock_id entry_date exit_date entry_sig_date exit_sig_date position period entry_index exit_index return entry_price exit_price mae gmfe bmfe mdd pdays next_weights
1416 廣豐 2021-10-01 NaT 2021-09-30 2022-06-30 0.510197 159.0 3566.0 -1.0 -3.539823e-02 10.60 NaN -0.053097 0.070796 0.070796 -0.115702 37.0 0.060665
1453 大將 2021-10-01 NaT 2021-09-30 2022-06-30 0.510197 159.0 3566.0 -1.0 2.348165e+00 8.43 NaN -0.086763 2.403782 0.245829 -0.624585 60.0 0.171264
1524 耿鼎 2022-04-01 NaT 2022-03-31 2022-06-30 0.611632 39.0 3686.0 -1.0 -3.330669e-16 10.60 NaN -0.476658 0.015152 0.000000 -0.476658 2.0 0.122168
2543 皇昌 2021-10-01 NaT 2021-09-30 NaT 0.510197 159.0 3566.0 -1.0 1.073232e-01 7.46 NaN -0.063131 0.363636 0.008838 -0.268519 44.0 0.070298
2701 萬企 2022-04-01 NaT 2022-03-31 2022-06-30 0.611632 39.0 3686.0 -1.0 -3.330669e-16 12.05 NaN -0.025105 0.029289 0.029289 -0.052846 18.0 0.063107

假如確認沒問題,可以計算每檔股票投資的張數:

from finlab.online.order_executor import Position

# total fund
fund = 1000000
position = Position.from_report(report, fund)
print(position)
可以產生出以下結果
[{'stock_id': '2330', 'quantity': 1, 'order_condition': <OrderCondition.CASH: 1>}]

上述 Position 代表您的帳戶中,只希望有一張 2330 台積電股票,並且是現貨。

進階部位調整

零股交易

欲使用零股部位,只要將上述程式碼做以下修改即可:

# 整股
position = Position.from_report(report, fund)

# 零股
position = Position.from_report(report, fund, odd_lot=True)

客製化部位張數

上述方法使用 Position.from_report 來建構部位,其實我們也可以單純用很直覺的方式建構想要再平衡的部位:

position = Position({'2330': 1, '1101': 1.001})
可以產生出以下結果
[{'stock_id': '2330', 'quantity': 1, 'order_condition': <OrderCondition.CASH: 1>}
 {'stock_id': '1101', 'quantity': 1.001, 'order_condition': <OrderCondition.CASH: 1>}]

部位增減調整

Position 是可以相加減的,我們可以用以下方法調整部位大小

# 減一張 2330 股票
new_position = position - Position({'2330': 1})
# 增加一張 1101 股票
new_position = position + Position({'1101': 1})

多策略權重加總

假如我們有多個 position,可以用以下方式加總:

from finlab import backtest
from finlab.online.order_executor import Position

report1 = backtest.sim(...)
report2 = backtest.sim(...)

position1 = Position.from_report(report1, 1000000) # 策略操作金額一百萬
position2 = Position.from_report(report2, 1000000) # 策略操作金額一百萬

total_position = position1 + position2

實際下單

1. 安裝券商 API

目前支援 玉山證券永豐證券元富證券富邦證券 的下單系統,擇一使用即可。

# 玉山證券
pip install esun-trade

# 永豐
pip install shioaji

2. 連接證券帳戶

目前支援 玉山證券永豐證券元富證券 以及 富邦證券 的下單系統。可以先將帳號密碼設定成環境變數,只要針對您需要的券商來設定即可,不需要多個券商同時串接。

選擇您的券商

from finlab.online.esun_account import EsunAccount
import os
os.environ['ESUN_CONFIG_PATH'] = '玉山證券交易設定檔(config.ini.example)路徑'
os.environ['ESUN_MARKET_API_KEY'] = '玉山證券的行情API Token'
os.environ['ESUN_ACCOUNT_PASSWORD'] = '玉山證券的帳號密碼'
os.environ['ESUN_CERT_PASSWORD'] = '玉山證券的憑證密碼'

acc = EsunAccount()
舊版 Fugle 環境變數(仍可使用)
from finlab.online.fugle_account import FugleAccount
import os
os.environ['FUGLE_CONFIG_PATH'] = '玉山證券交易設定檔(config.ini.example)路徑'
os.environ['FUGLE_MARKET_API_KEY'] = '玉山證券的行情API Token'
os.environ['FUGLE_ACCOUNT_PASSWORD'] = '玉山證券的帳號密碼'
os.environ['FUGLE_CERT_PASSWORD'] = '玉山證券的憑證密碼'

acc = FugleAccount()

假如使用中下單出現錯誤代碼,可以到玉山證券文件 來查找原因。假如對於 Finlab Package 下單不熟習,建議也可以先按照券商的教學來練習,假如串接成功,則 FinLab Package 的串接也就是如法炮製的設定囉!

  • 請先獲取憑證

    • Windows 憑證下載方式
    • MacOS 憑證下載:目前永豐雖支援 MacOS 下單,但並不支援用 MacOS 獲取憑證,可以先找一台 Windows 作業系統,並且使用上述方法獲取憑證後,再將憑證於 MacOS 來使用。

import os
from finlab.online.sinopac_account import SinopacAccount

os.environ['SHIOAJI_API_KEY'] = '永豐證券API_KEY'
os.environ['SHIOAJI_SECRET_KEY'] = '永豐證券SECRET_KEY'
os.environ['SHIOAJI_CERT_PERSON_ID']= '身份證字號'
os.environ['SHIOAJI_CERT_PATH']= '永豐證券憑證路徑'
os.environ['SHIOAJI_CERT_PASSWORD'] = '永豐證券憑證密碼' # 預設與身份證字號

acc = SinopacAccount()
假如對於 Finlab Package 下單不熟習,建議也可以先按照 券商的教學 來練習,假如串接成功,則 FinLab Package 的串接就會非常簡單囉!

請參考元富證券的教學 來獲取憑證,並且將憑證放在相對應的路徑之中,並且安裝相對應的套件後,即可執行:

import os
from finlab.online.masterlink_account import MasterlinkAccount

os.environ['MASTERLINK_NATIONAL_ID'] = '身分證字號'
os.environ['MASTERLINK_ACCOUNT'] = '交易帳號'
os.environ['MASTERLINK_ACCOUNT_PASS'] = '密碼'
os.environ['MASTERLINK_CERT_PATH'] = '元富證券憑證路徑'
os.environ['MASTERLINK_CERT_PASS'] = '元富證券憑證密碼' # 預設與身分證字號

acc = MasterlinkAccount()

即可完成設定!

請參考富邦證券的教學 來獲取憑證,並且將憑證放在相對應的路徑之中,並且安裝相對應的套件後,即可執行:

import os
from fubon_account import FubonAccount

# 設定環境變數
import os

os.environ['FUBON_NATIONAL_ID'] = "A123456789"
os.environ['FUBON_ACCOUNT_PASS'] = "your_password"
os.environ['FUBON_CERT_PATH'] = "/path/to/cert.pfx"


account = FubonAccount()

即可完成設定!

3. 批次下單

最後利用 OrderExecuter 將當證券帳戶的部位,按照 position 的部位進行調整。

from finlab.online.order_executor import OrderExecutor

# Order Executer
order_executer = OrderExecutor(position , account=acc)

若下單部位含有「 全額交割股」、「處置股」、「警示股」,需先於證券帳戶圈存。參考富果圈存永豐圈存方式。

# 顯示下單部位是否有警示股及相關資訊
order_executer.show_alerting_stocks()

買入 8101 0.429 張 - 總價約         2672.67
根據show_alerting_stocks顯示結果進行圈存後,即可繼續執行下單。

# 下單檢查(瀏覽模式,不會真的下單)
order_executer.create_orders(view_only=True)

# 執行下單(會真的下單,初次使用建議收市時測試)
# 預設使用最近一筆成交價當成限價
order_executer.create_orders()

# 更新限價(將最後一筆成交價當成新的限價)
order_executer.update_order_price()

# 刪除所有委託單
order_executer.cancel_orders()

查看帳戶部位

不論是永豐還是玉山證券帳戶,都可以用以下方式查找帳戶的部位

# 選擇欲使用的券商(擇一即可)

# 永豐
from finlab.online.sinopac_account import SinopacAccount
acc = SinopacAccount()

# 玉山證券
from finlab.online.esun_account import EsunAccount
acc = EsunAccount()

# 或使用舊版名稱(仍可使用)
# from finlab.online.fugle_account import FugleAccount
# acc = FugleAccount()

print(acc.get_position())
可以印出以下結果
[{'stock_id': '2330', 'quantity': 1, 'order_condition': <OrderCondition.CASH: 1>}
 {'stock_id': '1101', 'quantity': 1.001, 'order_condition': <OrderCondition.CASH: 1>}]

常見錯誤與解決方法

⚠️ 重要風險警告

實盤交易涉及真實金錢,錯誤可能導致財務損失!

下單前務必檢查: 1. ✅ 先在模擬帳戶測試至少 1 週 2. ✅ 初期資金不超過總資金的 10-20% 3. ✅ 使用 view_only=True 預覽訂單 4. ✅ 每日監控持倉與訂單狀態 5. ✅ 設定停損機制,避免單次巨大虧損

錯誤 1:帳戶連線失敗

現象:執行 acc = SinopacAccount()acc = EsunAccount() 時拋出連線錯誤

from finlab.online.sinopac_account import SinopacAccount
acc = SinopacAccount()
# ConnectionError: 無法連線至券商 API

原因: - 環境變數未正確設定(API KEY、憑證路徑等) - 憑證檔案不存在或路徑錯誤 - 憑證密碼錯誤 - 網路連線問題或券商 API 服務暫停

解決方法

import os
from finlab.online.sinopac_account import SinopacAccount

# 步驟 1:檢查環境變數是否設定
required_env_vars = [
    'SHIOAJI_API_KEY',
    'SHIOAJI_SECRET_KEY',
    'SHIOAJI_CERT_PERSON_ID',
    'SHIOAJI_CERT_PATH',
    'SHIOAJI_CERT_PASSWORD'
]

print("=== 環境變數檢查 ===")
for var in required_env_vars:
    value = os.environ.get(var)
    if value:
        # 隱藏敏感資訊
        if 'KEY' in var or 'PASSWORD' in var:
            print(f"✅ {var}: {'*' * 8} (已設定)")
        else:
            print(f"✅ {var}: {value}")
    else:
        print(f"❌ {var}: 未設定")

# 步驟 2:檢查憑證檔案是否存在
cert_path = os.environ.get('SHIOAJI_CERT_PATH')
if cert_path and os.path.exists(cert_path):
    print(f"\n✅ 憑證檔案存在:{cert_path}")
else:
    print(f"\n❌ 憑證檔案不存在:{cert_path}")
    print("請確認憑證路徑是否正確")
    exit(1)

# 步驟 3:嘗試連線並捕捉錯誤
try:
    acc = SinopacAccount()
    print("\n✅ 帳戶連線成功")

    # 驗證帳戶狀態
    balance = acc.get_balance()
    print(f"✅ 可用餘額:NT$ {balance:,.0f}")

except FileNotFoundError as e:
    print(f"\n❌ 憑證檔案錯誤:{e}")
    print("請檢查 SHIOAJI_CERT_PATH 是否正確")

except PermissionError as e:
    print(f"\n❌ 憑證權限錯誤:{e}")
    print("請確認憑證密碼是否正確(SHIOAJI_CERT_PASSWORD)")

except ConnectionError as e:
    print(f"\n❌ 網路連線錯誤:{e}")
    print("可能原因:")
    print("1. 網路連線不穩定")
    print("2. 券商 API 服務暫時無法使用")
    print("3. API KEY 或 SECRET KEY 錯誤")
    print("請稍後再試或聯繫券商客服")

except Exception as e:
    print(f"\n❌ 未知錯誤:{e}")
    print("請檢查所有環境變數是否正確設定")
    print("參考教學:https://doc.finlab.tw/details/order_api/")

玉山證券專用檢查

import os
from finlab.online.esun_account import EsunAccount

# 檢查玉山證券環境變數
required_vars = [
    'ESUN_CONFIG_PATH',
    'ESUN_MARKET_API_KEY',
    'ESUN_ACCOUNT_PASSWORD',
    'ESUN_CERT_PASSWORD'
]

for var in required_vars:
    if not os.environ.get(var):
        print(f"❌ {var} 未設定")
        print(f"   請參考:https://www.esunsec.com.tw/trading-platforms/api-trading/docs/trading/quick-start/")
        exit(1)

# 檢查 config.ini 是否存在
config_path = os.environ.get('ESUN_CONFIG_PATH')
if not os.path.exists(config_path):
    print(f"❌ Config 檔案不存在:{config_path}")
    exit(1)

try:
    acc = EsunAccount()
    print("✅ 玉山證券帳戶連線成功")
except Exception as e:
    print(f"❌ 連線失敗:{e}")
    print("請檢查 config.ini 內容是否正確")

錯誤 2:下單前安全檢查失敗

現象:資金不足、持倉過多或單股權重過高

原因: - 可用資金不足以買入 position 指定的股票 - 策略產生的持股數量超過風險限制 - 單一股票權重過高(未分散風險)

解決方法:實施多層安全檢查

from finlab.online.order_executor import OrderExecutor, Position
from finlab.online.sinopac_account import SinopacAccount

def safe_order_check(position, account, max_stocks=50, max_weight=0.15, min_cash_buffer=0.1):
    """
    下單前安全檢查函數

    Args:
        position: Position 物件
        account: 券商帳戶物件
        max_stocks: 最大持股數量(預設 50 檔)
        max_weight: 單股最大權重(預設 15%)
        min_cash_buffer: 最小現金緩衝比例(預設 10%)

    Raises:
        ValueError: 檢查不通過時拋出錯誤
    """
    print("=== 下單安全檢查 ===\n")

    # 檢查 1:持倉數量
    num_stocks = len(position)
    print(f"持倉數量:{num_stocks} 檔")
    if num_stocks > max_stocks:
        raise ValueError(
            f"❌ 持倉過多({num_stocks} 檔),超過限制 {max_stocks}\n"
            f"   建議:使用 .is_largest(N) 限制選股數量"
        )
    print(f"✅ 持倉數量檢查通過(< {max_stocks} 檔)\n")

    # 檢查 2:單一持股權重
    # 假設 position 是 dict 格式 {'stock_id': quantity}
    total_value = sum(position.values())  # 總價值(張數總和)

    for stock_id, quantity in position.items():
        weight = quantity / total_value if total_value > 0 else 0
        print(f"  {stock_id}: {quantity:.2f} 張 ({weight:.1%})")

        if weight > max_weight:
            raise ValueError(
                f"❌ 單股權重過高:{stock_id}{weight:.1%},超過限制 {max_weight:.0%}\n"
                f"   建議:使用 position_limit 參數限制單股權重"
            )

    print(f"\n✅ 單股權重檢查通過(< {max_weight:.0%}\n")

    # 檢查 3:可用資金
    available_cash = account.get_balance()
    print(f"可用資金:NT$ {available_cash:,.0f}")

    # 估算所需資金(簡化計算,實際需要考慮股價)
    # 這裡假設 position.get_required_cash() 方法存在
    # required_cash = position.get_required_cash()

    # 簡化版:假設平均每張 50,000 元
    estimated_cost = total_value * 50000
    print(f"預估成本:NT$ {estimated_cost:,.0f}")

    cash_buffer = available_cash * min_cash_buffer
    if estimated_cost > (available_cash - cash_buffer):
        raise ValueError(
            f"❌ 資金不足!\n"
            f"   可用資金:NT$ {available_cash:,.0f}\n"
            f"   預估成本:NT$ {estimated_cost:,.0f}\n"
            f"   現金緩衝:NT$ {cash_buffer:,.0f}(保留 {min_cash_buffer:.0%}\n"
            f"   建議:降低總資金比例或追加資金"
        )

    print(f"✅ 資金檢查通過(剩餘 {min_cash_buffer:.0%} 緩衝)\n")

    # 檢查 4:處置股/警示股檢查(需要額外資料)
    # 這部分在實際使用時需要調用 show_alerting_stocks()
    print("⚠️  提醒:下單前請執行 order_executer.show_alerting_stocks() 檢查處置股\n")

    print("=" * 50)
    print("✅ 所有安全檢查通過,可以執行下單")
    print("=" * 50)


# 使用範例
try:
    acc = SinopacAccount()
    position = Position.from_report(report, fund=1000000)

    # 執行安全檢查
    safe_order_check(position, acc, max_stocks=30, max_weight=0.10)

    # 通過檢查後,執行下單
    order_executor = OrderExecutor(position, account=acc)

    # 先預覽(不會真的下單)
    print("\n=== 預覽訂單 ===")
    order_executor.create_orders(view_only=True)

    # 確認無誤後,執行下單
    # order_executor.create_orders()  # 取消註解以實際下單

except ValueError as e:
    print(f"\n{e}")
    print("\n請修正問題後再執行下單")

except Exception as e:
    print(f"\n❌ 未預期的錯誤:{e}")
    print("請聯繫技術支援或查看詳細錯誤訊息")

錯誤 3:訂單被拒絕

現象:執行 create_orders() 後,部分或全部訂單被券商拒絕

order_executor.create_orders()
# 訂單狀態:部分成功、部分失敗

常見拒單原因

  1. 處置股未圈存
  2. 股票暫停交易
  3. 漲跌停鎖死
  4. 零股交易時段錯誤
  5. 帳戶權限不足

解決方法

from finlab.online.order_executor import OrderExecutor

# 建立 OrderExecutor
order_executor = OrderExecutor(position, account=acc)

# 步驟 1:檢查處置股/警示股
print("=== 檢查處置股 ===")
alerting_stocks = order_executor.show_alerting_stocks()

if alerting_stocks:
    print("⚠️  發現處置股/警示股,請至券商平台圈存後再下單")
    print("參考圈存教學:")
    print("  - 富果:https://support.fugle.tw/trading/trading-troubleshoot/5231/")
    print("  - 永豐:https://www.sinotrade.com.tw/richclub/freshman/...")
    # 等待用戶手動圈存後再繼續
    input("圈存完成後,按 Enter 繼續...")

# 步驟 2:檢查交易時段
from datetime import datetime
now = datetime.now()
hour = now.hour
minute = now.minute

if 9 <= hour < 13 or (hour == 13 and minute <= 30):
    print("✅ 當前為交易時段(09:00-13:30)")
else:
    print("⚠️  當前為非交易時段")
    print("   整股交易時段:09:00-13:30")
    print("   零股交易時段:09:00-13:40(盤中)、14:00-14:30(盤後)")

# 步驟 3:先預覽,再下單
print("\n=== 預覽訂單 ===")
order_executor.create_orders(view_only=True)

confirmation = input("\n確認無誤後輸入 'YES' 執行下單:")
if confirmation == 'YES':
    try:
        # 執行下單
        orders = order_executor.create_orders()

        # 檢查訂單狀態
        print("\n=== 訂單狀態 ===")
        for order in orders:
            print(f"{order['stock_id']}: {order['status']}")

        # 統計成功/失敗
        success_count = sum(1 for o in orders if o['status'] == 'success')
        fail_count = len(orders) - success_count

        if fail_count > 0:
            print(f"\n⚠️  {fail_count} 筆訂單失敗,請檢查拒單原因:")
            for order in orders:
                if order['status'] != 'success':
                    print(f"  {order['stock_id']}: {order['error_message']}")

            print("\n常見拒單原因:")
            print("1. 處置股未圈存")
            print("2. 股票暫停交易(停牌、全額交割)")
            print("3. 漲跌停鎖死無法成交")
            print("4. 零股交易時段錯誤")
            print("5. 資金不足")
        else:
            print(f"\n✅ 所有訂單成功送出({success_count} 筆)")

    except Exception as e:
        print(f"\n❌ 下單失敗:{e}")
        print("請檢查網路連線或聯繫券商客服")
else:
    print("已取消下單")

錯誤 4:零股下單失敗

現象:使用 odd_lot=True 但訂單失敗

position = Position.from_report(report, fund, odd_lot=True)
order_executor = OrderExecutor(position, account=acc)
order_executor.create_orders()
# 錯誤:零股交易時段不符

原因: - 零股交易時段限制(盤中 09:00-13:40、盤後 14:00-14:30) - 部分券商不支援零股 API 交易 - 零股成交量不足

解決方法

from datetime import datetime
from finlab.online.order_executor import Position, OrderExecutor

# 檢查是否在零股交易時段
now = datetime.now()
hour, minute = now.hour, now.minute

# 盤中零股:09:00-13:40
is_intraday_odd_lot = (9 <= hour < 13) or (hour == 13 and minute <= 40)

# 盤後零股:14:00-14:30
is_afterhours_odd_lot = (hour == 14 and 0 <= minute <= 30)

if not (is_intraday_odd_lot or is_afterhours_odd_lot):
    print("⚠️  當前不在零股交易時段")
    print("   盤中零股:09:00-13:40")
    print("   盤後零股:14:00-14:30")
    print("\n建議:")
    print("1. 等待零股交易時段再下單")
    print("2. 改用整股交易(移除 odd_lot=True)")
    exit(0)

# 建立零股部位
position = Position.from_report(report, fund=500000, odd_lot=True)
print(f"✅ 當前在零股交易時段,可執行下單")

# 執行下單
try:
    order_executor = OrderExecutor(position, account=acc)
    order_executor.create_orders(view_only=True)  # 先預覽
    # order_executor.create_orders()  # 確認後取消註解

except Exception as e:
    print(f"❌ 零股下單失敗:{e}")
    print("\n可能原因:")
    print("1. 券商不支援零股 API 交易(請聯繫券商確認)")
    print("2. 零股成交量不足(建議改用整股)")
    print("3. 網路連線問題")

實盤交易最佳實踐

1. 使用模擬帳戶測試

# 大多數券商提供模擬帳戶,建議先測試 1-2 週
# 確認所有流程正常後再使用真實帳戶

2. 設定每日下單排程

import schedule
import time

def daily_rebalance():
    """每日收盤後自動調倉"""
    try:
        # 1. 執行策略
        report = backtest.sim(position, resample='M')

        # 2. 計算部位
        position = Position.from_report(report, fund=1000000)

        # 3. 安全檢查
        safe_order_check(position, acc)

        # 4. 執行下單
        order_executor = OrderExecutor(position, account=acc)
        order_executor.create_orders()

        print(f"✅ {datetime.now()} - 下單完成")

    except Exception as e:
        print(f"❌ {datetime.now()} - 下單失敗:{e}")
        # 發送通知(LINE/Email)

# 每日 14:00 執行(收盤後)
schedule.every().day.at("14:00").do(daily_rebalance)

while True:
    schedule.run_pending()
    time.sleep(60)

3. 記錄所有交易

import json
from datetime import datetime

def log_trade(position, orders, status):
    """記錄交易日誌"""
    log_entry = {
        'timestamp': datetime.now().isoformat(),
        'position': position,
        'orders': orders,
        'status': status
    }

    with open('trade_log.json', 'a') as f:
        f.write(json.dumps(log_entry, ensure_ascii=False) + '\n')

參考資源


最後提醒

請務必在模擬帳戶充分測試後,才使用真實資金!

建議測試清單: - ✅ 帳戶連線穩定無錯誤 - ✅ 下單流程完整執行 - ✅ 持倉計算正確(與預期一致) - ✅ 資金控制正常(不超支) - ✅ 異常處理機制有效 - ✅ 連續運行 1-2 週無問題

只有在以上項目全部確認後,才考慮使用真實資金!