Session Expansion Lab v1.3 · ซอร์สโค้ด
Pine Script ต้นฉบับ
ไฟล์นี้ถูกคัดลอกจากต้นฉบับใน G:\Trader\PineSCript\ ตอนสร้างเว็บ
จึงเป็นโค้ดชุดเดียวกับที่ทำงานอยู่บนชาร์ตจริง ณ วันที่ปรับปรุง
Pine Script ยังไม่มีไวยากรณ์เฉพาะสำหรับตัวไฮไลต์ที่ใช้ จึงไฮไลต์ด้วยไวยากรณ์ TypeScript ซึ่งใกล้เคียงกันพอที่จะอ่านง่าย
ตัวเลขบรรทัดตรงกับไฟล์จริง ใช้อ้างอิงในบทเรียนได้ เช่น session-lab-v1.pine:120
สารบัญส่วนของโค้ด (28)
- 3 SESSION EXPANSION LAB v1.3 — เครื่องมือวิจัย ไม่ใช่เครื่องมือให้สัญญาณ
- 59 ① เซสชันและระดับ
- 102 ② ประเภทวัน
- 148 ②b Bollinger Bands
- 173 ③ สัญญาณที่จะทดสอบ
- 198 ④ ห้องทดลอง
- 199 ⑦ ตรวจก่อนยิง
- 281 ⑤ การแสดงผล
- 337 ATR รายวันของ "เมื่อวาน" — ห้ามมองอนาคต
- 344 กรอบ Asia — สะสมระหว่างเซสชัน แล้วล็อกเมื่อจบ
- 380 Bollinger Bands — เส้น และป้ายทางเลือก
- 406 A · ประเภทวัน — คำนวณครั้งเดียวตอนล็อก
- 465 ระดับการขยายตัว — จากกรอบที่ล็อกแล้ว ไม่ขยับอีกทั้งวัน
- 471 สัญญาณ + การบันทึกผล
- 573 สถิติ — คำนวณบนบาร์สุดท้ายเท่านั้น
- 575 หมายเหตุ: อย่าตั้งชื่อพารามิเตอร์ว่า from หรือ to — เป็นคำสงวนของ Pine
- 664 การวาด
- 706 ตารางผลวิจัย
- 709 ⑦ ตรวจก่อนยิง — คำนวณ
- 979 ⑦ ตรวจก่อนยิง — ตารางแยก
- 981 แยกตารางโดยตั้งใจ · ส่วนบนของไฟล์ตอบว่า "ป้ายนี้มีข้อมูลไหม" ซึ่งเป็นคำถาม
- 1004 ① ราคาสามจุด และการตรวจข้างที่ผิดพลาดง่ายที่สุด
- 1022 ② ขนาดไม้และเงินที่เสี่ยงจริง
- 1043 ③ R:R ที่จริง ไม่ใช่บนกระดาษ
- 1052 ④ จุดหยุดอยู่ในสัญญาณรบกวนไหม — ด่านเดียวกับส่วนวิจัย
- 1070 ⑤ ระดับที่คนอื่นก็เห็น
- 1086 ⑥ สวนเทรนด์ — ข้อที่แพงที่สุดในบันทึกทั้งหมด
- 1100 ⑦ เพดานประจำวัน — กันการล้างแค้น
//@version=6
// ═══════════════════════════════════════════════════════════════════════════
// SESSION EXPANSION LAB v1.3 — เครื่องมือวิจัย ไม่ใช่เครื่องมือให้สัญญาณ
// ═══════════════════════════════════════════════════════════════════════════
//
// คำถามเดียวที่เครื่องมือนี้ตอบ:
//
// "ช่วงที่สัญญาณทำเงิน อธิบายได้ด้วยสภาวะตลาดที่วัดได้ล่วงหน้า
// หรือเป็นแค่โชค"
//
// มันจึงเดินนาฬิกาสองเรือนขนานกัน แล้วถามว่าเรือนแรกทำนายเรือนที่สองได้ไหม
//
// A · ประเภทวัน — วัดตอนเปิดเซสชันแล้วล็อกค่า ห้ามจัดใหม่ย้อนหลัง
// B · สุขภาพสัญญาณ — expectancy เคลื่อนที่ของสัญญาณตัวมันเอง
//
// ถ้า A ทำนาย B ได้ → "อย่าเทรดวันแบบนี้" คือกฎที่มีเนื้อ
// ถ้าทำนายไม่ได้ → ตัวกรองประเภทวันของเราคือของประดับ และควรรู้ก่อนเสียเงิน
//
// ── ทำไมต้องมีตัวยับยั้ง ไม่ใช่แค่ตัวหาจุดเข้า ────────────────────────────
// Patrick Nill วัดไว้ว่า ~70% ของวันเป็นวันกรอบ การรู้ว่า "วันนี้ไม่ใช่วัน
// ที่ควรเทรดตามการขยายตัว" จึงมีค่ามากกว่าการหาจุดเข้าเพิ่มอีกสิบจุด
//
// ── ระดับที่ใช้ ──────────────────────────────────────────────────────────
// ตัวคูณที่ใส่มาเป็นค่าที่คาลิเบรตจากทองจริง 948 เซสชัน ไม่ใช่ค่าที่ยืมมา
// จาก NQ ซึ่งผิดทั้งขนาดและทิศทางความไม่สมมาตร กดดู "ค่าที่ยืมมา" ได้
// เพื่อเทียบ แต่ค่าตั้งต้นคือของจริง
//
// ระดับ เปอร์เซ็นไทล์ ขึ้น ลง โอกาสถูกแตะ
// L1 P40 0.115 0.021 60.0%
// AVR- P55 0.271 0.173 45.0%
// AVR P70 0.415 0.355 30.1%
// AVR+ P85 0.715 0.674 15.1%
// Max P95 1.081 1.088 5.1%
//
// base rate พวกนี้คือของแพงที่สุดในเครื่องมือนี้ เพราะมันทำให้ตั้ง R:R ได้
// ด้วยเหตุผล — ถ้าแตะ L1 แล้ว โอกาสไปต่อถึง AVR คือ 30.1/60.0 = 50%
// ไม่ใช่ 30% และไม่ใช่การเดา
//
// ── สิ่งที่เครื่องมือนี้จะไม่ทำ ────────────────────────────────────────────
// ไม่มีลูกศรเข้าออก ไม่มีคะแนนความมั่นใจ ไม่บอกทิศ
// และมันต้องพร้อมตอบว่า "ข้อมูลไม่พอ" เป็นคำตอบส่วนใหญ่
// ถ้ามันเจอ edge บ่อย ๆ แปลว่าสร้างมันผิด
//
// วาดด้วย line/label/box/table เท่านั้น จึงไม่กินโควตา 64 plots เลย
// ═══════════════════════════════════════════════════════════════════════════
indicator("Session Expansion Lab v1.3", "SEL v1.3", overlay = true,
max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500)
// ─── กลุ่มการตั้งค่า ───────────────────────────────────────────────────────
var string G_SES = "① เซสชันและระดับ"
var string G_DAY = "② การจำแนกประเภทวัน (ล็อกตอนเปิด)"
var string G_SIG = "③ นิยามสัญญาณที่จะทดสอบ"
var string G_LAB = "④ ห้องทดลอง — สองนาฬิกา · placebo · บัญชีนับครั้ง"
var string G_BB = "②b Bollinger Bands — ป้ายทางเลือกไว้เทียบ"
var string G_PTC = "⑦ ตรวจก่อนยิง — ด่านตรวจออเดอร์"
var string G_PTA = "⑦a บัญชีและเพดานประจำวัน"
// ── ① เซสชันและระดับ ─────────────────────────────────────────────────────
asiaSes = input.session("1400-2300", "กรอบ Asia (เวลา Exchange)", group = G_SES)
tradeSes = input.session("2300-1400", "ช่วงที่วัดการขยายตัว", group = G_SES)
atrLen = input.int(20, "ATR รายวัน", minval = 5, maxval = 100, group = G_SES)
// ── ชุดระดับ ────────────────────────────────────────────────────────────
// v1.1: ค่าตั้งต้นเปลี่ยนจาก "ไม่สมมาตร" เป็น "มีเงื่อนไข" หลังการทดสอบพบว่า
// ความไม่สมมาตรเป็นเงาของตลาดกระทิง ไม่ใช่คุณสมบัติของเซสชัน
// corr(ราคาเปลี่ยน, ความไม่สมมาตร) = +0.74 · ปีที่ทองนิ่งอัตราส่วน = 1.00 พอดี
// ในขาลงกลับด้านเป็น 0.83 · ตัดเทรนด์ออกแล้วสับป้าย 2,000 รอบ ได้ p = 0.109
// รายละเอียด: xaulearn.com/research/avr-asymmetry/
lvlSet = input.string("มีเงื่อนไข (แนะนำ)", "ชุดระดับ",
options = ["มีเงื่อนไข (แนะนำ)", "สมมาตร", "ไม่สมมาตร (เดิม)", "ยืมจาก NQ"],
group = G_SES,
tooltip = "มีเงื่อนไข = คิดเฉพาะเซสชันที่หลุดกรอบจริง (66% ของโอกาส) " +
"ซึ่งเป็น base rate ที่ระบบเบรกเอาต์ต้องใช้\n\n" +
"สมมาตร = รวมสองฝั่งเป็นการแจกแจงเดียว 1,864 ตัวอย่าง ไม่เดิมพันทิศทาง\n\n" +
"ไม่สมมาตร (เดิม) = ค่าที่คาลิเบรตแยกฝั่ง — พิสูจน์แล้วว่าเป็น artifact " +
"ของตลาดกระทิง เก็บไว้ให้เทียบเท่านั้น\n\n" +
"ยืมจาก NQ = ค่าที่ใช้อยู่ในเครื่องมือเดิม ไม่ได้วัดจากทอง")
// เฉพาะเซสชันที่หลุดกรอบจริง 66% ของโอกาส — base rate ของระบบเบรกเอาต์
var float[] M_COND = array.from(0.279, 0.403, 0.581, 0.854, 1.220)
// รวมสองฝั่ง 1,864 ตัวอย่าง — ไม่เดิมพันทิศทาง
var float[] M_SYM = array.from(0.064, 0.228, 0.399, 0.694, 1.096)
// แยกฝั่งตามการคาลิเบรตเดิม — เก็บไว้เทียบ ไม่แนะนำให้ใช้
var float[] M_ASYU = array.from(0.115, 0.271, 0.415, 0.715, 1.081)
var float[] M_ASYD = array.from(0.021, 0.173, 0.355, 0.674, 1.088)
var float[] M_NQ = array.from(0.326, 0.600, 0.850, 1.100, 1.350)
isCond = lvlSet == "มีเงื่อนไข (แนะนำ)"
isAsym = lvlSet == "ไม่สมมาตร (เดิม)"
mUp = isCond ? M_COND : lvlSet == "สมมาตร" ? M_SYM : isAsym ? M_ASYU : M_NQ
mDn = isCond ? M_COND : lvlSet == "สมมาตร" ? M_SYM : isAsym ? M_ASYD : M_NQ
// โอกาสถูกแตะ — ชุดมีเงื่อนไขต้องคูณด้วยสัดส่วนเซสชันที่หลุดกรอบจริง
// ถึงจะเทียบกับชุดอื่นได้ ไม่งั้นมันดูดีกว่าความจริง 1.5 เท่า
var float BRK_FRAC = 0.66
var float[] HIT_C = array.from(0.600, 0.450, 0.300, 0.150, 0.050)
hitOf(int i) =>
h = array.get(HIT_C, i - 1)
isCond ? h * BRK_FRAC : h
// ── ② ประเภทวัน ─────────────────────────────────────────────────────────
// ตัวแปรเดียว วัดได้ตอนเปิดเซสชัน และมีกลไกรองรับ:
// กรอบ Asia แคบเทียบกับ ATR = ตลาดอัดแน่น = มีแนวโน้มจะขยายตัว
// กรอบ Asia กว้างแล้ว = เคลื่อนไปเยอะแล้ว = การขยายตัวต่อมีโอกาสน้อยลง
//
// ⚑ ค่าคงที่ 0.45/0.90 ที่ใช้มาตลอด แบ่งวันออกมาเป็น 334/113/31 — เบ้เกือบ 11 ต่อ 1
// เกณฑ์ที่ต้องเอาชนะแปรผันกับ 1÷√(กลุ่มเล็กสุด) ดังนั้นกลุ่ม 31 ไม้เป็นตัวกำหนดพลัง
// ของการทดสอบทั้งหมด และการมี 334 ไม้ในอีกกลุ่มช่วยอะไรไม่ได้เลย
// การแบ่งด้วยเปอร์เซ็นไทล์บังคับให้กลุ่มสมดุลโดยโครงสร้าง จึงเป็นค่าตั้งต้นตั้งแต่ v1.2
splitMode = input.string("เปอร์เซ็นไทล์ (สมดุล · แนะนำ)", "วิธีแบ่งกลุ่มประเภทวัน",
options = ["เปอร์เซ็นไทล์ (สมดุล · แนะนำ)", "ค่าคงที่ (เดิม)"], group = G_DAY,
tooltip = "เปอร์เซ็นไทล์ = เทียบค่าวันนี้กับเซสชันที่ผ่านมา แล้วแบ่งตามอันดับ " +
"กลุ่มจึงมีขนาดใกล้เคียงกันเสมอ และใช้เกณฑ์ชุดเดียวกันทั้งสองป้าย " +
"ทำให้เทียบกันได้จริง · " +
"ค่าคงที่ = ค่าเดิม 0.45/0.90 เก็บไว้ทำซ้ำผลเก่าเท่านั้น ไม่แนะนำให้ใช้ตัดสิน · " +
"หมายเหตุ: เปอร์เซ็นไทล์ถามว่า อัดแน่นเทียบกับช่วงหลังนี้ไหม " +
"ซึ่งเป็นคนละคำถามกับ อัดแน่นในเชิงสัมบูรณ์ไหม")
pcLo = input.float(33, "อัดแน่น: เปอร์เซ็นไทล์ต่ำกว่า", minval = 5, maxval = 45,
step = 1, group = G_DAY)
pcHi = input.float(67, "คลายแล้ว: เปอร์เซ็นไทล์สูงกว่า", minval = 55, maxval = 95,
step = 1, group = G_DAY)
histLen = input.int(250, "เทียบกับกี่เซสชันย้อนหลัง", minval = 40, maxval = 2000,
step = 10, group = G_DAY,
tooltip = "หน้าต่างเลื่อน ไม่ใช่ทั้งประวัติ เพราะระดับความผันผวนของทองเปลี่ยนไปมาก " +
"จนค่าจากปี 2023 เทียบกับปีนี้ตรง ๆ ไม่ได้")
minHist = input.int(40, "ต้องมีประวัติกี่เซสชันก่อนเริ่มจำแนก", minval = 20, maxval = 500,
step = 10, group = G_DAY,
tooltip = "ก่อนถึงจำนวนนี้ อันดับเปอร์เซ็นไทล์ยังหยาบเกินไป จึงติดป้ายว่า ปกติ ไปก่อน " +
"ไม่ใช่เดาว่าอัดแน่นหรือคลาย")
coilLo = input.float(0.45, "โหมดค่าคงที่ — อัดแน่น: Asia/ATR ต่ำกว่า", minval = 0.05,
maxval = 2.0, step = 0.05, group = G_DAY)
coilHi = input.float(0.90, "โหมดค่าคงที่ — คลายแล้ว: Asia/ATR สูงกว่า", minval = 0.10,
maxval = 3.0, step = 0.05, group = G_DAY)
// ป้ายประเภทวันมีได้มากกว่าหนึ่งแบบ และนั่นคือประเด็นทั้งหมด — งาน VWAP สอนเราว่า
// placebo ตอบได้แค่ "ดีกว่าสุ่มไหม" ไม่ได้ตอบว่า "ดีกว่าป้ายอื่นที่หาได้ฟรีไหม"
// จึงต้องมีป้ายที่สองให้สลับไปวัดด้วยตัวชี้วัดชุดเดียวกัน
showB = input.bool(true, "วัดป้าย B (ความกว้าง BB) เทียบไปพร้อมกัน", group = G_DAY,
tooltip = "สองป้ายตอบคำถามเดียวกันคือ ตลาดอัดแน่นหรือคลายแล้ว แต่วัดคนละทางสนิท · " +
"ตารางแสดงพร้อมกันทั้งสอง ไม่ใช่สลับดูทีละอัน — เพราะการสลับไปมา " +
"แล้วจำตัวเลขเอง คือการค้นหาแบบหนึ่งที่ไม่มีใครนับ และเป็นวิธีที่เราหลอกตัวเองได้ง่ายที่สุด · " +
"ปิดได้ถ้ากราฟหน่วง เพราะ placebo ต้องรันสองรอบ")
useDir = input.bool(true, "เพิ่มตัวแปรที่รู้ทิศทางด้วย", group = G_DAY,
tooltip = "ScalperVBubble ล้มเพราะใช้ Kaufman-ER ซึ่งไม่สนใจทิศ จึงปล่อยให้ " +
"เทรดสวนเทรนด์ผ่านไปได้ ตัวนี้เพิ่มตำแหน่งราคาปิดในกรอบ Asia " +
"เพื่อทดสอบว่าการรู้ทิศช่วยจริงไหม")
// ── ②b Bollinger Bands ──────────────────────────────────────────────────
// ค่าตั้งต้นตรงกับที่ผู้ใช้ตั้งไว้บนชาร์ตจริง (20 · VWMA · close · 1.2)
// บทบาทของมันที่นี่มีสองอย่างเท่านั้น: เป็นเส้นให้ดู และเป็นป้ายทางเลือกให้ทดสอบ
// มันไม่ได้ถูกใส่มาเพื่อสร้างสัญญาณเข้า และจะไม่ถูกใช้แบบนั้นจนกว่าจะผ่านตารางนี้ก่อน
bbShow = input.bool(true, "วาด Bollinger Bands", group = G_BB)
bbLen = input.int(20, "Length", minval = 1, group = G_BB)
bbMaType = input.string("VWMA", "Basis MA Type",
options = ["SMA", "EMA", "SMMA (RMA)", "WMA", "VWMA"], group = G_BB)
bbSrc = input(close, "Source", group = G_BB)
bbMult = input.float(1.2, "StdDev", minval = 0.001, maxval = 50, step = 0.1, group = G_BB)
bbOff = input.int(0, "Offset", minval = -500, maxval = 500, group = G_BB)
bbRank = input.int(120, "ช่วงเทียบเปอร์เซ็นไทล์ความกว้าง (บาร์)", minval = 20,
maxval = 1000, step = 10, group = G_BB,
tooltip = "ความกว้างดิบเทียบข้ามช่วงเวลาไม่ได้ เพราะราคาทองเปลี่ยนระดับไปมาก " +
"จึงต้องแปลงเป็นเปอร์เซ็นไทล์เทียบกับตัวมันเองก่อน")
bbCoil = input.float(25.0, "อัดแน่น: เปอร์เซ็นไทล์ต่ำกว่า", minval = 1, maxval = 99,
step = 5, group = G_BB)
bbLoose = input.float(75.0, "คลายแล้ว: เปอร์เซ็นไทล์สูงกว่า", minval = 1, maxval = 99,
step = 5, group = G_BB)
bbBasCol = input.color(#2962FF, "สีเส้นกลาง", group = G_BB, inline = "bc")
bbUpCol = input.color(#F23645, "สีเส้นบน", group = G_BB, inline = "bc")
bbLoCol = input.color(#089981, "สีเส้นล่าง", group = G_BB, inline = "bc")
bbWidth = input.int(1, "ความหนาเส้น", minval = 1, maxval = 5, group = G_BB)
bbFill = input.color(color.rgb(33, 150, 243, 95), "สีพื้นระหว่างเส้น", group = G_BB)
// ── ③ สัญญาณที่จะทดสอบ ──────────────────────────────────────────────────
entryLvl = input.int(1, "เข้าเมื่อแตะระดับที่", minval = 1, maxval = 4, group = G_SIG)
tgtLvl = input.int(3, "เป้าหมายระดับที่", minval = 2, maxval = 5, group = G_SIG)
stopMode = input.string("ล็อก R:R ให้เท่ากันสองฝั่ง", "วิธีตั้งจุดหยุด",
options = ["ล็อก R:R ให้เท่ากันสองฝั่ง", "อิงกรอบ Asia", "อิงระยะเข้า"],
group = G_SIG,
tooltip = "เดิมใช้ 'อิงกรอบ Asia' ซึ่งสมมาตรทั้งสองฝั่ง แต่จุดเข้าไม่สมมาตร " +
"R:R จึงต่างกันได้ถึง 53% โดยไม่ได้เกิดจากขอบอะไรเลย\n\n" +
"โหมดล็อก R:R วางจุดหยุดให้ระยะเสี่ยงเป็นสัดส่วนคงที่ของระยะกำไร " +
"ทั้งขึ้นและลง ความต่างที่เหลือจึงมาจากตลาด ไม่ใช่จากเรขาคณิต")
targetRR = input.float(1.5, "R:R ที่ต้องการ (โหมดล็อก R:R)", minval = 0.5, maxval = 5.0,
step = 0.1, group = G_SIG)
stopBack = input.float(0.5, "ตัวคูณจุดหยุด (สองโหมดที่เหลือ)", minval = 0.1,
maxval = 4.0, step = 0.1, group = G_SIG)
spreadIn = input.float(0.30, "สเปรดเฉลี่ยของโบรกคุณ (ดอลลาร์)", minval = 0.0, step = 0.05,
group = G_SIG,
tooltip = "ใช้ตรวจว่าจุดเข้าอยู่ห่างขอบกรอบพอจะเรียกว่าเบรกเอาต์ไหม " +
"หรืออยู่ในสัญญาณรบกวน")
minSprd = input.int(10, "จุดเข้าต้องห่างขอบอย่างน้อยกี่เท่าของสเปรด", minval = 1,
maxval = 100, group = G_SIG,
tooltip = "ต่ำกว่านี้ไม่ใช่การเบรกเอาต์ เป็นการแตะขอบกรอบแล้วจ่ายสเปรด")
costPer = input.float(0.35, "ต้นทุนไปกลับ (ดอลลาร์ต่อออนซ์)", minval = 0.0, step = 0.05,
group = G_SIG,
tooltip = "spread + commission ของโบรกคุณ ระบบที่ขอบบางตายตรงนี้")
// ── ④ ห้องทดลอง ─────────────────────────────────────────────────────────
// ── ⑦ ตรวจก่อนยิง ────────────────────────────────────────────────────────
//
// โมดูลนี้ตอบคำถามคนละข้อกับทั้งไฟล์ที่เหลือ · ส่วนบนถามว่า "ป้ายนี้มีข้อมูลไหม"
// ส่วนนี้ถามว่า **"ออเดอร์ที่กำลังจะยิง ประกอบถูกและรอดได้ไหม"**
//
// ⚑ กฎเหล็ก: **มันห้ามได้ แต่อนุญาตไม่ได้** — คำตัดสินมีสองแบบเท่านั้น
// คือ "หยุด" กับ "ไม่พบข้อห้าม" · คำว่า "เข้าได้" หรือ "ความมั่นใจ %"
// จะไม่ปรากฏที่ใดในโมดูลนี้
//
// เหตุผล: เราฆ่าสมมติฐานสัญญาณไปสี่สายแล้ว (การขยายตัวรายเซสชัน · กลไก VWAP
// สามข้อบนสามระบอบ · สัญญาณ DeskTrend · การเข้าแบบลิมิต 12 ชุด) ·
// เครื่องมือที่ให้คะแนนจะสร้างความมั่นใจที่ไม่มีของรองรับ — และมีบันทึกว่า
// สแต็กเคยพิมพ์ CONF 90-95 ให้ฟาดที่ 4045-69 แล้วทองวิ่ง +129$ สวนหน้า
//
// เครื่องมือที่ห้ามได้อย่างเดียว สร้างความมั่นใจปลอมไม่ได้ · ที่ให้คะแนนได้ จะสร้างเสมอ
ptcOn = input.bool(false, "เปิดด่านตรวจก่อนยิง", group = G_PTC,
tooltip = "ปิดไว้ตอนทำวิจัย เปิดตอนจะยิงออเดอร์ · ใช้ตารางแยกคนละมุมกับตารางวิจัย " +
"เพราะเป็นคนละคำถามและคนละจังหวะ")
ptcSideS = input.string("ซื้อ", " ทิศทาง", options = ["ซื้อ", "ขาย"], group = G_PTC)
ptcNow = input.bool(true, " ใช้ราคาปัจจุบันเป็นจุดเข้า", group = G_PTC)
ptcEntry = input.float(0.0, " จุดเข้า (ถ้าไม่ใช้ราคาปัจจุบัน)", minval = 0, group = G_PTC)
ptcSL = input.float(0.0, " จุดหยุด (0 = คิดจาก ATR ให้)", minval = 0, group = G_PTC)
ptcTP = input.float(0.0, " เป้าหมาย (0 = คิดจาก R:R ให้)", minval = 0, group = G_PTC)
ptcRR = input.float(2.0, " R:R ที่ใช้เมื่อไม่ได้กรอกเป้าหมาย", minval = 0.5,
maxval = 10, step = 0.1, group = G_PTC)
ptcSLx = input.float(1.5, " ตัวคูณ ATR เมื่อไม่ได้กรอกจุดหยุด", minval = 0.2,
maxval = 10, step = 0.1, group = G_PTC)
ptcAtrLn = input.int(14, " ATR ของ TF นี้ (ใช้วัดสัญญาณรบกวน)", minval = 2,
maxval = 200, group = G_PTC)
ptcMinRR = input.float(1.0, " R:R หลังหักต้นทุน ต้องไม่ต่ำกว่า", minval = 0.1,
maxval = 10, step = 0.1, group = G_PTC)
ptcWarnRR = input.float(1.5, " ต่ำกว่านี้ให้เตือน", minval = 0.1, maxval = 10,
step = 0.1, group = G_PTC)
ptcNoise = input.float(0.30, " จุดหยุดแคบกว่ากี่เท่า ATR ถือว่าอยู่ในสัญญาณรบกวน",
minval = 0.05, maxval = 2, step = 0.05, group = G_PTC)
ptcWide = input.float(3.0, " จุดหยุดกว้างกว่ากี่เท่า ATR ถือว่ากว้างเกิน",
minval = 1, maxval = 10, step = 0.5, group = G_PTC)
ptcTol = input.float(0.25, " ถือว่า 'ชิด' ระดับเมื่อห่างน้อยกว่ากี่เท่า ATR",
minval = 0.05, maxval = 2, step = 0.05, group = G_PTC)
ptcTrend = input.bool(true, " เตือนเมื่อสวนเทรนด์ TF ใหญ่", group = G_PTC)
ptcTrTF = input.timeframe("240", " TF ที่ใช้ดูเทรนด์", group = G_PTC)
ptcTrLen = input.int(50, " EMA", minval = 5, maxval = 400, group = G_PTC)
ptcPos = input.string("ซ้ายล่าง", " ตำแหน่งตารางด่านตรวจ",
options = ["ขวาบน", "ขวาล่าง", "ซ้ายบน", "ซ้ายล่าง"], group = G_PTC)
ptcBal = input.float(300.0, "เงินในบัญชี (ดอลลาร์)", minval = 1, group = G_PTA)
ptcRisk = input.float(1.0, "เสี่ยงต่อไม้ (%)", minval = 0.05, maxval = 10,
step = 0.05, group = G_PTA)
ptcCommis = input.float(0.0, "คอมมิชชันต่อล็อต ต่อข้าง (ดอลลาร์)", minval = 0.0,
step = 0.5, group = G_PTA)
ptcContract = input.float(100.0, "ขนาดสัญญา (ออนซ์ต่อล็อต)", minval = 1, group = G_PTA)
ptcMinLot = input.float(0.01, "ล็อตต่ำสุด", minval = 0.001, step = 0.001, group = G_PTA)
ptcLotStep = input.float(0.01, "ขั้นล็อต", minval = 0.001, step = 0.001, group = G_PTA)
ptcDayPct = input.float(3.0, "เพดานขาดทุนรายวัน (% ของบัญชี)", minval = 0.5,
maxval = 20, step = 0.5, group = G_PTA)
ptcLossToday = input.float(0.0, " ขาดทุนไปแล้ววันนี้ (ดอลลาร์ ใส่เป็นบวก)",
minval = 0, step = 1, group = G_PTA)
ptcTradesDone = input.int(0, "ไม้ที่ทำไปแล้ววันนี้", minval = 0, maxval = 100, group = G_PTA)
ptcMaxTrades = input.int(4, " เพดานไม้ต่อวัน", minval = 1, maxval = 100, group = G_PTA,
tooltip = "Patrick Nill: แพ้สี่ไม้ติดให้หยุด · เพดานนี้บังคับสิ่งเดียวกันโดยไม่ต้องใช้ใจ")
healthN = input.int(20, "สุขภาพสัญญาณ: ดูย้อนหลังกี่ไม้", minval = 5, maxval = 100,
group = G_LAB)
minCell = input.int(15, "ขั้นต่ำต่อช่องก่อนยอมสรุป", minval = 5, maxval = 200,
group = G_LAB,
tooltip = "ต่ำกว่านี้เครื่องมือจะขึ้นว่า 'ข้อมูลไม่พอ' แทนที่จะให้ตัวเลข " +
"ที่ดูน่าเชื่อถือแต่เป็นสัญญาณรบกวน")
oosFrac = input.float(0.30, "สัดส่วนข้อมูลนอกกลุ่ม", minval = 0.1, maxval = 0.5,
step = 0.05, group = G_LAB)
runPlac = input.bool(true, "รัน placebo (สุ่มจุดเข้าแบบจับคู่)", group = G_LAB,
tooltip = "สุ่มโดยให้จำนวนไม้ ทิศทาง และระยะถือเท่าของจริงทุกอย่าง " +
"เหลือต่างกันแค่จังหวะ ถ้าชนะการสุ่มไม่ได้ จังหวะไม่มีข้อมูล")
placRuns = input.int(100, "จำนวนรอบ placebo", minval = 50, maxval = 500, step = 50,
group = G_LAB,
tooltip = "แต่ละรอบต้องสับป้ายทั้งชุดแล้วรวมใหม่ ยิ่งเทรดเยอะยิ่งหนัก " +
"ถ้า Pine ขึ้นว่าใช้เวลาเกิน ให้ลดเลขนี้ลง")
lookCnt = input.int(1, "ทดลองไปแล้วกี่ชุดพารามิเตอร์", minval = 1, maxval = 100000,
group = G_LAB,
tooltip = "นับตามจริง ทุกครั้งที่ปรับค่าแล้วดูผลใหม่คือหนึ่งชุด " +
"Pine จำข้ามการคอมไพล์ไม่ได้ คุณจึงต้องพิมพ์เอง " +
"เกณฑ์นัยสำคัญจะสูงขึ้นตามเลขนี้ ถ้าใส่ต่ำกว่าจริง คุณโกหกตัวเอง")
// ── ⑤ การแสดงผล ─────────────────────────────────────────────────────────
var string G_VTB = "⑤a ตาราง"
var string G_VLN = "⑤b เส้นและป้ายบนชาร์ต"
showTbl = input.bool(true, "แสดงตารางผลวิจัย", group = G_VTB)
tblPos = input.string("ขวาบน", "ตำแหน่ง",
options = ["ขวาบน", "ขวาล่าง", "ซ้ายบน", "ซ้ายล่าง"], group = G_VTB)
tblSizeS = input.string("เล็ก", "ขนาดตัวอักษร",
options = ["จิ๋ว", "เล็ก", "ปกติ", "ใหญ่", "ใหญ่มาก"], group = G_VTB)
tblBg = input.color(color.new(color.gray, 80), "สีพื้นหลัง", group = G_VTB)
tblInk = input.color(color.new(color.white, 0), "สีตัวอักษรหลัก", group = G_VTB)
tblDim = input.color(color.new(color.gray, 30), "สีตัวอักษรรอง", group = G_VTB)
tblAcc = input.color(color.new(color.orange, 0), "สีหัวข้อ", group = G_VTB)
tblGood = input.color(color.new(color.lime, 20), "สีค่าบวก", group = G_VTB)
tblBad = input.color(color.new(color.red, 20), "สีค่าลบ", group = G_VTB)
tblWarn = input.color(color.new(color.yellow, 0), "สีคำเตือน", group = G_VTB)
tblFrame = input.color(color.new(color.gray, 60), "สีกรอบตาราง", group = G_VTB)
showLvl = input.bool(true, "วาดระดับการขยายตัว", group = G_VLN)
lvlCol = input.color(color.orange, "สีเส้นระดับ", group = G_VLN,
tooltip = "ระดับที่โอกาสถูกแตะสูงจะทึบกว่า ระดับที่ไกลจะจางลง " +
"เพื่อให้เห็นน้ำหนักของแต่ละระดับโดยไม่ต้องอ่านตัวเลข")
lvlWidth = input.int(1, "ความหนาเส้น", minval = 1, maxval = 5, group = G_VLN)
lvlStyS = input.string("จุดไข่ปลา", "ประเภทเส้น",
options = ["เส้นทึบ", "เส้นประ", "จุดไข่ปลา"], group = G_VLN)
lvlExt = input.int(60, "ความยาวเส้น (บาร์)", minval = 10, maxval = 500, step = 10,
group = G_VLN,
tooltip = "บนกรอบเวลาเล็ก 60 บาร์สั้นกว่าหนึ่งเซสชัน — M3 60 บาร์ = 3 ชั่วโมง " +
"ถ้าเส้นจบก่อนเซสชันจบ ให้เพิ่มเลขนี้")
lblSizeS = input.string("จิ๋ว", "ขนาดตัวอักษรบนชาร์ต",
options = ["จิ๋ว", "เล็ก", "ปกติ", "ใหญ่", "ใหญ่มาก"], group = G_VLN)
lblCol = input.color(color.new(color.orange, 25), "สีตัวอักษรบนชาร์ต", group = G_VLN)
showBox = input.bool(true, "วาดกรอบ Asia", group = G_VLN)
boxBord = input.color(color.new(color.gray, 40), "สีขอบกรอบ Asia", group = G_VLN)
boxBg = input.color(color.new(color.gray, 92), "สีพื้นกรอบ Asia", group = G_VLN)
// แปลงชื่อไทยเป็นค่าคงที่ของ Pine
sizeOf(string sz) =>
sz == "จิ๋ว" ? size.tiny : sz == "เล็ก" ? size.small : sz == "ปกติ" ? size.normal
: sz == "ใหญ่" ? size.large : size.huge
// ปัดล็อตลงตามขั้นของโบรกเกอร์เสมอ — ปัดขึ้นคือเสี่ยงเกินที่ตั้งใจไว้
floorLot(float raw, float stepv) =>
stepv <= 0 ? raw : math.floor(raw / stepv) * stepv
posOf(string s) =>
s == "ขวาบน" ? position.top_right : s == "ขวาล่าง" ? position.bottom_right
: s == "ซ้ายบน" ? position.top_left : position.bottom_left
tblSize = sizeOf(tblSizeS)
lblSize = sizeOf(lblSizeS)
lvlSty = lvlStyS == "เส้นทึบ" ? line.style_solid
: lvlStyS == "เส้นประ" ? line.style_dashed : line.style_dotted
// ═══════════════════════════════════════════════════════════════════════════
// ATR รายวันของ "เมื่อวาน" — ห้ามมองอนาคต
// ใช้ [1] และ lookahead_off เพื่อให้ค่าที่ได้เป็นค่าที่ปิดแล้วจริง ๆ
// ═══════════════════════════════════════════════════════════════════════════
prevATR = request.security(syminfo.tickerid, "D", ta.atr(atrLen)[1],
lookahead = barmerge.lookahead_off)
// ═══════════════════════════════════════════════════════════════════════════
// กรอบ Asia — สะสมระหว่างเซสชัน แล้วล็อกเมื่อจบ
// ═══════════════════════════════════════════════════════════════════════════
inAsia = not na(time(timeframe.period, asiaSes, syminfo.timezone))
inTrade = not na(time(timeframe.period, tradeSes, syminfo.timezone))
var float aHi = na // ระหว่างสะสม
var float aLo = na
var float lockHi = na // ล็อกแล้ว ใช้ทั้งวัน
var float lockLo = na
var float lockATR = na
var int dayId = 0
asiaStart = inAsia and not inAsia[1]
asiaEnd = not inAsia and inAsia[1]
if asiaStart
aHi := high
aLo := low
else if inAsia
aHi := math.max(nz(aHi, high), high)
aLo := math.min(nz(aLo, low), low)
// ── การล็อก: ทุกอย่างที่ใช้ตัดสินใจทั้งวัน ถูกแช่ไว้ ณ วินาทีนี้ ───────────
// นี่คือกฎที่กันการวัดแบบวงกลม ถ้าเราจัดประเภทวันใหม่หลังจากเห็นผลแล้ว
// "วันเทรนด์ทำกำไรดี" จะกลายเป็นประโยคที่จริงโดยอัตโนมัติและไร้ความหมาย
newSession = asiaEnd and not na(aHi) and not na(aLo) and not na(prevATR) and prevATR > 0
if newSession
lockHi := aHi
lockLo := aLo
lockATR := prevATR
dayId += 1
asiaRange = lockHi - lockLo
haveDay = not na(lockHi) and not na(lockATR) and lockATR > 0
// ═══════════════════════════════════════════════════════════════════════════
// Bollinger Bands — เส้น และป้ายทางเลือก
// ═══════════════════════════════════════════════════════════════════════════
bbMa(source, len, _type) =>
switch _type
"SMA" => ta.sma(source, len)
"EMA" => ta.ema(source, len)
"SMMA (RMA)" => ta.rma(source, len)
"WMA" => ta.wma(source, len)
"VWMA" => ta.vwma(source, len)
bbBasis = bbMa(bbSrc, bbLen, bbMaType)
bbDev = bbMult * ta.stdev(bbSrc, bbLen)
bbUpper = bbBasis + bbDev
bbLower = bbBasis - bbDev
// ความกว้างต้องหารด้วยเส้นกลาง ไม่งั้นทองที่ $4,400 กับทองที่ $1,800 เทียบกันไม่ได้
// แล้วยังต้องแปลงเป็นเปอร์เซ็นไทล์อีกชั้น เพราะแม้แต่ความกว้างสัมพัทธ์ก็ยังไหลไปตามระบอบ
bbW = bbBasis != 0 ? (bbUpper - bbLower) / bbBasis : na
bbPct = ta.percentrank(bbW, bbRank)
pu = plot(bbShow ? bbUpper : na, "BB Upper", color = bbUpCol, linewidth = bbWidth, offset = bbOff)
pl = plot(bbShow ? bbLower : na, "BB Lower", color = bbLoCol, linewidth = bbWidth, offset = bbOff)
plot(bbShow ? bbBasis : na, "BB Basis", color = bbBasCol, linewidth = bbWidth, offset = bbOff)
fill(pu, pl, title = "BB Background", color = bbShow ? bbFill : na)
// ═══════════════════════════════════════════════════════════════════════════
// A · ประเภทวัน — คำนวณครั้งเดียวตอนล็อก
// ═══════════════════════════════════════════════════════════════════════════
var int dayType = 0
var int dayTypeB = 0 // -1 คลายแล้ว · 0 ปกติ · +1 อัดแน่น
var float coilVal = na
var int dirBias = 0 // -1 ล่างกรอบ · 0 กลาง · +1 บนกรอบ
// คำนวณ *ทั้งสองป้าย* ทุกเซสชัน ไม่มีสวิตช์ให้เลือกดูทีละอัน
// เหตุผลอยู่ในบันทึกของงาน VWAP: การสลับไปมาแล้วจำตัวเลขเอง คือการค้นหาแบบหนึ่ง
// ที่ไม่มีใครนับ — ถ้าอยากเทียบสองป้าย ต้องเห็นพร้อมกันในตารางเดียว
// อันดับเทียบเฉพาะเซสชัน *ก่อนหน้า* เท่านั้น ค่าวันนี้ยังไม่ถูกใส่เข้ากองตอนจัดอันดับ
// ไม่งั้นวันนี้จะช่วยกำหนดอันดับของตัวเอง ซึ่งคือการมองอนาคตแบบละเอียดอ่อน
rankAgainst(float[] hist, float v) =>
int m = array.size(hist)
int c = 0
for i = 0 to math.max(0, m - 1)
if m == 0
break
if array.get(hist, i) <= v
c += 1
m > 0 ? 100.0 * c / m : na
var float coilB = na
var float coilRank = na
var float[] coilHist = array.new<float>()
if newSession
// ทั้งสองป้ายล็อกค่า ณ วินาทีที่เปิดเซสชันเหมือนกัน ไม่งั้นเทียบกันไม่ได้
// และเงื่อนไขไม่รีเพนต์ก็จะหายไปพร้อมกัน
coilVal := asiaRange / lockATR
coilB := bbPct
// จัดอันดับก่อน แล้วค่อยเก็บค่าวันนี้ลงกอง ลำดับนี้สำคัญ
coilRank := rankAgainst(coilHist, coilVal)
array.push(coilHist, coilVal)
if array.size(coilHist) > histLen
array.shift(coilHist)
usePct = splitMode == "เปอร์เซ็นไทล์ (สมดุล · แนะนำ)"
ready = array.size(coilHist) > minHist
// โหมดเปอร์เซ็นไทล์ใช้ *เกณฑ์ชุดเดียวกัน* กับทั้งสองป้าย ขนาดกลุ่มจึงใกล้เคียงกัน
// โดยโครงสร้าง และความต่างที่เหลือคือความต่างของ "วิธีวัด" ล้วน ๆ ซึ่งคือสิ่งที่ทดสอบ
dayType := usePct
? (ready and not na(coilRank)
? (coilRank < pcLo ? 1 : coilRank > pcHi ? -1 : 0) : 0)
: (coilVal < coilLo ? 1 : coilVal > coilHi ? -1 : 0)
dayTypeB := na(bbPct) ? 0
: usePct ? (bbPct < pcLo ? 1 : bbPct > pcHi ? -1 : 0)
: (bbPct < bbCoil ? 1 : bbPct > bbLoose ? -1 : 0)
// ตำแหน่งราคาปิดในกรอบ Asia — ตัวแปรที่รู้ทิศ ซึ่งเป็นสิ่งที่ Kaufman-ER ไม่มี
posInRange = asiaRange > 0 ? (close - lockLo) / asiaRange : 0.5
dirBias := posInRange > 0.66 ? 1 : posInRange < 0.34 ? -1 : 0
typeName(int t) => t == 1 ? "อัดแน่น" : t == -1 ? "คลายแล้ว" : "ปกติ"
dayTypeName = typeName(dayType)
dayTypeNameB = typeName(dayTypeB)
// ═══════════════════════════════════════════════════════════════════════════
// ระดับการขยายตัว — จากกรอบที่ล็อกแล้ว ไม่ขยับอีกทั้งวัน
// ═══════════════════════════════════════════════════════════════════════════
lvlUp(int i) => lockHi + array.get(mUp, i - 1) * lockATR
lvlDn(int i) => lockLo - array.get(mDn, i - 1) * lockATR
// ═══════════════════════════════════════════════════════════════════════════
// สัญญาณ + การบันทึกผล
//
// นิยาม: ในช่วงวัดผล ถ้าราคาแตะระดับ entryLvl ให้เข้าตามทิศที่แตะ
// เป้าหมาย = ระดับ tgtLvl · หยุดขาดทุน = กลับเข้ากรอบ Asia
// หนึ่งไม้ต่อวัน ต่อทิศ — กันการนับซ้ำในวันที่แกว่งไปมา
// ═══════════════════════════════════════════════════════════════════════════
var int posSide = 0
var float posEntry = na
var float posTgt = na
var float posStop = na
var int posDay = 0
var int posType = 0
var int posTypeB = 0
var int posDir = 0 // อคติทิศทางที่ล็อกไว้ตอนเปิดเซสชัน
var int posBar = 0
var float[] pnl = array.new<float>() // ผลต่อไม้ หักต้นทุนแล้ว
var int[] pType = array.new<int>() // ป้าย A · กรอบ Asia ÷ ATR (ล็อกแล้ว)
var int[] pTypeB = array.new<int>() // ป้าย B · ความกว้าง BB (ล็อกแล้ว)
var int[] pHealth = array.new<int>() // สุขภาพสัญญาณตอนเข้า
var int[] pAligned = array.new<int>() // ทิศที่เข้า ตรงกับอคติที่ล็อกไว้ไหม
var int[] pSide = array.new<int>() // +1 ขึ้น / -1 ลง — ต้องแยกสถิติเสมอ
var int lastLongDay = 0
var int lastShortDay = 0
// สุขภาพสัญญาณ ณ ตอนนี้: expectancy ของ healthN ไม้ล่าสุด
signalHealth() =>
n = array.size(pnl)
if n < healthN
0 // ยังไม่รู้ ถือว่าเป็นกลาง
else
float s = 0.0
for i = n - healthN to n - 1
s += array.get(pnl, i)
avg = s / healthN
avg > 0 ? 1 : avg < 0 ? -1 : 0
// จุดหยุดตามโหมดที่เลือก
// ล็อก R:R — ระยะเสี่ยง = ระยะกำไร ÷ R:R ที่ต้องการ ทำให้สองฝั่งเทียบกันได้
// อิงกรอบ — วิธีเดิม สมมาตรทั้งสองฝั่งขณะที่จุดเข้าไม่สมมาตร
// อิงระยะเข้า — สัดส่วนของระยะที่จุดเข้าห่างจากขอบกรอบ
stopFor(int side, float entry, float target) =>
float st = na
if stopMode == "ล็อก R:R ให้เท่ากันสองฝั่ง"
st := entry - side * math.abs(target - entry) / math.max(0.1, targetRR)
else if stopMode == "อิงกรอบ Asia"
st := side == 1 ? lockHi - stopBack * asiaRange : lockLo + stopBack * asiaRange
else
off = math.abs(entry - (side == 1 ? lockHi : lockLo))
st := entry - side * stopBack * off
st
// ── เปิดสถานะ ────────────────────────────────────────────────────────────
if haveDay and inTrade and posSide == 0 and barstate.isconfirmed
eUp = lvlUp(entryLvl)
eDn = lvlDn(entryLvl)
if high >= eUp and dayId != lastLongDay
posSide := 1
posEntry := eUp
posTgt := lvlUp(tgtLvl)
posStop := stopFor(1, eUp, lvlUp(tgtLvl))
posDay := dayId
posType := dayType
posTypeB := dayTypeB
posDir := useDir ? dirBias : 0
posBar := bar_index
lastLongDay := dayId
else if low <= eDn and dayId != lastShortDay
posSide := -1
posEntry := eDn
posTgt := lvlDn(tgtLvl)
posStop := stopFor(-1, eDn, lvlDn(tgtLvl))
posDay := dayId
posType := dayType
posTypeB := dayTypeB
posDir := useDir ? dirBias : 0
posBar := bar_index
lastShortDay := dayId
// ── ปิดสถานะ ─────────────────────────────────────────────────────────────
// เขียนแบบนี้เพราะฟังก์ชันของ Pine แก้ค่าตัวแปรระดับบนไม่ได้ (posSide := 0
// ในฟังก์ชันคือ error) หาราคาปิดก่อน แล้วค่อยบันทึกและล้างสถานะข้างนอก
float exitPx = na
if posSide != 0 and barstate.isconfirmed
if posSide == 1
exitPx := low <= posStop ? posStop : high >= posTgt ? posTgt
: dayId != posDay ? close : na // วันจบก่อนถึงเป้าหรือหยุด
else
exitPx := high >= posStop ? posStop : low <= posTgt ? posTgt
: dayId != posDay ? close : na
if not na(exitPx)
array.push(pnl, posSide * (exitPx - posEntry) - costPer)
array.push(pType, posType)
array.push(pTypeB, posTypeB)
array.push(pHealth, signalHealth())
array.push(pAligned, posSide == posDir ? 1 : posDir == 0 ? 0 : -1)
array.push(pSide, posSide)
posSide := 0
// ═══════════════════════════════════════════════════════════════════════════
// สถิติ — คำนวณบนบาร์สุดท้ายเท่านั้น
// ═══════════════════════════════════════════════════════════════════════════
// หมายเหตุ: อย่าตั้งชื่อพารามิเตอร์ว่า from หรือ to — เป็นคำสงวนของ Pine
// (ใช้ในไวยากรณ์ลูป for) คอมไพเลอร์จะฟ้อง syntax error ที่พารามิเตอร์ตัวแรก
// ซึ่งชี้ผิดที่ และหาสาเหตุยากถ้าไม่รู้
typeStat(int[] src, int typ, int i0, int i1) =>
float s = 0.0
int c = 0
int w = 0
for i = i0 to i1
if array.get(src, i) == typ
s += array.get(pnl, i)
c += 1
if array.get(pnl, i) > 0
w += 1
[c, c > 0 ? s / c : na, c > 0 ? w / float(c) : na]
// ขึ้นกับลงคือสองระบบ ไม่ใช่ระบบเดียว การรวมกันคือการซ่อนว่าฝั่งไหนใช้ได้
sideStat(int sd) =>
float s = 0.0
int c = 0
int w = 0
for i = 0 to math.max(0, array.size(pnl) - 1)
if array.size(pnl) == 0
break
if array.get(pSide, i) == sd
s += array.get(pnl, i)
c += 1
if array.get(pnl, i) > 0
w += 1
[c, c > 0 ? s / c : na, c > 0 ? w / float(c) : na]
// สับป้ายแบบสุ่ม โดยคง pnl ของทุกไม้ไว้เหมือนเดิมทุกประการ ถ้าความต่างที่วัดได้จริง
// จมอยู่กลางการกระจายของการสับมั่ว แปลว่าป้ายนั้นไม่มีข้อมูล เราแค่หั่นตัวเลขจน
// บังเอิญเจอความต่าง · seed คงที่ ผลจึงทำซ้ำได้ ไม่ใช่กด refresh จนได้ผลที่ชอบ
//
// เดิมเขียน inline เพราะเข้าใจว่าฟังก์ชัน Pine แก้ตัวแปรระดับบนไม่ได้ — จริง
// แต่มัน *อ่าน* ได้ และ *คืนค่า* ได้ ซึ่งพอสำหรับงานนี้ พอทำเป็นฟังก์ชันแล้ว
// จึงเรียกกับป้ายไหนก็ได้ และนั่นคือสิ่งที่ทำให้เทียบสองป้ายพร้อมกันเป็นไปได้
placeboOf(int[] src, float realSpread, int n) =>
int seed = 20260907
int beat = 0
int[] shuf = array.new<int>()
for i = 0 to n - 1
array.push(shuf, array.get(src, i))
for pr = 0 to placRuns - 1
for i = n - 1 to 1
seed := (seed * 1103515 + 12345) % 2147483647
j = math.abs(seed) % (i + 1)
tmp = array.get(shuf, i)
array.set(shuf, i, array.get(shuf, j))
array.set(shuf, j, tmp)
float sC = 0.0
float sS = 0.0
int nC = 0
int nS = 0
for i = 0 to n - 1
lbl = array.get(shuf, i)
if lbl == 1
sC += array.get(pnl, i)
nC += 1
else if lbl == -1
sS += array.get(pnl, i)
nS += 1
if nC > 0 and nS > 0
if math.abs(sC / nC - sS / nS) >= math.abs(realSpread)
beat += 1
beat / float(placRuns)
dirStat(int aligned) =>
float s = 0.0
int c = 0
// การ์ดกันกับดักเดียวกับข้างบน ตอนนี้ผู้เรียกกันไว้แล้ว แต่ฟังก์ชันไม่ควร
// พึ่งว่าผู้เรียกจะจำได้ตลอดไป
for i = 0 to math.max(0, array.size(pnl) - 1)
if array.size(pnl) == 0
break
if array.get(pAligned, i) == aligned
s += array.get(pnl, i)
c += 1
[c, c > 0 ? s / c : na]
// ── placebo ────────────────────────────────────────────────────────────
// สับป้ายประเภทวันแบบสุ่ม โดยคง pnl ทุกไม้ไว้เหมือนเดิม แล้วดูว่าความต่าง
// ที่วัดได้จริงอยู่ตรงไหนของการกระจายนั้น — ตอบคำถามเดียวว่า "ป้ายประเภทวัน
// มีข้อมูล หรือเราแค่หั่นตัวเลขจนบังเอิญเจอความต่าง"
//
// PRNG เขียนแบบ inline เพราะฟังก์ชันของ Pine แก้ตัวแปรระดับบนไม่ได้
// seed คงที่ ผลจึงทำซ้ำได้ ไม่ใช่กด refresh จนได้ผลที่ชอบ
// ═══════════════════════════════════════════════════════════════════════════
// การวาด
// ═══════════════════════════════════════════════════════════════════════════
var box asiaBox = na
var line[] lvlLines = array.new<line>()
var label[] lvlLabels = array.new<label>()
if showBox and newSession
asiaBox := box.new(bar_index, lockHi, bar_index + 1, lockLo,
border_color = boxBord, bgcolor = boxBg)
if showBox and haveDay and inTrade and not na(asiaBox)
box.set_right(asiaBox, bar_index + 1)
if showLvl and newSession
// ⚠ กับดัก Pine: `for i = 0 to array.size(x) - 1` เมื่อ array ว่างจะกลายเป็น
// `for i = 0 to -1` ซึ่ง Pine นับ *ถอยหลัง* ลูปจึงรันด้วย i = 0 บน array ที่ว่าง
// แล้วระเบิดเป็น RE10045 "Index 0 is out of bounds, array size is 0"
// ภาษาอื่นลูปนี้ไม่รันเลย นี่คือสำนวนที่ยืมข้ามภาษามาแล้วผิด
// while + pop เขียนผิดแบบนี้ไม่ได้ เพราะมันเช็คขนาดทุกรอบอยู่แล้ว
while array.size(lvlLines) > 0
line.delete(array.pop(lvlLines))
while array.size(lvlLabels) > 0
label.delete(array.pop(lvlLabels))
for i = 1 to 5
hitPct = hitOf(i)
// ความจางไล่ตามโอกาสถูกแตะ — ระดับที่เกิดบ่อยเด่นกว่าระดับที่ไกล
// โดยยังเป็นสีเดียวกับที่ผู้ใช้เลือก ถ้าตั้งสีมาโปร่งอยู่แล้ว
// ระดับไกลจะจางลงต่อจากฐานนั้น ไม่ใช่ทับค่าที่ตั้งไว้
transp = math.min(90, color.t(lvlCol) + (i <= 2 ? 0 : i <= 4 ? 25 : 45))
col = color.new(lvlCol, transp)
xEnd = bar_index + lvlExt
array.push(lvlLines, line.new(bar_index, lvlUp(i), xEnd, lvlUp(i),
color = col, style = lvlSty, width = lvlWidth))
array.push(lvlLines, line.new(bar_index, lvlDn(i), xEnd, lvlDn(i),
color = col, style = lvlSty, width = lvlWidth))
array.push(lvlLabels, label.new(xEnd, lvlUp(i),
str.tostring(math.round(hitPct * 100)) + "%", style = label.style_none,
textcolor = lblCol, size = lblSize))
array.push(lvlLabels, label.new(xEnd, lvlDn(i),
str.tostring(math.round(hitPct * 100)) + "%", style = label.style_none,
textcolor = lblCol, size = lblSize))
// ═══════════════════════════════════════════════════════════════════════════
// ตารางผลวิจัย
// ═══════════════════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════════════
// ⑦ ตรวจก่อนยิง — คำนวณ
// ═══════════════════════════════════════════════════════════════════════════
ptcATR = ta.atr(ptcAtrLn)
ptcTrEma = request.security(syminfo.tickerid, ptcTrTF, ta.ema(close, ptcTrLen)[1],
lookahead = barmerge.lookahead_on)
ptcSide = ptcSideS == "ซื้อ" ? 1 : -1
ptcE = ptcNow or ptcEntry <= 0 ? close : ptcEntry
ptcS = ptcSL > 0 ? ptcSL : ptcE - ptcSide * ptcSLx * ptcATR
ptcSDist = math.abs(ptcE - ptcS)
ptcT = ptcTP > 0 ? ptcTP : ptcE + ptcSide * ptcRR * ptcSDist
ptcTDist = math.abs(ptcT - ptcE)
ptcRiskMoney = ptcBal * ptcRisk / 100.0
ptcRawLots = ptcSDist > 0 ? ptcRiskMoney / (ptcSDist * ptcContract) : 0.0
ptcLots = floorLot(ptcRawLots, ptcLotStep)
ptcRiskReal = ptcLots * ptcSDist * ptcContract
ptcCost = ptcLots * (spreadIn * ptcContract + 2.0 * ptcCommis)
ptcGross = ptcLots * ptcTDist * ptcContract
ptcLoss = ptcLots * ptcSDist * ptcContract
ptcRRraw = ptcSDist > 0 ? ptcTDist / ptcSDist : na
ptcRRnet = (ptcLoss + ptcCost) > 0 ? (ptcGross - ptcCost) / (ptcLoss + ptcCost) : na
// ── ด่าน · ทุกด่านตอบได้แค่ ผ่าน / เตือน / ห้าม ─────────────────────────
ptcSLbad = ptcSide == 1 ? ptcS >= ptcE : ptcS <= ptcE
ptcTPbad = ptcSide == 1 ? ptcT <= ptcE : ptcT >= ptcE
ptcLotBad = ptcLots < ptcMinLot
ptcSprdX = spreadIn > 0 ? ptcSDist / spreadIn : na
ptcTight = not na(ptcSprdX) and ptcSprdX < minSprd
ptcAtrX = ptcATR > 0 ? ptcSDist / ptcATR : na
ptcNarrow = not na(ptcAtrX) and ptcAtrX < ptcNoise
ptcBroad = not na(ptcAtrX) and ptcAtrX > ptcWide
ptcRRbad = not na(ptcRRnet) and ptcRRnet < ptcMinRR
ptcRRwarn = not na(ptcRRnet) and ptcRRnet >= ptcMinRR and ptcRRnet < ptcWarnRR
// ระดับที่คนอื่นก็เห็น — ใช้กรอบ Asia ที่ล็อกไว้แล้ว ซึ่งไม่รีเพนต์
ptcTolPx = ptcATR * ptcTol
ptcSLnear = haveDay and (math.abs(ptcS - lockHi) < ptcTolPx or
math.abs(ptcS - lockLo) < ptcTolPx)
ptcTPblock = haveDay and (ptcSide == 1 ? (lockHi > ptcE and lockHi < ptcT)
: (lockLo < ptcE and lockLo > ptcT))
ptcFade = ptcTrend and not na(ptcTrEma) and
((ptcSide == 1 and close < ptcTrEma) or (ptcSide == -1 and close > ptcTrEma))
ptcDayCap = ptcBal * ptcDayPct / 100.0
ptcOverDay = ptcLossToday + ptcRiskReal > ptcDayCap
ptcOverCnt = ptcTradesDone >= ptcMaxTrades
// เตือนไม่นับเป็นข้อห้าม — มีแต่ข้อที่เป็นเลขคณิตหรือเพดานเท่านั้นที่ห้ามได้
ptcBlocked = ptcSLbad or ptcTPbad or ptcLotBad or ptcTight or ptcRRbad or
ptcOverDay or ptcOverCnt
tPos = tblPos == "ขวาบน" ? position.top_right : tblPos == "ขวาล่าง" ? position.bottom_right
: tblPos == "ซ้ายบน" ? position.top_left : position.bottom_left
var table T = table.new(tPos, 5, 24, border_width = 1,
frame_color = tblFrame, frame_width = 1)
cell(int c, int r, string txt, color tc, color bg) =>
table.cell(T, c, r, txt, text_color = tc, bgcolor = bg, text_size = tblSize,
text_halign = text.align_left)
if showTbl and barstate.islast
table.clear(T, 0, 0, 4, 23)
n = array.size(pnl)
hdr = tblBg
ink = tblInk
dim = tblDim
cell(0, 0, "SESSION EXPANSION LAB", tblAcc, hdr)
cell(1, 0, "", ink, hdr)
cell(2, 0, "", ink, hdr)
cell(3, 0, "", ink, hdr)
cell(4, 0, "", ink, hdr)
// แถวนี้บอกว่าวันนี้ *ทั้งสองป้าย* ว่าอย่างไร ถ้ามันไม่ตรงกัน นั่นคือข้อมูล
cell(0, 1, "วันนี้", dim, hdr)
onPct = splitMode == "เปอร์เซ็นไทล์ (สมดุล · แนะนำ)"
cell(1, 1, "A " + dayTypeName + (onPct
? (na(coilRank) ? "" : " (P" + str.tostring(coilRank, "#") + ")")
: (na(coilVal) ? "" : " (" + str.tostring(coilVal, "#.00") + ")")), ink, hdr)
cell(2, 1, showB ? "B " + dayTypeNameB +
(na(coilB) ? "" : " (" + str.tostring(coilB, "#") + ")") : "B ปิดอยู่",
showB ? ink : dim, hdr)
cell(3, 1, "ทิศ " + (dirBias > 0 ? "บน" : dirBias < 0 ? "ล่าง" : "กลาง"), dim, hdr)
cell(4, 1, isAsym ? "⚠ ไม่สมมาตร" : lvlSet, isAsym ? tblBad : dim, hdr)
// ── ตรวจก่อนดูสถิติ: จุดเข้าอยู่ห่างขอบกรอบพอไหม ──────────────────────
// ระดับที่เปอร์เซ็นไทล์ต่ำอาจอยู่ในกองศูนย์ (38% ของเซสชันไม่หลุดกรอบล่างเลย)
// และระยะที่วัดเป็นดอลลาร์อาจเล็กกว่าสเปรดไม่กี่เท่า ซึ่งไม่ใช่การเบรกเอาต์
// แต่เป็นการแตะขอบกรอบแล้วจ่ายค่าธรรมเนียม
entryOffUp = haveDay ? array.get(mUp, entryLvl - 1) * lockATR : na
entryOffDn = haveDay ? array.get(mDn, entryLvl - 1) * lockATR : na
minOff = na(entryOffUp) ? na : math.min(entryOffUp, entryOffDn)
sprdMult = spreadIn > 0 and not na(minOff) ? minOff / spreadIn : na
tooTight = not na(sprdMult) and sprdMult < minSprd
cell(0, 17, tooTight ? "⚠ จุดเข้าอยู่ในสัญญาณรบกวน" : "ระยะจุดเข้า",
tooTight ? tblBad : dim, hdr)
cell(1, 17, na(minOff) ? "-" : "$" + str.tostring(minOff, "#.00"), ink, hdr)
cell(2, 17, na(sprdMult) ? "-" : str.tostring(sprdMult, "#") + " เท่าของสเปรด",
tooTight ? tblBad : dim, hdr)
cell(3, 17, "ต้องเกิน " + str.tostring(minSprd), dim, hdr)
cell(4, 17, isAsym and entryLvl <= 2 ? "⚠ อยู่ในกองศูนย์" : "", tblBad, hdr)
if n < minCell * 2
cell(0, 3, "ข้อมูลไม่พอ", tblWarn, hdr)
cell(1, 3, str.tostring(n) + " ไม้", ink, hdr)
cell(2, 3, "ต้องการอย่างน้อย " + str.tostring(minCell * 2), dim, hdr)
cell(0, 4, "เครื่องมือนี้จะไม่สรุปจากตัวอย่างน้อย", dim, hdr)
cell(0, 5, "เลื่อนกราฟไปด้านหลังหรือใช้ TF ใหญ่ขึ้น", dim, hdr)
else
isEnd = math.floor(n * (1 - oosFrac)) - 1
// เกณฑ์สูงขึ้นตามจำนวนชุดที่ทดลอง — ราคาของการดูซ้ำ ต้องมองเห็น
// ⚠ การดูสองป้ายพร้อมกันคือการดูสองครั้ง ไม่ใช่ครั้งเดียว จึงคูณสองให้อัตโนมัติ
// ข้อนี้ผู้ใช้ลืมนับง่ายที่สุด และเป็นช่องทางที่ผลบวกปลอมเดินเข้ามา
looks = lookCnt * (showB ? 2 : 1)
penalty = math.sqrt(2 * math.log(math.max(2, looks)))
sd = 0.0
for i = 0 to n - 1
sd += math.pow(array.get(pnl, i), 2)
sd := math.sqrt(sd / n)
// ── บล็อกป้าย A และ B — วาดด้วยโค้ดชุดเดียวกัน ─────────────────────
// เดิมมีสวิตช์ให้ดูทีละป้าย ซึ่งบังคับให้ผู้ใช้จำตัวเลขข้ามการสลับ
// นั่นคือการค้นหาที่ไม่มีใครนับ · ตอนนี้ทั้งสองอยู่ในสายตาพร้อมกัน
float spA = na
float spB = na
float pA = na
float pB = na
bool okA = false
bool okB = false
for blk = 0 to (showB ? 1 : 0)
isB = blk == 1
src = isB ? pTypeB : pType
row0 = isB ? 8 : 2
// ความเบ้ของกลุ่มต้องอยู่ตรงหัวบล็อก เพราะมันเป็นสิ่งแรกที่ต้องดู
// ก่อนอ่านตัวเลขใด ๆ ข้างล่าง — กลุ่มที่เบ้มากทำให้ผล "ไม่ต่างกัน" ไร้ความหมาย
[nCo, xCo, yCo] = typeStat(src, 1, 0, n - 1)
[nNo, xNo, yNo] = typeStat(src, 0, 0, n - 1)
[nSl, xSl, ySl] = typeStat(src, -1, 0, n - 1)
biggest = math.max(nCo, math.max(nNo, nSl))
smallest = math.min(nCo, math.min(nNo, nSl))
skew = smallest > 0 ? biggest / float(smallest) : na
cell(0, row0, (isB ? "ป้าย B · ความกว้าง BB" : "ป้าย A · กรอบ Asia ÷ ATR") +
(na(skew) ? "" : " · เบ้ " + str.tostring(skew, "#.0") + " ต่อ 1"),
na(skew) or skew > 4 ? tblBad : tblAcc, hdr)
cell(1, row0, "n", dim, hdr)
cell(2, row0, "expectancy", dim, hdr)
cell(3, row0, "ชนะ", dim, hdr)
cell(4, row0, "นอกกลุ่ม", dim, hdr)
int r = row0 + 1
for t = 1 to -1
[cIS, eIS, wIS] = typeStat(src, t, 0, isEnd)
[cOOS, eOOS, wOOS] = typeStat(src, t, isEnd + 1, n - 1)
enough = cIS >= minCell
cell(0, r, typeName(t), ink, hdr)
cell(1, r, str.tostring(cIS), enough ? ink : dim, hdr)
cell(2, r, enough ? str.tostring(eIS, "#.00") : "ไม่พอ",
not enough ? dim : eIS > 0 ? tblGood : tblBad, hdr)
cell(3, r, enough ? str.tostring(wIS * 100, "#") + "%" : "-", dim, hdr)
cell(4, r, cOOS >= minCell ? str.tostring(eOOS, "#.00")
: "n=" + str.tostring(cOOS),
cOOS < minCell ? dim : eOOS > 0 ? tblGood : tblBad, hdr)
r += 1
[cC, eC, wC] = typeStat(src, 1, 0, n - 1)
[cS, eS, wS] = typeStat(src, -1, 0, n - 1)
ok = cC >= minCell and cS >= minCell
sp = ok ? eC - eS : na
nd = ok ? penalty * sd / math.sqrt(math.min(cC, cS)) : na
pv = runPlac and ok ? placeboOf(src, sp, n) : na
cell(0, r, "ต่างกัน (อัดแน่น − คลาย)", tblAcc, hdr)
cell(1, r, ok ? str.tostring(sp, "#.00") : "ข้อมูลไม่พอ", ink, hdr)
cell(2, r, "ต้องเกิน", dim, hdr)
cell(3, r, ok ? str.tostring(nd, "#.00") : "-", ink, hdr)
cell(4, r, ok ? (math.abs(sp) >= nd ? "ผ่านเกณฑ์" : "ไม่ผ่านเกณฑ์") : "-",
ok and math.abs(sp) >= nd ? tblGood : tblBad, hdr)
cell(0, r + 1, "สับป้ายมั่ว " + str.tostring(placRuns) + " รอบ", dim, hdr)
cell(1, r + 1, na(pv) ? "-" : str.tostring(pv * 100, "#") + "%", ink, hdr)
cell(2, r + 1, "ที่ทำได้ดีเท่าหรือดีกว่า", dim, hdr)
cell(3, r + 1, na(pv) ? "-"
: pv <= 0.05 ? "ผ่าน" : pv <= 0.20 ? "ก้ำกึ่ง" : "ไม่ผ่าน",
na(pv) ? dim : pv <= 0.05 ? tblGood : pv <= 0.20 ? tblWarn : tblBad, hdr)
cell(4, r + 1, "ลอง " + str.tostring(looks) + " ชุด" +
(showB ? " (สองป้าย)" : ""), looks > 20 ? tblWarn : dim, hdr)
if isB
spB := sp
pB := pv
okB := ok
else
spA := sp
pA := pv
okA := ok
// ── เทียบสองป้าย — เหตุผลทั้งหมดที่ต้องแสดงพร้อมกัน ────────────────
cmp = not showB
? "วัดป้ายเดียว — ไม่มีอะไรให้เทียบ เปิด “วัดป้าย B” เพื่อตรวจว่าผลผูกกับวิธีวัดไหม"
: not (okA and okB) ? "ยังเทียบไม่ได้ — ป้ายใดป้ายหนึ่งข้อมูลไม่พอ"
: na(pA) or na(pB) ? "ยังเทียบไม่ได้ — placebo ยังไม่ได้รัน"
: pA > 0.20 and pB > 0.20
? "ทั้งสองป้ายไม่ผ่าน — สมมติฐานตายทั้งสาย ไม่ใช่แค่วัดผิดวิธี"
: pA <= 0.05 and pB <= 0.05
? "ผ่านทั้งสองป้าย — สองวิธีวัดที่ไม่เกี่ยวกันชี้ทางเดียวกัน ควรสนใจ"
: "⚠ ผ่านป้ายเดียว — สิ่งที่วัดได้ผูกกับวิธีวัด ไม่ใช่กับตลาด อย่าเพิ่งเชื่อ"
cmpCol = not showB ? dim
: not (okA and okB) or na(pA) or na(pB) ? tblWarn
: pA <= 0.05 and pB <= 0.05 ? tblGood
: pA > 0.20 and pB > 0.20 ? tblBad : tblWarn
cell(0, 14, cmp, cmpCol, hdr)
// ── แยกทิศ — ขึ้นกับลงคือสองระบบ ────────────────────────────────
// ก่อนหน้านี้เอามารวมถังเดียว ซึ่งเฉลี่ยระบบที่ EV บวกเข้ากับระบบที่ EV ลบ
// แล้วได้ตัวเลขที่ไม่ได้อธิบายอะไรเลย
[cL, eL, wL] = sideStat(1)
[cSh, eSh, wSh] = sideStat(-1)
cell(0, 15, "แยกทิศ", tblAcc, hdr)
cell(1, 15, "ขึ้น n=" + str.tostring(cL), dim, hdr)
cell(2, 15, cL >= minCell ? str.tostring(eL, "#.00") : "ไม่พอ",
cL < minCell ? dim : eL > 0 ? tblGood : tblBad, hdr)
cell(3, 15, "ลง n=" + str.tostring(cSh), dim, hdr)
cell(4, 15, cSh >= minCell ? str.tostring(eSh, "#.00") : "ไม่พอ",
cSh < minCell ? dim : eSh > 0 ? tblGood : tblBad, hdr)
// ── มิติทิศทาง — ข้อที่ ScalperVBubble ไม่มีและเป็นสาเหตุที่มันตาย ──
[cAl, eAl] = dirStat(1)
[cAg, eAg] = dirStat(-1)
cell(0, 16, useDir ? "เข้าตรงทิศที่ล็อกไว้" : "ไม่ได้ทดสอบมิติทิศทาง",
useDir ? tblAcc : dim, hdr)
cell(1, 16, useDir ? str.tostring(cAl) + " ไม้" : "-", ink, hdr)
cell(2, 16, useDir and cAl >= minCell ? str.tostring(eAl, "#.00") : "ไม่พอ", ink, hdr)
cell(3, 16, useDir ? "สวนทิศ " + str.tostring(cAg) : "", dim, hdr)
cell(4, 16, useDir and cAg >= minCell ? str.tostring(eAg, "#.00") : "ไม่พอ", ink, hdr)
// ถ้าฝั่งขึ้นกับฝั่งลงให้คำตอบคนละทาง การสรุปรวมคือการซ่อนความจริง
sidesSplit = cL >= minCell and cSh >= minCell and
((eL > 0 and eSh < 0) or (eL < 0 and eSh > 0))
// คำตัดสินรวมยึดป้าย A เป็นหลัก เพราะเป็นสมมติฐานต้นทาง ส่วน B คือพยาน
verdict = tooTight ? "⚠ จุดเข้าอยู่ในสัญญาณรบกวน — ผลไม่มีความหมาย"
: sidesSplit ? "⚠ ขึ้นกับลงให้คำตอบคนละทาง — ต้องแยกทดสอบ ห้ามสรุปรวม"
: not okA ? "ข้อมูลไม่พอ — ยังตัดสินไม่ได้"
: not na(pA) and pA > 0.20
? "ประเภทวันเป็นของประดับ — การสับป้ายมั่วก็ทำได้พอกัน"
: spA > 0 ? "ประเภทวันมีข้อมูลจริง — วันอัดแน่นดีกว่า"
: "⚠ กลับด้านจากที่คาด — วันคลายแล้วดีกว่า ตรวจสมมติฐานใหม่"
vc = tooTight or sidesSplit ? tblBad
: not okA ? tblWarn
: not na(pA) and pA > 0.20 ? tblBad : tblGood
cell(0, 18, verdict, vc, hdr)
cell(0, 19, "เตือน", tblBad, hdr)
cell(1, 19, "นี่คือผลบนกราฟเดียว", dim, hdr)
cell(2, 19, "ไม่ใช่ walk-forward", dim, hdr)
cell(3, 19, "ห้ามใช้สด", tblBad, hdr)
cell(0, 20, "ต้องพอร์ตไปทดสอบนอกกราฟก่อนเสมอ", dim, hdr)
cell(0, 21, isCond
? "โอกาสถูกแตะที่แสดง คูณด้วย 0.66 แล้ว (สัดส่วนเซสชันที่หลุดกรอบ)"
: isAsym ? "⚠ ชุดไม่สมมาตรพิสูจน์แล้วว่าเป็น artifact ของตลาดกระทิง"
: "ที่มาของระดับ: xaulearn.com/research/avr-asymmetry/", dim, hdr)
// ═══════════════════════════════════════════════════════════════════════════
// ⑦ ตรวจก่อนยิง — ตารางแยก
// ═══════════════════════════════════════════════════════════════════════════
// แยกตารางโดยตั้งใจ · ส่วนบนของไฟล์ตอบว่า "ป้ายนี้มีข้อมูลไหม" ซึ่งเป็นคำถาม
// ตอนทำวิจัย · ส่วนนี้ตอบว่า "ออเดอร์นี้ประกอบถูกไหม" ซึ่งเป็นคำถามตอนจะยิง
// ปนกันในตารางเดียวเมื่อไร คนอ่านจะเอาคำตอบของคำถามหนึ่งไปใช้กับอีกคำถาม
var table P = table.new(posOf(ptcPos), 4, 20, border_width = 1,
frame_color = tblFrame, frame_width = 1)
pcell(int c, int r, string txt, color tc) =>
table.cell(P, c, r, txt, text_color = tc, bgcolor = tblBg, text_size = tblSize,
text_halign = text.align_left)
if ptcOn and barstate.islast
table.clear(P, 0, 0, 3, 19)
ok = tblGood
wn = tblWarn
bd = tblBad
ink = tblInk
dim = tblDim
pcell(0, 0, "ตรวจก่อนยิง", tblAcc)
pcell(1, 0, ptcSideS + " " + str.tostring(ptcLots, "#.###") + " ล็อต", ink)
pcell(2, 0, "", dim)
pcell(3, 0, "", dim)
// ── ① ราคาสามจุด และการตรวจข้างที่ผิดพลาดง่ายที่สุด ───────────────
pcell(0, 1, "จุดเข้า", dim)
pcell(1, 1, str.tostring(ptcE, format.mintick), ink)
pcell(2, 1, ptcNow ? "ราคาปัจจุบัน" : "กรอกเอง", dim)
pcell(3, 1, "", dim)
pcell(0, 2, "จุดหยุด", dim)
pcell(1, 2, str.tostring(ptcS, format.mintick), ptcSLbad ? bd : ink)
pcell(2, 2, ptcSLbad ? "⛔ อยู่ผิดข้างของจุดเข้า"
: "ห่าง " + str.tostring(ptcSDist, "#.00") + "$", ptcSLbad ? bd : dim)
pcell(3, 2, ptcSLbad ? "ห้าม" : "ผ่าน", ptcSLbad ? bd : ok)
pcell(0, 3, "เป้าหมาย", dim)
pcell(1, 3, str.tostring(ptcT, format.mintick), ptcTPbad ? bd : ink)
pcell(2, 3, ptcTPbad ? "⛔ อยู่ผิดข้างของจุดเข้า"
: "ห่าง " + str.tostring(ptcTDist, "#.00") + "$", ptcTPbad ? bd : dim)
pcell(3, 3, ptcTPbad ? "ห้าม" : "ผ่าน", ptcTPbad ? bd : ok)
// ── ② ขนาดไม้และเงินที่เสี่ยงจริง ────────────────────────────────
pcell(0, 4, "ขนาดไม้", dim)
pcell(1, 4, str.tostring(ptcLots, "#.###") + " ล็อต", ptcLotBad ? bd : ink)
pcell(2, 4, ptcLotBad
? "⛔ ต่ำกว่าล็อตต่ำสุด " + str.tostring(ptcMinLot, "#.###") +
" — จุดหยุดกว้างเกินไปสำหรับเงินก้อนนี้"
: "จากเสี่ยง " + str.tostring(ptcRisk, "#.##") + "% = " +
str.tostring(ptcRiskMoney, "#.00") + "$", ptcLotBad ? bd : dim)
pcell(3, 4, ptcLotBad ? "ห้าม" : "ผ่าน", ptcLotBad ? bd : ok)
pcell(0, 5, "เสี่ยงจริงหลังปัดล็อต", dim)
pcell(1, 5, str.tostring(ptcRiskReal, "#.00") + "$", ink)
pcell(2, 5, "ตั้งใจไว้ " + str.tostring(ptcRiskMoney, "#.00") + "$ · ปัดล็อตลงเสมอ", dim)
pcell(3, 5, "", dim)
pcell(0, 6, "ต้นทุนไปกลับ", dim)
pcell(1, 6, str.tostring(ptcCost, "#.00") + "$", ink)
pcell(2, 6, "สเปรด " + str.tostring(spreadIn, "#.00") + "$/ออนซ์" +
(ptcCommis > 0 ? " + คอมมิชชัน" : "") + " · ใช้ค่าเดียวกับส่วนวิจัย", dim)
pcell(3, 6, "", dim)
// ── ③ R:R ที่จริง ไม่ใช่บนกระดาษ ─────────────────────────────────
pcell(0, 7, "R:R", dim)
pcell(1, 7, na(ptcRRraw) ? "-" : str.tostring(ptcRRraw, "#.00") + " บนกระดาษ", dim)
pcell(2, 7, na(ptcRRnet) ? "-"
: str.tostring(ptcRRnet, "#.00") + " หลังหักต้นทุนจริง",
ptcRRbad ? bd : ptcRRwarn ? wn : ok)
pcell(3, 7, ptcRRbad ? "ห้าม" : ptcRRwarn ? "เตือน" : "ผ่าน",
ptcRRbad ? bd : ptcRRwarn ? wn : ok)
// ── ④ จุดหยุดอยู่ในสัญญาณรบกวนไหม — ด่านเดียวกับส่วนวิจัย ─────────
pcell(0, 8, "จุดหยุด ÷ สเปรด", dim)
pcell(1, 8, na(ptcSprdX) ? "-" : str.tostring(ptcSprdX, "#") + " เท่า",
ptcTight ? bd : ink)
pcell(2, 8, ptcTight
? "⛔ จุดหยุดอยู่ในสัญญาณรบกวน — จ่ายค่าธรรมเนียมเพื่อแตะขอบ ไม่ใช่เทรดการเคลื่อนไหว"
: "ต้องเกิน " + str.tostring(minSprd) + " เท่า", ptcTight ? bd : dim)
pcell(3, 8, ptcTight ? "ห้าม" : "ผ่าน", ptcTight ? bd : ok)
pcell(0, 9, "จุดหยุด ÷ ATR", dim)
pcell(1, 9, na(ptcAtrX) ? "-" : str.tostring(ptcAtrX, "#.00") + " เท่า",
ptcNarrow or ptcBroad ? wn : ink)
pcell(2, 9, ptcNarrow ? "แคบกว่าความผันผวนปกติ — โอกาสโดนสะกิดสูง"
: ptcBroad ? "กว้างกว่าความผันผวนปกติมาก — ขนาดไม้จะเล็กจนต้นทุนกินสัดส่วนใหญ่"
: "อยู่ในช่วงปกติ", ptcNarrow or ptcBroad ? wn : dim)
pcell(3, 9, ptcNarrow or ptcBroad ? "เตือน" : "ผ่าน",
ptcNarrow or ptcBroad ? wn : ok)
// ── ⑤ ระดับที่คนอื่นก็เห็น ───────────────────────────────────────
pcell(0, 10, "จุดหยุดชิดขอบกรอบ Asia", dim)
pcell(1, 10, not haveDay ? "-" : ptcSLnear ? "ใช่" : "ไม่", ptcSLnear ? wn : ink)
pcell(2, 10, not haveDay ? "ยังไม่มีกรอบของวันนี้"
: ptcSLnear ? "ซุกใต้ขอบที่คนอื่นก็เห็น — เป็นจุดที่ถูกกวาดบ่อย"
: "กรอบ " + str.tostring(lockLo, format.mintick) + " – " +
str.tostring(lockHi, format.mintick), ptcSLnear ? wn : dim)
pcell(3, 10, not haveDay ? "-" : ptcSLnear ? "เตือน" : "ผ่าน", ptcSLnear ? wn : ok)
pcell(0, 11, "ระดับขวางก่อนถึงเป้า", dim)
pcell(1, 11, not haveDay ? "-" : ptcTPblock ? "มี" : "ไม่มี", ptcTPblock ? wn : ink)
pcell(2, 11, ptcTPblock
? "ขอบกรอบ Asia อยู่ระหว่างจุดเข้ากับเป้า — ราคาอาจหยุดตรงนั้นก่อน"
: "ไม่มีขอบกรอบขวาง", ptcTPblock ? wn : dim)
pcell(3, 11, not haveDay ? "-" : ptcTPblock ? "เตือน" : "ผ่าน", ptcTPblock ? wn : ok)
// ── ⑥ สวนเทรนด์ — ข้อที่แพงที่สุดในบันทึกทั้งหมด ──────────────────
pcell(0, 12, "เทียบเทรนด์ " + ptcTrTF, dim)
pcell(1, 12, not ptcTrend ? "ปิดอยู่" : na(ptcTrEma) ? "-"
: close > ptcTrEma ? "ราคาเหนือ EMA" : "ราคาใต้ EMA", ptcFade ? wn : ink)
pcell(2, 12, ptcFade ? "⚑ กำลังสวนเทรนด์ — ความผิดพลาดที่แพงที่สุดในบันทึกของเรา"
: "ไปทางเดียวกับเทรนด์", ptcFade ? wn : dim)
pcell(3, 12, not ptcTrend ? "-" : ptcFade ? "เตือน" : "ผ่าน", ptcFade ? wn : ok)
pcell(0, 13, ptcFade ? "เคยฟาดที่ CONF 90-95 แล้วทองวิ่ง +129$ สวนหน้า" : "",
ptcFade ? wn : dim)
pcell(1, 13, "", dim)
pcell(2, 13, "", dim)
pcell(3, 13, "", dim)
// ── ⑦ เพดานประจำวัน — กันการล้างแค้น ────────────────────────────
pcell(0, 14, "งบขาดทุนวันนี้", dim)
pcell(1, 14, str.tostring(math.max(0, ptcDayCap - ptcLossToday), "#.00") + "$ เหลือ",
ptcOverDay ? bd : ink)
pcell(2, 14, ptcOverDay ? "⛔ ไม้นี้จะทะลุเพดานรายวัน"
: "เพดาน " + str.tostring(ptcDayCap, "#.00") + "$ · เสียแล้ว " +
str.tostring(ptcLossToday, "#.00") + "$", ptcOverDay ? bd : dim)
pcell(3, 14, ptcOverDay ? "ห้าม" : "ผ่าน", ptcOverDay ? bd : ok)
pcell(0, 15, "จำนวนไม้วันนี้", dim)
pcell(1, 15, str.tostring(ptcTradesDone) + " / " + str.tostring(ptcMaxTrades),
ptcOverCnt ? bd : ink)
pcell(2, 15, ptcOverCnt ? "⛔ ครบเพดานแล้ว — พรุ่งนี้ค่อยว่ากัน"
: "ยังเทรดได้อีก " + str.tostring(ptcMaxTrades - ptcTradesDone) + " ไม้",
ptcOverCnt ? bd : dim)
pcell(3, 15, ptcOverCnt ? "ห้าม" : "ผ่าน", ptcOverCnt ? bd : ok)
// ── คำตัดสิน — มีสองแบบเท่านั้น และจะไม่มีวันมีแบบที่สาม ──────────
pcell(0, 16, ptcBlocked ? "⛔ หยุด — เจอข้อห้าม" : "ตรวจแล้วไม่พบข้อห้าม",
ptcBlocked ? bd : ok)
pcell(1, 16, "", dim)
pcell(2, 16, "", dim)
pcell(3, 16, "", dim)
// แถวนี้เขียนตายตัว และเป็นเหตุผลทั้งหมดที่โมดูลนี้มีหน้าตาแบบนี้
pcell(0, 17, "ไม่มีช่องใดในตารางนี้บอกว่าเทรดนี้จะกำไร", tblAcc)
pcell(1, 17, "", dim)
pcell(2, 17, "", dim)
pcell(3, 17, "", dim)
pcell(0, 18, "มันตรวจได้แค่ว่าออเดอร์ประกอบถูกและรอดได้ · การตัดสินใจยังเป็นของคุณ", dim)
pcell(1, 18, "", dim)
pcell(2, 18, "", dim)
pcell(3, 18, "", dim)