Files
Kunthawat Greethong b26c8199a5 Update skills: add website-creator, mql-developer, ecommerce-astro
Changes:
- Add FAL_KEY and GEMINI_API_KEY to .env.example
- Update picture-it to use ~/.config/opencode/.env (unified creds)
- Remove shodh-memory skill (no longer used)
- Remove alphaear-* skills (deprecated)
- Remove thai-frontend-dev skill (replaced by website-creator)
- Remove theme-factory skill
- Add mql-developer skill (MQL5 trading)
- Add ecommerce-astro skill (Astro e-commerce)
- Add website-creator skill (Next.js + Payload CMS)
- Update install script for new skills
2026-04-16 17:40:27 +07:00

130 lines
4.9 KiB
Markdown

---
name: mql5-dashboard-survive-tf-change
description: >
ทำให้ Dashboard edit fields และ runtime values อยู่รอดเมื่อ user เปลี่ยน
timeframe (OnDeinit + OnInit re-run) โดยใช้ JSON file persistence.
รวมถึงวิธีแก้ข้อจำกัดของ MQL5 ที่ห้ามมี input declarations ใน .mqh files.
---
# MQL5 Dashboard: ทำให้ค่าบน Chart Objects อยู่รอดเมื่อเปลี่ยน Timeframe
## ปัญหา
เมื่อ user เปลี่ยน timeframe บน MetaTrader, terminal จะ:
1. เรียก `OnDeinit(reason)`
2. เรียก `OnInit()` ใหม่
ถ้า `OnInit()` อ่านค่าจาก `input` parameters หรือ global variables ที่ไม่ได้ persist, ค่าที่ user พิมพ์ผ่าน dashboard edit fields จะ **หายหมด**เมื่อเปลี่ยน TF
## วิธีแก้: Persistent Storage ด้วย JSON File
### 1. บันทึกก่อน OnDeinit
```mql5
string MakeRuntimeFilePath() {
string path = TerminalInfoString(TERMINAL_DATA_PATH);
string safeSymbol = StringReplace(g_symbol, "/", "_");
return path + "\\MQL5\\Files\\SmartEntry_" + safeSymbol + "_runtime.json";
}
bool SaveRuntimeValues() {
string path = MakeRuntimeFilePath();
int handle = FileOpen(path, FILE_WRITE | FILE_ANSI | FILE_COMMON);
if(handle == INVALID_HANDLE) return false;
string json = StringFormat(
"{\"upperZone\":%.5f,\"lowerZone\":%.5f,\"sl\":%.5f,\"tp\":%.5f,\"beOffset\":%d,\"isBuySetup\":%s}",
g_upperZone, g_lowerZone, g_SL, g_TP, (int)g_beOffset, g_isBuySetup ? "true" : "false");
FileWriteString(handle, json + "\n");
FileClose(handle);
return true;
}
void OnDeinit(const int reason) {
SaveRuntimeValues(); // ← บันทึกก่อน cleanup ทุกครั้ง
// ... release handles, destroy panel
}
```
### 2. โหลดกลับตอน OnInit
```mql5
bool LoadRuntimeValues() {
string path = MakeRuntimeFilePath();
int handle = FileOpen(path, FILE_READ | FILE_ANSI | FILE_COMMON);
if(handle == INVALID_HANDLE) return false; // ไม่มีไฟล์ = first run
string json = FileReadString(handle);
FileClose(handle);
g_upperZone = StringToDouble(ParseJsonField(json, "upperZone"));
g_lowerZone = StringToDouble(ParseJsonField(json, "lowerZone"));
// ...
return true;
}
int OnInit() {
// Load ก่อน ถ้าไม่มีไฟล์ค่อย fallback ไป input defaults
if(!LoadRuntimeValues()) {
g_upperZone = InputUpperZone;
// ...
}
// ...
}
```
### 3. Parse JSON field (MQL5 ไม่มี native JSON)
```mql5
string ParseJsonField(string json, string fieldName) {
string search = "\"" + fieldName + "\":";
int pos = StringFind(json, search);
if(pos < 0) return "0";
int start = pos + StringLen(search);
while(start < StringLen(json) && json[start] == ' ') start++;
if(json[start] == '"') {
int end = start + 1;
while(end < StringLen(json) && json[end] != '"') end++;
return StringSubstr(json, start + 1, end - start - 1);
} else {
int end = start;
while(end < StringLen(json) && json[end] != ',' && json[end] != '}' && json[end] != '\n') end++;
return StringSubstr(json, start, end - start);
}
}
```
## ข้อควรระวัง: `input` ใน .mqh File
**MQL5 ไม่อนุญาต `input` declarations ใน .mqh include files** — compile error
### ผิด (compile error):
```mql5
// Dashboard.mqh
input double InputUpperZone = 0; // ← ERROR: input not allowed in .mqh
```
### ถูก:
```mql5
// Dashboard.mqh — ไม่มี input declarations ที่นี่
// สร้าง method ให้ EA เรียกหลัง Init
void CDashboard::SetInitialEditTexts(double upperZone, double lowerZone,
double sl, double tp, int beOffset) {
if(upperZone > 0)
ObjectSetString(0, m_prefix + "EREdit", OBJPROP_TEXT, DoubleToString(upperZone, _Digits));
// ...
}
// SmartEntryEA.mq5
g_dashboard.Init(g_symbol, true);
g_dashboard.SetInitialEditTexts(g_upperZone, g_lowerZone, g_SL, g_TP, (int)g_beOffset);
```
## ข้อควรระวัง: Chart Objects หายเมื่อ RecreatePanel
`CreatePanel()` สร้าง objects ใหม่ทุกครั้งที่ `ObjectFind() < 0` check อยู่แล้ว — safe สำหรับ TF change เพราะ OnDeinit จะ `DestroyPanel()` ก่อน
## Symbols
- `g_symbol` — ต้องกำหนดก่อนเรียก `MakeRuntimeFilePath()`
- File path ใช้ `FILE_COMMON` flag เพื่อเขียนได้ทุก profile