diff --git a/mt5_app/README.md b/mt5_app/README.md index c92ffeb..e218b63 100644 --- a/mt5_app/README.md +++ b/mt5_app/README.md @@ -342,3 +342,9 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\mt5_app\install-autost - ไฟล์ `data/supervisor.stop` เป็นเครื่องหมายหยุดที่คงอยู่ข้ามรอบ Task จึงไม่ถูกล้างเองทุกครั้งที่ตัวดูแลเริ่ม ติดตั้ง/เปิดผ่าน `install-autostart.ps1` จะล้างเครื่องหมายโดยตั้งใจ ส่วน `-Remove` ปิด trigger ก่อนหยุดและถอด Task ยังไม่ทราบว่าโปรแกรมใดเปิด/ล็อกไฟล์ในจังหวะนั้น การแก้ไม่เปลี่ยนสิทธิ์ไฟล์หรือใช้ Administrator และไม่ล้างฐานข้อมูลบัญชี/คำสั่งเดิม + +### ตรวจความครบของข้อมูล SET หลังเปิดแอปกลับ + +ระหว่างกู้แอปวันที่ 29 ต.ค. พบว่าเว็บและ MT5 ยังเชื่อมต่อได้ แต่สัญญาณคำนวณต่อไม่ได้เพราะ SET ใน SQLite ถึง 27 ต.ค. ขณะที่หุ้นถึง 28 ต.ค. แม้ MT5 มี SET วันที่ 28 แล้ว สาเหตุคือแอปจำวันที่ร้องขอเป็นวันที่โหลดสำเร็จ แล้วข้าม SET ในรอบถัดไป + +แก้ให้ตรวจวันที่แท่ง SET ที่เก็บจริงก่อนใช้ cache หากยังไม่ถึงวันเป้าหมายจะดึง SET ใหม่และเก็บ latest_day ถ้าแหล่งข้อมูลยังไม่ครบจะลองใหม่ตามรอบข้อมูลเดิม หากสัญญาณเก่ากว่าข้อมูลที่ต้องใช้จะคำนวณใหม่ และ dashboard จะขึ้นรอข้อมูลแทนแผนเก่า โดยไม่เปลี่ยนกฎ Donchian/EMA หรือเติมดัชนีที่ไม่มีข้อมูลจริงเอง diff --git a/mt5_app/research.py b/mt5_app/research.py index 5f84e47..c64f175 100644 --- a/mt5_app/research.py +++ b/mt5_app/research.py @@ -43,7 +43,8 @@ def plain(value): def download_history(broker, store, settings, progress, *, force=False): through = settings.closed_through() - if store.get('history_checked_through') == through and not force: + benchmark_ready = store.last_bar_day('SET', through) == through + if store.get('history_checked_through') == through and benchmark_ready and not force: progress('history', 'ใช้ประวัติ MT5 ที่เก็บไว้แล้ว', 1, 1) return store.get('history_download', {}) symbols = [s.name for s in broker.symbols() @@ -57,8 +58,11 @@ def download_history(broker, store, settings, progress, *, force=False): progress('history', f'อ่าน D1 {symbol} · {number}/{len(symbols)}', number, len(symbols)) try: symbol_state = store.get('history_symbol:' + symbol, {}) - if (symbol_state.get('through') == through - or (symbol in previous and previous_record.get('through') == through)) and not force: + cached = (symbol_state.get('through') == through + or (symbol in previous and previous_record.get('through') == through)) + # A successful download request is not proof the benchmark reached + # the requested session. Retry SET until the actual D1 bar arrives. + if cached and not force and (symbol != 'SET' or benchmark_ready): successful.append(symbol) if symbol in previous_record.get('zero_real_volume', []): zero_volume.append(symbol) @@ -69,8 +73,8 @@ def download_history(broker, store, settings, progress, *, force=False): rates = broker.history(symbol, count) rows = [] for r in rates: - # D1 timestamps label the exchange session. MT5 stores them as - # UTC; do not use the workstation timezone to shift the date. + # D1 calendar dates label broker sessions; keep that raw date. + # Intraday quote/server-clock conversion must not shift D1 labels. day = datetime.fromtimestamp(int(r['time']), timezone.utc).date().isoformat() prices = [float(r[c]) for c in ('open', 'high', 'low', 'close')] if day < settings.warmup_start or day > through: @@ -83,7 +87,11 @@ def download_history(broker, store, settings, progress, *, force=False): if symbol != 'SET' and all(r[6] <= 0 for r in rows): zero_volume.append(symbol) store.save_bars(rows) - store.put('history_symbol:' + symbol, {'through': through, 'rows': len(rows), 'checked_at': now_iso()}) + latest_day = max(r[1] for r in rows) + store.put('history_symbol:' + symbol, {'through': through, 'latest_day': latest_day, + 'rows': len(rows), 'checked_at': now_iso()}) + if symbol == 'SET' and latest_day != through: + raise ValueError(f'SET D1 ล่าสุด {latest_day}; ต้องถึง {through} จะลองดึงใหม่ในรอบถัดไป') successful.append(symbol) except Exception as exc: failures.append({'symbol': symbol, 'error': str(exc)}) diff --git a/mt5_app/service.py b/mt5_app/service.py index 1c258c0..51f2cc1 100644 --- a/mt5_app/service.py +++ b/mt5_app/service.py @@ -247,16 +247,23 @@ class Desk: last_attempt = previous.get('checked_at') retry_due = (not last_attempt or time.time() - datetime.fromisoformat(last_attempt).timestamp() >= 600) needs_new_day = previous.get('through') != self.settings.closed_through() - incomplete = self.store.get('history_checked_through') != self.settings.closed_through() - if research or not model or needs_new_day or (incomplete and retry_due): + incomplete = (self.store.get('history_checked_through') != self.settings.closed_through() + or self.store.last_bar_day('SET', self.settings.closed_through()) + != self.settings.closed_through()) + model_due = model.get('signal_as_of') != self.settings.closed_through() + if research or not model or needs_new_day or ((incomplete or model_due) and retry_due): before = self.store.bar_summary() download_history(self.broker, self.store, self.settings, self.progress) - if research or not model or needs_new_day or self.store.bar_summary() != before: + if research or not model or needs_new_day or model_due or self.store.bar_summary() != before: model = run_research(self.store, self.settings, self.progress) snapshot = self.broker.snapshot() self.progress('quotes', 'อ่านราคาเสนอซื้อขายของหุ้นในแผน') self.update_quotes(snapshot, model) - self.store.put('job', {'running': False, 'stage': 'complete', 'message': 'พร้อมใช้งาน', 'updated_at': now_iso()}) + pending = model.get('signal_as_of') != self.settings.closed_through() + message = (f'พอร์ตอัปเดตแล้ว แต่สัญญาณถึง {model.get("signal_as_of", "ยังไม่มี")} ' + f'รอข้อมูล/คำนวณถึง {self.settings.closed_through()}' if pending else 'พร้อมใช้งาน') + self.store.put('job', {'running': False, 'stage': 'waiting_data' if pending else 'complete', + 'message': message, 'updated_at': now_iso()}) except Exception as exc: self.log.exception('refresh_failed') self.store.put('connection_error', {'message': str(exc), 'at': now_iso()}) @@ -274,6 +281,7 @@ class Desk: job = self.store.get('job', {}) plan = make_plan(snap, model, self.store.skipped(day), self.settings) if (not model.get('data_quality', {}).get('calendar_source') + or model.get('signal_as_of') != self.settings.closed_through() or (job.get('running') and job.get('stage') in ('history', 'archive', 'signals', 'comparison'))): for row in plan['rows']: row.update(action='WAIT', quantity=0, reason='รอการตรวจปฏิทินข้อมูล MT5 ให้ครบ')