เหตุใดเราจึงต้องมีสคริปต์ของ Google Apps
ในการแลกเปลี่ยนข้อมูลการค้าระหว่างเทอร์มินัล MetaTrader ที่แยกจากกัน จำเป็นต้องมีเซิร์ฟเวอร์รีเลย์แบบธรรมดา Google Apps Script ทำหน้าที่เป็นตัวกลางฟรีที่โอนกิจกรรมการค้าจากบัญชี Grasp ไปยังบัญชี Slave ช่วยให้มั่นใจได้ถึงการจัดกิจกรรมที่เชื่อถือได้ แม้ว่าอินเทอร์เน็ตจะหยุดชะงักหรือรีสตาร์ทเทอร์มินัล และไม่ต้องใช้ VPS หรือเซิร์ฟเวอร์เฉพาะ
วิธีสร้างและปรับใช้ Google Apps Script
-
ไปที่ https://script.google.com และ คลิก เริ่มเขียนสคริปต์

-
คลิก โครงการใหม่

-
ลบรหัสเริ่มต้นและ วางสคริปต์ที่ให้ไว้ด้านล่าง ขั้นตอนการสอน

-
กด Ctrl + ส เพื่อบันทึกโครงการ (เมนูด้านบนจะเปิดใช้งาน)

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

-
ในหน้าต่างที่เปิดอยู่ คลิกเลือกประเภท (⚙️)แล้วเลือก เว็บแอป

-
ฉันไม่ใช่ คำอธิบาย สนาม, ป้อน เวอร์ชัน 1 (ข้อความใดก็ได้) ชุด ใครเข้าได้บ้าง ถึง ใครก็ได้ และจากไป ดำเนินการเป็น ไม่เปลี่ยนแปลง

-
คลิก ปรับใช้ – ของคุณ URL สคริปต์ของแอป จะถูกสร้างขึ้น คัดลอกและวาง URL นี้ลงในการตั้งค่าอินพุตของ EA

const API_KEY = 'I_AM_API_KEY'; const MAX_PRUNE = 200; const CONSUMER_TTL_MS = 6 * 60 * 60 * 1000; const MAX_CONSUMERS_PER_CHANNEL = 50; perform doPost(e) { const lock = LockService.getScriptLock(); let locked = false; strive { lock.waitLock(10000); locked = true; if (!e || !e.postData || !e.parameter) return _resp({ okay:false, error:'no information' }); const key = (e.parameter.key || '').toString(); if (key !== API_KEY) return _resp({ okay:false, error:'forbidden' }); const channel = (e.parameter.channel || 'default').toString(); const shopper = (e.parameter.shopper || '').toString(); const c = shopper || 'single'; const retailer = PropertiesService.getScriptProperties(); let uncooked = e.postData.contents || '{}'; uncooked = uncooked.substitute(/(u0000-u001F)+$/g, ''); let physique; strive { physique = JSON.parse(uncooked); } catch (parseErr) { return _resp({ okay:false, error:'dangerous json', particulars:String(parseErr) }); } _touchConsumerFast(retailer, channel, c); if (physique && physique.motion === 'ack') { const lastId = Quantity(physique.last_id || 0); if (!lastId) return _resp({ okay:false, error:'dangerous ack' }); retailer.setProperty(_ackKey(channel, c), String(lastId)); _pruneByMinAckFast(retailer, channel); return _resp({ okay:true, ack:lastId }); } const nextId = _nextSeq(retailer, channel); physique.id = nextId; physique.server_time_ms = Date.now(); retailer.setProperty(_evKey(channel, nextId), JSON.stringify(physique)); const minKey = _minKey(channel); const curMin = Quantity(retailer.getProperty(minKey) || '0'); if (!curMin) retailer.setProperty(minKey, String(nextId)); return _resp({ okay:true, last_id: nextId }); } catch (err) { return _resp({ okay:false, error:'exception', message:String(err), stack:(err && err.stack) ? String(err.stack) : '' }); } lastly { if (locked) { strive { lock.releaseLock(); } catch(_) {} } } } perform doGet(e) { const lock = LockService.getScriptLock(); let locked = false; strive { lock.waitLock(10000); locked = true; if (!e || !e.parameter) return _resp({ okay:false, error:'no params' }); const key = (e.parameter.key || '').toString(); if (key !== API_KEY) return _resp({ okay:false, error:'forbidden' }); const channel = (e.parameter.channel || 'default').toString(); const shopper = (e.parameter.shopper || '').toString(); const c = shopper || 'single'; const restrict = Math.max(1, Math.min(100, Quantity(e.parameter.restrict || 20))); const retailer = PropertiesService.getScriptProperties(); _touchConsumerFast(retailer, channel, c); const minId = Quantity(retailer.getProperty(_minKey(channel)) || '0'); const seq = Quantity(retailer.getProperty(_seqKey(channel)) || '0'); const ackKey = _ackKey(channel, c); let ack = Quantity(retailer.getProperty(ackKey) || '0'); if (minId > 0) { const floorAck = Math.max(0, minId - 1); if (ack < floorAck) { ack = floorAck; retailer.setProperty(ackKey, String(ack)); } } const mode = (e.parameter.mode || '').toString(); if (mode === 'well being' || mode === 'debug') { const shoppers = _listActiveConsumersFast(retailer, channel); const minAck = _minAckFast(retailer, channel, shoppers); const out = { okay:true, channel, shopper:c, ack, seq, min_id:minId, active_consumers:shoppers, min_ack:minAck }; if (mode === 'debug') { const seen = {}; for (const cc of shoppers) '0'); out.seen = seen; } return _resp(out); } const occasions = (); let missing_id = 0; for (let id = ack + 1; id <= seq && occasions.size < restrict; id++) { const evStr = retailer.getProperty(_evKey(channel, id)); if (!evStr) { missing_id = id; break; } strive { occasions.push(JSON.parse(evStr)); } catch (parseErr) { missing_id = id; break; } } if (missing_id && minId > 0 && missing_id < minId) { const newAck = Math.max(0, minId - 1); retailer.setProperty(ackKey, String(newAck)); const events2 = (); let missing2 = 0; for (let id = newAck + 1; id <= seq && events2.size < restrict; id++) { const evStr = retailer.getProperty(_evKey(channel, id)); if (!evStr) { missing2 = id; break; } strive { events2.push(JSON.parse(evStr)); } catch (_) { missing2 = id; break; } } if (!missing2) { return _resp({ okay:true, ack: newAck, seq: seq, occasions: events2 }); } return _resp({ okay:false, error:'gap_detected', ack: newAck, seq: seq, missing_id: missing2 }); } if (missing_id) { return _resp({ okay:false, error:'gap_detected', ack: ack, seq: seq, missing_id: missing_id }); } return _resp({ okay:true, ack: ack, seq: seq, occasions: occasions }); } catch (err) { return _resp({ okay:false, error:'exception', message:String(err), stack:(err && err.stack) ? String(err.stack) : '' }); } lastly { if (locked) { strive { lock.releaseLock(); } catch(_) {} } } } perform _nextSeq(retailer, channel) '0') + 1; retailer.setProperty(okay, String(subsequent)); return subsequent; perform _touchConsumerFast(retailer, channel, shopper) { const now = Date.now(); retailer.setProperty(_seenKey(channel, shopper), String(now)); const listKey = _consumersKey(channel); let arr = (); strive '()'); catch(_) { arr = (); } if (arr.indexOf(shopper) < 0) { arr.push(shopper); if (arr.size > MAX_CONSUMERS_PER_CHANNEL) arr = arr.slice(arr.size - MAX_CONSUMERS_PER_CHANNEL); retailer.setProperty(listKey, JSON.stringify(arr)); } const ackKey = _ackKey(channel, shopper); const ackStr = retailer.getProperty(ackKey); if (ackStr === null || ackStr === undefined || ackStr === '') '0'); retailer.setProperty(ackKey, String(Math.max(0, seq))); return; const minId = Quantity(retailer.getProperty(_minKey(channel)) || '0'); const ack = Quantity(ackStr || '0'); if (minId > 0) { const floorAck = Math.max(0, minId - 1); if (ack < floorAck) retailer.setProperty(ackKey, String(floorAck)); } } perform _listActiveConsumersFast(retailer, channel) { const now = Date.now(); const listKey = _consumersKey(channel); let arr = (); strive '()'); catch(_) { arr = (); } const energetic = (); for (const c of arr) if (energetic.size === 0) energetic.push('single'); return energetic; } perform _minAckFast(retailer, channel, shoppers) { let min = null; for (const c of shoppers) return min === null ? 0 : min; } perform _pruneByMinAckFast(retailer, channel) { const shoppers = _listActiveConsumersFast(retailer, channel); const minAck = _minAckFast(retailer, channel, shoppers); if (minAck <= 0) return; _pruneAckedUpTo(retailer, channel, minAck); } perform _pruneAckedUpTo(retailer, channel, ackId) { const minKey = _minKey(channel); let minId = Quantity(retailer.getProperty(minKey) || '0'); if (!minId) return; let eliminated = 0; whereas (minId && minId <= ackId && eliminated < MAX_PRUNE) { retailer.deleteProperty(_evKey(channel, minId)); minId++; eliminated++; } const seq = Quantity(retailer.getProperty(_seqKey(channel)) || '0'); if (minId > seq) { retailer.deleteProperty(minKey); } else { retailer.setProperty(minKey, String(minId)); } } perform _seqKey(channel) { return channel + '__seq'; } perform _minKey(channel) { return channel + '__min'; } perform _ackKey(channel, shopper) { return channel + '__ack__' + shopper; } perform _evKey(channel, id) { return channel + '__ev__' + id; } perform _seenKey(channel, shopper) { return channel + '__seen__' + shopper; } perform _consumersKey(channel) { return channel + '__consumers'; } perform _resp(obj) { return ContentService .createTextOutput(JSON.stringify(obj)) .setMimeType(ContentService.MimeType.JSON); }
