วิธีคำนวณ Dividend Yield แบบถ่วงน้ำหนักของพอร์ต
เรียนรู้วิธีคำนวณ Dividend Yield ของพอร์ตจากรายได้ปันผลต่อปีหารด้วยมูลค่าตลาด พร้อมเหตุผลที่การเฉลี่ยคอลัมน์ yield อาจทำให้ตัวเลขสูงเกินจริง
พอร์ตโฟลิโอของคุณมี dividend yield อยู่หนึ่งตัวเลข นั่นคือรายได้เงินปันผลรวมต่อปีที่สินทรัพย์ในพอร์ตมีกำหนดจ่าย หารด้วยมูลค่าตลาดรวมของสินทรัพย์เหล่านั้น หากเขียนกลับกัน ตัวเลขนี้คือค่าเฉลี่ยของ yield รายตัวที่ถ่วงน้ำหนักด้วยมูลค่าตลาด โดยน้ำหนักของแต่ละสินทรัพย์เท่ากับมูลค่าเป็นดอลลาร์ของสินทรัพย์นั้นหารด้วยมูลค่ารวม ค่าเฉลี่ยของคอลัมน์ yield บนหน้าจอของโบรกเกอร์เป็นคนละตัวเลข และในพอร์ตจริงส่วนใหญ่ มักเป็นตัวเลขที่ดูดีเกินจริง
วิธีคำนวณ dividend yield ของพอร์ตโฟลิโอ
สำหรับแต่ละสถานะ ให้คูณจำนวนหุ้นด้วยเงินปันผลต่อหุ้นต่อปี จะได้รายได้ต่อปีของสถานะนั้นในรูปดอลลาร์ จากนั้นรวมรายได้ทั้งหมด แล้วหารด้วยผลรวมของจำนวนหุ้นคูณราคาในทุกสถานะ ก่อนคูณด้วย 100 นี่คือการคำนวณทั้งหมด ไม่ว่าจะมีสินทรัพย์สี่รายการหรือสี่สิบรายการ
เขียนได้สองรูปแบบที่ให้ผลเท่ากัน:
- รายได้รวมต่อปีหารด้วยมูลค่าตลาดรวมของพอร์ตโฟลิโอ
- ผลรวมของน้ำหนักคูณ yield ของแต่ละสถานะ โดยน้ำหนักเท่ากับมูลค่าของสถานะหารด้วยมูลค่าพอร์ตโฟลิโอ
รูปแบบที่สองแสดงให้เห็นว่าค่าเฉลี่ยแบบธรรมดาผิดพลาดอย่างไร สินทรัพย์ทุกตัวปรากฏอยู่ในทั้งสองรูปแบบ แต่ในรูปแบบที่ถูกต้อง สินทรัพย์แต่ละตัวมีน้ำหนักตามมูลค่าเงิน ไม่ใช่มีสิทธิ์ออกเสียงเท่ากันทุกตัว เงินสดที่นับเป็นส่วนหนึ่งของพอร์ตโฟลิโอต้องรวมอยู่ในตัวหารด้วย หากไม่นับ เงิน yield ที่แสดงจะสูงขึ้นโดยไม่ได้สร้างรายได้เพิ่มแม้แต่เซนต์เดียว สำหรับการคำนวณในกรณีหุ้นตัวเดียว โปรดดู วิธีคำนวณ dividend yield
เหตุใดการหาค่าเฉลี่ยของคอลัมน์ yield จึงทำให้รายได้สูงเกินจริง
ความแตกต่างระหว่างสองวิธีเห็นได้ชัดที่สุดจากตัวอย่างการคำนวณด้านล่าง โค้ดนี้เป็น Python 3 แบบพื้นฐาน โดยไม่มีการนำเข้าไลบรารีใด ๆ เครื่องใดก็ตามที่มี python3 สามารถเรียกใช้ได้ สถานะทั้งห้าเป็นตัวเลขสมมติแบบกลม ๆ ซึ่งเลือกมาเพื่อให้เห็นผลกระทบอย่างชัดเจน แต่ละรายการประกอบด้วยชื่อ จำนวนหุ้น ราคา และเงินปันผลต่อหุ้นต่อปี
#!/usr/bin/env python3
# each row: (name, shares, price, annual dividend per share)
holdings = [
('A', 200, 50.00, 1.50),
('B', 150, 100.00, 2.00),
('C', 100, 80.00, 0.80),
('D', 300, 20.00, 1.00),
('E', 100, 10.00, 0.90),
]
total_value = sum(sh * px for _, sh, px, _ in holdings)
total_income = sum(sh * dps for _, sh, _, dps in holdings)
naive = sum(100 * dps / px for _, _, px, dps in holdings) / len(holdings)
print(f'market value : ${total_value:,.2f}')
print(f'annual income : ${total_income:,.2f}')
print(f'portfolio yield : {100 * total_income / total_value:.2f}%')
print(f'average of yields : {naive:.2f}%')
for name, sh, px, dps in holdings:
value = sh * px
income = sh * dps
print(
f'{name} weight {100 * value / total_value:5.1f}%'
f' yield {100 * dps / px:5.2f}%'
f' income ${income:8,.2f}'
f' share of income {100 * income / total_income:5.1f}%'
)
เมื่อเรียกใช้โค้ด บรรทัดสรุปสองบรรทัดจะให้ผลต่างกันมาก พอร์ตถือหุ้นมูลค่า $40,000 และจ่ายรายได้ $1,070 ต่อปี คิดเป็น weighted yield ที่ 2.68% ส่วนค่าเฉลี่ยแบบธรรมดาของ yield ทั้งห้าตัวแสดงผลที่ 4.00% หรือประมาณหนึ่งจุดห้าเท่าของตัวเลขที่แท้จริง
สถานะ E คือสาเหตุทั้งหมด สถานะนี้มี yield 9.00% ซึ่งสูงที่สุดในพอร์ต แต่มีมูลค่าหุ้นเพียง $1,000 คิดเป็น 2.5% ของมูลค่าพอร์ตและ 8.4% ของรายได้ ในค่าเฉลี่ยแบบธรรมดา สถานะนี้มีสิทธิ์ออกเสียงหนึ่งในห้า เท่ากับสถานะมูลค่า $15,000 ที่อยู่ถัดไป ตัวเลข yield จะมีความหมายก็ต่อเมื่อพิจารณาควบคู่กับมูลค่าเงินที่รองรับตัวเลขนั้น
น้ำหนักของสถานะจริงในพอร์ตเป็นอย่างไร
หุ้นที่จ่ายเงินปันผลในพอร์ตของครัวเรือนมี yield แตกต่างกันในวงกว้าง นี่คือเหตุผลที่การเลือกวิธีถ่วงน้ำหนักมีความสำคัญ ตารางด้านล่างคำนวณ trailing yield ของแต่ละรายการจากเงินปันผลต่อหุ้นที่ประกาศในช่วงสิบสองเดือนล่าสุด หารด้วยราคาปิดรายวันที่ล่าสุด
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH
last_px AS
(
SELECT
ticker,
toFloat64(argMax(close, date)) AS px,
formatDateTime(max(date), '%b %e, %Y') AS priced_through
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('AAPL', 'MSFT', 'HD', 'KO', 'PG', 'PEP', 'JNJ', 'XOM', 'CVX', 'VZ')
AND date >= today() - 30
GROUP BY ticker
),
ttm_dps AS
(
SELECT
ticker,
sum(amount) AS dps
FROM
(
SELECT
ticker,
id,
toFloat64(any(cash_amount)) AS amount
FROM global_markets.stocks_dividends
WHERE ticker IN ('AAPL', 'MSFT', 'HD', 'KO', 'PG', 'PEP', 'JNJ', 'XOM', 'CVX', 'VZ')
AND ex_dividend_date > today() - 365
AND ex_dividend_date <= today()
GROUP BY ticker, id
)
GROUP BY ticker
)
SELECT
p.ticker AS symbol,
round(d.dps, 2) AS ttm_dividend_usd,
round(100 * d.dps / p.px, 2) AS yield_pct,
p.priced_through AS priced_through
FROM last_px AS p
INNER JOIN ttm_dps AS d ON d.ticker = p.ticker
ORDER BY yield_pct DESCในบรรดา 10 รายการเหล่านี้ ซึ่งมีราคาผ่าน Aug 7, 2026 yield อยู่ในช่วงตั้งแต่ 5.96% ที่ VZ ไปจนถึง 0.34% ที่ AAPL พอร์ตที่สร้างจากช่วง yield เช่นนี้จะไม่ได้อยู่ใกล้ค่ากึ่งกลางของคอลัมน์ เว้นแต่มูลค่าของแต่ละสถานะจะเท่ากันพอดี
จากนั้นนำห้ารายการมาใส่ในพอร์ตที่กำหนดจำนวนหุ้นคงที่ แล้วอ่านตัวเลขสองชุดเปรียบเทียบกัน ได้แก่ สัดส่วนของแต่ละสถานะต่อมูลค่าตลาด และสัดส่วนของแต่ละสถานะต่อรายได้ต่อปี
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH
positions AS
(
SELECT
tupleElement(p, 1) AS ticker,
toFloat64(tupleElement(p, 2)) AS shares
FROM
(
SELECT arrayJoin([('MSFT', 30.), ('PG', 60.), ('XOM', 80.), ('KO', 100.), ('VZ', 60.)]) AS p
)
),
last_px AS
(
SELECT
ticker,
toFloat64(argMax(close, date)) AS px
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('MSFT', 'PG', 'XOM', 'KO', 'VZ')
AND date >= today() - 30
GROUP BY ticker
),
ttm_dps AS
(
SELECT
ticker,
sum(amount) AS dps
FROM
(
SELECT
ticker,
id,
toFloat64(any(cash_amount)) AS amount
FROM global_markets.stocks_dividends
WHERE ticker IN ('MSFT', 'PG', 'XOM', 'KO', 'VZ')
AND ex_dividend_date > today() - 365
AND ex_dividend_date <= today()
GROUP BY ticker, id
)
GROUP BY ticker
),
holding AS
(
SELECT
pos.ticker AS ticker,
pos.shares * lp.px AS value_usd,
pos.shares * d.dps AS income_usd
FROM positions AS pos
INNER JOIN last_px AS lp ON lp.ticker = pos.ticker
INNER JOIN ttm_dps AS d ON d.ticker = pos.ticker
)
SELECT
h.ticker AS symbol,
round(100 * h.value_usd / t.total_value, 1) AS weight_pct,
round(100 * h.income_usd / t.total_income, 1) AS income_share_pct,
round(100 * h.income_usd / h.value_usd, 2) AS yield_pct
FROM holding AS h
CROSS JOIN
(
SELECT
sum(value_usd) AS total_value,
sum(income_usd) AS total_income
FROM holding
) AS t
ORDER BY weight_pct DESCรูปแบบจากสคริปต์เกิดขึ้นซ้ำกับราคาจริง MSFT เป็นสถานะที่ใหญ่ที่สุด โดยคิดเป็น 31.4% ของมูลค่าตลาด แต่สร้างรายได้ 10% ของรายได้รวม ด้วย yield ที่ 0.72% ในอีกด้านหนึ่ง VZ คิดเป็น 5.9% ของมูลค่า แต่จ่ายรายได้ 15.7% ของรายได้รวม ด้วย yield ที่ 5.96% น้ำหนักและสัดส่วนรายได้จะแยกออกจากกันเมื่อ yield แตกต่างกัน
เมื่อนำสถานะห้ารายการเดียวกันมาหาค่าเฉลี่ยด้วยสองวิธี จะได้คำตอบต่างกันสองตัวเลข
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH
positions AS
(
SELECT
tupleElement(p, 1) AS ticker,
toFloat64(tupleElement(p, 2)) AS shares
FROM
(
SELECT arrayJoin([('MSFT', 30.), ('PG', 60.), ('XOM', 80.), ('KO', 100.), ('VZ', 60.)]) AS p
)
),
last_px AS
(
SELECT
ticker,
toFloat64(argMax(close, date)) AS px
FROM global_markets.stocks_daily_aggs
WHERE ticker IN ('MSFT', 'PG', 'XOM', 'KO', 'VZ')
AND date >= today() - 30
GROUP BY ticker
),
ttm_dps AS
(
SELECT
ticker,
sum(amount) AS dps
FROM
(
SELECT
ticker,
id,
toFloat64(any(cash_amount)) AS amount
FROM global_markets.stocks_dividends
WHERE ticker IN ('MSFT', 'PG', 'XOM', 'KO', 'VZ')
AND ex_dividend_date > today() - 365
AND ex_dividend_date <= today()
GROUP BY ticker, id
)
GROUP BY ticker
),
holding AS
(
SELECT
pos.shares * lp.px AS value_usd,
pos.shares * d.dps AS income_usd
FROM positions AS pos
INNER JOIN last_px AS lp ON lp.ticker = pos.ticker
INNER JOIN ttm_dps AS d ON d.ticker = pos.ticker
)
SELECT
tupleElement(m, 1) AS method,
round(tupleElement(m, 2), 2) AS yield_pct
FROM
(
SELECT arrayJoin([
('Market-value weighted portfolio yield', weighted_yield),
('Simple average of the five yields', naive_yield)
]) AS m
FROM
(
SELECT
100 * sum(income_usd) / sum(value_usd) AS weighted_yield,
avg(100 * income_usd / value_usd) AS naive_yield
FROM holding
)
)
ORDER BY yield_pct DESCเส้นที่สูงกว่าซึ่งอยู่ที่ 2.93% คือค่า Simple average of the five yields อีกค่าหนึ่งแสดงผลที่ 2.24% มีเพียงตัวเลขเดียวที่สอดคล้องกับเงินสด คูณตัวเลขที่ถ่วงน้ำหนักด้วยมูลค่าตลาดของพอร์ต แล้วจะได้รายได้ต่อปี หากทำแบบเดียวกันกับค่าเฉลี่ยธรรมดา จะได้ตัวเลขที่ไม่มีวันปรากฏในใบแจ้งยอดใด ๆ ทิศทางของความแตกต่างเป็นผลจากโครงสร้าง เมื่อหุ้นที่มี yield สูงมีสถานะขนาดเล็ก ค่าเฉลี่ยธรรมดาจะอยู่สูงกว่า portfolio yield ที่แท้จริง และเมื่อหุ้น yield สูงมีสถานะขนาดใหญ่ ค่าเฉลี่ยธรรมดาจะอยู่ต่ำกว่า
Yield on cost ไม่ใช่ portfolio yield
Yield on cost หารเงินปันผลต่อปีปัจจุบันด้วยราคาที่จ่ายซื้อ ไม่ใช่มูลค่าของสถานะในวันนี้ สถานะที่ซื้อในราคา $20 และปัจจุบันซื้อขายที่ $80 โดยยังจ่ายเงินปันผลปีละ $1.00 ต่อหุ้น จะแสดง yield on cost ที่ 5.00% เทียบกับ current yield ที่ 1.25% เงินปันผลในรูปดอลลาร์เท่ากัน สิ่งที่เปลี่ยนมีเพียงตัวหาร
ตัวเลขทั้งสองแบบใช้ได้ และตอบคำถามคนละข้อ มูลค่าตลาดปัจจุบันในตัวหารบอกว่า วันนี้พอร์ตให้ yield เท่าใด ซึ่งเป็นตัวเลขที่นำไปเปรียบเทียบกับ dividend yield ที่ดี หรือ Treasury bill ได้ ส่วน cost basis ในตัวหารบอกข้อมูลเกี่ยวกับการซื้อที่เกิดขึ้นในอดีต ปัญหาเริ่มขึ้นเมื่อผสมตัวเลขทั้งสองแบบไว้ในยอดรวมเดียวของพอร์ต เช่น ใช้ current yield กับหุ้นที่เพิ่งซื้อ และใช้ yield on cost กับหุ้นที่ถือมาสิบปี การผสมเช่นนี้ทำให้ yield ของพอร์ตเพื่อสร้างรายได้ดูสูงขึ้นเป็นสองเท่าเป็นประจำ และไม่ตรงกับตัวเลขใดที่โบรกเกอร์รายงาน กับดัก dividend yield อธิบายภาพลวงตาในทางตรงกันข้าม ซึ่งเป็นกรณีที่ yield แสดงสูงขึ้นหลังราคาหุ้นปรับลดลงอย่างมาก
การใช้ตัวเลขย้อนหลังหรือคาดการณ์ล่วงหน้าทำให้คำตอบต่างกัน
ตัวเลขทุกตัวข้างต้นขึ้นอยู่กับว่าจะนำเงินปันผลจำนวนใดมาใส่ในตัวเศษ Trailing yield รวมเงินปันผลที่ประกาศจ่ายจริงในช่วงสิบสองเดือนล่าสุด ส่วน forward yield คำนวณเงินปันผลล่าสุดต่อปี โดยทั่วไปคือการนำเงินปันผลรายไตรมาสครั้งล่าสุดคูณด้วยสี่ ตัวเลขทั้งสองจะแตกต่างกันสำหรับหุ้นที่เปลี่ยนนโยบายการจ่าย หรือจ่ายเงินปันผลพิเศษในระหว่างปี และสถานะขนาดใหญ่เพียงรายการเดียวอาจทำให้ยอดรวมของพอร์ตเปลี่ยนไปอย่างมีนัยสำคัญ ตารางในบทความนี้ใช้ trailing inputs ส่วน Trailing vs forward dividend yield อธิบายว่าแต่ละแบบเหมาะกับกรณีใด
Yield เป็นอัตรา แต่เงินสดเข้ามาเป็นก้อน
Weighted yield เป็นอัตรารายปี ไม่ใช่ตารางเวลาการจ่ายเงิน สถานะห้ารายการเดียวกันจ่ายเงินออกมาในช่วงเดือนที่กระจุกตัวอยู่ไม่กี่เดือน เนื่องจากบริษัทสหรัฐส่วนใหญ่ขึ้นเครื่องหมาย XD รายไตรมาสและมีปฏิทินการจ่ายเงินใกล้เคียงกัน
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH
positions AS
(
SELECT
tupleElement(p, 1) AS ticker,
toFloat64(tupleElement(p, 2)) AS shares
FROM
(
SELECT arrayJoin([('MSFT', 30.), ('PG', 60.), ('XOM', 80.), ('KO', 100.), ('VZ', 60.)]) AS p
)
),
payments AS
(
SELECT
ticker,
id,
any(ex_dividend_date) AS ex_date,
toFloat64(any(cash_amount)) AS amount
FROM global_markets.stocks_dividends
WHERE ticker IN ('MSFT', 'PG', 'XOM', 'KO', 'VZ')
AND ex_dividend_date > today() - 365
AND ex_dividend_date <= today()
GROUP BY ticker, id
)
SELECT
formatDateTime(toStartOfMonth(pay.ex_date), '%Y-%m') AS month,
formatDateTime(toStartOfMonth(pay.ex_date), '%b %Y') AS month_label,
round(sum(pos.shares * pay.amount), 2) AS income_usd
FROM payments AS pay
INNER JOIN positions AS pos ON pos.ticker = pay.ticker
GROUP BY month, month_label
ORDER BY monthเงินสดเข้ามาใน 12 เดือนที่แตกต่างกันตลอดช่วงสิบสองเดือนย้อนหลัง โดยเดือนล่าสุดคือ Jul 2026 ที่จำนวน $107.76 พอร์ตที่มี yield เพียงไม่กี่เปอร์เซ็นต์ไม่ได้บอกว่าเงินจะเข้ามาในเดือนไหน นี่คือเหตุผลที่แผนรายได้ซึ่งอิงตัวเลขรายเดือนต้องดูปฏิทินควบคู่กับอัตรา yield สำหรับระดับของตลาดโดยรวมที่ใช้เปรียบเทียบตัวเลขพอร์ตเหล่านี้ โปรดดู S&P 500 dividend yield
คำถามที่พบบ่อย
ฉันจะคำนวณ dividend yield ของพอร์ตทั้งหมดได้อย่างไร
รวมรายได้เงินปันผลต่อปีของแต่ละสถานะเข้าด้วยกัน โดยคำนวณจากจำนวนหุ้นคูณเงินปันผลต่อหุ้นต่อปี จากนั้นรวมมูลค่าตลาดของแต่ละสถานะ โดยคำนวณจากจำนวนหุ้นคูณราคา แล้วนำรายได้รวมหารด้วยมูลค่ารวมและคูณด้วย 100 ผลลัพธ์คือค่าเฉลี่ยของ yield ในพอร์ตที่ถ่วงน้ำหนักด้วยมูลค่าตลาด
เหตุใดค่าเฉลี่ย yield ของสถานะในพอร์ตจึงสูงกว่า portfolio yield
ค่าเฉลี่ยแบบธรรมดาให้แต่ละสถานะมีสิทธิ์ออกเสียงหนึ่งเสียง ส่วน portfolio yield ให้แต่ละสถานะมีน้ำหนักตามมูลค่าเงิน เมื่อหุ้นที่มี yield สูงที่สุดเป็นสถานะขนาดเล็ก ค่าเฉลี่ยแบบธรรมดาจะสูงกว่า yield ที่พอร์ตจ่ายได้จริง บางครั้งสูงกว่าประมาณหนึ่งจุดห้าเท่า
เงินสดนับรวมในการคำนวณ portfolio dividend yield หรือไม่
หากถือว่าเงินสดเป็นส่วนหนึ่งของพอร์ต เงินสดต้องรวมอยู่ในตัวหารและมีเงินปันผลเป็นศูนย์ ซึ่งจะทำให้ yield ลดลง หากไม่นับเงินสด ตัวเลขจะสะท้อน yield ของส่วนที่ลงทุนเท่านั้น ทั้งสองวิธีใช้ได้ ตราบใดที่ใช้เกณฑ์เดียวกันทุกครั้งที่วัดผล
Yield on cost เหมือนกับ yield ของพอร์ตหรือไม่
ไม่เหมือนกัน Yield on cost หารเงินปันผลในวันนี้ด้วยราคาที่จ่ายซื้อเมื่อหลายปีก่อน ดังนั้นจะเพิ่มขึ้นเมื่อราคาหุ้นปรับตัวสูงขึ้น ส่วน portfolio yield ใช้มูลค่าตลาดปัจจุบัน ซึ่งเป็นตัวเลขที่เชื่อมโยงโดยตรงกับรายได้ที่สถานะในพอร์ตสร้างได้ในปัจจุบัน
การเพิ่มหุ้นที่มี yield สูงหนึ่งตัวจะทำให้ portfolio yield เพิ่มขึ้นมากหรือไม่
เพิ่มขึ้นตามสัดส่วนของเงินที่ลงทุนในหุ้นตัวนั้นเท่านั้น หุ้นที่มี yield 9% และมีสัดส่วน 2.5% ของพอร์ต จะเพิ่ม weighted yield ประมาณ 0.2 จุดเปอร์เซ็นต์ (0.09 คูณด้วย 0.025) ไม่ว่าจะส่งผลอย่างไรต่อค่าเฉลี่ยของคอลัมน์ yield
ทุกตารางในบทความนี้มี SQL ที่ใช้สร้างตารางนั้นแนบไว้ครบถ้วน ทำให้ตรวจสอบการถ่วงน้ำหนักได้ทีละบรรทัด หากต้องการคำนวณแบบเดียวกันกับรายชื่อหุ้นและจำนวนหุ้นของคุณเอง ให้ขอคำสั่งเป็นภาษาอังกฤษทั่วไปบน Strasmore terminal