0Pricing
AI Agents · บทเรียน

การทดสอบย้อนหลังการตัดสินใจของเอเจนต์

จำลองกลยุทธ์ของเอเจนต์กับข้อมูลย้อนหลังเพื่อยืนยันประสิทธิภาพ

การทดสอบย้อนหลังการตัดสินใจของเอเจนต์ เป็นบทเรียน AI Agents ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Agents และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงต้องทดสอบย้อนหลังการตัดสินใจของเอเจนต์

คุณไม่อาจทราบได้ว่าเอเจนต์ด้านการเงินกำลังสร้างคำแนะนำที่ดีหรือไม่ หากไม่ได้ทดสอบกับข้อมูลในอดีต การทดสอบย้อนหลังจะจำลองสภาวะตลาดในอดีตผ่านตรรกะการตัดสินใจของเอเจนต์ และวัดว่าการตัดสินใจเหล่านั้นจะให้ผลลัพธ์เป็นอย่างไร

นี่คือเครื่องมือหลักสำหรับตรวจสอบคุณภาพของกลยุทธ์ก่อนนำไปใช้งานจริง

กับดักอคติจากการใช้ข้อมูลอนาคต

อคติจากการใช้ข้อมูลอนาคตเป็นข้อผิดพลาดที่พบบ่อยและอันตรายที่สุดในการทดสอบย้อนหลัง นั่นคือการใช้ข้อมูลอนาคตเพื่อตัดสินใจในสิ่งที่ควรเป็นการตัดสินใจในอดีต ตัวอย่างเช่น การใช้ราคาปิดของวันนั้นเพื่อตัดสินใจว่าจะซื้อ ตอนตลาดเปิด ในวันเดียวกันหรือไม่

การตัดสินใจในอดีตทุกครั้งต้องใช้เฉพาะข้อมูลที่มีอยู่ ณ เวลาตัดสินใจจำลองเท่านั้น

import pandas as pd
import yfinance as yf

# WRONG — uses today's close to decide today's trade
def bad_signal(history: pd.DataFrame, date: str) -> str:
    today_close = history.loc[date, 'Close']  # look-ahead!
    if today_close > history['Close'].mean():
        return 'BUY'
    return 'HOLD'

# CORRECT — uses only data available BEFORE the decision date
def good_signal(history: pd.DataFrame, date: str) -> str:
    past_data = history.loc[:date].iloc[:-1]  # exclude today
    if past_data.empty:
        return 'HOLD'
    today_open = history.loc[date, 'Open']    # open price known at market open
    if today_open > past_data['Close'].mean():
        return 'BUY'
    return 'HOLD'

สร้างลูปเล่นซ้ำข้อมูลย้อนหลัง

วนดูวันที่ในอดีตทีละวัน ในแต่ละขั้นตอน เอเจนต์จะเห็นเฉพาะข้อมูลที่มีอยู่จนถึงเวลานั้น บันทึกการตัดสินใจ แล้วเลื่อนไปยังวันถัดไปเพื่อสังเกตผลลัพธ์

import yfinance as yf
import pandas as pd

def backtest_simple(ticker: str, start: str, end: str) -> list[dict]:
    hist = yf.Ticker(ticker).history(start=start, end=end)
    results = []

    for i in range(20, len(hist)):  # need 20 days of history for signals
        window   = hist.iloc[:i]           # data available on day i
        today    = hist.iloc[i]
        decision = good_signal(window, str(hist.index[i].date()))

        results.append({
            'date':     str(hist.index[i].date()),
            'decision': decision,
            'open':     float(today['Open']),
            'close':    float(today['Close']),
            'next_day_return': None  # filled in next iteration
        })
    return results

จำลองตรรกะของเอเจนต์ในแต่ละขั้นตอน

เพื่อให้การทดสอบย้อนหลังสมจริงยิ่งขึ้น ให้จำลองการเรียกเครื่องมือของเอเจนต์ทั้งหมดในแต่ละวัน ซึ่งรวมถึงการสร้างพรอมต์ การเรียก LLM และการแยกวิเคราะห์การตัดสินใจ วิธีนี้จะทดสอบทั้งระบบ ไม่ใช่เพียงตรรกะของสัญญาณ

import openai, json
import yfinance as yf

client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')

def agent_decision_at_date(ticker: str, hist_up_to: pd.DataFrame) -> str:
    recent = hist_up_to.tail(20)
    closes = recent['Close'].tolist()
    returns = [round((closes[i] - closes[i-1]) / closes[i-1], 4)
               for i in range(1, len(closes))]
    prompt = (
        f'Ticker: {ticker}\n'
        f'Last 20 closing prices: {closes}\n'
        f'Daily returns: {returns}\n'
        f'Based on this data, recommend: BUY, SELL, or HOLD.\n'
        f'Return JSON: {{"action": "BUY/SELL/HOLD", "reason": "..."}}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        response_format={'type': 'json_object'}
    )
    return json.loads(resp.choices[0].message.content).get('action', 'HOLD')

คำนวณอัตราการทายถูก

อัตราการทายถูกวัดว่าการตัดสินใจ BUY มีสัดส่วนเท่าใดที่ตามมาด้วยผลตอบแทนวันถัดไปเป็นบวก อัตราการทายถูกที่สูงกว่า 50% หมายความว่าเอเจนต์คาดการณ์ทิศทางได้ถูกต้องบ่อยกว่าการเดาสุ่ม

def compute_hit_rate(results: list[dict]) -> dict:
    buy_decisions = [r for r in results if r['decision'] == 'BUY'
                     and r.get('next_day_return') is not None]

    if not buy_decisions:
        return {'hit_rate': None, 'sample_size': 0}

    correct = sum(1 for r in buy_decisions if r['next_day_return'] > 0)
    return {
        'hit_rate':     round(correct / len(buy_decisions), 4),
        'sample_size':  len(buy_decisions),
        'correct':      correct,
        'interpretation': 'above_chance' if correct / len(buy_decisions) > 0.52 else 'at_or_below_chance'
    }

if __name__ == '__main__':
    results = [
        {'decision': 'BUY', 'next_day_return': 0.012},
        {'decision': 'BUY', 'next_day_return': -0.004},
        {'decision': 'HOLD', 'next_day_return': None},
        {'decision': 'BUY', 'next_day_return': 0.007},
    ]
    print('Hit rate:', compute_hit_rate(results))

คำนวณผลตอบแทนเทียบเกณฑ์มาตรฐาน

เปรียบเทียบผลตอบแทนของกลยุทธ์กับเกณฑ์มาตรฐานแบบซื้อแล้วถือ หากเอเจนต์ให้ผลลัพธ์ด้อยกว่าการถือดัชนีไว้เฉย ๆ ความซับซ้อนของเอเจนต์ก็ไม่มีเหตุผลรองรับ

def compare_to_benchmark(results: list[dict], ticker: str, period: str = '1y') -> dict:
    import yfinance as yf

    # Strategy return: only invested on BUY days
    buy_days = [r for r in results if r['decision'] == 'BUY'
                and r.get('next_day_return') is not None]
    strategy_return = sum(r['next_day_return'] for r in buy_days)

    # Benchmark: buy-and-hold
    hist = yf.Ticker(ticker).history(period=period)['Close']
    bh_return = float((hist.iloc[-1] - hist.iloc[0]) / hist.iloc[0])

    return {
        'strategy_return_pct':  round(strategy_return * 100, 2),
        'benchmark_return_pct': round(bh_return * 100, 2),
        'outperformed':         strategy_return > bh_return,
        'alpha_pct':            round((strategy_return - bh_return) * 100, 2)
    }

ผลกระทบของต้นทุนธุรกรรม

หากไม่คำนึงถึงต้นทุนธุรกรรม เช่น ค่าคอมมิชชัน ส่วนต่างราคา และการเลื่อนหลุด กลยุทธ์อาจดูเหมือนทำกำไรได้ แต่ในทางปฏิบัติกลับขาดทุน แม้ต้นทุน 0.1% ต่อการซื้อขายก็สามารถทบต้นจนมีผลอย่างมากเมื่อมีการซื้อขายหลายครั้ง

COMMISSION_PCT = 0.001   # 0.1% per trade (buy + sell)
SLIPPAGE_PCT   = 0.0005  # 0.05% per trade

def returns_after_costs(decisions: list[dict]) -> dict:
    total_return = 0.0
    total_cost   = 0.0

    for d in decisions:
        if d['decision'] == 'BUY' and d.get('next_day_return') is not None:
            gross = d['next_day_return']
            cost  = COMMISSION_PCT + SLIPPAGE_PCT
            net   = gross - cost
            total_return += net
            total_cost   += cost

    return {
        'gross_return_pct': round((total_return + total_cost) * 100, 2),
        'net_return_pct':   round(total_return * 100, 2),
        'total_costs_pct':  round(total_cost * 100, 2),
        'num_trades':       sum(1 for d in decisions if d['decision'] == 'BUY')
    }

if __name__ == '__main__':
    decisions = [
        {'decision': 'BUY', 'next_day_return': 0.012},
        {'decision': 'BUY', 'next_day_return': -0.004},
        {'decision': 'HOLD', 'next_day_return': None},
    ]
    print('Returns after trading costs:', returns_after_costs(decisions))

การตรวจสอบแบบเดินหน้า

การปรับให้เหมาะสมภายในตัวอย่าง หรือการฝึกกับช่วงเวลาเดียวกับที่ใช้ทดสอบ จะทำให้เกิดการปรับเข้ากับข้อมูลมากเกินไป การตรวจสอบแบบเดินหน้าใช้ช่วงหน้าต่างฝึกที่เลื่อนไปเรื่อย ๆ ตามด้วยช่วงทดสอบใหม่ เช่นเดียวกับการนำไปใช้งานจริง

def walk_forward_test(ticker: str, total_years: int = 3,
                      train_months: int = 12, test_months: int = 3) -> list[dict]:
    import yfinance as yf
    from dateutil.relativedelta import relativedelta
    from datetime import date

    results = []
    start = date(date.today().year - total_years, 1, 1)
    end   = date.today()

    window_start = start
    while True:
        train_end = window_start + relativedelta(months=train_months)
        test_end  = train_end + relativedelta(months=test_months)
        if test_end > end:
            break

        # In practice: train your signal on window_start->train_end
        # then evaluate on train_end->test_end
        results.append({
            'train_period': f'{window_start} to {train_end}',
            'test_period':  f'{train_end} to {test_end}'
        })
        window_start += relativedelta(months=test_months)

    return results

รายงานการทดสอบย้อนหลัง

สรุปผลการทดสอบย้อนหลังในรายงานที่มีโครงสร้าง โดยระบุอัตราการทายถูก ผลตอบแทนรวมเทียบกับเกณฑ์มาตรฐาน แอลฟา อัตราส่วนชาร์ป การลดลงสูงสุด จำนวนการซื้อขาย และผลตอบแทนสุทธิหลังหักต้นทุน

def backtest_report(ticker: str, results: list[dict]) -> dict:
    hit = compute_hit_rate(results)
    benchmark = compare_to_benchmark(results, ticker)
    costs = returns_after_costs(results)

    return {
        'ticker':                  ticker,
        'total_decisions':         len(results),
        'buy_signals':             costs['num_trades'],
        'hit_rate':                hit.get('hit_rate'),
        'strategy_return_gross':   f'{costs["gross_return_pct"]}%',
        'strategy_return_net':     f'{costs["net_return_pct"]}%',
        'benchmark_return':        f'{benchmark["benchmark_return_pct"]}%',
        'alpha':                   f'{benchmark["alpha_pct"]}%',
        'outperformed_benchmark':  benchmark['outperformed'],
        'transaction_costs':       f'{costs["total_costs_pct"]}%'
    }

ตีความผลการทดสอบย้อนหลัง

ผลการทดสอบย้อนหลังที่ดีไม่ได้รับประกันผลลัพธ์ในอนาคต แต่ผลลัพธ์ที่ไม่ดีย่อมช่วยระบุกลยุทธ์ที่ไม่ควรนำไปใช้งาน สัญญาณเตือนสำคัญ ได้แก่ อัตราการทายถูกต่ำกว่า 50% แอลฟาติดลบหลังหักต้นทุน อัตราส่วนชาร์ปต่ำกว่า 0.5 หรือการลดลงสูงสุดเกินกว่าระดับความเสี่ยงที่คุณยอมรับได้

def interpret_backtest(report: dict) -> list[str]:
    warnings = []

    hit_rate = report.get('hit_rate')
    if hit_rate and hit_rate < 0.50:
        warnings.append(f'Hit rate {hit_rate:.1%} is below chance — strategy predicts direction poorly')

    alpha_str = report.get('alpha', '0%')
    alpha = float(alpha_str.replace('%', '')) / 100
    if alpha < 0:
        warnings.append(f'Negative alpha {alpha_str}: strategy underperforms buy-and-hold')

    if not warnings:
        return ['Backtest results look acceptable — proceed with paper trading before live deployment']
    return warnings

if __name__ == '__main__':
    report = {'hit_rate': 0.45, 'alpha': '-1.2%'}
    for warning in interpret_backtest(report):
        print('-', warning)

การจำลองมอนติคาร์โลเพื่อทดสอบความทนทาน

การทดสอบย้อนหลังเพียงครั้งเดียวมีความอ่อนไหวต่อช่วงเวลาที่เลือกโดยเฉพาะ การจำลองมอนติคาร์โลจะสุ่มลำดับผลตอบแทนในอดีตใหม่ เพื่อทดสอบว่าผลลัพธ์ยังคงเดิมในลำดับต่าง ๆ หรือไม่ ช่วยให้เห็นว่ากลยุทธ์มีความทนทานจริงหรือเพียงแค่โชคดี

import numpy as np

def monte_carlo_backtest(daily_returns: list[float],
                         simulations: int = 1000) -> dict:
    returns_arr = np.array(daily_returns)
    final_returns = []

    for _ in range(simulations):
        shuffled = np.random.choice(returns_arr, size=len(returns_arr), replace=True)
        cumulative = float((1 + shuffled).prod() - 1)
        final_returns.append(cumulative)

    final_returns = sorted(final_returns)
    return {
        'median_return':      round(float(np.median(final_returns)) * 100, 2),
        'percentile_5':       round(float(np.percentile(final_returns, 5)) * 100, 2),
        'percentile_95':      round(float(np.percentile(final_returns, 95)) * 100, 2),
        'prob_positive':      round(sum(1 for r in final_returns if r > 0) / simulations, 3)
    }

อคติจากการใช้ข้อมูลอนาคตในการทดสอบย้อนหลังคืออะไร

อคติจากการใช้ข้อมูลอนาคตเป็นวิธีที่พบบ่อยที่สุดในการทำให้ผลการทดสอบย้อนหลังดูดีเกินจริงโดยไม่ตั้งใจ การเข้าใจคำจำกัดความอย่างแม่นยำจะช่วยป้องกันการสร้างการทดสอบย้อนหลังที่ใช้การไม่ได้

สรุปการทดสอบย้อนหลังการตัดสินใจของเอเจนต์

การทดสอบย้อนหลังที่ถูกต้องต้องประกอบด้วย การหลีกเลี่ยงอคติจากการใช้ข้อมูลอนาคต โดยการตัดสินใจใช้เฉพาะข้อมูลในอดีต การจำลองตรรกะของเอเจนต์ให้สมจริง ในแต่ละขั้นตอนของอดีต การวัด อัตราการทายถูก และ แอลฟาเทียบกับเกณฑ์มาตรฐาน การคำนึงถึงต้นทุนธุรกรรม และการตรวจสอบด้วย การแบ่งข้อมูลแบบเดินหน้า เพื่อค้นหาการปรับเข้ากับข้อมูลมากเกินไป

คำถามที่พบบ่อย

บทเรียน “การทดสอบย้อนหลังการตัดสินใจของเอเจนต์” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การทดสอบย้อนหลังการตัดสินใจของเอเจนต์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Agents ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การทดสอบย้อนหลังการตัดสินใจของเอเจนต์”

จำลองกลยุทธ์ของเอเจนต์กับข้อมูลย้อนหลังเพื่อยืนยันประสิทธิภาพ คุณปฏิบัติ AI Agents ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Agents หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Agents บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การทดสอบย้อนหลังการตัดสินใจของเอเจนต์” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน AI Agents นี้ได้ไหม

ได้ บทเรียน AI Agents ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การผสาน API ข้อมูลตลาด
  2. เครื่องมือเอเจนต์สำหรับวิเคราะห์พอร์ตการลงทุน
  3. กรอบป้องกันความเสี่ยงและการปฏิบัติตามข้อกำหนด
  4. การทดสอบย้อนหลังการตัดสินใจของเอเจนต์
← กลับไปที่ AI Agents