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
This commit is contained in:
129
skills/mql-developer/mql5-dashboard-survive-tf-change/SKILL.md
Normal file
129
skills/mql-developer/mql5-dashboard-survive-tf-change/SKILL.md
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user