Strasmore Research

โครงสร้างตลาด BOS และ CHoCH วัดจากข้อมูลจริง

swing high swing low BOS และ CHoCH คืออะไร นิยามแบบนับแท่งที่เขียนโค้ดตามได้ พร้อมสถิติจากหุ้นสหรัฐรายวันว่าหลังเกิด BOS ราคาไปต่อบ่อยแค่ไหน เทียบกับอัตราฐาน

โครงสร้างตลาด (market structure) คือการอ่านลำดับของจุดสูงและจุดต่ำบนกราฟ แล้วตั้งชื่อให้ลำดับนั้น เช่น higher high, lower low, break of structure (BOS) และ change of character (CHoCH) ในกลุ่มเทรดไทยและคลิปสอนจำนวนมาก คำพวกนี้จบลงที่การลากเส้นบนภาพ ไม่ใช่การวัด หน้านี้ทำสองอย่าง: ตั้งนิยามแบบนับแท่งที่ชัดพอจะเขียนโค้ดตามได้ แล้วเอากฎเดียวกันนั้นไปรันกับแท่งรายวันจริงของหุ้นและ ETF สหรัฐหกตัว เพื่อรายงานว่าหลังเกิด BOS ราคาไปต่อบ่อยแค่ไหน เทียบกับอัตราฐานของชุดข้อมูลเดียวกัน

swing high และ swing low วัดยังไงให้เขียนโค้ดตามได้

ทั้งบทความนี้ตั้งอยู่บนกฎเดียว และกฎนั้นสั้นมาก

  • swing high คือแท่งที่ราคาสูงสุด (high) ของมัน สูงกว่า high ของสามแท่งก่อนหน้าทุกแท่ง และสูงกว่า high ของสามแท่งถัดไปทุกแท่ง
  • swing low คือแท่งที่ราคาต่ำสุด (low) ของมัน ต่ำกว่า low ของสามแท่งก่อนหน้าทุกแท่ง และต่ำกว่า low ของสามแท่งถัดไปทุกแท่ง

เลข 3 คือพารามิเตอร์ ไม่ใช่ความจริงของตลาด ใช้ 2 จะได้จุดสวิงถี่ขึ้นและเล็กลง ใช้ 5 จะได้จุดสวิงน้อยลงและใหญ่ขึ้น ตัวเลขทุกตัวในหน้านี้ผูกกับเลข 3 เปลี่ยนพารามิเตอร์เมื่อไร ตัวเลขทั้งหมดก็เปลี่ยนตาม นั่นคือเหตุผลที่การเขียนกฎให้ชัดมีน้ำหนักกว่าการเถียงกันว่าใครลากเส้นสวยกว่า

ข้อที่มักถูกข้ามไป: swing high ยืนยันช้าเสมอ 3 แท่ง คุณจะรู้ว่าแท่งวันจันทร์เป็น swing high ได้เร็วที่สุดก็ตอนตลาดปิดวันพฤหัสบดี กราฟย้อนหลังทำให้จุดสวิงดูชัดเจน เพราะเรามองเห็นทั้งสองฝั่งพร้อมกัน ตอนเทรดสดคุณเห็นแค่ฝั่งซ้าย ความช้า 3 แท่งนี้ถูกใส่ไว้ในทุกการวัดของหน้านี้แล้ว

แท่งที่ใช้คือแท่งรายวันของตลาดหุ้นสหรัฐ ตามเวลาไทย ตลาดเปิด 20:30 น. และปิด 03:00 น. ของเช้าวันถัดไป ในช่วง daylight saving คือปลายมีนาคมถึงต้นพฤศจิกายน ส่วนช่วงที่เหลือของปีคือ 21:30 น. ถึง 04:00 น. แท่งรายวันหนึ่งแท่งจึงปิดตอนที่คนไทยส่วนใหญ่นอนแล้ว ใครอยากเห็นว่า high, low, close ถูกประกอบขึ้นจากการซื้อขายจริงอย่างไร อ่านต่อที่ แท่ง OHLCV ถูกประกอบขึ้นมาอย่างไร

แผงแรกคือการเอากฎข้างบนไปนับตรงๆ กับหกชื่อที่คนไทยคุ้นเคย ตั้งแต่ต้นปี 2021 ถึงสิ้นเดือนสิงหาคม 2026

คำสั่งดึงข้อมูลจำนวน swing high และ swing low ตามกฎสามแท่ง ปี 2021 ถึงสิงหาคม 2026
tickersessionsswing_highsswing_lowsbars_per_swing_high
AAPL141512913511
JPM141512412611.4
KO141513013310.9
MSFT141512912911
QQQ141512713711.1
SPY141512113611.7
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH px AS
(
    SELECT
        ticker,
        date,
        toFloat64(high) AS h,
        toFloat64(low)  AS l
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('AAPL', 'JPM', 'KO', 'MSFT', 'QQQ', 'SPY')
      AND date >= '2021-01-01'
      AND date <  '2026-09-01'
),
flagged AS
(
    SELECT
        ticker,
        ((h > max(h) OVER w_prev) AND (h > max(h) OVER w_next)) AS is_swing_high,
        ((l < min(l) OVER w_prev) AND (l < min(l) OVER w_next)) AS is_swing_low,
        count() OVER w_prev AS bars_before,
        count() OVER w_next AS bars_after
    FROM px
    WINDOW
        w_prev AS (PARTITION BY ticker ORDER BY date ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING),
        w_next AS (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 3 FOLLOWING)
)
SELECT
    ticker,
    count()                AS sessions,
    countIf(is_swing_high) AS swing_highs,
    countIf(is_swing_low)  AS swing_lows,
    round(count() / greatest(countIf(is_swing_high), 1), 1) AS bars_per_swing_high
FROM flagged
WHERE bars_before = 3
  AND bars_after = 3
GROUP BY ticker
ORDER BY ticker
ลองรันด้วยตัวเอง

ตารางมี 6 ชื่อ ชื่อแรกคือ AAPL ซึ่งมี 1415 แท่งรายวันที่เข้าเกณฑ์วัดได้ในหน้าต่างนี้ ในจำนวนนั้นเข้าเงื่อนไข swing high 129 ครั้ง และ swing low 135 ครั้ง เฉลี่ยแล้วเจอ swing high หนึ่งจุดทุกๆ 11 แท่ง ตัวเลขนี้มีประโยชน์กว่าที่คิด: มันบอกว่ากราฟรายวันหนึ่งปีจะให้จุดสวิงฝั่งบนราวสามสิบจุด ไม่ใช่สามจุดอย่างที่ภาพสอนมักวาด คนสอนเลือกวาดเฉพาะจุดที่ใหญ่ที่สุด กฎที่เขียนเป็นโค้ดไม่เลือก

HH HL LH LL อ่านจากกฎเดียวกันนี้ได้ทันที

เมื่อมีชุดจุดสวิงแล้ว ที่เหลือคือการเปรียบเทียบจุดล่าสุดกับจุดก่อนหน้าของชนิดเดียวกัน

  • higher high (HH): swing high ล่าสุด สูงกว่า swing high ก่อนหน้า
  • higher low (HL): swing low ล่าสุด สูงกว่า swing low ก่อนหน้า
  • lower high (LH) และ lower low (LL): กรณีตรงข้ามทั้งคู่

โครงสร้างขาขึ้นคือการต่อกันของ HH กับ HL โครงสร้างขาลงคือ LH กับ LL ส่วนกรณีที่ผสมกัน เช่น HH แต่ LL เรียกว่าช่วงไร้โครงสร้างหรือ range สังเกตว่าทุกคำข้างบนไม่ได้เพิ่มสมมติฐานใหม่เลย มันคือการเปรียบเทียบตัวเลขสองตัวที่มาจากกฎสามแท่งเดิม ใครก็ตามที่นิยาม HH ด้วยสายตาแทนการเปรียบเทียบจุดที่นับได้ จะได้คำตอบไม่ตรงกันในกราฟเดียวกัน

BOS กับ CHoCH ต่างกันตรงไหน

BOS ฝั่งขึ้นคือ: ราคาปิดรายวันปิดเหนือระดับของ swing high ที่ยืนยันแล้วล่าสุด CHoCH คือการทะลุครั้งแรกที่สวนทางกับลำดับที่กำลังเป็นอยู่ เช่น ขณะอยู่ในลำดับ HH กับ HL แล้วราคาปิดใต้ swing low ที่ยืนยันแล้วล่าสุด พูดให้ตรงกว่านั้น: BOS กับ CHoCH ใช้การคำนวณชุดเดียวกัน ต่างกันแค่ทิศของการทะลุเทียบกับลำดับก่อนหน้า

ก่อนจะวัดได้ ต้องตัดสินใจสามเรื่องที่คลิปสอนส่วนใหญ่ไม่เคยระบุ

  1. ใช้ราคาปิดหรือไส้เทียน หน้านี้ใช้ราคาปิดรายวัน เพราะไส้เทียนแตะระดับแล้วถอยกลับเป็นคนละเหตุการณ์กับการปิดเหนือระดับ
  2. เริ่มนับการทะลุจากแท่งไหน หน้านี้เริ่มนับหลังจุดสวิงยืนยันครบ 3 แท่งแล้วเท่านั้น
  3. ให้เวลานานแค่ไหนถึงเลิกรอ หน้านี้ให้เวลา 30 วันทำการ ถ้าไม่มีการปิดเหนือระดับภายในนั้น ถือว่าจุดสวิงนั้นไม่เกิด BOS

สามข้อนี้คือส่วนที่ทำให้ตัวเลขข้างล่างทำซ้ำได้ เปลี่ยนข้อไหนข้อหนึ่ง ตัวเลขก็ขยับ

หลังเกิด BOS ราคาไปต่อบ่อยแค่ไหน

นี่คือคำถามที่คนสอนไม่ค่อยตอบด้วยตัวเลข วิธีวัดในแผงถัดไปตรงไปตรงมา: ทุกครั้งที่เกิด BOS ฝั่งขึ้น ให้จดราคาปิดของวันที่ทะลุไว้ แล้วดูราคาปิดอีก n วันทำการถัดมา

  • continuation_pct คือสัดส่วนของเหตุการณ์ที่ราคาปิด n วันหลังจากนั้น สูงกว่าราคาปิดวันที่ทะลุ
  • base_rate_pct คืออัตราฐาน: ถ้าหยิบวันไหนก็ได้ในชุดข้อมูลเดียวกันแบบไม่สนใจโครงสร้างเลย ราคาปิดอีก n วันถัดมาสูงกว่าเดิมกี่เปอร์เซ็นต์
  • edge_pp คือส่วนต่างของสองคอลัมน์แรก หน่วยเป็นจุดเปอร์เซ็นต์
  • back_inside_pct คือสัดส่วนของเหตุการณ์ที่ราคาปิดกลับลงไปใต้ระดับที่เพิ่งทะลุ อย่างน้อยหนึ่งวันภายใน n วัน

คอลัมน์อัตราฐานคือหัวใจ ตลาดหุ้นสหรัฐในหน้าต่างนี้ขึ้นมากกว่าลง ดังนั้นตัวเลข "ไปต่อ 55%" จะดูดีทันทีถ้าไม่มีอะไรให้เทียบ

คำสั่งดึงข้อมูลหลัง BOS ฝั่งขึ้น: อัตราไปต่อ เทียบอัตราฐาน และอัตราปิดกลับใต้ระดับเดิม
horizon_labelbos_eventscontinuation_pctbase_rate_pctedge_ppback_inside_pct
1 วัน44854.953.31.621.7
3 วัน44859.2563.240.2
5 วัน44857.156.60.549.1
10 วัน44856.958.1-1.262.5
20 วัน44860.760.10.670.8
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH
px AS
(
    SELECT
        ticker,
        date,
        toFloat64(high)  AS h,
        toFloat64(close) AS c
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('AAPL', 'JPM', 'KO', 'MSFT', 'QQQ', 'SPY')
      AND date >= '2021-01-01'
      AND date <  '2026-09-01'
),
sorted AS
(
    SELECT
        ticker,
        arraySort(x -> x.1, groupArray((date, h, c))) AS bars
    FROM px
    GROUP BY ticker
),
arrs AS
(
    SELECT
        ticker,
        arrayMap(x -> x.2, bars) AS highs,
        arrayMap(x -> x.3, bars) AS closes
    FROM sorted
),
pivots AS
(
    SELECT
        ticker,
        highs,
        closes,
        arrayJoin(arrayFilter(i ->
            (i > 3)
            AND (i <= length(highs) - 3)
            AND (highs[i] > arrayMax(arraySlice(highs, i - 3, 3)))
            AND (highs[i] > arrayMax(arraySlice(highs, i + 1, 3))),
            arrayEnumerate(highs))) AS pivot_i
    FROM arrs
),
breaks AS
(
    SELECT
        ticker,
        closes,
        pivot_i,
        highs[pivot_i] AS level,
        pivot_i + 3 + arrayFirstIndex(x -> x > highs[pivot_i], arraySlice(closes, pivot_i + 4, 30)) AS break_i
    FROM pivots
    WHERE arrayFirstIndex(x -> x > highs[pivot_i], arraySlice(closes, pivot_i + 4, 30)) > 0
),
events AS
(
    SELECT
        ticker,
        break_i,
        argMax(level, pivot_i) AS level,
        any(closes)            AS closes
    FROM breaks
    GROUP BY ticker, break_i
),
after_bos AS
(
    SELECT
        n,
        count()                                                    AS bos_events,
        round(100 * avg(closes[break_i + n] > closes[break_i]), 1) AS continuation_pct,
        round(100 * avg(arrayMin(arraySlice(closes, break_i + 1, n)) < level), 1) AS back_inside_pct
    FROM
    (
        SELECT
            break_i,
            level,
            closes,
            arrayJoin([1, 3, 5, 10, 20]) AS n
        FROM events
        WHERE break_i + 20 <= length(closes)
    )
    GROUP BY n
),
base AS
(
    SELECT
        n,
        round(100 * avg(closes[t + n] > closes[t]), 1) AS base_rate_pct
    FROM
    (
        SELECT
            closes,
            t,
            arrayJoin([1, 3, 5, 10, 20]) AS n
        FROM
        (
            SELECT
                closes,
                arrayJoin(arrayEnumerate(closes)) AS t
            FROM arrs
        )
        WHERE t + 20 <= length(closes)
    )
    GROUP BY n
)
SELECT
    concat(toString(a.n), ' วัน')                 AS horizon_label,
    a.bos_events                                   AS bos_events,
    a.continuation_pct                             AS continuation_pct,
    b.base_rate_pct                                AS base_rate_pct,
    round(a.continuation_pct - b.base_rate_pct, 1) AS edge_pp,
    a.back_inside_pct                              AS back_inside_pct
FROM after_bos AS a
INNER JOIN base AS b ON b.n = a.n
ORDER BY a.n
ลองรันด้วยตัวเอง

ชุดข้อมูลนี้ให้เหตุการณ์ BOS ฝั่งขึ้นทั้งหมด 448 ครั้ง ที่กรอบ 1 วัน อัตราไปต่ออยู่ที่ 54.9% เทียบกับอัตราฐาน 53.3% ส่วนต่างคือ 1.6 จุดเปอร์เซ็นต์ ที่กรอบยาวสุดในตารางคือ 20 วัน อัตราไปต่ออยู่ที่ 60.7% เทียบอัตราฐาน 60.1% ส่วนต่าง 0.6 จุดเปอร์เซ็นต์ และภายในกรอบเดียวกันนั้น 70.8% ของเหตุการณ์มีอย่างน้อยหนึ่งวันที่ราคาปิดกลับลงไปใต้ระดับที่เพิ่งทะลุ

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

มีอีกสามข้อที่ต้องพูดให้ครบ หนึ่ง เหตุการณ์ BOS ไม่ได้กระจายตัวสม่ำเสมอ มันกระจุกในช่วงที่ตลาดขึ้นอยู่แล้ว การเทียบกับอัตราฐานช่วยแก้ได้บางส่วน แต่ไม่หมด สอง หน้าต่าง 2021 ถึง 2026 คือยุคสมัยหนึ่งของตลาด ไม่ใช่ตลาดทุกยุค สาม ความถี่ไม่เท่ากับผลตอบแทน ลองคิดเลขแบบสมมติดูเล่นๆ: ถ้าเหตุการณ์หนึ่งไปต่อได้ 8 บาทต่อหุ้นใน 55% ของครั้ง และย้อนกลับ 10 บาทต่อหุ้นในอีก 45% ผลรวมจะติดลบ ทั้งที่ตัวเลข "ถูกมากกว่าผิด" ยังดูดีอยู่ ขนาดของการเคลื่อนไหวและต้นทุนการซื้อขายไม่ได้อยู่ในตารางข้างบนเลย

แยกเป็นรายตัวแล้วยังเหมือนเดิมไหม

ค่าเฉลี่ยรวมซ่อนความต่างระหว่างชื่อได้เก่งมาก แผงถัดไปใช้เครื่องคำนวณชุดเดิมทุกบรรทัด แต่ตรึงกรอบเวลาไว้ที่ 10 วันทำการ แล้วแยกตามชื่อ พร้อมอัตราฐานของชื่อนั้นเอง

คำสั่งดึงข้อมูลอัตราไปต่อ 10 วันหลัง BOS เทียบอัตราฐานของแต่ละชื่อ
tickerbos_eventscontinuation_pctbase_rate_pctedge_pp
AAPL7064.356.77.6
SPY826162.2-1.2
MSFT7454.155.5-1.4
KO7353.455.6-2.2
QQQ7357.560-2.5
JPM8048.857.8-9
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH
px AS
(
    SELECT
        ticker,
        date,
        toFloat64(high)  AS h,
        toFloat64(close) AS c
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('AAPL', 'JPM', 'KO', 'MSFT', 'QQQ', 'SPY')
      AND date >= '2021-01-01'
      AND date <  '2026-09-01'
),
sorted AS
(
    SELECT
        ticker,
        arraySort(x -> x.1, groupArray((date, h, c))) AS bars
    FROM px
    GROUP BY ticker
),
arrs AS
(
    SELECT
        ticker,
        arrayMap(x -> x.2, bars) AS highs,
        arrayMap(x -> x.3, bars) AS closes
    FROM sorted
),
pivots AS
(
    SELECT
        ticker,
        highs,
        closes,
        arrayJoin(arrayFilter(i ->
            (i > 3)
            AND (i <= length(highs) - 3)
            AND (highs[i] > arrayMax(arraySlice(highs, i - 3, 3)))
            AND (highs[i] > arrayMax(arraySlice(highs, i + 1, 3))),
            arrayEnumerate(highs))) AS pivot_i
    FROM arrs
),
breaks AS
(
    SELECT
        ticker,
        closes,
        pivot_i,
        highs[pivot_i] AS level,
        pivot_i + 3 + arrayFirstIndex(x -> x > highs[pivot_i], arraySlice(closes, pivot_i + 4, 30)) AS break_i
    FROM pivots
    WHERE arrayFirstIndex(x -> x > highs[pivot_i], arraySlice(closes, pivot_i + 4, 30)) > 0
),
events AS
(
    SELECT
        ticker,
        break_i,
        argMax(level, pivot_i) AS level,
        any(closes)            AS closes
    FROM breaks
    GROUP BY ticker, break_i
),
by_name AS
(
    SELECT
        ticker,
        count()                                                     AS bos_events,
        round(100 * avg(closes[break_i + 10] > closes[break_i]), 1) AS continuation_pct
    FROM events
    WHERE break_i + 10 <= length(closes)
    GROUP BY ticker
),
base AS
(
    SELECT
        ticker,
        round(100 * avg(closes[t + 10] > closes[t]), 1) AS base_rate_pct
    FROM
    (
        SELECT
            ticker,
            closes,
            arrayJoin(arrayEnumerate(closes)) AS t
        FROM arrs
    )
    WHERE t + 10 <= length(closes)
    GROUP BY ticker
)
SELECT
    e.ticker                                       AS ticker,
    e.bos_events                                   AS bos_events,
    e.continuation_pct                             AS continuation_pct,
    b.base_rate_pct                                AS base_rate_pct,
    round(e.continuation_pct - b.base_rate_pct, 1) AS edge_pp
FROM by_name AS e
INNER JOIN base AS b ON b.ticker = e.ticker
ORDER BY edge_pp DESC
ลองรันด้วยตัวเอง

เรียงจากส่วนต่างมากไปน้อย ชื่อบนสุดคือ AAPL ที่ 7.6 จุดเปอร์เซ็นต์ จากเหตุการณ์ 70 ครั้ง (อัตราไปต่อ 64.3% เทียบอัตราฐานของตัวเอง 56.7%) ชื่อล่างสุดคือ JPM ที่ -9 จุดเปอร์เซ็นต์ ระยะห่างระหว่างหัวกับท้ายตารางกว้างกว่าส่วนต่างเฉลี่ยของทั้งชุด ซึ่งเป็นรูปแบบที่เจอบ่อยเวลาแบ่งกลุ่มตัวอย่างให้เล็กลง: จำนวนเหตุการณ์ต่อชื่อเหลือหลักสิบถึงหลักร้อย ความแปรปรวนจึงกินพื้นที่มากกว่าตัวสัญญาณ ใครที่เห็นตัวเลขของชื่อใดชื่อหนึ่งแล้วอยากสรุปว่า "วิธีนี้ใช้ได้กับหุ้นตัวนี้" ควรนับจำนวนเหตุการณ์ในคอลัมน์ bos_events ก่อนเสมอ

ตัวอย่างจริงที่ปักวันที่ไว้แล้ว

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

คำสั่งดึงข้อมูลเส้นราคาปิดของ AAPL เทียบระดับ swing high ที่ยืนยันแล้ว ช่วงต้นปี 2025
57 rows (showing 20)
session_datecloseswing_high_levelpivot_label
2025-03-25223.75212.9414/04/2025
2025-03-26221.53212.9414/04/2025
2025-03-27223.85212.9414/04/2025
2025-03-28217.9212.9414/04/2025
2025-03-31222.13212.9414/04/2025
2025-04-01223.19212.9414/04/2025
2025-04-02223.89212.9414/04/2025
2025-04-03203.19212.9414/04/2025
2025-04-04188.38212.9414/04/2025
2025-04-07181.46212.9414/04/2025
2025-04-08172.42212.9414/04/2025
2025-04-09198.85212.9414/04/2025
2025-04-10190.42212.9414/04/2025
2025-04-11198.15212.9414/04/2025
2025-04-14202.52212.9414/04/2025
2025-04-15202.14212.9414/04/2025
2025-04-16194.27212.9414/04/2025
2025-04-17196.98212.9414/04/2025
2025-04-21193.16212.9414/04/2025
2025-04-22199.74212.9414/04/2025
SQL เบื้องหลังตัวเลขแต่ละตัว
WITH
px AS
(
    SELECT
        date,
        toFloat64(high)  AS h,
        toFloat64(close) AS c
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'AAPL'
      AND date >= '2025-01-02'
      AND date <  '2025-08-01'
),
flagged AS
(
    SELECT
        date,
        h,
        c,
        max(h) OVER w_prev  AS prev_high,
        max(h) OVER w_next  AS next_high,
        count() OVER w_prev AS bars_before,
        count() OVER w_next AS bars_after
    FROM px
    WINDOW
        w_prev AS (ORDER BY date ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING),
        w_next AS (ORDER BY date ROWS BETWEEN 1 FOLLOWING AND 3 FOLLOWING)
),
pivot AS
(
    SELECT
        date AS pivot_date,
        h    AS level
    FROM flagged
    WHERE bars_before = 3
      AND bars_after = 3
      AND h > prev_high
      AND h > next_high
      AND date <= '2025-04-30'
    ORDER BY date DESC
    LIMIT 1
)
SELECT
    toString(f.date)                         AS session_date,
    round(f.c, 2)                            AS close,
    round(p.level, 2)                        AS swing_high_level,
    formatDateTime(p.pivot_date, '%d/%m/%Y') AS pivot_label
FROM flagged AS f
CROSS JOIN pivot AS p
WHERE f.date >= p.pivot_date - 20
  AND f.date <= p.pivot_date + 60
ORDER BY f.date
ลองรันด้วยตัวเอง

จุดสวิงที่เลือกมาอยู่ที่วันที่ 14/04/2025 ระดับราคาของมันคือ 212.94 ดอลลาร์ ซึ่งคือเส้นแนวนอนในกราฟ แผงนี้แสดง 57 วันทำการรอบจุดนั้น เริ่มจากราคาปิด 223.75 ดอลลาร์ และวันสุดท้ายในหน้าต่างปิดที่ 196.45 ดอลลาร์ สิ่งที่ควรดูไม่ใช่ว่าเส้นราคาจบที่ไหน แต่คือจำนวนครั้งที่เส้นราคาตัดผ่านเส้นแนวนอนไปมา ระดับโครงสร้างหนึ่งระดับมักถูกทดสอบหลายรอบ ไม่ใช่ครั้งเดียวแบบในภาพสอน และนี่คือเหตุผลที่คอลัมน์ back_inside_pct ในแผงก่อนหน้าไม่ใช่ตัวเลขเล็ก

ข้อควรรู้เรื่องข้อมูลและข้อจำกัดของการวัดชุดนี้

ราคาที่ใช้เป็นราคาที่ซื้อขายจริงในแต่ละวัน ไม่ได้ปรับย้อนหลัง การแตกพาร์ (stock split) จะทำให้ราคากระโดดจนกฎนับแท่งอ่านผิดทันที หน้าต่าง 2021 ถึง 2026 ถูกเลือกเพราะหกชื่อนี้ไม่มีการแตกพาร์ในช่วงดังกล่าว แผงข้างล่างคือใบเสร็จของข้อนั้น

คำสั่งดึงข้อมูลประวัติการแตกพาร์ของหกชื่อที่ใช้ และจำนวนครั้งภายในหน้าต่างทดสอบ
tickersplits_in_windowlast_split_on_record
AAPL02020-08-31
JPM0ไม่พบในฐานข้อมูล
KO02012-08-13
MSFT02003-02-18
QQQ0ไม่พบในฐานข้อมูล
SPY0ไม่พบในฐานข้อมูล
SQL เบื้องหลังตัวเลขแต่ละตัว
SELECT
    t.ticker                                  AS ticker,
    toUInt32(ifNull(s.splits_in_window, 0))   AS splits_in_window,
    if(empty(ifNull(s.last_split_on_record, '')), 'ไม่พบในฐานข้อมูล', s.last_split_on_record) AS last_split_on_record
FROM
(
    SELECT arrayJoin(['AAPL', 'JPM', 'KO', 'MSFT', 'QQQ', 'SPY']) AS ticker
) AS t
LEFT JOIN
(
    SELECT
        ticker,
        countIf(execution_date >= '2021-01-01') AS splits_in_window,
        toString(max(execution_date))           AS last_split_on_record
    FROM global_markets.stocks_splits
    WHERE ticker IN ('AAPL', 'JPM', 'KO', 'MSFT', 'QQQ', 'SPY')
    GROUP BY ticker
) AS s ON s.ticker = t.ticker
ORDER BY t.ticker
ลองรันด้วยตัวเอง

ข้อจำกัดที่เหลือ เขียนไว้ให้ครบ

  • วัดเฉพาะการทะลุฝั่งขึ้น การทะลุฝั่งลงเป็นภาพกระจกของคำสั่งชุดเดียวกัน และมีอัตราฐานคนละตัว
  • ใช้ราคาปิดรายวันเท่านั้น การทะลุด้วยไส้เทียนระหว่างวันไม่ถูกนับ
  • เงินปันผลทำให้ราคาปิดลดลงเล็กน้อยในวันขึ้นเครื่องหมาย ผลต่อจุดสวิงรายวันเล็กมากแต่ไม่ใช่ศูนย์
  • ตัวเลขทั้งหมดผูกกับพารามิเตอร์สามแท่ง หน้าต่างรอ 30 วันทำการ และหุ้นหกตัวนี้เท่านั้น
  • ทุกอย่างนับจากแท่งรายวัน ไม่มีการคิดค่าคอมมิชชัน สเปรด หรือภาษีใดๆ

ส่วนไหนคือนิยาม ส่วนไหนคือข้ออ้างที่ทดสอบได้

นี่คือเส้นแบ่งที่ทำให้อ่านเนื้อหาแนวนี้ในกลุ่มเทรดได้ปลอดภัยขึ้นมาก

เป็นนิยาม ไม่ต้องพิสูจน์ และเถียงไม่ได้: swing high และ swing low ตามกฎนับแท่ง, HH HL LH LL, คำว่า BOS, คำว่า CHoCH สิ่งเหล่านี้เป็นจริงตามที่เรากำหนดมันขึ้นมา เหมือนกฎการนับคะแนนในกีฬา เปลี่ยนพารามิเตอร์แล้วได้ชุดจุดใหม่ ซึ่งก็ยังเป็นนิยามที่ถูกต้องอยู่ดี

เป็นข้ออ้างที่ทดสอบได้ และต้องมีตัวเลขกำกับเสมอ: "หลัง BOS ราคามักไปต่อ", "CHoCH บอกว่าเทรนด์จบแล้ว", "โครงสร้างขาขึ้นแปลว่าโอกาสขึ้นมากกว่า" ทุกประโยคในกลุ่มนี้แปลเป็นการวัดได้ และเมื่อวัดแล้ว คำตอบจะมาพร้อมขนาดของมัน ตารางในหน้านี้คือคำตอบเวอร์ชันหนึ่ง ภายใต้เงื่อนไขที่ระบุไว้ครบทุกข้อ

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

ต่อยอดไปที่ liquidity sweep และ gap

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

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

อย่างที่สองคือช่องว่างราคา วันที่เปิดกระโดดข้ามระดับโครงสร้างคือกรณีที่กฎ "ปิดเหนือระดับ" กับ "เปิดเหนือระดับ" ให้คำตอบคนละแบบ สถิติว่าช่องว่างถูกปิดบ่อยแค่ไหนอยู่ที่ ช่องว่างราคาถูกปิดบ่อยแค่ไหน และถ้าอยากรู้ว่าวันที่เกิด BOS มีคนซื้อขายหนาแน่นผิดปกติหรือไม่ ตัวชี้วัดที่ตรงที่สุดคือปริมาณเทียบค่าปกติของตัวมันเอง ซึ่งอธิบายไว้ที่ relative volume คืออะไร ส่วนใครที่อยากได้ระดับราคาอ้างอิงที่ผูกกับเหตุการณ์แทนการผูกกับจุดสวิง ลองอ่าน anchored VWAP ต่อ

FAQ

swing high ต่างจากแนวต้านอย่างไร

swing high คือแท่งหนึ่งแท่งที่ผ่านกฎนับแท่ง ระบุตำแหน่งได้แน่นอนและตรวจซ้ำได้ ส่วนแนวต้านคือบริเวณราคาที่คนลากขึ้นมาเอง สองคนลากกราฟเดียวกันได้คนละเส้น ข้อดีของ swing high คือการทำซ้ำได้ ข้อเสียคือมันไม่ยืดหยุ่นตามสายตา

BOS กับ CHoCH ต่างกันตรงไหน

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

ต้องใช้กี่แท่งในการยืนยันจุดสวิง

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

สถิติในหน้านี้แปลว่า BOS ทำกำไรได้ไหม

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

ใช้กฎเดียวกันนี้กับกราฟ 15 นาทีหรือรายสัปดาห์ได้ไหม

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


ทุกแผงในหน้านี้เปิดดู SQL ที่อยู่เบื้องหลังได้ ถ้าอยากเปลี่ยนพารามิเตอร์จากสามแท่งเป็นห้าแท่ง เปลี่ยนกรอบเป็น 60 วันทำการ หรือเปลี่ยนชุดหุ้นเป็นชื่อที่คุณตามอยู่ ลองถามเป็นภาษาไทยบน Strasmore terminal แล้วดูว่ากฎเดิมให้คำตอบเดิมหรือเปล่า

#โครงสร้างตลาด#break of structure#price action#swing high swing low#เทคนิคอล