การทดสอบย้อนหลังการตัดสินใจของเอเจนต์
จำลองกลยุทธ์ของเอเจนต์กับข้อมูลย้อนหลังเพื่อยืนยันประสิทธิภาพ
การทดสอบย้อนหลังการตัดสินใจของเอเจนต์ เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การผสาน API ข้อมูลตลาด
- เครื่องมือเอเจนต์สำหรับวิเคราะห์พอร์ตการลงทุน
- กรอบป้องกันความเสี่ยงและการปฏิบัติตามข้อกำหนด
- การทดสอบย้อนหลังการตัดสินใจของเอเจนต์