---
# System prepended metadata

title: 回測環境開發

---

## 回測環境開發流程

#### <font style="color: #30598c;">🐞完整程式碼範例</font>

<a class="button" href="https://drive.google.com/file/d/1UonyRRj5Q1zMkxv8wbVrLofDWI7r_8de/view?usp=sharing">
    <font class="button_text">程式碼下載</font>
</a>

</br>

#### 1.	下載歷史股價
```python
def fetch_history_data(stock_code: str, start_date: str, end_date: str) -> pd.DataFrame:
    """
    獲取歷史股價資料
    """
    # 透過 yfinance 下載歷史股價
    source_data = yf.download(tickers=f"{stock_code}.TW", start=start_date, end=end_date, auto_adjust=False)
    
    # 回傳下載後的資料
    return source_data
```
</br>

#### 2.	調整資料欄位 (def format_data_columns)
```python
def format_data_columns(data: pd.DataFrame) -> pd.DataFrame:
    """
    格式化資料欄位
    """
    # 取出欄位名稱的最上層
    data.columns = data.columns.get_level_values(0)

    # 去掉欄位名稱 (Price)
    data.columns.name = None

    # 重設索引，將 Date 從 index 轉成欄位
    pre_data = data.reset_index()

    # 依需求的欄位順序
    pre_data = pre_data[["Date", "Open", "High", "Low", "Close", "Volume"]]

    # 回傳格式化後的資料
    return pre_data
```

#### <font style="color: #30598c;">🐞如果沒有辦法成功透過 `yfinance` 獲取資料，點擊連結下載</font>

<a class="button" href="https://drive.google.com/file/d/1zX0sczmbLkC35UcbQ72Loodo-P_7guhJ/view?usp=sharing">
    <font class="button_text">歷史資料下載</font>
</a>

</br>

#### 3.	計算技術指標 (def fetch_TA_data)
```python
def fetch_TA_data(data: pd.DataFrame) -> pd.DataFrame:
    """
    計算技術指標
    """
    # 計算移動平均線
    data["MA10"] = data["Close"].rolling(window=10).mean()
    data["MA60"] = data["Close"].rolling(window=60).mean()

    # 四捨五入到小數點後兩位
    ta_df = data.round(2)

    # 回傳包含技術指標的資料
    return ta_df
```
</br>

#### 4.	資料逐筆回傳 (def recieve_kBar)
```python
# 將資料逐筆拆分給回測系統
for index, row in ta_data.iterrows():

    # 將 Series 轉成 DataFrame 格式
    chart = row.to_frame().T.reset_index(drop=True)

    # 將資料傳遞給回測系統
    run_backtest(chart)
```
</br>

#### 5.	新增接收行情通道
```python
def receive_kBar(chart: pd.DataFrame):
    """
    執行回測
    """
    # 打印行情資訊
    cs.print("接收行情資訊:", style="bold yellow")
    cs.print(f"{chart}\n")
```
</br>

#### 6.	判斷交易信號 (fetch_trade_singnal)
```python
# 交易信號: Buy, Sell, Clear, Hold
trade_signal = "Hold"
```

```python
def fetch_trade_signal(chart: pd.DataFrame):
    """
    獲取交易信號
    """
    global trade_signal, trade_signal_data, trade_order

    # 將最新 k 棒資料加入交易信號資料集
    trade_signal_data = pd.concat([trade_signal_data, chart], ignore_index=True)

    # 確保資料數大於 2 以便後續判斷
    if len(trade_signal_data) < 2:
        trade_signal = "Hold"
        return

    # 僅保留最近兩筆資料
    trade_signal_data = trade_signal_data.tail(2)

    # 擷取今日、昨日 k 棒資訊
    curr_date = trade_signal_data.iloc[-1]["Date"]
    prev_close = trade_signal_data.iloc[0]["Close"]
    curr_close = trade_signal_data.iloc[-1]["Close"]
    prev_ma10 = trade_signal_data.iloc[0]["MA10"]
    curr_ma10 = trade_signal_data.iloc[-1]["MA10"]
    curr_ma60 = trade_signal_data.iloc[-1]["MA60"]

    # 更新委託單資訊
    trade_order["date"] = curr_date.strftime("%Y-%m-%d")
    trade_order["price"] = float(curr_close)

    # 判斷買賣訊號
    if curr_close < curr_ma60:
        trade_signal = "Clear"
    elif (prev_close <= prev_ma10) and (curr_close > curr_ma10) and (position_count == 0):
        trade_signal = "Buy"
    elif (prev_close >= prev_ma10) and (curr_close < curr_ma10) and (position_count > 0):
        trade_signal = "Sell"
    else:
        trade_signal = "Hold"
```
</br>

#### 7.	執行交易 (trading)
```python
# 建立委託單格式
trade_order = {"action": None, "price": 0, "quantity": 0, "fee": 0, "tax": 0, "ar": 0, "date": None}
```

```python
def trading() -> str:
    """
    執行交易
    """
    global position_count

    # 根據交易信號決定交易動作
    if trade_signal == "Buy":
        action = "Buy"
        trade_order["quantity"] = 1
    elif trade_signal == "Sell":
        action = "Sell"
        trade_order["quantity"] = 1
    elif trade_signal == "Clear" and position_count > 0:
        action = "Sell"
        trade_order["quantity"] = position_count
    else:
        action = "Hold"
        return action

    # 更新委託單資訊
    trade_order["action"] = action

    # 計算委託價金
    cost = trade_order["price"] * trade_order["quantity"] * 1000

    # 計算交易稅
    if action == "Buy":
        trade_order["tax"] = 0
    else:
        trade_order["tax"] = int(cost * 0.003)

    # 計算手續費
    trade_order["fee"] = int(cost * 0.001425)
    trade_order["ar"] = cost + trade_order["fee"] + trade_order["tax"]

    # 紀錄整張委託單
    trade_order_list.append(trade_order.copy())

    # 更新庫存數量
    if trade_signal == "Buy":
        position_count += 1
    elif trade_signal == "Sell":
        position_count -= 1
    else:
        position_count = 0

    # 回傳交易動作
    return action
```
</br>


#### 8.	更新損益紀錄
```python
# 建立損益紀錄格式
trade_record = {"trade_count": {"Buy": 0, "Sell": 0, "Total": 0}, "pnl": {"Realized": 0, "Unrealized": 0}, "pnl_count": {"Profit": 0, "Loss": 0, "Total": 0}, "transaction_cost": {"Fee": 0, "Tax": 0, "Total": 0}}
```

```python
def update_pnl_record(action: str):
    """
    更新交易、損益資訊
    """
    global backtest_capital, trade_record

    # 更新回測本金
    if action == "Buy":
        backtest_capital -= trade_order["ar"]
    elif action == "Sell":
        backtest_capital += trade_order["ar"]
    else:
        return

    # 更新交易次數
    trade_record["trade_count"][action] += 1
    trade_record["trade_count"]["Total"] = trade_record["trade_count"]["Buy"] + trade_record["trade_count"]["Sell"]

    # 更新已實現損益紀錄
    if action == "Buy":
        trade_record["pnl"]["Unrealized"] += trade_order["ar"]
    # 更新未實現損益紀錄
    else:
        curr_realized = (trade_order["ar"] - trade_record["pnl"]["Unrealized"])
        trade_record["pnl"]["Realized"] += curr_realized
        trade_record["pnl"]["Unrealized"] = 0

        # 更新獲利/虧損次數
        if curr_realized > 0:
            trade_record["pnl_count"]["Profit"] += 1
        elif curr_realized < 0:
            trade_record["pnl_count"]["Loss"] += 1
        trade_record["pnl_count"]["Total"] = trade_record["pnl_count"]["Profit"] + trade_record["pnl_count"]["Loss"]

    # 更新交易成本
    trade_record["transaction_cost"]["Fee"] += trade_order["fee"]
    trade_record["transaction_cost"]["Tax"] += trade_order["tax"]
    trade_record["transaction_cost"]["Total"] = trade_record["transaction_cost"]["Fee"] + trade_record["transaction_cost"]["Tax"]
```


<style>
    .tips {
        display: flex;
        gap: 2px;
        padding-bottom: 16px;
    }
    
    .tips-content {
        font-family: "Noto Sans TC";
        color: #3f628a;
        font-weight: 600;
    }
    
    .button {
        display: flex;
        justify-content: center;
        background-color: #3f628a;
        padding: 3px 5px 5px 5px;
        width: 20%;
        border-radius: 6px;
    }
    
    .red_btn {
        background-color: #b00d0d;
    }
    
    .button_text {
        font-family: "Noto Sans TC";
        font-weight: 500;
        color: #FFFFFF;
        
    }
</style>