├── .gitignore ├── EA_Deploy ├── master.ex4 ├── ea_mapping.json ├── server.py ├── multi_config.ini ├── server (1).py ├── EAdeployer.py ├── master.mq4 └── socket-library-mt4-mt5.mqh ├── app ├── receiver │ ├── package.json │ └── zeromq.js ├── utils │ ├── mt4-bridge.js │ ├── mql.js │ ├── mt4-price-fetcher.js │ ├── mt4-dll.js │ └── verifyToken.js ├── routes │ ├── auth.routes.js │ ├── history.routes.js │ ├── trading.routes.js │ └── configuration.routes.js ├── config │ └── db.config.js ├── models │ ├── trading.stopout.model.js │ ├── index.js │ ├── trading.externalSignal.model.js │ ├── auth.model.js │ ├── trading.tradingSignal.model.js │ ├── configuration.master.model.js │ ├── history.transaction.model.js │ ├── configuration.subscriber.model.js │ ├── configuration.portfolioStrategy.model.js │ └── configuration.strategy.model.js ├── profile.js └── controllers │ ├── auth.controller.js │ ├── history.controller.js │ ├── trading.controller.js │ └── configuration.controller.js ├── .env.example ├── aliases.js ├── LICENSE ├── package.json ├── server.js ├── yarn-error.log ├── webpack.config.js ├── README.md └── socket_server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /EA_Deploy/master.ex4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marksantiago290/API_trading/HEAD/EA_Deploy/master.ex4 -------------------------------------------------------------------------------- /app/receiver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "zeromq": "^6.0.0-beta.20" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /EA_Deploy/ea_mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "MasterEA1.ex4": ["1_MT4", "2_MT4"], 3 | "MasterEA2.ex5": ["1_MT5", "2_MT5", "3_MT5"], 4 | "SlaveEA1.ex4": ["4_MT4", "5_MT4"], 5 | "SlaveEA2.ex5": ["4_MT5", "5_MT5"] 6 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ATLAS_URL=mongodb+srv://username:password@copytrading.wvukzrr.mongodb.net/?retryWrites=true&w=majority&appName=copytrading 2 | 3 | 4 | DATABASE_URL=mongodb://0.0.0.0:27017/crystal_db 5 | 6 | JWT_SECRET_KEY=copytradingmaster -------------------------------------------------------------------------------- /app/utils/mt4-bridge.js: -------------------------------------------------------------------------------- 1 | const zmq = require('zeromq'); 2 | const sock = new zmq.Reply(); 3 | 4 | async function run() { 5 | await sock.bind("tcp://*:5556"); 6 | console.log("Server bound to port 5556"); 7 | 8 | for await (const [msg] of sock) { 9 | console.log("Received request:", msg.toString()); 10 | await sock.send("World"); 11 | } 12 | } 13 | 14 | run(); -------------------------------------------------------------------------------- /app/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const auth = require("../controllers/auth.controller.js"); 3 | 4 | var router = require("express").Router(); 5 | 6 | router.post('/auth/login', auth.login); 7 | 8 | router.post('/auth/register', auth.register); 9 | 10 | router.post('/auth/logout', auth.logout); 11 | 12 | app.use("/api", router); 13 | }; 14 | -------------------------------------------------------------------------------- /app/receiver/zeromq.js: -------------------------------------------------------------------------------- 1 | const zmq = require('zeromq'); 2 | const sock = new zmq.Request(); 3 | 4 | async function run() { 5 | await sock.connect("tcp://localhost:5555"); 6 | console.log("Client connected to port 5555"); 7 | 8 | await sock.send("Hello"); 9 | const [result] = await sock.receive(); 10 | console.log("Received reply:", result.toString()); 11 | } 12 | 13 | run(); -------------------------------------------------------------------------------- /app/config/db.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | url: "mongodb+srv://watcherznet:fFaotOG6gqR774ax@copytrading.wvukzrr.mongodb.net/?retryWrites=true&w=majority&appName=copytrading" 4 | // url: process.env.ATLAS_URL 5 | }; 6 | // mongodb+srv://crystal:@cluster0.tod58ko.mongodb.net/ 7 | // ATLAS_URL 8 | // DATABASE_URL 9 | // module.exports = { 10 | // url: process.env.DATABASE_URL 11 | // }; -------------------------------------------------------------------------------- /EA_Deploy/server.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import json 4 | from datetime import datetime 5 | 6 | buffer_size = 326582 7 | host = "127.0.0.1" 8 | port = 5555 9 | 10 | while True: 11 | 12 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 | sock.settimeout(30.0) 14 | # sock.setblocking(0) 15 | sock.connect((host, port)) 16 | data = sock.recv(buffer_size).decode('ascii') 17 | 18 | data = data.split("\n") 19 | print("data", data) 20 | 21 | -------------------------------------------------------------------------------- /EA_Deploy/multi_config.ini: -------------------------------------------------------------------------------- 1 | [Server_1] 2 | hostname = server1_ip 3 | username = user1 4 | password = pass1 5 | 6 | [Server_1_MT4_Paths] 7 | ea_folder = /path/to/server1/mt4/experts 8 | 9 | [Server_1_MT5_Paths] 10 | ea_folder = /path/to/server1/mt5/experts 11 | 12 | [Server_1_MT4_Commands] 13 | restart = command_to_restart_mt4_on_server1 14 | 15 | [Server_1_MT5_Commands] 16 | restart = command_to_restart_mt5_on_server1 17 | 18 | [Server_2] 19 | hostname = server2_ip 20 | username = user2 21 | password = pass2 -------------------------------------------------------------------------------- /aliases.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const moduleAlias = require('module-alias'); 3 | 4 | console.log(process.version); 5 | 6 | moduleAlias.addAliases({ 7 | '@axios': getAxiosAlias(), 8 | }); 9 | 10 | function getAxiosAlias() { 11 | const { version } = process; 12 | const [v] = version.split('.'); 13 | 14 | switch (v) { 15 | case 'v10': 16 | return 'axios/dist/node/axios.cjs'; 17 | case 'v11': 18 | return 'axios/dist/node/axios.cjs'; 19 | default: 20 | return 'axios'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EA_Deploy/server (1).py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import json 4 | from datetime import datetime 5 | 6 | buffer_size = 326582 7 | host = "0.0.0.0" 8 | port = 5555 9 | print('5') 10 | while True: 11 | 12 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 | sock.settimeout(30.0) 14 | # sock.setblocking(0) 15 | sock.bind((host, port)) 16 | sock.listen() 17 | conn, addr = sock.accept() 18 | data = conn.recv(buffer_size).decode('ascii') 19 | data = data.split("\n") 20 | print("data", data[0]) 21 | 22 | -------------------------------------------------------------------------------- /app/models/trading.stopout.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | subscriberId: { 4 | type: String, 5 | default:'' 6 | }, 7 | strategy: { 8 | id: String, 9 | name: String, 10 | }, 11 | partial: { 12 | type: Boolean, 13 | default: true, 14 | }, 15 | reason: String, 16 | reasonDescription: String, 17 | closePositions: Boolean, 18 | StoppedAt: { 19 | type: Date, 20 | default: new Date(), 21 | }, 22 | StoppedTill: { 23 | type: Date, 24 | default: new Date(), 25 | } 26 | }) 27 | } -------------------------------------------------------------------------------- /app/utils/mql.js: -------------------------------------------------------------------------------- 1 | const mql = require('mql-api'); 2 | 3 | // Set up the connection to MT4 4 | const connection = mql.createConnection({ 5 | server: 'VantageInternational-Demo 2 - Vantage International Group Limited', 6 | login: '891403696', 7 | password: '&wD0D0bY' 8 | }); 9 | // Place a buy order 10 | connection.placeOrder({ 11 | symbol: 'EURUSD', 12 | type: mql.OrderType.BUY, 13 | lots: 0.1, 14 | slippage: 10, 15 | comment: 'My buy order' 16 | }) 17 | .then(order => { 18 | console.log('Order placed:', order); 19 | }) 20 | .catch(error => { 21 | console.error('Error placing order:', error); 22 | }); 23 | 24 | // Retrieve account information 25 | connection.getAccountInfo() 26 | .then(accountInfo => { 27 | console.log('Account info:', accountInfo); 28 | }) 29 | .catch(error => { 30 | console.error('Error getting account info:', error); 31 | }); 32 | -------------------------------------------------------------------------------- /app/models/index.js: -------------------------------------------------------------------------------- 1 | const dbConfig = require("../config/db.config.js"); 2 | 3 | const mongoose = require("mongoose"); 4 | mongoose.Promise = global.Promise; 5 | 6 | const db = {}; 7 | db.mongoose = mongoose; 8 | db.url = dbConfig.url; 9 | 10 | //auth DB. 11 | db.authentications = require("./auth.model.js")(mongoose); 12 | 13 | //configuration DB. 14 | db.strategies = require("./configuration.strategy.model.js")(mongoose); 15 | db.masters = require("./configuration.master.model.js")(mongoose); 16 | db.portfolioStrategies = require("./configuration.portfolioStrategy.model.js")(mongoose); 17 | db.subscribers = require("./configuration.subscriber.model.js")(mongoose); 18 | 19 | //history DB. 20 | db.transactionfields = require("./history.transaction.model.js")(mongoose); 21 | 22 | //trading DB. 23 | db.tradingSignals = require("./trading.tradingSignal.model.js")(mongoose); 24 | db.externalSignals = require("./trading.externalSignal.model.js")(mongoose); 25 | module.exports = db; 26 | -------------------------------------------------------------------------------- /app/models/trading.externalSignal.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | strategyId: {type: String, default: ''}, 4 | symbol: {type: String, default: 'EURUSD'}, 5 | type: {type: String, default: 'DEAL_TYPE_BUY'}, 6 | time: {type: Date, default: new Date()}, 7 | updateTime: {type: Date, default: new Date()}, 8 | side: {type: String, default: ''}, 9 | volume: Number, 10 | magic: Number, 11 | stopLoss: Number, 12 | takeProfit: Number, 13 | openPrice: Number, 14 | removedTime: {type: Date, default: new Date()}, 15 | }); 16 | 17 | schema.method("toJSON", function() { 18 | const { __v, _id, ...object } = this.toObject(); 19 | object.id = _id; 20 | return object; 21 | }); 22 | 23 | const ExternalSignal = mongoose.model("ExternalSignal", schema); // Changed model name to "ExternalSignal" 24 | return ExternalSignal; 25 | }; -------------------------------------------------------------------------------- /app/routes/history.routes.js: -------------------------------------------------------------------------------- 1 | const { verifyUser } = require('../utils/verifyToken.js'); 2 | module.exports = app => { 3 | const history = require("../controllers/history.controller.js"); 4 | 5 | var router = require("express").Router(); 6 | 7 | // Save Transactions 8 | router.get("/saveTransactions", verifyUser, history.saveTransactions); 9 | 10 | // Get Provided Transactions 11 | router.get("/provided-transactions", verifyUser, history.providedTransaction); 12 | 13 | // Get Subscription Transactions 14 | router.get("/subscription-transactions", verifyUser, history.subscriptionTransaction); 15 | 16 | // Get Strategy Transaction Stream 17 | router.get("/strategies/:strategyId/transactions/stream", verifyUser, history.strategyTransactionStream); 18 | 19 | // Get Subscriber Transaction Stream 20 | router.get("/subscribers/:subscriberId/transactions/stream", verifyUser, history.subscriberTransactionstream); 21 | 22 | app.use("/api/history", router); 23 | }; -------------------------------------------------------------------------------- /app/models/auth.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | accountId: { 4 | type: String, 5 | required: true, 6 | default: '' 7 | }, 8 | name: { 9 | type: String, 10 | default: '', 11 | }, 12 | email: { 13 | type: String, 14 | required: true, 15 | unique: true, 16 | }, 17 | password: { 18 | type: String, 19 | required: true, 20 | default: '' 21 | }, 22 | logined: { 23 | type: Boolean, 24 | default: false, 25 | } 26 | }); 27 | 28 | schema.method("toJSON", function() { 29 | const { _id, ...object } = this.toObject(); 30 | object.id = _id; 31 | return object; 32 | }); 33 | 34 | 35 | const Authentication = mongoose.model("Authentication", schema); // Changed model name to "Master" 36 | return Authentication; 37 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MARK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-express-mongodb", 3 | "version": "1.0.0", 4 | "description": "Node.js Restful CRUD API with Node.js, Express and MongoDB", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon server.js" 9 | }, 10 | "engines": { 11 | "node": ">=16" 12 | }, 13 | "keywords": [ 14 | "nodejs", 15 | "express", 16 | "rest", 17 | "api", 18 | "mongodb" 19 | ], 20 | "author": "crystal", 21 | "license": "ISC", 22 | "dependencies": { 23 | "@truffle/hdwallet-provider": "^1.7.0", 24 | "aws-sdk": "^2.1654.0", 25 | "cors": "^2.8.5", 26 | "csv-parser": "^3.0.0", 27 | "dotenv": "^16.3.1", 28 | "ethers": "^5.4.0", 29 | "express": "^4.18.2", 30 | "express-fileupload": "^1.4.3", 31 | "jsonwebtoken": "^9.0.2", 32 | "jwt-encode": "^1.0.1", 33 | "metaapi.cloud-sdk": "^27.0.3", 34 | "method-override": "^3.0.0", 35 | "mongoose": "^6.11.1", 36 | "nodemon": "^3.0.1", 37 | "socket.io": "^4.7.4", 38 | "socket.io-server": "^1.0.0-b", 39 | "web3": "^4.1.1", 40 | "zeromq": "^6.0.0-beta.20" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/utils/mt4-price-fetcher.js: -------------------------------------------------------------------------------- 1 | const { Client } = require('mt4-api'); 2 | 3 | // Replace with your MT4 platform URL 4 | const MT4_URL = 'VantageInternational-Demo 2 - Vantage International Group Limited'; 5 | 6 | // Initialize the MT4 client 7 | const client = new Client({ 8 | url: MT4_URL, 9 | username: '891403696', 10 | password: '&wD0D0bY', 11 | }); 12 | 13 | // Function to fetch the market prices for all symbols 14 | async function fetchAllSymbolPrices() { 15 | try { 16 | // Fetch the list of symbols from the MT4 platform 17 | const symbols = await client.symbols(); 18 | 19 | const prices = await Promise.all(symbols.map(async (symbol) => { 20 | const quote = await client.quote(symbol); 21 | return { 22 | symbol, 23 | price: quote ? quote['05. price'] : null, 24 | }; 25 | })); 26 | 27 | return prices; 28 | } catch (error) { 29 | console.error('Error fetching all symbol prices:', error); 30 | return []; 31 | } 32 | } 33 | 34 | // Example usage 35 | fetchAllSymbolPrices() 36 | .then((prices) => { 37 | console.log(prices); 38 | }) 39 | .catch((error) => { 40 | console.error('Error:', error); 41 | }); -------------------------------------------------------------------------------- /app/utils/mt4-dll.js: -------------------------------------------------------------------------------- 1 | const ffi = require('ffi-napi'); 2 | 3 | // Load the MT4 API DLL 4 | const mt4Api = ffi.Library('mt4_api.dll', { 5 | 'ConnectToServer': ['int', ['string', 'string', 'string']], 6 | 'PlaceOrder': ['int', ['string', 'int', 'double', 'double', 'double', 'string']], 7 | 'GetAccountInfo': ['int', ['double*', 'double*', 'double*']] 8 | }); 9 | 10 | // Connect to the MT4 server 11 | const connectionStatus = mt4Api.ConnectToServer('your-mt4-server.com', 'your-mt4-login', 'your-mt4-password'); 12 | if (connectionStatus !== 0) { 13 | console.error('Failed to connect to MT4 server:', connectionStatus); 14 | return; 15 | } 16 | 17 | console.log('Connected to MT4 server'); 18 | 19 | // Place a buy order 20 | const orderStatus = mt4Api.PlaceOrder('EURUSD', 0, 0.1, 1.2000, 1.1950, 'My buy order'); 21 | if (orderStatus !== 0) { 22 | console.error('Failed to place order:', orderStatus); 23 | return; 24 | } 25 | 26 | console.log('Order placed successfully'); 27 | console.log('Order placed successfully'); 28 | 29 | // Retrieve account information 30 | let balance, equity, margin; 31 | const accountInfoStatus = mt4Api.GetAccountInfo(balance, equity, margin); 32 | if (accountInfoStatus !== 0) { 33 | console.error('Failed to retrieve account information:', accountInfoStatus); 34 | return; 35 | } 36 | 37 | console.log('Account balance:', balance); 38 | console.log('Account equity:', equity); 39 | console.log('Account margin:', margin); -------------------------------------------------------------------------------- /app/routes/trading.routes.js: -------------------------------------------------------------------------------- 1 | 2 | // import { verifyUser } from '../utils/verifyToken.js' 3 | const { verifyUser } = require('../utils/verifyToken.js'); 4 | module.exports = app => { 5 | const trading = require("../controllers/trading.controller.js"); 6 | 7 | var router = require("express").Router(); 8 | 9 | //save TradingSignals 10 | router.post("/saveTradingSignals", verifyUser, trading.saveTradingSignals) 11 | 12 | //Get Trading Signals 13 | router.get("/subscribers/:subscriberId/signals", verifyUser, trading.getTradingSignals) 14 | 15 | //Update Trading Signals 16 | router.put("/subscribers/:subscriberId/strategy/:strategyId", verifyUser, trading.updateTradingSignals) 17 | 18 | //Get External Trading Signals 19 | router.get("/strategies/:strategyId/external-signals", verifyUser, trading.getExternalTradingSignals) 20 | 21 | //Update External Trading Signals 22 | router.put("/strategies/:strategyId/external-signals/:id", verifyUser, trading.updateExternalTradingSignals) 23 | 24 | //Remove External Trading Signals 25 | router.post("/strategies/:strategyId/external-signals/:id/remove", verifyUser, trading.removeExternalTradingSignals) 26 | 27 | //Signal process 28 | router.post("/accounts/:accountId/signals", verifyUser, trading.signalProcessing) 29 | 30 | router.post("/subscribers/:subscriberId/orders", verifyUser, trading.orders) 31 | 32 | app.use("/api/trading", router); 33 | }; 34 | -------------------------------------------------------------------------------- /app/models/trading.tradingSignal.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | strategyId: {type: String, default: ""}, 4 | subscriberId: {type:String, default:""}, 5 | positionId: String, 6 | subscriberPositionId: String, 7 | time: {type: Date, default: new Date()}, 8 | symbol: {type: String, default: 'EURUSD'}, 9 | type: {type: String, default: 'market'}, 10 | side: {type: String, default: 'buy'}, 11 | server: {type: String, default: "MT4"}, 12 | openPrice: Number, 13 | stopLoss: String, 14 | takeProfit: String, 15 | signalVolume: Number, 16 | subscriberVolume: Number, 17 | subscriberProfit: Number, 18 | leverage: Number, 19 | lotSize: {type: String, default: 'standard'}, 20 | // pendingOrder: {}, 21 | timeFrame: {type: String, default: '1m'}, 22 | profit: {type: Number, default: 0}, 23 | closeAfter: Date, 24 | closeOnly: Boolean, 25 | slipPage: {type: Number, default: 0}, 26 | demo: {type: Boolean, default: false} 27 | }); 28 | 29 | schema.method("toJSON", function() { 30 | const { _id, ...object } = this.toObject(); 31 | object.id = _id; 32 | return object; 33 | }); 34 | 35 | const TradingSignal = mongoose.model("TradingSignal", schema); // Changed model name to "TradingSignal" 36 | return TradingSignal; 37 | }; -------------------------------------------------------------------------------- /app/profile.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | const Router=express.Router() 3 | import { sendVerifyCode ,verifyPhoneNumber} from '../Controllers/phoneVerify.js' 4 | import {downLoad,upLoad} from '../Controllers/fileController.js' 5 | import multer from "multer" 6 | import { verifyUser } from '../utils/verifyToken.js' 7 | import path from 'path' 8 | const upload = multer({ dest: 'assets/' }); 9 | const storage = multer.diskStorage({ 10 | destination: function (req, file, cb) { 11 | cb(null, 'assets/backgroundimage'); // Save files to public/images directory 12 | }, 13 | filename: function (req, file, cb) { 14 | 15 | cb(null, "background.png"); // Set filename to fieldname-currentTimestamp.extension 16 | } 17 | }); 18 | const uploadBackground = multer({ storage: storage }); 19 | Router.post("/sendPhoneNumber",sendVerifyCode) 20 | Router.post("/verifyPhoneNumber",verifyPhoneNumber) 21 | Router.post("/upload-avatar",upload.single('avatar'),downLoad) 22 | Router.get("/download-avatar",verifyUser,upLoad) 23 | Router.post("/upload-background",uploadBackground.single('background'),(req, res) => { 24 | if (!req.file) { 25 | return res.status(400).json({message:'No file uploaded.'}); 26 | } 27 | 28 | // Do something with the uploaded file 29 | console.log(`Uploaded file: ${req.file.filename}`); 30 | 31 | res.json({message:'File uploaded successfully.'}); 32 | }) 33 | Router.get("/download-background",(req,res)=>{ 34 | try{ 35 | const absolutePath=process.cwd()+"\\assets\\backgroundimage\\background.png" 36 | console.log(absolutePath) 37 | res.status(200).sendFile(absolutePath) 38 | }catch(err){ 39 | console.log(err) 40 | } 41 | }) 42 | export default Router -------------------------------------------------------------------------------- /app/models/configuration.master.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | _id: { 4 | type: String, 5 | required: true, 6 | unique: true 7 | }, 8 | name: {type: String, default: ''}, 9 | description: {type: String, default: ''}, 10 | server: {type: String, default: "MT4", required: true}, 11 | demo: {type: Boolean, default: false}, 12 | accountId: {type: String, default: '', required: true}, 13 | symbol: {type: String, default: 'EURUSD'}, 14 | currency: {type: String, default: 'USD'}, 15 | leverage: {type: Number, default: 1}, 16 | tradeVolume: {type: Number, default: 0}, 17 | stopLoss: {type: Number, default: 0}, 18 | takeProfit: {type: Number, default: 0}, 19 | pendingOrder: { 20 | buyLimit: Number, 21 | buyStop: Number, 22 | sellLimit: Number, 23 | sellStop: Number 24 | }, 25 | maxTradeRisk: {type: Number, default: 0}, 26 | drawDown: {type: Number, default: 0}, 27 | timeFrame: {type: String, default: "1m"}, 28 | closeVolume: {type:Number, default: 0}, 29 | // skipPendingOrders: { type: Boolean, default: false }, 30 | closeAll: {type: String, default: 'Nothing'}, 31 | specificPrice: { 32 | breakEven: {type: Boolean, default: false}, 33 | moveStopLoss: {type: Number, default: 0}, 34 | moveTakeProfit: {type: Number, default: 0}, 35 | entryPoint: {type:Number, default: 0} 36 | }, 37 | isStopLoss: {type: Boolean, default:true} 38 | }); 39 | 40 | const Masters = mongoose.model("Masters", schema); // Changed model name to "Master" 41 | return Masters; 42 | }; -------------------------------------------------------------------------------- /app/utils/verifyToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const db = require("../models"); 3 | const Auth = db.authentications; 4 | const expirationTime = 2592000; 5 | 6 | const verifyToken = (req, res, next) => { 7 | const { token } = req.headers; 8 | if (!token) { 9 | return res.status(401).json({ success: false, message: "You are not authorized!" }); 10 | } 11 | 12 | // If token exists, verify the token 13 | jwt.verify(token, process.env.JWT_SECRET_KEY, (err, user) => { 14 | if (err) { 15 | return res.status(401).json({ success: false, message: "Token is invalid" }); 16 | } 17 | req.user = user; 18 | next(); 19 | }); 20 | }; 21 | 22 | const setToken = (tokendata) => { 23 | const token = jwt.sign(tokendata, process.env.JWT_SECRET_KEY); 24 | return token; 25 | } 26 | 27 | 28 | const verifyUser = (req, res, next) => { 29 | verifyToken(req, res, async () => { 30 | const isUser = await Auth.findOne({email: req.user.email, accountId: req.user.accountId}) 31 | if (isUser) { 32 | const currentDate = Math.floor(Date.now() / 1000); 33 | console.log(currentDate); 34 | if (currentDate < req.user.exp){ 35 | req.user = isUser; 36 | next(); 37 | } else { 38 | res.status(401).json({success: false, message: "Token is expired"}) 39 | } 40 | } 41 | else res.status(401).json({success: false, message: "You are not authenticated!"}) 42 | }); 43 | 44 | }; 45 | 46 | const verifyAdmin = (req, res, next) => { 47 | verifyToken(req, res, () => { 48 | if (req.user.role === 'admin') { 49 | next(); 50 | } else { 51 | return res.status(401).json({ success: false, message: "You are not authorized" }); 52 | } 53 | }); 54 | }; 55 | 56 | module.exports = { 57 | verifyToken, 58 | verifyUser, 59 | verifyAdmin, 60 | setToken 61 | }; -------------------------------------------------------------------------------- /app/models/history.transaction.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | accountId: { 4 | type: String, 5 | // required: true, 6 | }, 7 | type: {type: String, default: 'buy'}, 8 | time: {type: Date, default: new Date()}, 9 | subscriberId: {type: String, default: ''}, 10 | symbol: {type: String, default: 'EURVUSD'}, 11 | // subscriberUser: { 12 | // id: String, 13 | // name: String, 14 | // strategies: [{ 15 | // id: String, 16 | // name: String, 17 | // }], 18 | // }, 19 | // demo: Boolean, 20 | // providerUser: { 21 | // id: String, 22 | // name: String, 23 | // strategies: [{ 24 | // id: String, 25 | // name: String, 26 | // }], 27 | // }, 28 | // strategy: { 29 | // id: String, 30 | // name: String, 31 | // }, 32 | positionId: String, 33 | slavePositionId: String, 34 | // improvement: Number, 35 | // providerCommission: Number, 36 | // platformCommission: Number, 37 | // incomingProviderCommission: Number, 38 | // incomingPlatformCommission: Number, 39 | quantity: Number, 40 | lotPrice: Number, 41 | tickPrice: Number, 42 | amount: Number, 43 | // commission: Number, 44 | // swap: Number, 45 | profit: Number, 46 | // metrics: { 47 | // tradeCopyingLatency: Number, 48 | // tradeCopyingSlippageInBasisPoints: Number, 49 | // tradeCopyyingSlippageInAccountCurrency: Number, 50 | // mtAndBrokerSignalLatency: Number, 51 | // tradeAlgorithmLatency: Number, 52 | // mtANdBrokerTradeLantency: Number, 53 | // }, 54 | closed: {type: Boolean, default: false} 55 | }); 56 | 57 | schema.method("toJSON", function() { 58 | const { __v, _id, ...object } = this.toObject(); 59 | object.id = _id; 60 | return object; 61 | }); 62 | 63 | const Transactioned = mongoose.model("Transactioned", schema); // Changed model name to "Transaction" 64 | return Transactioned; 65 | }; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const dotenv = require("dotenv"); 4 | const bodyParser = require("body-parser"); 5 | const fileUpload = require('express-fileupload'); 6 | const http = require('http'); 7 | const AWS = require('aws-sdk'); // If interacting with other AWS services 8 | // const mql = require('./app/utils/mql.js'); 9 | AWS.config.update({ 10 | region: 'Virginia', 11 | accessKeyId: 'ec2-54-11-191-126.compute-1.amazonaws.com', 12 | secretAccessKey: '9Lvm8aouN&GvO1xD=EDPQa$udoX09-Yj' 13 | }); 14 | const app = express(); 15 | 16 | app.use(fileUpload()); 17 | // require("./app/socketServer"); 18 | // require("./app/walletavatar") 19 | 20 | require("./socket_server"); 21 | require("./app/utils/mt4-bridge") 22 | 23 | 24 | var corsOptions = { 25 | origin: "*" 26 | 27 | }; 28 | dotenv.config(); 29 | app.use(cors(corsOptions)); 30 | app.use(bodyParser.json({limit: '50mb'})); 31 | app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); 32 | // parse requests of content-type - application/json 33 | app.use(express.json()); 34 | // mongoose.connect("mongodb://localhost/phantom-avatars", { useNewUrlParser: true, useUnifiedTopology: true }); 35 | // parse requests of content-type - application/x-www-form-urlencoded 36 | app.use(express.urlencoded({ extended: true })); 37 | 38 | const db = require("./app/models"); 39 | db.mongoose 40 | .connect(db.url, { 41 | useNewUrlParser: true, 42 | useUnifiedTopology: true 43 | }) 44 | .then(() => { 45 | console.log("Connected to the database!"); 46 | }) 47 | .catch(err => { 48 | console.log("Cannot connect to the database!", err); 49 | process.exit(); 50 | }); 51 | 52 | 53 | 54 | // simple route 55 | app.get("/", (req, res) => { 56 | res.json({ message: "Welcome to bezkoder application." }); 57 | }); 58 | 59 | require("./app/routes/configuration.routes")(app); 60 | require("./app/routes/history.routes")(app); 61 | require("./app/routes/trading.routes")(app); 62 | require("./app/routes/auth.routes")(app); 63 | 64 | // Create an HTTP server to handle HTTP requests 65 | const httpServer = http.createServer(app); 66 | // require("./socket_server")(httpServer); 67 | // set port, listen for requests 68 | const PORT = process.env.PORT || 80; 69 | // app.listen(PORT, () => { 70 | // console.log(`Server is running on port ${PORT}.`); 71 | // }); 72 | // Start the HTTP server on port 5000 73 | httpServer.listen(PORT, () => { 74 | console.log(`HTTP Server running on port ${PORT}`); 75 | }); 76 | -------------------------------------------------------------------------------- /app/routes/configuration.routes.js: -------------------------------------------------------------------------------- 1 | 2 | // import { verifyUser } from '../utils/verifyToken.js' 3 | const { verifyUser } = require('../utils/verifyToken.js'); 4 | 5 | module.exports = app => { 6 | const configuration = require("../controllers/configuration.controller.js"); 7 | 8 | // var verifyUser = require("../utils/verifyToken.js"); 9 | 10 | var router = require("express").Router(); 11 | 12 | 13 | //Save Master Strategy 14 | router.post("/master-strategies", configuration.saveMasterStrategy); 15 | 16 | // Get Strategies 17 | router.get("/masters", verifyUser, configuration.getMasters) 18 | 19 | //update Master Strategy 20 | router.put("/master-strategies/:accountId", verifyUser, configuration.updateMasterStrategy) 21 | 22 | // Generate New Strategy Id 23 | router.post("/slave-settings", configuration.saveSlaveSettings); 24 | 25 | // Get Strategies 26 | router.get("/strategies", verifyUser, configuration.getStrategies) 27 | 28 | // Get Strategy 29 | router.get("/strategies/:strategyId", verifyUser, configuration.getStrategy) 30 | 31 | //Update Strategy 32 | router.put("/strategies/:strategyId", verifyUser, configuration.updateStrategy) 33 | 34 | //Delete Strategy 35 | router.delete("/strategies/:strategyId", verifyUser, configuration.deleteStrategy) 36 | 37 | // Get Portfolio Strategies 38 | router.get("/portfolio-strategies", verifyUser, configuration.getPortfolioStrategies) 39 | 40 | // Get Portfolio Strategy 41 | router.get("/portfolio-strategies/:strategyId", verifyUser, configuration.getPortfolioStrategy) 42 | 43 | //Update Portfolio Strategy 44 | router.put("/portfolio-strategies/:strategyId", verifyUser, configuration.updatePortfolioStrategy) 45 | 46 | //Delete Portfolio Strategy 47 | router.delete("/portfolio-strategies/:strategyId", verifyUser, configuration.deletePortfolioStrategy) 48 | 49 | //Delete Portfolio Member Strategy 50 | router.delete("/portfolio-strategies/:strategyId/member/:strategyMemberId", verifyUser, configuration.deletePortfolioMemberStrategy) 51 | 52 | // Get Portfolio Strategies 53 | router.get("/subscribers", verifyUser, configuration.getSubscribers) 54 | 55 | // Get Portfolio Strategy 56 | router.get("/subscribers/:subscriberId", verifyUser, configuration.getSubscriber) 57 | 58 | //Update Portfolio Strategy 59 | router.put("/subscribers/:subscriberId", verifyUser, configuration.updateSubscriber) 60 | 61 | //Delete Portfolio Strategy 62 | router.delete("/subscribers/:subscriberId", verifyUser, configuration.deleteSubscriber) 63 | 64 | //Delete Portfolio Member Strategy 65 | router.delete("/subscribers/:subscriberId/subscription/:strategyId", verifyUser, configuration.deleteSubscription) 66 | 67 | 68 | app.use("/api/configuration", router); 69 | }; 70 | -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | C:\Program Files\nodejs\node.exe C:\Users\pc\AppData\Roaming\npm\node_modules\yarn\bin\yarn.js 3 | 4 | PATH: 5 | C:\Users\pc\bin;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\local\bin;C:\Program Files\Git\usr\bin;C:\Program Files\Git\usr\bin;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Users\pc\bin;C:\Program Files\Common Files\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\ProgramData\chocolatey\bin;C:\Program Files\Git\cmd;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Program Files\Java\jdk1.8.0_211\bin;C:\Users\pc\AppData\Roaming\nvm;C:\Program Files\nodejs;C:\Program Files\php;C:\composer;C:\Users\pc\AppData\Local\Programs\Python\Python311\Scripts;C:\Users\pc\AppData\Local\Programs\Python\Python311;C:\Users\pc\AppData\Local\.meteor\;C:\Users\pc\AppData\Roaming\nvm\v16.20.0\node_modules\meteor\node_modules\.bin;C:\Users\pc\AppData\Roaming\nvm\v16.20.0\node_modules\node_modules\.bin;C:\Users\pc\AppData\Roaming\nvm\v16.20.0\node_modules\.bin;C:\Users\pc\AppData\Roaming\nvm\node_modules\.bin;C:\Users\pc\AppData\Roaming\node_modules\.bin;C:\Users\pc\AppData\node_modules\.bin;C:\Users\pc\node_modules\.bin;C:\Users\node_modules\.bin;C:\node_modules\.bin;C:\Users\pc\AppData\Roaming\nvm\v16.20.0\node_modules\npm\node_modules\@npmcli\run-script\lib\node-gyp-bin;C:\Users\pc\bin;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\local\bin;C:\Program Files\Git\usr\bin;C:\Program Files\Git\usr\bin;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Users\pc\bin;C:\Program Files\Common Files\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\ProgramData\chocolatey\bin;C:\Program Files\Git\cmd;C:\Prog;C:\Users\pc\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\pc\AppData\Roaming\npm;C:\Program Files\Git\usr\bin\vendor_perl;C:\Program Files\Git\usr\bin\core_perl 6 | 7 | Yarn version: 8 | 1.22.19 9 | 10 | Node version: 11 | 20.0.0 12 | 13 | Platform: 14 | win32 x64 15 | 16 | Trace: 17 | Error: ENOENT: no such file or directory, open 'C:\Users\pc\AppData\Local\Yarn\Cache\v6\npm-@aws-sdk-middleware-logger-3.370.0-c9f694d7e1dd47b5e6e8eab94793fc1e272b1e26-integrity\node_modules\@aws-sdk\middleware-logger\.yarn-metadata.json' 18 | 19 | npm manifest: 20 | { 21 | "name": "node-express-mongodb", 22 | "version": "1.0.0", 23 | "description": "Node.js Restful CRUD API with Node.js, Express and MongoDB", 24 | "main": "server.js", 25 | "scripts": { 26 | "test": "echo \"Error: no test specified\" && exit 1" 27 | }, 28 | "keywords": [ 29 | "nodejs", 30 | "express", 31 | "rest", 32 | "api", 33 | "mongodb" 34 | ], 35 | "author": "bezkoder", 36 | "license": "ISC", 37 | "dependencies": { 38 | "cors": "^2.8.5", 39 | "express": "^4.18.2", 40 | "mongoose": "^6.11.1" 41 | } 42 | } 43 | 44 | yarn manifest: 45 | No manifest 46 | 47 | Lockfile: 48 | No lockfile 49 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | /* Global */ 5 | const resolve = { 6 | extensions: ['.es6', '.js'], 7 | alias: { 8 | process: 'process/browser', 9 | '@axios': 'axios' 10 | }, 11 | fallback: { 12 | buffer: false, 13 | stream: false, 14 | fs: false, 15 | assert: false, 16 | constants: false, 17 | crypto: false, 18 | http: false, 19 | https: false, 20 | process: false, 21 | url: false, 22 | util: false, 23 | zlib: false, 24 | path: false, 25 | net: false, 26 | os: false 27 | } 28 | }; 29 | 30 | /* configs */ 31 | const swcESM = require('./swc/swcrc.esm'); 32 | const webESM = { 33 | mode: 'production', 34 | entry: './lib/index.es6', 35 | devtool: 'source-map', 36 | 37 | target: 'web', 38 | 39 | module: { 40 | rules: [ 41 | { 42 | loader: 'swc-loader', 43 | options: swcESM, 44 | test: /\.es6$/, 45 | exclude: /(node_modules)/ 46 | } 47 | ] 48 | }, 49 | 50 | output: { 51 | filename: 'index.js', 52 | path: path.resolve(__dirname, './dists/esm/'), 53 | library: { 54 | type: 'module', 55 | } 56 | }, 57 | 58 | experiments: { 59 | outputModule: true, 60 | }, 61 | 62 | plugins: [ 63 | new webpack.ProvidePlugin({ 64 | process: 'process/browser' 65 | }), 66 | ], 67 | 68 | resolve 69 | }; 70 | 71 | const swcrcUMD = require('./swc/swcrc.umd'); 72 | const webUMD = { 73 | entry: './lib/index.es6', 74 | target: 'web', 75 | mode: 'production', 76 | devtool: 'source-map', 77 | 78 | module: { 79 | rules: [ 80 | { 81 | loader: 'swc-loader', 82 | test: /\.es6$/, 83 | exclude: /(node_modules)/, 84 | options: swcrcUMD 85 | } 86 | ] 87 | }, 88 | 89 | output: { 90 | filename: 'index.js', 91 | path: path.resolve(__dirname, './dists/umd/'), 92 | globalObject: 'this', 93 | library: { 94 | name: 'CopyFactory', 95 | type: 'umd', 96 | export: 'default' 97 | } 98 | }, 99 | 100 | plugins: [ 101 | new webpack.ProvidePlugin({ 102 | process: 'process/browser' 103 | }), 104 | ], 105 | 106 | resolve 107 | }; 108 | 109 | const swcrcCJS = require('./swc/swcrc.cjs'); 110 | const nodeCJS = { 111 | entry: './lib/index.es6', 112 | target: 'node', 113 | mode: 'production', 114 | devtool: 'source-map', 115 | 116 | module: { 117 | rules: [ 118 | { 119 | loader: 'swc-loader', 120 | test: /\.es6$/, 121 | exclude: /(node_modules)/, 122 | options: swcrcCJS 123 | } 124 | ] 125 | }, 126 | 127 | output: { 128 | filename: 'index.js', 129 | path: path.resolve(__dirname, './dists/cjs/'), 130 | library: { 131 | type: 'commonjs' 132 | } 133 | }, 134 | 135 | resolve: { 136 | extensions: ['.es6', '.js'], 137 | alias: { 138 | '@axios': 'axios' 139 | } 140 | } 141 | }; 142 | 143 | module.exports = [ webESM, webUMD, nodeCJS ]; 144 | -------------------------------------------------------------------------------- /app/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | const jwtEncode = require('jwt-encode') 2 | const db = require("../models"); 3 | const { setToken } = require('../utils/verifyToken'); 4 | const Auth = db.authentications; 5 | 6 | const limitAccNum = 100; 7 | const expirationTime = 2592000; 8 | //Regiseter Account 9 | exports.register = async (req, res) => { 10 | try { 11 | console.log("regiester"); 12 | const {name, email, password, accountId} = req.body; 13 | // const accountId = req.params.accountId; 14 | const isUser = await Auth.findOne({email: email}); 15 | console.log(isUser); 16 | if(!isUser) { 17 | const auth = new Auth({name: name, email: email, password: password, accountId: accountId, logined: true}); 18 | await auth.save(); 19 | const payload = { 20 | email: email, 21 | accountId: accountId, 22 | iat: Math.floor(Date.now() / 1000), // Issued at time 23 | exp: Math.floor(Date.now() / 1000) + expirationTime // Expiration time 24 | } 25 | const token = setToken(payload); 26 | console.log(token); 27 | res.status(201).json({message: "Successfully Regisetered", token: token}); 28 | } 29 | else { 30 | res.status(409).json({message: "The Email is already registered"}) 31 | } 32 | } catch(e) { 33 | console.log(e); 34 | return res.status(500).json({message: "An Error Occured!"}); 35 | } 36 | } 37 | 38 | //Login Account 39 | exports.login = async (req, res) => { 40 | try { 41 | console.log("LogIn"); 42 | const {email, password} = req.body; 43 | const isUser = await Auth.findOne({email: email, password: password}); 44 | if (isUser) { 45 | 46 | const payload = { 47 | email: email, 48 | accountId: isUser.accountId, 49 | iat: Math.floor(Date.now() / 1000), // Issued at time 50 | exp: Math.floor(Date.now() / 1000) + expirationTime // Expiration time 51 | } 52 | const token = setToken(payload); 53 | console.log(token); 54 | if (token) { 55 | const updateUser = await Auth.updateOne({email: email}, {$set: {logined: true}}); 56 | res.status(200).json({message: "Successfully Logined!", token: token}); 57 | } 58 | else { 59 | res.status(400).json({message: "Cannot logined User!"}) 60 | } 61 | } 62 | else { 63 | res.status(404).json({message: "User Not Found! Please Register First."}) 64 | } 65 | } catch(e) { 66 | console.log(e); 67 | return res.status(500).json({message: "An Error Occured!"}) 68 | } 69 | } 70 | 71 | //Logout Account 72 | exports.logout = async (req, res) => { 73 | try { 74 | console.log('Logout'); 75 | const email = req.body; 76 | const logoutUser = await Auth.updateOne({accountId: accountId}, {$set: {logined: false}}); 77 | res.status(200).json({email: email, logined: logined}) 78 | } catch (e) { 79 | console.log(e); 80 | return res.status(500).json({message: "An Error Occured!"}); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /EA_Deploy/EAdeployer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import paramiko 4 | import configparser 5 | from watchdog.observers import Observer 6 | from watchdog.events import FileSystemEventHandler 7 | import json 8 | 9 | class EADeployer: 10 | def __init__(self, config_path): 11 | self.config = self.load_config(config_path) 12 | self.ssh_clients = {} 13 | self.setup_ssh_connections() 14 | 15 | def load_config(self, config_path): 16 | config = configparser.ConfigParser() 17 | config.read(config_path) 18 | return config 19 | 20 | def setup_ssh_connections(self): 21 | for server in self.config.sections(): 22 | if server.startswith('Server_'): 23 | self.ssh_clients[server] = self.create_ssh_client(server) 24 | 25 | def create_ssh_client(self, server): 26 | ssh_client = paramiko.SSHClient() 27 | ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 28 | ssh_client.connect( 29 | hostname=self.config[server]['hostname'], 30 | username=self.config[server]['username'], 31 | password=self.config[server]['password'] 32 | ) 33 | return ssh_client 34 | 35 | def deploy_ea(self, ea_path, server, platform): 36 | ssh_client = self.ssh_clients[server] 37 | remote_path = self.config[f'{server}_{platform}_Paths']['ea_folder'] 38 | ea_name = os.path.basename(ea_path) 39 | remote_file_path = f"{remote_path}/{ea_name}" 40 | 41 | sftp = ssh_client.open_sftp() 42 | sftp.put(ea_path, remote_file_path) 43 | sftp.close() 44 | 45 | print(f"Deployed {ea_name} to {server} {platform} at {remote_file_path}") 46 | 47 | def restart_terminal(self, server, platform): 48 | ssh_client = self.ssh_clients[server] 49 | command = self.config[f'{server}_{platform}_Commands']['restart'] 50 | stdin, stdout, stderr = ssh_client.exec_command(command) 51 | print(f"Restarted {server} {platform} terminal") 52 | 53 | class EAHandler(FileSystemEventHandler): 54 | def __init__(self, deployer, mapping_file): 55 | self.deployer = deployer 56 | self.mapping = self.load_mapping(mapping_file) 57 | 58 | def load_mapping(self, mapping_file): 59 | with open(mapping_file, 'r') as f: 60 | return json.load(f) 61 | 62 | def on_created(self, event): 63 | if event.is_directory: 64 | return 65 | ea_name = os.path.basename(event.src_path) 66 | if ea_name in self.mapping: 67 | for target in self.mapping[ea_name]: 68 | server, platform = target.split('_') 69 | self.deployer.deploy_ea(event.src_path, f'Server_{server}', platform) 70 | self.deployer.restart_terminal(f'Server_{server}', platform) 71 | 72 | def main(): 73 | config_path = 'multi_config.ini' 74 | mapping_file = 'ea_mapping.json' 75 | watch_folder = 'path/to/ea/folder' 76 | 77 | deployer = EADeployer(config_path) 78 | event_handler = EAHandler(deployer, mapping_file) 79 | observer = Observer() 80 | observer.schedule(event_handler, watch_folder, recursive=False) 81 | observer.start() 82 | 83 | try: 84 | while True: 85 | pass 86 | except KeyboardInterrupt: 87 | observer.stop() 88 | observer.join() 89 | 90 | if __name__ == "__main__": 91 | main() 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API Trading Platform 2 | 3 | A robust trading signal management and copy trading system built with Node.js and MongoDB. 4 | 5 | ## Overview 6 | 7 | This platform enables automated trading signal management, strategy configuration, and copy trading functionality across multiple trading accounts. It provides a comprehensive API for managing trading operations, user authentication, and real-time signal processing. 8 | 9 | ## Core Features 10 | 11 | - **Authentication System** 12 | - JWT-based secure authentication 13 | - User registration and login 14 | - Token verification and role-based access control 15 | 16 | - **Trading Signal Management** 17 | - External signal processing 18 | - Trading signal generation and distribution 19 | - Real-time signal copying between accounts 20 | 21 | - **Strategy Configuration** 22 | - Master strategy management 23 | - Slave settings configuration 24 | - Portfolio strategy handling 25 | - Risk management settings 26 | 27 | - **Transaction Tracking** 28 | - Detailed transaction history 29 | - Performance metrics 30 | - Stop-out management 31 | 32 | ## Technical Stack 33 | 34 | - **Backend**: Node.js/Express.js 35 | - **Database**: MongoDB 36 | - **Authentication**: JWT 37 | - **Trading Integration**: MT4 API 38 | - **Security**: Crypto module for randomization 39 | 40 | ## API Endpoints 41 | 42 | ### Authentication 43 | - POST `/auth/register` - User registration 44 | - POST `/auth/login` - User login 45 | 46 | ### Configuration 47 | - POST `/configuration/master-strategies` - Create master strategy 48 | - GET `/configuration/strategies` - Retrieve strategies 49 | - PUT `/configuration/strategies/:strategyId` - Update strategy 50 | - DELETE `/configuration/strategies/:strategyId` - Remove strategy 51 | 52 | ### Trading 53 | - Multiple endpoints for signal management and trade execution 54 | - Portfolio management endpoints 55 | - Transaction history endpoints 56 | 57 | ## Data Models 58 | 59 | - **Authentication Model**: User credentials and access management 60 | - **Strategy Models**: Master, Slave, and Portfolio configurations 61 | - **Trading Models**: Signals, External signals, Stop-outs 62 | - **Transaction Model**: Trading history and performance tracking 63 | 64 | ## Security Features 65 | 66 | - JWT-based authentication 67 | - Token expiration handling 68 | - Role-based access control 69 | - Secure password handling 70 | 71 | ## Installation 72 | 73 | ```bash 74 | npm install 75 | npm start 76 | ``` 77 | 78 | ## Environment Variables 79 | 80 | Create a `.env` file in the root directory with the following variables: 81 | ``` 82 | PORT=3000 83 | MONGODB_URI=your_mongodb_uri 84 | JWT_SECRET=your_jwt_secret 85 | MT4_API_KEY=your_mt4_api_key 86 | MT4_API_SECRET=your_mt4_api_secret 87 | ``` 88 | 89 | ## Usage 90 | 91 | 1. Configure trading strategies 92 | 2. Set up signal copying rules 93 | 3. Monitor transactions and performance 94 | 4. Manage portfolio strategies 95 | 5. Track trading signals and execution 96 | 97 | ## Contribution 98 | 99 | 1. Fork the repository 100 | 2. Create your feature branch (`git checkout -b feature/AmazingFeature`) 101 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) 102 | 4. Push to the branch (`git push origin feature/AmazingFeature`) 103 | 5. Open a pull request 104 | 105 | ## License 106 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 107 | -------------------------------------------------------------------------------- /app/models/configuration.subscriber.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | _id: { 4 | type: String, 5 | required: true, 6 | 7 | }, 8 | name: String, 9 | reservedMarginFraction: Number, 10 | phoneNumbers: [Number], 11 | minTradeAmount: {type: Number, default: 0}, 12 | closeOnly: {type:String, default: 'immediately'}, 13 | riskLimits: [{ 14 | type: {type: String, default: 'day'}, 15 | applyTo: {type: String, default: 'balance-difference'}, 16 | maxAbsoluteRisk: Number, 17 | maxRelativeRisk: Number, 18 | closePositions: {type: Boolean, default: false}, 19 | startTime: String, 20 | }], 21 | maxLeverage: {type: Number, default: undefined}, 22 | copyStopLoss: {type: Boolean, default: true}, 23 | copyTakeProfit: {type: Boolean, default: true}, 24 | allowedSides: {type: [String], default: ["all"]}, 25 | minTradeVolume: Number, 26 | maxTradeVolume: Number, 27 | signalDelay: { 28 | mininSeconds: Number, 29 | maxinSeconds: Number 30 | }, 31 | subscriptions: [{ 32 | strategyId: { 33 | type: String, 34 | // required: true, 35 | // unique: true 36 | }, 37 | multiplier: {type: Number, default: 1}, 38 | skipPendingOrders: { type: Boolean, default: false }, 39 | maxTradeRisk: {type: Number, default: undefined}, 40 | reverse: { type: Boolean, default: false }, 41 | reduceCorrelations: {type: String, default: undefined}, 42 | symbolFilter: { 43 | included: [String], 44 | excluded: [String] 45 | }, 46 | newsFilter: { 47 | breakingNewsFilter: { 48 | priorities: [String], 49 | closePositionTimeGapInMinutes: Number, 50 | openPositionFollowingTimeGapInMinutes: Number 51 | }, 52 | calendarNewsFilter: { 53 | priorities: [String], 54 | closePositionTimeGapInMinutes: Number, 55 | openPositionPrecedingTimeGapInMinutes: Number, 56 | openPositionFollowingTimeGapInMinutes: Number 57 | } 58 | }, 59 | riskLimits: [{ 60 | type: {type: String, default: 'day'}, 61 | applyTo: {type: String, default: 'balance-difference'}, 62 | maxAbsoluteRisk: Number, 63 | maxRelativeRisk: Number, 64 | closePositions: {type: Boolean, default: false}, 65 | startTime: String, 66 | }], 67 | maxStopLoss: { 68 | value: Number, 69 | units: String 70 | }, 71 | maxLeverage: {type: Number, default: undefined}, 72 | symbolMapping: { 73 | to: String, 74 | from: String 75 | }, 76 | tradeSizeScaling: { 77 | mode: String, 78 | tradeVolumne: Number, 79 | riskFraction: Number, 80 | forceTinyTrades: Boolean, 81 | maxRiskCoefficient: Number, 82 | expression: String 83 | }, 84 | copyStopLoss: Boolean, 85 | copyTakeProfit: Boolean, 86 | allowedSides: [String], 87 | minTradeVolume: Number, 88 | maxTradeVolume: Number, 89 | signalDelay: { 90 | mininSeconds: Number, 91 | maxinSeconds: Number 92 | }, 93 | removedState: {type: Boolean, default: false} 94 | }], 95 | removedState: {type: Boolean, default: false} 96 | }); 97 | 98 | const Subscriber = mongoose.model("Subscriber", schema); // Changed model name to "Subscriber" 99 | return Subscriber; 100 | }; -------------------------------------------------------------------------------- /app/models/configuration.portfolioStrategy.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | _id: { 4 | type: String, 5 | required: true, 6 | unique: true 7 | }, 8 | name: String, 9 | description: {type: String, default: ''}, 10 | members: [{ 11 | strategyId: { 12 | type: String, 13 | // required: true, 14 | // unique: true 15 | }, 16 | multiplier: Number, 17 | skipPendingOrders: { type: Boolean, default: false }, 18 | maxTradeRisk: {type: Number, default: undefined}, 19 | reverse: { type: Boolean, default: false }, 20 | reduceCorrelations: {type: String, default: undefined}, 21 | symbolFilter: { 22 | included: [String], 23 | excluded: [String] 24 | }, 25 | newsFilter: { 26 | breakingNewsFilter: { 27 | priorities: [String], 28 | closePositionTimeGapInMinutes: Number, 29 | openPositionFollowingTimeGapInMinutes: Number 30 | }, 31 | calendarNewsFilter: { 32 | priorities: [String], 33 | closePositionTimeGapInMinutes: Number, 34 | openPositionPrecedingTimeGapInMinutes: Number, 35 | openPositionFollowingTimeGapInMinutes: Number 36 | } 37 | }, 38 | riskLimits: [{ 39 | type: {type: String, default: 'day'}, 40 | applyTo: {type: String, default: 'balance-difference'}, 41 | maxAbsoluteRisk: Number, 42 | maxRelativeRisk: Number, 43 | closePositions: {type: Boolean, default: false}, 44 | startTime: String, 45 | }], 46 | maxStopLoss: { 47 | value: Number, 48 | units: String 49 | }, 50 | maxLeverage: {type: Number, default: undefined}, 51 | symbolMapping: { 52 | to: String, 53 | from: String 54 | }, 55 | tradeSizeScaling: { 56 | mode: String, 57 | tradeVolumne: Number, 58 | riskFraction: Number, 59 | forceTinyTrades: Boolean, 60 | maxRiskCoefficient: Number, 61 | expression: String 62 | }, 63 | copyStopLoss: Boolean, 64 | copyTakeProfit: Boolean, 65 | allowedSides: [String], 66 | minTradeVolume: Number, 67 | maxTradeVolume: Number, 68 | signalDelay: { 69 | mininSeconds: Number, 70 | maxinSeconds: Number 71 | }, 72 | closeOnRemovalMode: String, 73 | removedState: {type: Boolean, default: false} 74 | }], 75 | commissionScheme: { 76 | type: {type:String, default: ""}, 77 | billingPeriod: { type: String, default: 'week' }, 78 | commissionRate: { type: Number, default: 0 } 79 | }, 80 | skipPendingOrders: { type: Boolean, default: false }, 81 | maxTradeRisk: {type: Number, default: undefined}, 82 | reverse: { type: Boolean, default: false }, 83 | reduceCorrelations: {type: String, default: undefined}, 84 | symbolFilter: { 85 | included: [String], 86 | excluded: [String] 87 | }, 88 | newsFilter: { 89 | breakingNewsFilter: { 90 | priorities: [String], 91 | closePositionTimeGapInMinutes: Number, 92 | openPositionFollowingTimeGapInMinutes: Number 93 | }, 94 | calendarNewsFilter: { 95 | priorities: [String], 96 | closePositionTimeGapInMinutes: Number, 97 | openPositionPrecedingTimeGapInMinutes: Number, 98 | openPositionFollowingTimeGapInMinutes: Number 99 | } 100 | }, 101 | riskLimits: [{ 102 | type: {type: String, default: 'day'}, 103 | applyTo: {type: String, default: 'balance-difference'}, 104 | maxAbsoluteRisk: Number, 105 | maxRelativeRisk: Number, 106 | closePositions: {type: Boolean, default: false}, 107 | startTime: String, 108 | }], 109 | maxStopLoss: { 110 | value: Number, 111 | units: String 112 | }, 113 | maxLeverage: {type: Number, default: undefined}, 114 | symbolMapping: { 115 | to: String, 116 | from: String 117 | }, 118 | tradeSizeScaling: { 119 | mode: String, 120 | tradeVolumne: Number, 121 | riskFraction: Number, 122 | forceTinyTrades: Boolean, 123 | maxRiskCoefficient: Number, 124 | expression: String 125 | }, 126 | copyStopLoss: Boolean, 127 | copyTakeProfit: Boolean, 128 | allowedSides: [String], 129 | minTradeVolume: Number, 130 | maxTradeVolume: Number, 131 | signalDelay: { 132 | mininSeconds: Number, 133 | maxinSeconds: Number 134 | }, 135 | platformCommissionRate: {type: Number, default: undefined}, 136 | closeOnRemovalMode: String, 137 | removedState: {type: Boolean, default: false} 138 | }); 139 | 140 | const PortfolioStrategy = mongoose.model("PortfolioStrategy", schema); // Changed model name to "PortfolioStrategy" 141 | return PortfolioStrategy; 142 | }; -------------------------------------------------------------------------------- /app/models/configuration.strategy.model.js: -------------------------------------------------------------------------------- 1 | module.exports = mongoose => { 2 | var schema = mongoose.Schema({ 3 | _id: { 4 | type: String, 5 | required: true, 6 | unique: true 7 | }, 8 | name: {type: String, default: ""}, 9 | minTradeVolume: {type: Number, default: 0}, 10 | maxTradeVolume: {type: Number, default: 1}, 11 | copyStopLoss: {type: Boolean, default: true}, 12 | copyTakeProfit: {type: Boolean, default: true}, 13 | allowedSides: {type: [String], default: ["buy", "selll", "buyLimit", "sellLimit", "buyStop", "sellStop"]}, 14 | slaveaccountId: {type: String, required: true}, 15 | signalDelay: { 16 | mininSeconds: {type: Number, default: 0}, 17 | maxinSeconds: {type: Number, default: 0} 18 | }, 19 | maxLeverage: {type: Number, default: 1000}, 20 | // subscirpition: [{ 21 | strategyId: {type: String, required: 'true'}, 22 | skipPendingOrders: { type: Boolean, default: false }, 23 | maxTradeRisk: {type: Number, default: 0}, 24 | reverse: { type: Boolean, default: false }, 25 | symbolFilter: { 26 | included: [], 27 | excluded: [] 28 | }, 29 | riskLimits: { 30 | type: {type: String, default: 'day'}, 31 | applyTo: {type: String, default: 'balance-difference'}, 32 | maxAbsoluteRisk: {type: Number, default: 0}, 33 | maxAbsoluteProfit: {type: Number, default: 0}, 34 | dailyMaxRisk: {type: Number, default: 0}, 35 | dailyMaxProfit: {type: Number, default: 0}, 36 | closePositions: {type: Boolean, default: false}, 37 | }, 38 | maxStopLoss: {type: Number, default: 0}, 39 | removedState: {type: Boolean, default: false}, 40 | server: {type: String, default: "MT4"}, 41 | demo: {type: Boolean, default: false}, 42 | symbol: {type: String, default: 'EURUSD'}, 43 | currency: {type: String, default: 'USD'}, 44 | leverage: {type: Number, default: 1}, 45 | tradeVolume: {type: Number, default: 0.01}, 46 | stopLoss: {type: String, default: "0"}, 47 | takeProfit: {type: String, default: "0"}, 48 | balance: {type: Number, required: true}, 49 | profit: {type: Number, default: 0}, 50 | dailyProfit: {type: Number, default: 0}, 51 | pendingOrder: { 52 | buyLimit: Number, 53 | buyStop: Number, 54 | sellLimit: Number, 55 | sellStop: Number 56 | }, 57 | drawDown: {type: Number, default: undefined}, 58 | timeFrame: {type: String, default: "1m"}, 59 | closeVolume: {type:Number, default: 0}, 60 | // isPendingOrder: {type: Boolean, default: false}, 61 | closeAll: {type: String, default: 'Nothing'}, 62 | specificPrice: { 63 | breakEven: {type: Boolean, default: false}, 64 | moveStopLoss: {type: Number, default: 0}, 65 | moveTakeProfit: {type: Number, default: 0}, 66 | entryPoint: {type:Number, default: 0} 67 | }, 68 | isStopLoss: {type: Boolean, default:true}, 69 | trailing: {type:Boolean, default: false}, 70 | lotSize: {type: Number, default: 100000} 71 | // accountId: {type: String, default: ''}, 72 | // }], 73 | }); 74 | 75 | const Strategy = mongoose.model("Strategy", schema); // Changed model name to "Strategy" 76 | return Strategy; 77 | }; 78 | 79 | 80 | 81 | // description: {type: String, default: ''}, 82 | // commissionScheme: { 83 | // type: {type:String, default: ''}, 84 | // billingPeriod: { type: String, default: 'week' }, 85 | // commissionRate: { type: Number, default: 0 } 86 | // }, 87 | // platformCommissionRate: {type: Number, default: undefined}, 88 | // reduceCorrelations: {type: String, default: undefined}, 89 | // newsFilter: { 90 | // breakingNewsFilter: { 91 | // priorities: [], 92 | // closePositionTimeGapInMinutes: Number, 93 | // openPositionFollowingTimeGapInMinutes: Number 94 | // }, 95 | // calendarNewsFilter: { 96 | // priorities: [], 97 | // closePositionTimeGapInMinutes: Number, 98 | // openPositionPrecedingTimeGapInMinutes: Number, 99 | // openPositionFollowingTimeGapInMinutes: Number 100 | // } 101 | // }, 102 | // symbolMapping: [{ 103 | // to: String, 104 | // from: String 105 | // }], 106 | // tradeSizeScaling: { 107 | // mode: String, 108 | // tradeVolumne: Number, 109 | // riskFraction: Number, 110 | // forceTinyTrades: Boolean, 111 | // maxRiskCoefficient: Number, 112 | // expression: String 113 | // }, 114 | // magicFilter: { 115 | // included: [String], 116 | // excluded: [String] 117 | // }, 118 | // equityCurveFilter: { 119 | // period: Number, 120 | // timeframe: String 121 | // }, 122 | // drawdownFilter: { 123 | // maxRelativeDrawdown: Number, 124 | // maxAbsoluteDrawdown: Number, 125 | // action: String 126 | // }, 127 | // symbolsTrade: [String], 128 | // timeSettings: { 129 | // maxRelativeDrawdown: Number, 130 | // maxAbsoluteDrawdown: Number, 131 | // expirePendingOrderSignals: Boolean 132 | // }, 133 | // closeOnRemovalMode: String, -------------------------------------------------------------------------------- /socket_server.js: -------------------------------------------------------------------------------- 1 | // // Node.js TCP Server Code 2 | // // This code receives data from an MQL4 client 3 | 4 | // const net = require('net'); 5 | // const mongoose = require('mongoose'); 6 | // const port = 5555; 7 | // const host = '127.0.0.1'; 8 | 9 | // const server = net.createServer(); 10 | 11 | // // const db = mongoose.connection; 12 | // const db = require("./app/models"); 13 | // const { json } = require('express'); 14 | // const { send } = require('process'); 15 | // const TradingSignal = db.tradingSignals; 16 | // const ExternalTradingSignal = db.externalSignals; 17 | // const Strategy = db.strategies; 18 | 19 | // server.listen(port, host, () => { 20 | // console.log('TCP Server is running on port ' + port + '.'); 21 | // }); 22 | 23 | // // Array to store all connected clients 24 | // let sockets = []; 25 | 26 | // // Handle incoming data from clients 27 | // server.on('connection', function(sock) { 28 | // console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort); 29 | 30 | // // Add the new client socket to the array 31 | // sockets.push(sock); 32 | 33 | // // Receive data from the client 34 | // sock.on('data', async function(data) { 35 | // console.log('DATA ' + sock.remoteAddress + ': ' + data); 36 | // const {accountId, time, symbol, type, side, openPrice, positionId, stopLoss, takeProfit, signalVolume, server, timeFrame, leverage, lotSize} = JSON.parse(data); 37 | // console.log('accountId', accountId) 38 | // const followers = await Strategy.find({accountId: accountId, server: server}) 39 | // console.log(followers) 40 | // let sendingData = []; 41 | // await followers.forEach(async item => { 42 | // const tradingSignal = new TradingSignal({accountId: accountId}); 43 | // const isTradigSignal = true; 44 | // tradingSignal.subscriberId = item.slaveaccountId; 45 | // console.log('subscriberId', item) 46 | // tradingSignal.positionId = positionId; 47 | // tradingSignal.subscriberPositionId = positionId; 48 | // tradingSignal.timeFrame = timeFrame; 49 | // if (item.symbolFilter.include) { 50 | // if (item.symbolFilter.include.includes(symbol)) tradingSignal.symbol = symbol; 51 | // else isTradigSignal= false; 52 | // } 53 | // if (item.symbolFilter.exclude) { 54 | // if (!item.symbolFilter.exclude.includes(symbol)) tradingSignal.symbol = symbol; 55 | // else isTradigSignal= false; 56 | // } 57 | // // if (item.symbolMapping.from === symbol) tradingSignal.symbol = item.symbolMapping.to; 58 | // // else tradingSignal.symbol = symbol; 59 | // tradingSignal.time = Date(time); 60 | // tradingSignal.type = type; 61 | // if (item.reverse) { 62 | // if (side === "buy") tradingSignal.side = 'sell'; 63 | // else if(side ==="sell") tradingSignal.side = 'buy'; 64 | // } 65 | // else { 66 | // if (side === "buy") tradingSignal.side = 'buy'; 67 | // else tradingSignal.side = 'sell'; 68 | // } 69 | // tradingSignal.openPrice = openPrice; 70 | // if (item.copyStopLoss){ 71 | // if ((openPrice + item.maxStopLoss)>stopLoss) tradingSignal.stopLoss = openPrice + item.maxStopLoss; 72 | // else tradingSignal.stopLoss = stopLoss 73 | // } 74 | // else tradingSignal.stopLoss = openPrice + item.maxStopLoss; 75 | // if (item.copyTakeProfit) { 76 | // if ((openPrice + item.maxTakeProfit)item.maxLeverage) tradingSignal.leverage = item.maxLeverage; 82 | // else tradingSignal.leverage = leverage; 83 | // // if (item.minTradeVolume>signalVolume) tradingSignal.signalVolume = item.minTradeVolume; 84 | // // else if (item.maxTradeVolume", subVolume); 97 | // // Adjusting subscriber volume based on riskLimit 98 | // if (item.riskLimits.maxAbsoluteRisk > item.riskLimits.maxRelativeRisk) { 99 | // // If maxAbsoluteRisk is higher than maxRelativeRisk 100 | // if (subVolume > item.riskLimits.maxAbsoluteRisk) { 101 | // // Decrease subscriber volume if it exceeds maxAbsoluteRisk 102 | // subVolume = item.riskLimits.maxAbsoluteRisk; 103 | // console.log("Adjusted subscriber volume to maxAbsoluteRisk"); 104 | // } 105 | // } else if (item.riskLimits.maxAbsoluteRisk < item.riskLimits.maxRelativeRisk) { 106 | // // If maxRelativeRisk is higher than maxAbsoluteRisk 107 | // if (subVolume > item.riskLimits.maxRelativeRisk) { 108 | // // Decrease subscriber volume if it exceeds maxRelativeRisk 109 | // subVolume = item.riskLimits.maxRelativeRisk; 110 | // console.log("Adjusted subscriber volume to maxRelativeRisk"); 111 | // } 112 | // } 113 | // tradingSignal.subscriberVolume = subVolume; 114 | // sendingData.push(tradingSignal) 115 | // // console.log("tradingSignal----------------------->", sendingData); 116 | // await tradingSignal.save(); 117 | // // res.write(tradingSignalString); 118 | // }) 119 | // console.log(sendingData) 120 | // // Broadcast the received data to all connected clients 121 | // const sendingJsonData = JSON.stringify({'out': sendingData}) 122 | // sockets.forEach(function(client) { 123 | // if (client !== sock) { 124 | // client.write(sendingJsonData); 125 | // } 126 | // }); 127 | // }); 128 | 129 | // // Handle client disconnection 130 | // sock.on('close', function(data) { 131 | // let index = sockets.findIndex(function(o) { 132 | // return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort; 133 | // }); 134 | // if (index !== -1) sockets.splice(index, 1); 135 | // console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort); 136 | // }); 137 | // }); 138 | 139 | 140 | // // const net = require('net'); 141 | 142 | // // const bufferSize = 326582; 143 | // // const host = '127.0.0.1'; 144 | // // const port = 5555; 145 | 146 | // // while (true) { 147 | // // const sock = new net.Socket(); 148 | // // // sock.setTimeout(30000); 149 | // // sock.connect(port, host, () => { 150 | // // sock.on('data', (data) => { 151 | // // const dataString = data.toString('ascii'); 152 | // // const dataArray = dataString.split('\n'); 153 | // // console.log('data', dataArray); 154 | // // }); 155 | // // }); 156 | 157 | // // sock.on('error', (err) => { 158 | // // console.error('Socket error:', err); 159 | // // sock.destroy(); 160 | // // }); 161 | 162 | // // sock.on('timeout', () => { 163 | // // console.log('Socket timed out'); 164 | // // sock.end(); 165 | // // }); 166 | 167 | // // sock.on('close', () => { 168 | // // console.log('Socket closed'); 169 | // // }); 170 | // // } 171 | 172 | // const net = require('net'); 173 | 174 | // let newOrder = []; 175 | // const host = 'ec2-54-11-191-126.compute-1.amazonaws.com'; 176 | // const HOST = '0.0.0.0'; 177 | 178 | 179 | 180 | // function compareArrays(arr1, arr2) { 181 | // if (arr1.length !== arr2.length) return false; 182 | // return arr1.every((value, index) => value === arr2[index]); 183 | // } 184 | // // Function to connect to the TCP server and handle data 185 | // function connectToServer() { 186 | // const client = new net.Socket(); 187 | 188 | // client.connect(5555, HOST, () => { 189 | // // console.log('Connected to TCP server'); 190 | // }); 191 | 192 | // client.on('data', (data) => { 193 | // const orderData = data.toString('ascii').split('\n') 194 | // if (!compareArrays(newOrder, orderData)) { 195 | // newOrder = orderData; 196 | // console.log('newOrder', newOrder) 197 | // } 198 | // client.end() 199 | // }); 200 | // client.on('error', (err) => { 201 | // // console.error('Socket connection error:', err); 202 | // }); 203 | 204 | // client.on('close', () => { 205 | // console.log('Connection closed'); 206 | // // Remove all event listeners and destroy the client socket 207 | // client.removeAllListeners(); 208 | // client.destroy(); 209 | // }); 210 | // } 211 | // let timers = 0; 212 | // // Set interval to connect to the server every 50 seconds 213 | // const interval = setInterval(() => { 214 | // connectToServer(); 215 | // }, 500); 216 | 217 | // // Clear the interval after a specified time (e.g., 10 minutes) 218 | // // setTimeout(() => { 219 | // // clearInterval(interval); 220 | // // console.log('Interval cleared after 10 minutes'); 221 | // // }, 60000); 222 | 223 | const net = require('net'); 224 | 225 | const server = net.createServer((socket) => { 226 | console.log('Client connected'); 227 | 228 | socket.on('data', (data) => { 229 | console.log('Received data:', data.toString()); 230 | // socket.write('Hello from Node.js server!'); 231 | }); 232 | 233 | socket.on('end', () => { 234 | console.log('Client disconnected'); 235 | }); 236 | }); 237 | 238 | server.listen(5555, '0.0.0.0', () => { 239 | console.log('TCP server listening on port 5555'); 240 | }); 241 | -------------------------------------------------------------------------------- /app/controllers/history.controller.js: -------------------------------------------------------------------------------- 1 | const jwtEncode = require('jwt-encode') 2 | const db = require("../models"); 3 | const Transaction = db.transactionfields; 4 | const secret = 'secret'; 5 | const crypto = require('crypto'); 6 | const { type } = require('os'); 7 | 8 | 9 | function generateRandomString(length) { 10 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 11 | let result = ""; 12 | const charactersLength = characters.length; 13 | 14 | for (let i = 0; i < length; i++) { 15 | const randomIndex = crypto.randomInt(0, charactersLength); 16 | result += characters.charAt(randomIndex); 17 | } 18 | 19 | return result; 20 | } 21 | //Generate New Strategy 22 | exports.saveTransactions = async (req, res) => { 23 | try { 24 | // const randomString = generateRandomString(4); 25 | const randomString = "2235076747:close" 26 | const isTransaction = await Transaction.findOne({ id:randomString }); 27 | console.log('isTransaction------->', isTransaction); 28 | if (!(isTransaction)) { 29 | const actionTime = new Date("2022-07-22T13:48:22") 30 | const subscriberId = '105646d8-8c97-4d4d-9b74-413bd66cd4ed'; 31 | const subscriberUser = [ 32 | { 33 | id: "5013f1322ae00d69167803d959e9f7dc", 34 | name: "Bruce Wayne", 35 | strategies: [ 36 | { 37 | id: "KiTn", 38 | name: "Test Strategy" 39 | }, 40 | { 41 | id: "dWBM", 42 | name: "Test Strategy" 43 | }, 44 | ] 45 | } 46 | ]; 47 | const demo = true; 48 | const providerUser = { 49 | id: "5013f1322ae00d69167803d959e9f7dc", 50 | name: "Clark Kent", 51 | }; 52 | const strategy = { 53 | id: "dWBM", 54 | name: "Test Strategy", 55 | }; 56 | const positionId = "+apNW3"; 57 | const slavePositionId = "+apMW3"; 58 | const improvement = 0; 59 | const providerCommission = 0.01; 60 | const platformCommission = 0.05; 61 | const incomingProviderCommission = 0; 62 | const incomingPlatformCommission = 0; 63 | const quantity = 0; 64 | const lotPrice = 121569.99999999999; 65 | const tickPrice = 1.2157; 66 | const amount = 241924.2999999999996; 67 | const commission = -3.98; 68 | const swap = 0.06; 69 | const profit = -55.72; 70 | const metrics = { 71 | tradeCopyingLatency: 261, 72 | tradeCopyingSlippageInBasisPoints: -0.24676531795690237, 73 | mtAndBrokerSignalLatency: 72, 74 | tradeAlgorithmLantency: 35, 75 | mtAndBrokertradeLatency: 176, 76 | } 77 | const newTransaction = new Transaction({ 78 | id:randomString, 79 | time: actionTime, 80 | subscriberId: subscriberId, 81 | subscriberUser: subscriberUser, 82 | demo: demo, 83 | providerUser: providerUser, 84 | strategy: strategy, 85 | positionId: positionId, 86 | slavePositionId: slavePositionId, 87 | improvement: improvement, 88 | providerCommission: providerCommission, 89 | platformCommission: platformCommission, 90 | incomingPlatformCommission: incomingPlatformCommission, 91 | incomingProviderCommission: incomingProviderCommission, 92 | quantity: quantity, 93 | lotPrice: lotPrice, 94 | tickPrice: tickPrice, 95 | amount: amount, 96 | commission: commission, 97 | swap: swap, 98 | profit: profit, 99 | metrics: metrics, 100 | }); 101 | console.log('newTransaction------->', newTransaction); 102 | await newTransaction.save(); 103 | console.log('saved') 104 | return res.status(200).json({id: randomString}); 105 | } 106 | else { 107 | console.log('ddd') 108 | return res.status(404).json({message: "Error! Faild to generate Strategy id."}); 109 | } 110 | } catch (e) { 111 | console.log(e) 112 | res.status(500).json({message: 'An Error Occurred'}) 113 | } 114 | } 115 | //Get Providede Transactions 116 | exports.providedTransaction = async (req, res) => { 117 | try { 118 | const {from, till, strategyId, subscriberId, offset, limit} = req.query; 119 | let fromDate = new Date('0000-01-01T00:00:00'); 120 | if (from) { 121 | fromDate = new Date(from); 122 | } 123 | let tillDate = new Date('9999-12-31T23:59:59'); 124 | if (till) { 125 | tillDate = new Date(till); 126 | } 127 | let offset1 = 0; 128 | if (offset) { 129 | offset1 = offset; 130 | } else { 131 | offset1 = 0; 132 | } 133 | let limit_number = 1000; 134 | if (limit) { 135 | console.log('limit_number') 136 | limit_number = limit; 137 | } else { 138 | limit_number = 1000; 139 | } 140 | const skipValue = offset1 * limit_number; 141 | console.log('request------------>', from, till, strategyId, subscriberId, offset1, limit_number, skipValue) 142 | // console.log(fromDate, tillDate)const searchResult = {}; 143 | if (fromDate && tillDate) { 144 | 145 | if (strategyId && strategyId.length > 0 && subscriberId && subscriberId.length > 0) { 146 | console.log('perfect', subscriberId); 147 | 148 | const subscriberIdArray = Array.isArray(subscriberId) ? subscriberId : [subscriberId]; 149 | const strategyIdArray = Array.isArray(strategyId) ? strategyId : [strategyId]; 150 | console.log(typeof(strategyIdArray), strategyIdArray) 151 | searchResult = await Transaction.find({ 152 | time: { 153 | $gte: fromDate, 154 | $lt: tillDate 155 | }, 156 | 'strategy.id': { $in: strategyIdArray }, 157 | subscriberId: { $in: subscriberIdArray } 158 | }).skip(skipValue).limit(limit_number); 159 | console.log(searchResult) 160 | } else if (strategyId && strategyId.length > 0) { 161 | console.log('wonderful'); 162 | searchResult = await Transaction.find({ 163 | time: { 164 | $gte: fromDate, 165 | $lt: tillDate, 166 | }, 167 | 'strategy.id': { $in: strategyId }, 168 | }).skip(skipValue).limit(limit_number); 169 | } else if (subscriberId && subscriberId.length > 0) { 170 | console.log('dirty'); 171 | searchResult = await Transaction.find({ 172 | time: { 173 | $gte: fromDate, 174 | $lt: tillDate, 175 | }, 176 | subscriberId: { $in: subscriberId }, 177 | }).skip(skipValue).limit(limit_number); 178 | } else { 179 | console.log('break'); 180 | searchResult = await Transaction.find({ 181 | time: { 182 | $gte: fromDate, 183 | $lt: tillDate, 184 | }, 185 | }).skip(skipValue).limit(limit_number); 186 | } 187 | 188 | return res.status(200).json(searchResult); 189 | } 190 | } catch (e) { 191 | res.status(500).json({message: 'An Error Occurred', error: e}) 192 | } 193 | } 194 | 195 | //Get Subscription Transactions 196 | exports.subscriptionTransaction = async (req, res) => { 197 | try { 198 | const {from, till, strategyId, subscriberId, offset, limit} = req.query; 199 | let fromDate = new Date('0000-01-01T00:00:00'); 200 | if (from) { 201 | fromDate = new Date(from); 202 | } 203 | let tillDate = new Date('9999-12-31T23:59:59'); 204 | if (till) { 205 | tillDate = new Date(till); 206 | } 207 | let offset1 = 0; 208 | if (offset) { 209 | offset1 = offset; 210 | } else { 211 | offset1 = 0; 212 | } 213 | let limit_number = 1000; 214 | if (limit) { 215 | console.log('limit_number') 216 | limit_number = limit; 217 | } else { 218 | limit_number = 1000; 219 | } 220 | const skipValue = offset1 * limit_number; 221 | console.log('request------------>', from, till, strategyId, subscriberId, offset1, limit_number, skipValue) 222 | // console.log(fromDate, tillDate)const searchResult = {}; 223 | if (fromDate && tillDate) { 224 | 225 | if (strategyId && strategyId.length > 0 && subscriberId && subscriberId.length > 0) { 226 | console.log('perfect', subscriberId); 227 | 228 | const subscriberIdArray = Array.isArray(subscriberId) ? subscriberId : [subscriberId]; 229 | const strategyIdArray = Array.isArray(strategyId) ? strategyId : [strategyId]; 230 | console.log(typeof(strategyIdArray), strategyIdArray) 231 | searchResult = await Transaction.find({ 232 | time: { 233 | $gte: fromDate, 234 | $lt: tillDate 235 | }, 236 | 'strategy.id': { $in: strategyIdArray }, 237 | subscriberId: { $in: subscriberIdArray } 238 | }).skip(skipValue).limit(limit_number); 239 | console.log(searchResult) 240 | } else if (strategyId && strategyId.length > 0) { 241 | console.log('wonderful'); 242 | searchResult = await Transaction.find({ 243 | time: { 244 | $gte: fromDate, 245 | $lt: tillDate, 246 | }, 247 | 'strategy.id': { $in: strategyId }, 248 | }).skip(skipValue).limit(limit_number); 249 | } else if (subscriberId && subscriberId.length > 0) { 250 | console.log('dirty'); 251 | searchResult = await Transaction.find({ 252 | time: { 253 | $gte: fromDate, 254 | $lt: tillDate, 255 | }, 256 | subscriberId: { $in: subscriberId }, 257 | }).skip(skipValue).limit(limit_number); 258 | } else { 259 | console.log('break'); 260 | searchResult = await Transaction.find({ 261 | time: { 262 | $gte: fromDate, 263 | $lt: tillDate, 264 | }, 265 | }).skip(skipValue).limit(limit_number); 266 | } 267 | 268 | return res.status(200).json(searchResult); 269 | } 270 | } catch (e) { 271 | res.status(500).json({message: 'An Error Occurred', error: e}) 272 | } 273 | } 274 | 275 | //Get Strategy Transaction Streams 276 | exports.strategyTransactionStream = async (req, res) => { 277 | try { 278 | const {startTime, limit} = req.query; 279 | const strategyId = req.params.strategyId; 280 | let fromDate = new Date('0000-01-01T00:00:00'); 281 | if (startTime) { 282 | fromDate = new Date(startTime); 283 | } 284 | let limit_number = 1000; 285 | if (limit) { 286 | console.log('limit_number') 287 | limit_number = limit; 288 | } else { 289 | limit_number = 1000; 290 | } 291 | console.log(fromDate) 292 | if (fromDate) { 293 | searchResult = await Transaction.find({ 294 | time: { 295 | $gte: fromDate, 296 | }, 297 | 'strategy.id': strategyId, 298 | }); 299 | const isStrategyId = await Transaction.findOne({ 300 | 'strategy.id': strategyId 301 | }) 302 | if ( isStrategyId ) { 303 | return res.status(200).json(searchResult); 304 | } 305 | else { 306 | return res.status(404).json({message: "StrategyId not found"}) 307 | } 308 | } 309 | } catch (e) { 310 | res.status(500).json({message: 'An Error Occurred', error: e}) 311 | } 312 | } 313 | 314 | 315 | //Get Subscriber Transaction Streams 316 | exports.subscriberTransactionstream = async (req, res) => { 317 | try { 318 | const {startTime, limit} = req.query; 319 | const subscriberId = req.params.subscriberId; 320 | let fromDate = new Date('0000-01-01T00:00:00'); 321 | if (startTime) { 322 | fromDate = new Date(startTime); 323 | } 324 | let limit_number = 1000; 325 | if (limit) { 326 | console.log('limit_number', subscriberId) 327 | limit_number = limit; 328 | } else { 329 | limit_number = 1000; 330 | } 331 | console.log(fromDate) 332 | if (fromDate) { 333 | searchResult = await Transaction.find({ 334 | time: { 335 | $gte: fromDate, 336 | }, 337 | subscriberId: subscriberId, 338 | }); 339 | const isSubscriberId = await Transaction.findOne({ 340 | subscriberId: subscriberId 341 | }) 342 | if ( isSubscriberId ) { 343 | return res.status(200).json(searchResult); 344 | } 345 | else { 346 | return res.status(404).json({message: "SubscriberId not found"}) 347 | } 348 | } 349 | } catch (e) { 350 | res.status(500).json({message: 'An Error Occurred', error: e}) 351 | } 352 | } -------------------------------------------------------------------------------- /EA_Deploy/master.mq4: -------------------------------------------------------------------------------- 1 | //+------------------------------------------------------------------+ 2 | //| master.mq4 | 3 | //| Copyright 2024, MetaQuotes Ltd. | 4 | //| https://www.mql5.com | 5 | //+------------------------------------------------------------------+ 6 | #property copyright "Copyright 2024, MetaQuotes Ltd." 7 | #property link "https://www.mql5.com" 8 | #property version "1.00" 9 | #property strict 10 | 11 | //+------------------------------------------------------------------+ 12 | //|Include socket library, asking for event handling | 13 | //+------------------------------------------------------------------+ 14 | 15 | #define SOCKET_LIBRARY_USE_EVENTS 16 | #include 17 | 18 | //+------------------------------------------------------------------+ 19 | //|EA user inputs | 20 | //+------------------------------------------------------------------+ 21 | 22 | input ushort ServerPort = 5555; //Server port 23 | 24 | #define TIMER_FREQUENCY_MS 1000 25 | 26 | // Server socket 27 | ServerSocket * glbServerSocket = NULL; 28 | 29 | // Array of current clients 30 | ClientSocket * glbClients[]; 31 | 32 | // Watch for need to create timer; 33 | bool glbCreatedTimer = false; 34 | 35 | int ordersTotal = 0, marketorderTotal = 0, pendingorderTotal = 0, orderCnt = 0, pendingCnt = 0; 36 | string Orderlist[1000], PendingOrderlist[1000], order_type, orderText, symbol, price, lots, takeprofit, stoploss, type; 37 | double previousStopLoss[1000], previousTakeProfit[1000], previousLots[1000], previousPrice[1000]; 38 | //+------------------------------------------------------------------+ 39 | //| Expert initialization function | 40 | //+------------------------------------------------------------------+ 41 | int OnInit() 42 | { 43 | // If the EA is being reloaded, e.g. because of change of timeframe, 44 | // then we may already have done all the setup. See the 45 | // termination code in OnDeinit. 46 | if (glbServerSocket) { 47 | Print("Reloading EA with existing server socket"); 48 | } else { 49 | // Create the server socket 50 | glbServerSocket = new ServerSocket(ServerPort, false); 51 | if (glbServerSocket.Created()) { 52 | Print("Server socket created"); 53 | 54 | // Note: this can fail if MT4/5 starts up 55 | // with the EA already attached to a chart. Therefore, 56 | // we repeat in OnTick() 57 | glbCreatedTimer = EventSetMillisecondTimer(TIMER_FREQUENCY_MS); 58 | } else { 59 | Print("Server socket FAILED - is the port already in use?"); 60 | } 61 | 62 | ordersTotal = MarketOrder(); 63 | marketorderTotal = MarketOrder(); 64 | Sleep(1000); 65 | pendingorderTotal = PendingOrder(); 66 | Sleep(1000); 67 | ordersTotal += pendingorderTotal; 68 | } 69 | 70 | return(INIT_SUCCEEDED); 71 | } 72 | //+------------------------------------------------------------------+ 73 | //| Expert deinitialization function | 74 | //+------------------------------------------------------------------+ 75 | void OnDeinit(const int reason) 76 | { 77 | switch (reason) { 78 | case REASON_CHARTCHANGE: 79 | // Keep the server socket and all its clients if 80 | // the EA is going to be reloaded because of a 81 | //change to chart symbol or timeframe 82 | break; 83 | 84 | default: 85 | //For any other unload of the EA, delete the 86 | //server socket and all the clients 87 | glbCreatedTimer = false; 88 | 89 | //Delete all clients currently connected 90 | for(int i = 0; i < ArraySize(glbClients); i++) { 91 | delete glbClients[i]; 92 | } 93 | ArrayResize(glbClients, 0); 94 | 95 | //Free the server socket. *VERY* important, or else 96 | //the port number remains in use and un-reusable until 97 | //MT4/5 is shut down 98 | delete glbServerSocket; 99 | glbServerSocket = NULL; 100 | Print("Server socket terminated"); 101 | break; 102 | } 103 | 104 | } 105 | //+------------------------------------------------------------------+ 106 | //| Expert tick function | 107 | //+------------------------------------------------------------------+ 108 | void OnTick() 109 | { 110 | 111 | } 112 | //+------------------------------------------------------------------+ 113 | //| Timer function | 114 | //+------------------------------------------------------------------+ 115 | void OnTimer() 116 | { 117 | 118 | if(MarketOrder() + PendingOrder() == OrdersTotal()) 119 | { 120 | MonitorModifiedOrders(); 121 | MonitorPendingOrders(); 122 | //MonitorCloseOrders(); 123 | } 124 | if(marketorderTotal < MarketOrder()) 125 | { 126 | long max = 0; 127 | marketorderTotal = MarketOrder(); 128 | if(OrderSelect(marketorderTotal-1, SELECT_BY_POS, MODE_TRADES)) 129 | { 130 | if(OrderType() == ORDER_TYPE_BUY) 131 | order_type = "buy"; 132 | if(OrderType() == ORDER_TYPE_SELL) 133 | order_type = "sell"; 134 | if(OrderType() == ORDER_TYPE_BUY || OrderType() == ORDER_TYPE_SELL) 135 | { 136 | symbol = OrderSymbol(); 137 | price = OrderOpenPrice(); 138 | takeprofit = OrderTakeProfit(); 139 | stoploss = OrderStopLoss(); 140 | lots = OrderLots(); 141 | orderText = StringFormat("{\"ordertype\": \"%s\", \"symbol\": \"%s\", \"lots\": \"%s\", \"price\": \"%s\", \"takeprofit\": \"%s\", \"stoploss\": \"%s\"}", order_type, symbol, lots, price, takeprofit, stoploss); 142 | Alert("Market order sent"); 143 | } 144 | } 145 | } 146 | 147 | if(pendingorderTotal < PendingOrder()) 148 | { 149 | ordersTotal = MarketOrder() + PendingOrder(); 150 | pendingorderTotal = PendingOrder(); 151 | Print("OK"); 152 | if(OrderSelect(marketorderTotal + pendingorderTotal - 1, SELECT_BY_POS, MODE_TRADES)) 153 | { 154 | if(OrderType() == ORDER_TYPE_BUY_LIMIT || OrderType() == ORDER_TYPE_BUY_STOP) 155 | order_type = "buy"; 156 | if(OrderType() == ORDER_TYPE_SELL_LIMIT || OrderType() == ORDER_TYPE_SELL_STOP) 157 | order_type = "sell"; 158 | 159 | if(OrderType() == ORDER_TYPE_BUY_STOP) 160 | { 161 | 162 | symbol = OrderSymbol(); 163 | price = OrderOpenPrice(); 164 | lots = OrderLots(); 165 | takeprofit = OrderTakeProfit(); 166 | stoploss = OrderStopLoss(); 167 | type = "stop"; 168 | orderText = StringFormat("{\"ordertype\": \"%s\", \"symbol\": \"%s\", \"lots\": \"%s\", \"price\": \"%s\", \"takeprofit\": \"%s\", \"stoploss\": \"%s\", \"type\": \"%s\"}", order_type, symbol, lots, price, takeprofit, stoploss, type); 169 | Alert("Pending Order Sent"); 170 | } 171 | 172 | if(OrderType() == ORDER_TYPE_BUY_LIMIT) 173 | { 174 | 175 | symbol = OrderSymbol(); 176 | price = OrderOpenPrice(); 177 | lots = OrderLots(); 178 | takeprofit = OrderTakeProfit(); 179 | stoploss = OrderStopLoss(); 180 | type = "limit"; 181 | orderText = StringFormat("{\"ordertype\": \"%s\", \"symbol\": \"%s\", \"lots\": \"%s\", \"price\": \"%s\", \"takeprofit\": \"%s\", \"stoploss\": \"%s\", \"type\": \"%s\"}", order_type, symbol, lots, price, takeprofit, stoploss, type); 182 | Alert("Pending Order Sent"); 183 | } 184 | 185 | if(OrderType() == ORDER_TYPE_SELL_STOP) 186 | { 187 | 188 | symbol = OrderSymbol(); 189 | price = OrderOpenPrice(); 190 | lots = OrderLots(); 191 | takeprofit = OrderTakeProfit(); 192 | stoploss = OrderStopLoss(); 193 | type = "stop"; 194 | orderText = StringFormat("{\"ordertype\": \"%s\", \"symbol\": \"%s\", \"lots\": \"%s\", \"price\": \"%s\", \"takeprofit\": \"%s\", \"stoploss\": \"%s\", \"type\": \"%s\"}", order_type, symbol, lots, price, takeprofit, stoploss, type); 195 | Alert("Pending Order Sent"); 196 | } 197 | 198 | if(OrderType() == ORDER_TYPE_SELL_LIMIT) 199 | { 200 | 201 | symbol = OrderSymbol(); 202 | price = OrderOpenPrice(); 203 | lots = OrderLots(); 204 | takeprofit = OrderTakeProfit(); 205 | stoploss = OrderStopLoss(); 206 | type = "limit"; 207 | orderText = StringFormat("{\"ordertype\": \"%s\", \"symbol\": \"%s\", \"lots\": \"%s\", \"price\": \"%s\", \"takeprofit\": \"%s\", \"stoploss\": \"%s\", \"type\": \"%s\"}", order_type, symbol, lots, price, takeprofit, stoploss, type); 208 | Alert("Pending Order Sent"); 209 | } 210 | 211 | } 212 | } 213 | //Accept any new pending connections 214 | AcceptNewConnections(); 215 | // Process any incoming data on each client socket, 216 | // bearing in mind that HandleSocketIncomingData() 217 | // can delete sockets and reduce the size of the array 218 | // if a socket has been closed 219 | 220 | //for (int i = ArraySize(glbClients) - 1; i >= 0; i--) { 221 | // HandleSocketIncomingData(i); 222 | //} 223 | } 224 | //+------------------------------------------------------------------+ 225 | //+------------------------------------------------------------------+ 226 | //|Accepts new connections on the server socket, creating new 227 | // entries in the glbClients[] array | 228 | //+------------------------------------------------------------------+ 229 | 230 | void AcceptNewConnections() 231 | { 232 | // Keep accepting any pending connections until Accept() returns NULL 233 | ClientSocket * pNewClient = NULL; 234 | do { 235 | pNewClient = glbServerSocket.Accept(); 236 | 237 | if (pNewClient != NULL) { 238 | int sz = ArraySize(glbClients); 239 | ArrayResize(glbClients, sz + 1); 240 | glbClients[sz] = pNewClient; 241 | Print("New client connection"); 242 | pNewClient.Send(orderText+"\n"); 243 | } 244 | 245 | } while (pNewClient != NULL); 246 | } 247 | 248 | //+------------------------------------------------------------------+ 249 | //| | 250 | //+------------------------------------------------------------------+ 251 | int MarketOrder(){ 252 | int marketOrderSize = 0; 253 | for(int i=OrdersTotal()-1; i>=0; i--){ 254 | if(OrderSelect(i, SELECT_BY_POS)){ 255 | if(OrderType() == OP_BUY || OrderType() == OP_SELL){ 256 | marketOrderSize++; 257 | } 258 | } 259 | } 260 | return marketOrderSize; 261 | } 262 | //+------------------------------------------------------------------+ 263 | int PendingOrder() 264 | { 265 | int pendingOrderSize = 0; 266 | for(int mi = OrdersTotal(); mi >= 0; mi--) 267 | { 268 | if(OrderSelect(mi, SELECT_BY_POS)) 269 | { 270 | if(OrderType() == OP_BUYLIMIT || OrderType() == OP_BUYSTOP || OrderType() == OP_SELLLIMIT || OrderType() == OP_SELLSTOP) 271 | { 272 | pendingOrderSize++; 273 | } 274 | } 275 | } 276 | return pendingOrderSize; 277 | } 278 | //+------------------------------------------------------------------+ 279 | //+------------------------------------------------------------------+ 280 | void MonitorModifiedOrders() 281 | { 282 | for(int i = MarketOrder()-1; i>=0; i--) 283 | { 284 | if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) 285 | { 286 | if(previousStopLoss[i] != OrderStopLoss() || previousTakeProfit[i] != OrderTakeProfit()) 287 | { 288 | previousStopLoss[i] = OrderStopLoss(); //send to server 289 | previousTakeProfit[i] = OrderTakeProfit(); // send to server 290 | string modifypositionID = FindOrderID(OrderTicket()); 291 | Print("Order Ticket", OrderTicket(), "tp", previousTakeProfit[i], "sl", previousStopLoss[i]); 292 | 293 | } 294 | } 295 | 296 | } 297 | } 298 | //+------------------------------------------------------------------+ 299 | long FindOrderID(string ticket_number) 300 | { 301 | string results; 302 | for(int j = 0; j < orderCnt; j++) 303 | { 304 | if(Orderlist[j] == ticket_number) 305 | results = Orderlist[j+1]; 306 | } 307 | return results; 308 | 309 | } 310 | //+------------------------------------------------------------------+ 311 | 312 | //+------------------------------------------------------------------+ 313 | void MonitorPendingOrders() 314 | { 315 | for(int j = PendingOrder() + MarketOrder() -1; j >= MarketOrder(); j--) 316 | { 317 | if(OrderSelect(j, SELECT_BY_POS, MODE_TRADES)) 318 | { 319 | if(previousStopLoss[j] != OrderStopLoss() || previousTakeProfit[j] != OrderTakeProfit() || previousLots[j] != OrderLots() || previousPrice[j] != OrderOpenPrice()) 320 | { 321 | previousLots[j] = OrderLots(); 322 | previousPrice[j] = OrderOpenPrice(); 323 | previousStopLoss[j] = OrderStopLoss(); 324 | previousTakeProfit[j] = OrderTakeProfit(); 325 | string modifyorderID = FindPendingID(OrderTicket()); 326 | } 327 | } 328 | } 329 | } 330 | //+------------------------------------------------------------------+ 331 | 332 | //+------------------------------------------------------------------+ 333 | long FindPendingID(string ticket_number) 334 | { 335 | string results; 336 | for(int k = 0; k < pendingCnt; k++) 337 | { 338 | if(PendingOrderlist[k] == ticket_number) 339 | results = PendingOrderlist[k]; 340 | } 341 | 342 | return results; 343 | } -------------------------------------------------------------------------------- /app/controllers/trading.controller.js: -------------------------------------------------------------------------------- 1 | const jwtEncode = require('jwt-encode') 2 | const db = require("../models"); 3 | const TradingSignal = db.tradingSignals; 4 | const ExternalTradingSignal = db.externalSignals; 5 | const Strategy = db.strategies; 6 | const Transaction = db.transactionfields; 7 | const secret = 'secret'; 8 | const crypto = require('crypto'); 9 | const { type } = require('os'); 10 | const tradingExternalSignalModel = require('../models/trading.externalSignal.model'); 11 | const tradingTradingSignalModel = require('../models/trading.tradingSignal.model'); 12 | 13 | 14 | function generateRandomString(length) { 15 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 16 | let result = ""; 17 | const charactersLength = characters.length; 18 | 19 | for (let i = 0; i < length; i++) { 20 | const randomIndex = crypto.randomInt(0, charactersLength); 21 | result += characters.charAt(randomIndex); 22 | } 23 | 24 | return result; 25 | } 26 | 27 | //Generate New Strategy 28 | exports.saveExternalSignals = async (req, res) => { 29 | try { 30 | console.log('unused strategy') 31 | const randomString = generateRandomString(4); 32 | // const randomString = "105646d8-8c97-4d4d-9b74-413bd66cd4ed" 33 | console.log('randomString', randomString) 34 | const isStrategy = await Strategy.findOne({ _id:randomString }); 35 | console.log('isStrategy------->', isStrategy); 36 | if (!isStrategy) { 37 | const newStrategy = new Strategy({ _id:randomString}); 38 | console.log('newStrategy------->', newStrategy); 39 | await newStrategy.save(); 40 | console.log('saved') 41 | return res.status(200).json({id: newStrategy._id}); 42 | } 43 | else { 44 | console.log('404 Error!') 45 | return res.status(404).json({message: "Error! Faild to generate Strategy id."}); 46 | } 47 | } catch (e) { 48 | console.log(e) 49 | res.status(500).json({message: 'An Error Occurred'}) 50 | } 51 | } 52 | 53 | //Generate New Strategy 54 | exports.saveTradingSignals = async (req, res) => { 55 | try { 56 | request = req.body; 57 | console.log(request) 58 | const tradingSignal = new TradingSignal(request); 59 | await tradingSignal.save(); 60 | return res.status(200).json({message: "saved"}); 61 | } catch (e) { 62 | console.log(e) 63 | res.status(500).json({message: 'An Error Occurred'}) 64 | } 65 | } 66 | 67 | exports.getTradingSignals = async (req, res) => { 68 | try { 69 | const subscriberId = req.params.subscriberId; 70 | console.log("subscriberId", subscriberId); 71 | const isSubscriberId = await TradingSignal.find({subscriberId: subscriberId}); 72 | if (isSubscriberId) { 73 | res.status(200).json(isSubscriberId); 74 | } 75 | else res.status(404).json({message: "Subscriber id not founded."}); 76 | } catch (e) { 77 | console.log(e); 78 | return res.status(500).json({message: "An Error Occured"}); 79 | } 80 | } 81 | 82 | //Update existing signals 83 | exports.updateTradingSignals = async (req, res) => { 84 | try { 85 | console.log("update Trading Signals"); 86 | const request = req.body; 87 | const strategyId = req.params.strategyId; 88 | const subscriberId = req.params.subscriberId; 89 | console.log("Id------------>", strategyId); 90 | const item = await TradingSignal.findOne({subscriberId: subscriberId, strategyId: strategyId}); 91 | 92 | if (item) { 93 | console.log("found", item); 94 | TradingSignal.findOneAndUpdate({subscriberId: subscriberId, strategyId: strategyId}, { $set: request }, { new: false }, (err, updatedDocument) => { 95 | if (err) { 96 | // Handle the error, e.g., return an error response 97 | res.status(500).json({ error: err }); 98 | console.log(err); 99 | } else { 100 | console.log("updated", updatedDocument); 101 | // Document updated successfully, return the updated document as the response 102 | res.status(200).json({ message: 'Trading Signals saved Successfully' }); 103 | } 104 | }); 105 | } else { 106 | console.log("strategy not found"); 107 | res.status(404).json({ message: "Trading Signals not found!" }); 108 | } 109 | } catch (e) { 110 | res.status(500).json({ message: 'An Error Occurred', error: e }); 111 | } 112 | } 113 | 114 | //get External Trading Signals 115 | exports.getExternalTradingSignals = async (req, res) => { 116 | try { 117 | const strategyId = req.params.strategyId; 118 | console.log("strategyId", strategyId); 119 | const isstrategyId = await TradingSignal.find({strategyId: strategyId}); 120 | if (isstrategyId) { 121 | res.status(200).json(isstrategyId); 122 | } 123 | else res.status(404).json({message: "Strategy id not founded."}); 124 | } catch (e) { 125 | console.log(e); 126 | return res.status(500).json({message: "An Error Occured"}); 127 | } 128 | } 129 | 130 | exports.updateExternalTradingSignals = async (req, res) => { 131 | try { 132 | console.log("update Trading Signals") 133 | const request = req.body; 134 | const strategyId = req.params.strategyId 135 | const id = req.params.id; 136 | console.log("Id------------>", strategyId) 137 | // console.log("request----------->", request) 138 | const item = await ExternalTradingSignal.findOne({_id: id, strategyId: strategyId}) 139 | // request.riskLimits = Array.isArray(request.riskLimits) ? request.riskLimits : [request.riskLimits] 140 | if(item) { 141 | console.log("found", item) 142 | ExternalTradingSignal.findByIdAndUpdate(id, request, { new: false }, (err, updatedDocument) => { 143 | if (err) { 144 | // Handle the error, e.g., return an error response 145 | res.status(500).json({ error: e }); 146 | console.log(err) 147 | } else { 148 | console.log("updated", updatedDocument) 149 | // Document updated successfully, return the updated document as the response 150 | res.status(200).json({message: 'Trading Signals saved Successfully'}); 151 | } 152 | }); 153 | } 154 | else { 155 | console.log("strategynot font"); 156 | res.status(404).json({message: "Trading Signals not founded!"}) 157 | } 158 | } catch (e) { 159 | res.status(500).json({message: 'An Error Occurred', error: e}) 160 | } 161 | } 162 | 163 | function calculateTradeRisk(riskPercentage, accountBalance) { 164 | // Convert risk percentage to decimal 165 | let riskDecimal = riskPercentage / 100; 166 | 167 | // Calculate trade risk 168 | let tradeRisk = riskDecimal * accountBalance; 169 | 170 | return tradeRisk; 171 | } 172 | 173 | 174 | exports.removeExternalTradingSignals = async (req, res) => { 175 | try { 176 | 177 | } catch(e) { 178 | console.log(e); 179 | res.status(500).json({message: "An Error Occured!"}); 180 | } 181 | } 182 | 183 | // Simulated market data 184 | let marketBuyPrices = { 185 | 'BTCUSDT': 50000, 186 | 'ETHUSDT': 2000, 187 | 'EURUSD': 1.07 188 | }; 189 | // Simulated market data 190 | let marketSellPrices = { 191 | 'BTCUSDT': 50000, 192 | 'ETHUSDT': 2000, 193 | 'EURUSD': 1.06998 194 | }; 195 | let buyPrice; 196 | let sellPrice; 197 | // Function to update market prices 198 | setInterval(() => { 199 | marketBuyPrices = { 200 | 'BTCUSDT': marketBuyPrices['BTCUSDT'] + Math.floor(Math.random() * 100) - 40, 201 | 'ETHUSDT': marketBuyPrices['ETHUSDT'] + Math.floor(Math.random() * 50) - 15, 202 | 'EURUSD': marketBuyPrices['EURUSD'] + Math.random() * 0.000005-0.000002 203 | }; 204 | marketSellPrices = { 205 | 'BTCUSDT': marketBuyPrices['BTCUSDT'] + Math.floor(Math.random() * 100) - 50, 206 | 'ETHUSDT': marketBuyPrices['ETHUSDT'] + Math.floor(Math.random() * 50) - 25, 207 | 'EURUSD': marketBuyPrices['EURUSD'] + Math.random() * 0.000005-0.0000025 208 | }; 209 | }, 1000); // Update market prices every 1 second 210 | 211 | exports.signalProcessing = async (req, res) => { 212 | try { 213 | console.log("signal processing") 214 | const accountId = req.params.accountId; 215 | const {time, symbol, type, side, openPrice, positionId, stopLoss, takeProfit, signalVolume, server, timeFrame, lotSize, demo, slipPage} = req.body; 216 | console.log('accountId', accountId, server) 217 | const followers = await Strategy.find({strategyId: accountId, server: server}) 218 | console.log(followers) 219 | followers.forEach(async item => { 220 | const tradingSignal = new TradingSignal({strategyId: accountId, server: server}); 221 | const isTradigSignal = true; 222 | tradingSignal.subscriberId = item.slaveaccountId; 223 | console.log('subscriberId', tradingSignal.subscriberId) 224 | tradingSignal.positionId = positionId; 225 | tradingSignal.subscriberPositionId = positionId; 226 | tradingSignal.timeFrame = timeFrame; 227 | tradingSignal.demo = demo; 228 | if (item.symbolFilter.include) { 229 | if (item.symbolFilter.include.includes(symbol)) tradingSignal.symbol = symbol; 230 | else isTradigSignal= false; 231 | } 232 | if (item.symbolFilter.exclude) { 233 | if (!item.symbolFilter.exclude.includes(symbol)) tradingSignal.symbol = symbol; 234 | else isTradigSignal= false; 235 | } 236 | if (!item.skipPendingOrders && item.closeAll != "pendingOrder") { 237 | // const pendingOrder = item.pendingOrder; 238 | tradingSignal.type = type; 239 | } 240 | else { 241 | if (type == "market") { 242 | tradingSignal.type = type; 243 | } 244 | else { 245 | isTradigSignal = false; 246 | } 247 | } 248 | // if (item.symbolMapping.from === symbol) tradingSignal.symbol = item.symbolMapping.to; 249 | // else tradingSignal.symbol = symbol; 250 | if (item.reverse) { 251 | if (side === "buy") tradingSignal.side = 'sell'; 252 | else if(side ==="sell") tradingSignal.side = 'buy'; 253 | else tradingSignal.side = side; 254 | } 255 | else { 256 | if (side === "buy") tradingSignal.side = 'buy'; 257 | else if(side ==="sell") tradingSignal.side = 'sell'; 258 | else tradingSignal.side = side; 259 | } 260 | tradingSignal.time = Date(time); 261 | tradingSignal.openPrice = openPrice; 262 | tradingSignal.slippage = slipPage; 263 | if (item.copyStopLoss){ 264 | if (! item.specificPrice.breakEven) { 265 | if(item.specificPrice.moveStopLoss == 0){ 266 | if (item.maxStopLoss>extractNumbers(stopLoss)[0]) tradingSignal.stopLoss = item.maxStopLoss.toString(); 267 | else tradingSignal.stopLoss = stopLoss 268 | } 269 | else { 270 | if ((item.maxStopLoss)>item.specificPrice.moveStopLoss) tradingSignal.stopLoss = item.maxStopLoss.toString(); 271 | else tradingSignal.stopLoss = item.specificPrice.moveStopLoss.toString(); 272 | } 273 | } 274 | else tradingSignal.stopLoss = "0"; 275 | } 276 | else tradingSignal.stopLoss = item.maxStopLoss.toString(); 277 | if (item.copyTakeProfit) { 278 | if (item.specificPrice.moveTakeProfit) { 279 | if (item.maxTakeProfititem.specificPrice.moveTakeProfit) tradingSignal.takeProfit = item.maxTakeProfit.toString(); 284 | else tradingSignal.takeProfit = item.specificPrice.moveTakeProfit.toString(); 285 | } 286 | } 287 | else tradingSignal.takeProfit = item.maxTakeProfit.toString(); 288 | // if (!item.skipPendingOrders) tradingSignal.pendingOrder = pendingOrder; 289 | console.log(item.maxLeverage) 290 | if (item.leverage > item.maxLeverage) {tradingSignal.leverage = item.maxLeverage;} 291 | else tradingSignal.leverage = item.leverage; 292 | // if (item.minTradeVolume>signalVolume) tradingSignal.signalVolume = item.minTradeVolume; 293 | // else if (item.maxTradeVolume item.maxTradeVolume) 308 | tradingSignal.subscriberVolume = item.maxTradeVolume*volumeFactor; 309 | else if (signalVolume < item.minTradeVolume) 310 | tradingSignal.subscriberVolume = item.minTradeVolume*volumeFactor; 311 | else tradingSignal.subscriberVolume = signalVolume*volumeFactor; 312 | console.log("tradingSignal----------------------->", tradingSignal); 313 | await tradingSignal.save(); 314 | const tradingSignalString = JSON.stringify(tradingSignal); 315 | const result = order(subscriberId, symbol, orderType, volume, openPrice, slipPage, stopLoss, takeProfit, comment, accountId) 316 | if (!result) res.status(404).json({error: "Subscriber is already closed"}) 317 | else res.status(200).json({"message": "success"}) 318 | // res.write(tradingSignalString); 319 | }) 320 | // res.end(); 321 | return res.status(200).json({message: "Okay"}) 322 | } catch(e) { 323 | console.log(e); 324 | res.status(500).json({message: "Internal Sever Error!"}) 325 | } 326 | } 327 | 328 | async function setInitialDailyTrade () { 329 | const closeState = await Strategy.find({}); 330 | const interval = setInterval(async () => { 331 | let currentime = new Date(); 332 | closeState.forEach(async item => { 333 | 334 | if (currentime.getHours() === 0 && currentime.getMinutes() === 0 && currentime.getSeconds() === 0) 335 | await Strategy.updateOne(item, {$set: {dailyProfit: 0}}); 336 | // console.log('setdaily', item.dailyProfit) 337 | }) 338 | }, 1000) 339 | 340 | } 341 | setInitialDailyTrade(); 342 | 343 | function extractNumbers(input) { 344 | const str = String(input) 345 | // Use a regular expression to find all real numbers in the string 346 | const numbers = str.match(/\b\d+(\.\d+)?\b/g); 347 | 348 | // Convert the extracted strings to actual numbers 349 | return numbers ? numbers.map(Number) : []; 350 | } 351 | 352 | function buySell (orderType, benefit, tradeExecuted) { 353 | if (orderType == "buy stop" || orderType == "buy limit" || orderType == "buy") { 354 | benefit = (currentPrice - orderPrice) * volume * 100000; 355 | if (executionPrice >= takeProfit) { 356 | tradeExecuted = true; 357 | } else if (executionPrice <= stopLoss) { 358 | tradeExecuted = true; 359 | } 360 | } 361 | else if (orderType == "sell limit" || orderType == "sell stop" || orderType == "sell") { 362 | benefit = (orderPrice-currentPrice) * volume * 100000; 363 | if (executionPrice >= takeProfit) { 364 | tradeExecuted = true; 365 | } else if (executionPrice <= stopLoss) { 366 | tradeExecuted = true; 367 | } 368 | } 369 | 370 | } 371 | 372 | async function order (subscriberId, symbol, orderType, volume, openPrice, slipPage, stopLoss, takeProfit, comment, accountId) { 373 | 374 | let stop_loss = stopLoss; 375 | let take_profit = takeProfit; 376 | let orderPrice; 377 | // Set the first market price 378 | if (!orderPrice) { 379 | if (orderType=="buy" || orderType=="sell") { 380 | switch (orderType) { 381 | case "sell": 382 | case "sell limit": 383 | case "sell stop": 384 | orderPrice = marketBuyPrices[symbol]; 385 | // stop_loss = orderPrice - stopLoss; 386 | // take_profit = orderPrice + takeProfit; 387 | break; 388 | default: 389 | orderPrice = marketSellPrices[symbol]; 390 | // stop_loss = orderPrice - stopLoss; 391 | // take_profit = orderPrice + takeProfit; 392 | } 393 | } 394 | else orderPrice = openPrice 395 | } 396 | let benefit = 0; 397 | let cnt1 = 0; 398 | let cnt2 = 0; 399 | console.log(subscriberId) 400 | const strategy = await Strategy.findOne({strategyId: accountId, subscriberId: subscriberId, type:orderType, symbol: symbol}); 401 | if(strategy.removedState === false) { 402 | const tradSignal = await TradingSignal.findOne({strategyId: accountId, subscriberId: subscriberId, type:orderType, symbol: symbol}); 403 | const transaction = new Transaction({accountId: Strategy.accountId, subscriberId: subscriberId, type:orderType, symbol: symbol, tickPrice: orderPrice, amount: volume, profit: benefit, closed: false}); 404 | const interval = setInterval(async () => { 405 | console.log(tradSignal.stopLoss) 406 | stop_loss = tradSignal.stopLoss; 407 | take_profit = tradSignal.takeProfit; 408 | let trailing = strategy.trailing; 409 | let executionPrice; 410 | let tradeExecuted = false; 411 | let previousPrice = orderPrice 412 | let currentPrice; 413 | let currentDate = new Date(); 414 | if (currentDate previousPrice ) { 472 | if (orderType == "buy" || orderType == "buy limit" || orderType == "buy stop"){ 473 | stop_loss = currentPrice + strategy.stopLoss; 474 | take_profit = currentPrice - strategy.takeProfit; 475 | } 476 | else { 477 | stop_loss = currentPrice - strategy.stopLoss; 478 | take_profit = currentPrice + strategy.takeProfit; 479 | } 480 | previousPrice = currentPrice; 481 | } 482 | } 483 | 484 | //when pending order, 485 | if (currentPrice <= orderPrice ) {cnt1 = cnt1; cnt2 = 1;} 486 | else {cnt1 = 1; cnt2 = cnt2;} 487 | 488 | if (orderType === "buy limit" || orderType === "sell stop") { 489 | if (cnt1 == 0 ) benefit =0; 490 | else { 491 | buySell(orderType, benefit, tradeExecuted) 492 | } 493 | tradSignal.profit = benefit; 494 | tradSignal.save() 495 | } 496 | else if (orderType === "buy stop" || orderType === "sell limit") { 497 | if (cnt2 == 0 ) benefit =0; 498 | else { 499 | buySell(orderType, benefit, tradeExecuted) 500 | } 501 | tradSignal.profit = benefit; 502 | tradSignal.save() 503 | } 504 | else { 505 | buySell(orderType, benefit, tradeExecuted) 506 | tradSignal.profit = benefit; 507 | tradSignal.save() 508 | } 509 | } 510 | else { 511 | tradeExecuted = true; 512 | } 513 | await transaction.updateOne({accountId: accountId, subscriberId: subscriberId, symbol: symbol}, {$set: {profit: benefit}}); 514 | await Strategy.updateOne({accountId: accountId, subscriberId: subscriberId, symbol: symbol}, {$set: {profit: strategy.profit+tradSignal.profit, dailyProfit: strategy.dailyProfit + benefit}}); 515 | if (strategy.profit >= strategy.riskLimits.maxAbsoluteProfit || strategy.profit <= strategy.riskLimits.maxAbsoluteRisk) { 516 | tradeExecuted = true; 517 | await Strategy.updateOne({accountId: accountId, subscriberId: subscriberId, symbol: symbol}, {$set: {"removedState": true}}); 518 | await transaction.updateOne({accountId: accountId, subscriberId: subscriberId, symbol: symbol}, {$set: {closed: true}}); 519 | } 520 | if (strategy.dailyProfit >= strategy.riskLimits.dailyMaxProfit || strategy.dailyProfit <= strategy.riskLimits.dailyMaxRisk) { 521 | tradeExecuted = true; 522 | await transaction.updateOne({accountId: accountId, subscriberId: subscriberId, symbol: symbol}, {$set: {closed: true}}); 523 | } 524 | if (tradeExecuted) { 525 | // Simulate the trade execution 526 | clearInterval(interval); 527 | console.log(`Executed a buy order for ${symbol} at ${currentPrice} with a volume of ${volume}. Benefit: ${benefit}`); 528 | return true; 529 | } // benefit += 1; 530 | // if(benefit == 10) clearInterval(interval); 531 | },100) 532 | } 533 | else { 534 | return false; 535 | } 536 | } 537 | 538 | exports.orders = async (req, res) => { 539 | try { 540 | console.log("orders"); 541 | const subscriberId = req.params.subscriberId; 542 | const {symbol, orderType, volume, openPrice, slipPage, stopLoss, takeProfit, comment, accountId} = req.body; 543 | if (!symbol || !orderType || !volume || !slipPage || !stopLoss || !takeProfit) { 544 | return res.status(400).json({ error: 'Missing required parameters' }); 545 | } 546 | 547 | // if (orderType !== 'buy') { 548 | // return res.status(400).json({ error: 'Only buy orders are supported' }); 549 | // } 550 | 551 | if (volume <= 0) { 552 | return res.status(400).json({ error: 'Volume must be a positive number' }); 553 | } 554 | 555 | if (slipPage < 0 || stopLoss < 0 || takeProfit < 0) { 556 | return res.status(400).json({ error: 'Slippage, stop loss, and take profit must be positive numbers' }); 557 | } 558 | 559 | // Validate input parameters 560 | if (!symbol || !orderType || !volume || !openPrice || !slipPage || !stopLoss || !takeProfit) { 561 | return res.status(400).json({ error: 'Missing required parameters' }); 562 | } 563 | console.log('subscriberId', subscriberId) 564 | const result = order(subscriberId, symbol, orderType, volume, openPrice, slipPage, stopLoss, takeProfit, comment, accountId) 565 | if (!result) res.status(404).json({error: "Subscriber is already closed"}) 566 | else res.status(200).json({"message": "success"}) 567 | } catch (e) { 568 | console.log(e); 569 | return res.status(500).json({message: "Internal Server Error!"}) 570 | } 571 | } 572 | 573 | -------------------------------------------------------------------------------- /app/controllers/configuration.controller.js: -------------------------------------------------------------------------------- 1 | const jwtEncode = require('jwt-encode') 2 | const db = require("../models"); 3 | const Strategy = db.strategies 4 | const PortfolioStrategy = db.portfolioStrategies 5 | const Master = db.masters 6 | const Subscriber = db.subscribers 7 | const secret = 'secret'; 8 | const crypto = require('crypto'); 9 | const exp = require('constants'); 10 | const { request } = require('http'); 11 | const { updateExternalTradingSignals } = require('./trading.controller'); 12 | const { Stream } = require('stream'); 13 | const { getAccountPath } = require('ethers/lib/utils'); 14 | 15 | function generateRandomString(length) { 16 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 17 | let result = ""; 18 | const charactersLength = characters.length; 19 | 20 | for (let i = 0; i < length; i++) { 21 | const randomIndex = crypto.randomInt(0, charactersLength); 22 | result += characters.charAt(randomIndex); 23 | } 24 | 25 | return result; 26 | } 27 | 28 | function deleteItemInObjects(array, itemToDelete) { 29 | return array.map(obj => { 30 | // Create a new object to avoid modifying the original object 31 | let newObj = {}; 32 | for (let key in obj._doc) { 33 | if(key !== itemToDelete) 34 | newObj[key] = obj[key]; 35 | } 36 | return newObj; 37 | }); 38 | } 39 | 40 | //Save Master Strategy 41 | exports.saveMasterStrategy = async (req, res) => { 42 | try { 43 | console.log("save master"); 44 | const randomString = generateRandomString(4); 45 | // const randomString = "105646d8-8c97-4d4d-9b74-413bd66cd4ed" 46 | console.log('randomString', randomString) 47 | const isMaster = await Master.findOne({ _id:randomString }); 48 | console.log('isStrategy------->', isMaster); 49 | const request = req.body; 50 | request._id = randomString; 51 | if (!isMaster) { 52 | const newMaster = new Master(request); 53 | console.log('newMaster------------->', newMaster); 54 | await newMaster.save(); 55 | console.log('saved'); 56 | return res.status(200).json({message: "Master Account successfully saved!"}) 57 | } 58 | else { 59 | console.log('404 Errror').json({message: "Error! Already Exist Same Account."}) 60 | } 61 | } catch (e) { 62 | console.log(e) 63 | return res.status(500).json({message: "An Error Occured!"}); 64 | } 65 | } 66 | 67 | //update master Strategy. 68 | exports.updateMasterStrategy = async (req, res) => { 69 | try { 70 | console.log("update Strategy") 71 | const request = req.body; 72 | const accountId = req.params.accountId 73 | console.log("Id------------>", accountId) 74 | // console.log("request----------->", request) 75 | const item = await Master.findOne({_id: accountId}) 76 | // request.riskLimits = Array.isArray(request.riskLimits) ? request.riskLimits : [request.riskLimits] 77 | if(item) { 78 | console.log("found", item) 79 | Master.findByIdAndUpdate(accountId, request, { new: false }, (err, updatedDocument) => { 80 | if (err) { 81 | // Handle the error, e.g., return an error response 82 | res.status(500).json({ error: e }); 83 | console.log(err) 84 | } else { 85 | console.log("updated", updatedDocument) 86 | // Document updated successfully, return the updated document as the response 87 | compareStrategy(accountId); 88 | res.status(200).json({message: 'Master Setting updated Successfully'}); 89 | } 90 | }); 91 | } 92 | else { 93 | console.log("strategynot font"); 94 | res.status(404).json({message: "Strategy not founded!"}) 95 | } 96 | } catch (e) { 97 | res.status(500).json({message: 'An Error Occurred', error: e}) 98 | } 99 | } 100 | 101 | async function compareStrategy (accountId) { 102 | const masterStrategy = await Master.findOne({_id: accountId}); 103 | if (masterStrategy) { 104 | let slaveStrategy = await Strategy.find({strategyId: accountId}); 105 | if (slaveStrategy) { 106 | slaveStrategy.forEach(async item => { 107 | let slave = item; 108 | item.server = masterStrategy.server; 109 | item.demo = masterStrategy.demo; 110 | item.leverage = masterStrategy.leverage; 111 | if(!item.symbolFilter.include == []) { 112 | if (item.symbolFilter.included.includes(masterStrategy.symbol) || !item.symbolFilter.excluded.includes(item.symbol)) { 113 | item.symbol = masterStrategy.symbol; 114 | } 115 | } 116 | else if(!item.symbolFilter.excluded.includes(masterStrategy.symbol)) { 117 | item.symbol = masterStrategy.symbol; 118 | } 119 | if((item.minTradeVolume < masterStrategy.tradeVolume) && (item.maxTradeVolume > masterStrategy.tradeVolume)) item.tradeVolume = masterStrategy.tradeVolume; 120 | else if (item.minTradeVolume > masterStrategy.tradeVolume) item.tradeVolume = item.minTradeVolume; 121 | else if (item.maxTradeVolume < masterStrategy.tradeVolume) item.tradeVolume = item.maxTradeVolume; 122 | if (item.copyStopLoss && masterStrategy.stopLoss < item.maxStopLoss) item.stopLoss = masterStrategy.stopLoss; 123 | if (item.copyTakeProfit) item.takeProfit = masterStrategy.takeProfit; 124 | if (!item.skipPendingOrder) item.pendingOrder = masterStrategy.pendingOrder; 125 | if (masterStrategy.maxTradeRisk <= item.maxTradeRisk) item.maxTradeRisk = masterStrategy.maxTradeRisk; 126 | if (masterStrategy.leverage > item.maxLeverage) item.leverage = item.maxLeverage; 127 | else item.leverage = masterStrategy.leverage 128 | item.currency = masterStrategy.currency; 129 | item.drawDown = masterStrategy.drawDown; 130 | item.timeFrame = masterStrategy.timeFrame; 131 | item.closeVolume = masterStrategy.closeVolume; 132 | item.closeAll = masterStrategy.closeAll; 133 | item.specificPrice = masterStrategy.specificPrice; 134 | item.isStopLoss = masterStrategy.isStopLoss; 135 | await item.save(); 136 | }) 137 | } 138 | } 139 | // else { 140 | // return 141 | // } 142 | } 143 | 144 | // Get Masters 145 | exports.getMasters = async (req, res) => { 146 | try { 147 | // console.log('dddd') 148 | const {limit, offset, includeRemoved} = req.query; 149 | console.log(limit, offset, includeRemoved) 150 | let offset1 = 0; 151 | if (offset) { 152 | offset1 = offset; 153 | } else { 154 | offset1 = 0; 155 | } 156 | let limit_number = 1000; 157 | if (limit) { 158 | console.log('limit_number') 159 | limit_number = limit; 160 | } else { 161 | limit_number = 1000; 162 | } 163 | let includeRemoved1 = "false"; 164 | if (includeRemoved) { 165 | includeRemoved1 = includeRemoved; 166 | } else { 167 | includeRemoved1 = "false"; 168 | } 169 | const skipValue = offset1 * limit_number; 170 | console.log(skipValue, "this is skipvalue-------", includeRemoved1) 171 | if (includeRemoved1 !== "false") { 172 | console.log("true"); 173 | const strategies = await Master.find({}).skip(skipValue).limit(limit_number); 174 | const updateStrategies = deleteItemInObjects(strategies, "removedState"); 175 | res.status(200).json(updateStrategies); 176 | } 177 | else { 178 | console.log("false"); 179 | const strategies = await Strategy.find({removedState: false}).skip(skipValue).limit(limit_number); 180 | const updateStrategies = deleteItemInObjects(strategies, "removedState"); 181 | res.status(200).json(updateStrategies); 182 | } 183 | } catch (e) { 184 | res.status(500).json({message: 'An Error Occurred', error: e}) 185 | } 186 | } 187 | 188 | 189 | //Generate New Strategy 190 | exports.saveSlaveSettings = async (req, res) => { 191 | try { 192 | console.log('unused strategy') 193 | const randomString = generateRandomString(4); 194 | // const randomString = "105646d8-8c97-4d4d-9b74-413bd66cd4ed" 195 | const request = req.body; 196 | console.log('randomString', randomString) 197 | const isStrategy = await Strategy.findOne({ _id:randomString }); 198 | console.log('isStrategy------->', isStrategy); 199 | if (!isStrategy) { 200 | request._id = randomString; 201 | request.slaveaccountId = req.user.accountId; 202 | request.name = req.user.name; 203 | const newStrategy = new Strategy(request); 204 | console.log('newStrategy------->', newStrategy); 205 | await newStrategy.save(); 206 | console.log('saved ->', newStrategy) 207 | const updateStrategy = await Strategy.findOne({ _id: randomString }) 208 | const masteraccount = await Master.findOne({strateyId: updateStrategy.strategyId, server: updateStrategy.server}); 209 | console.log(updateStrategy, masteraccount) 210 | updateStrategy.demo = masteraccount.demo; 211 | if(!updateStrategy.symbolFilter.include == []) { 212 | if (updateStrategy.symbolFilter.included.includes(masteraccount.symbol) || !updateStrategy.symbolFilter.excluded.includes(updateStrategy.symbol)) { 213 | updateStrategy.symbol = masteraccount.symbol; 214 | } 215 | } 216 | else if(!updateStrategy.symbolFilter.excluded.includes(masteraccount.symbol)) { 217 | updateStrategy.symbol = masteraccount.symbol; 218 | } 219 | if((updateStrategy.minTradeVolume < masteraccount.tradeVolume) && (updateStrategy.maxTradeVolume > masteraccount.tradeVolume)) updateStrategy.tradeVolume = masteraccount.tradeVolume; 220 | else if (updateStrategy.minTradeVolume > masteraccount.tradeVolume) updateStrategy.tradeVolume = updateStrategy.minTradeVolume; 221 | else if (updateStrategy.maxTradeVolume < masteraccount.tradeVolume) updateStrategy.tradeVolume = updateStrategy.maxTradeVolume; 222 | if (updateStrategy.copyStopLoss && masteraccount.stopLoss < updateStrategy.maxStopLoss) updateStrategy.stopLoss = masteraccount.stopLoss; 223 | if (updateStrategy.copyTakeProfit) updateStrategy.takeProfit = masteraccount.takeProfit; 224 | if (!updateStrategy.skipPendingOrder) updateStrategy.pendingOrder = masteraccount.pendingOrder; 225 | if (masteraccount.maxTradeRisk <= updateStrategy.maxTradeRisk) updateStrategy.maxTradeRisk = masteraccount.maxTradeRisk; 226 | if (masteraccount.leverage > updateStrategy.maxLeverage) updateStrategy.leverage = updateStrategy.maxLeverage; 227 | else updateStrategy.leverage = masteraccount.leverage 228 | updateStrategy.currency = masteraccount.currency; 229 | updateStrategy.drawDown = masteraccount.drawDown; 230 | updateStrategy.timeFrame = masteraccount.timeFrame; 231 | updateStrategy.closeVolume = masterStrategy.closeVolume; 232 | updateStrategy.closeAll = masterStrategy.closeAll; 233 | updateStrategy.specificPrice = masterStrategy.specificPrice; 234 | updateStrategy.isStopLoss = masterStrategy.isStopLoss; 235 | await updateStrategy.save(); 236 | console.log('save') 237 | 238 | return res.status(200).json({id: newStrategy._id}); 239 | } 240 | else { 241 | console.log('404 Error!') 242 | return res.status(404).json({message: "Error! Faild to generate Strategy id."}); 243 | } 244 | } catch (e) { 245 | console.log(e) 246 | res.status(500).json({message: 'An Error Occurred'}) 247 | } 248 | } 249 | // Get New Strategies 250 | exports.getStrategies = async (req, res) => { 251 | try { 252 | // console.log('dddd') 253 | const {limit, offset, includeRemoved} = req.query; 254 | console.log(limit, offset, includeRemoved) 255 | let offset1 = 0; 256 | if (offset) { 257 | offset1 = offset; 258 | } else { 259 | offset1 = 0; 260 | } 261 | let limit_number = 1000; 262 | if (limit) { 263 | console.log('limit_number') 264 | limit_number = limit; 265 | } else { 266 | limit_number = 1000; 267 | } 268 | let includeRemoved1 = "false"; 269 | if (includeRemoved) { 270 | includeRemoved1 = includeRemoved; 271 | } else { 272 | includeRemoved1 = "false"; 273 | } 274 | const skipValue = offset1 * limit_number; 275 | console.log(skipValue, "this is skipvalue-------", includeRemoved1) 276 | if (includeRemoved1 !== "false") { 277 | console.log("true"); 278 | const strategies = await Strategy.find({}).skip(skipValue).limit(limit_number); 279 | const updateStrategies = deleteItemInObjects(strategies, "removedState"); 280 | res.status(200).json(updateStrategies); 281 | } 282 | else { 283 | console.log("false"); 284 | const strategies = await Strategy.find({removedState: false}).skip(skipValue).limit(limit_number); 285 | const updateStrategies = deleteItemInObjects(strategies, "removedState"); 286 | res.status(200).json(updateStrategies); 287 | } 288 | } catch (e) { 289 | res.status(500).json({message: 'An Error Occurred', error: e}) 290 | } 291 | } 292 | 293 | exports.getStrategy = async (req, res) => { 294 | try { 295 | const strategyId = req.params.strategyId 296 | console.log("Id------------>", strategyId) 297 | const strategies = await Strategy.findOne({_id: strategyId, removedState: false}); 298 | if (strategies) { 299 | console.log('strategies', strategies) 300 | return res.status(200).json(strategies); 301 | } 302 | else { 303 | return res.status(404).json({message: "Strategy not founded."}) 304 | } 305 | } catch (e) { 306 | res.status(500).json({message: 'An Error Occurred', error: e}) 307 | } 308 | } 309 | 310 | exports.updateStrategy = async (req, res) => { 311 | try { 312 | console.log("update Strategy") 313 | const request = req.body; 314 | const strategyId = req.params.strategyId 315 | console.log("Id------------>", strategyId) 316 | // console.log("request----------->", request) 317 | const item = await Strategy.findOne({_id: strategyId, removedState: false}) 318 | // request.riskLimits = Array.isArray(request.riskLimits) ? request.riskLimits : [request.riskLimits] 319 | if(item) { 320 | console.log("found", item) 321 | Strategy.findByIdAndUpdate(strategyId, request, { new: false }, (err, updatedDocument) => { 322 | if (err) { 323 | // Handle the error, e.g., return an error response 324 | res.status(500).json({ error: e }); 325 | console.log(err) 326 | } else { 327 | console.log("updated", updatedDocument) 328 | // Document updated successfully, return the updated document as the response 329 | res.status(200).json({message: 'Strategy saved Successfully'}); 330 | } 331 | }); 332 | } 333 | else { 334 | console.log("strategynot font"); 335 | res.status(404).json({message: "Strategy not founded!"}) 336 | } 337 | } catch (e) { 338 | res.status(500).json({message: 'An Error Occurred', error: e}) 339 | } 340 | } 341 | 342 | 343 | exports.deleteStrategy = async (req, res) => { 344 | try { 345 | // const request = req.body; 346 | const strategyId = req.params.strategyId 347 | console.log("Id------------>", strategyId) 348 | const strategies = await Strategy.findOne({_id: strategyId}); 349 | if (strategies) { 350 | console.log('strategies', strategies) 351 | const strategy = await Strategy.findByIdAndUpdate(strategyId, {removedState: true}); 352 | return res.status(200).json({message: 'Strategy deleted Successfully'}); 353 | } 354 | else { 355 | return res.status(404).json({message: "Strategy not founded."}) 356 | } 357 | } catch (e) { 358 | res.status(500).json({message: 'An Error Occurred', error: e}) 359 | } 360 | } 361 | 362 | exports.getPortfolioStrategies = async (req, res) => { 363 | try { 364 | const {limit, offset, includeRemoved} = req.query; 365 | console.log(limit, offset, includeRemoved) 366 | let offset1 = 0; 367 | if (offset) { 368 | offset1 = offset; 369 | } else { 370 | offset1 = 0; 371 | } 372 | let limit_number = 1000; 373 | if (limit) { 374 | console.log('limit_number') 375 | limit_number = limit; 376 | } else { 377 | limit_number = 1000; 378 | } 379 | let includeRemoved1 = "false"; 380 | if (includeRemoved) { 381 | includeRemoved1 = includeRemoved; 382 | } else { 383 | includeRemoved1 = "false"; 384 | } 385 | const skipValue = offset1 * limit_number; 386 | console.log(skipValue, "this is skipvalue-------") 387 | if (includeRemoved1 !== "false") { 388 | console.log("true"); 389 | const strategies = await PortfolioStrategy.find({}).skip(skipValue).limit(limit_number); 390 | const updateStrategies = deleteItemInObjects(strategies, "removedState"); 391 | res.status(200).json(updateStrategies); 392 | } 393 | else { 394 | console.log("false"); 395 | const strategies = await PortfolioStrategy.find({removedState: false}).skip(skipValue).limit(limit_number); 396 | const updateStrategies = deleteItemInObjects(strategies, "removedState"); 397 | res.status(200).json(updateStrategies); 398 | } 399 | } catch (e) { 400 | res.status(500).json({message: 'An Error Occurred', error: e}) 401 | } 402 | } 403 | 404 | exports.getPortfolioStrategy = async (req, res) => { 405 | try { 406 | const strategyId = req.params.strategyId 407 | console.log("Id------------>", strategyId) 408 | const strategies = await PortfolioStrategy.findOne({_id: strategyId}); 409 | if (strategies) { 410 | console.log('strategies', strategies) 411 | return res.status(200).json(strategies); 412 | } 413 | else { 414 | return res.status(404).json({message: "PortfolioStrategy not founded."}) 415 | } 416 | } catch (e) { 417 | res.status(500).json({message: 'An Error Occurred', error: e}) 418 | } 419 | } 420 | 421 | exports.updatePortfolioStrategy = async (req, res) => { 422 | try { 423 | const request = req.body; 424 | const strategyId = req.params.strategyId 425 | console.log("Id------------>", strategyId) 426 | const item = await PortfolioStrategy.findOne({_id: strategyId, removedState: false}) 427 | if(item) { 428 | console.log("found", item) 429 | PortfolioStrategy.findByIdAndUpdate(strategyId, request, { new: false }, (err, updatedDocument) => { 430 | if (err) { 431 | // Handle the error, e.g., return an error response 432 | res.status(500).json({ error: err }); 433 | console.log(err) 434 | } else { 435 | console.log("updated", updatedDocument) 436 | // Document updated successfully, return the updated document as the response 437 | res.status(200).json({message: 'PortfolioStrategy saved Successfully'}); 438 | } 439 | }); 440 | } 441 | else { 442 | console.log("Portfolio strategy not found"); 443 | res.status(404).json({message: "PortfolioStrategy not founded!"}) 444 | } 445 | } catch (e) { 446 | res.status(500).json({message: 'An Error Occurred', error: e}) 447 | } 448 | } 449 | 450 | 451 | exports.deletePortfolioStrategy = async (req, res) => { 452 | try { 453 | // const request = req.body; 454 | const strategyId = req.params.strategyId 455 | console.log("Id------------>", strategyId) 456 | const strategies = await PortfolioStrategy.findOne({_id: strategyId}); 457 | if (strategies) { 458 | console.log('strategies', strategies) 459 | const strategy = await PortfolioStrategy.findByIdAndUpdate(strategyId, {removedState: true}); 460 | return res.status(200).json({message: 'Portfolio Strategy deleted Successfully'}); 461 | } 462 | else { 463 | return res.status(404).json({message: "Portfolio Strategy not founded."}) 464 | } 465 | } catch (e) { 466 | res.status(500).json({message: 'An Error Occurred', error: e}) 467 | } 468 | } 469 | 470 | 471 | exports.deletePortfolioMemberStrategy = async (req, res) => { 472 | try { 473 | // const request = req.body; 474 | const strategyId = req.params.strategyId 475 | const strategyMemberId = req.params.strategyMemberId 476 | console.log("Id------------>", strategyId) 477 | const strategies = await PortfolioStrategy.findOne({_id: strategyId, "members.strategyId": strategyMemberId, removedState: false}); 478 | if (strategies) { 479 | console.log('strategies', strategies) 480 | const strategy = await PortfolioStrategy.findOneAndUpdate({_id: strategyId, "members.strategyId": strategyMemberId, removedState: false}, { $set: { 'members.$.removedState': true } }); 481 | return res.status(200).json({message: 'Portfolio Member Strategy deleted Successfully'}); 482 | } 483 | else { 484 | return res.status(404).json({message: "Portfolio Member Strategy not founded."}) 485 | } 486 | } catch (e) { 487 | console.log(e) 488 | res.status(500).json({message: 'An Error Occurred', error: e}) 489 | } 490 | } 491 | 492 | exports.getSubscribers = async (req, res) => { 493 | try { 494 | // console.log('dddd') 495 | const {limit, offset, includeRemoved} = req.query; 496 | console.log(limit, offset, includeRemoved) 497 | let offset1 = 0; 498 | if (offset) { 499 | offset1 = offset; 500 | } else { 501 | offset1 = 0; 502 | } 503 | let limit_number = 1000; 504 | if (limit) { 505 | console.log('limit_number') 506 | limit_number = limit; 507 | } else { 508 | limit_number = 1000; 509 | } 510 | let includeRemoved1 = "false"; 511 | if (includeRemoved) { 512 | includeRemoved1 = includeRemoved; 513 | } else { 514 | includeRemoved1 = "false"; 515 | } 516 | const skipValue = offset1 * limit_number; 517 | console.log(skipValue, "this is skipvalue-------") 518 | if (includeRemoved1 !== "false") { 519 | console.log("true"); 520 | const strategies = await Subscriber.find({}).skip(skipValue).limit(limit_number); 521 | const updateStrategies = deleteItemInObjects(strategies, "removedState"); 522 | res.status(200).json(updateStrategies); 523 | } 524 | else { 525 | console.log("false"); 526 | const strategies = await Subscriber.find({removedState: false}).skip(skipValue).limit(limit_number); 527 | const updateStrategies = deleteItemInObjects(strategies, "removedState"); 528 | res.status(200).json(updateStrategies); 529 | } 530 | } catch (e) { 531 | res.status(500).json({message: 'An Error Occurred', error: e}) 532 | } 533 | } 534 | 535 | exports.getSubscriber = async (req, res) => { 536 | try { 537 | const subscriberId = req.params.subscriberId 538 | console.log("Id------------>", subscriberId) 539 | const strategies = await Subscriber.findOne({_id: subscriberId, removedState: false}); 540 | if (strategies) { 541 | console.log('strategies', strategies) 542 | return res.status(200).json(strategies); 543 | } 544 | else { 545 | return res.status(404).json({message: "Subscriber not founded."}) 546 | } 547 | } catch (e) { 548 | res.status(500).json({message: 'An Error Occurred', error: e}) 549 | } 550 | } 551 | 552 | exports.updateSubscriber = async (req, res) => { 553 | try { 554 | const request = req.body; 555 | const subscriberId = req.params.subscriberId 556 | console.log("Id------------>", subscriberId) 557 | const item = await Subscriber.findOne({_id: subscriberId, removedState: false}) 558 | if(item) { 559 | console.log("found", item) 560 | Subscriber.findByIdAndUpdate(subscriberId, request, { new: false }, (err, updatedDocument) => { 561 | if (err) { 562 | // Handle the error, e.g., return an error response 563 | res.status(500).json({ error: err }); 564 | console.log(err) 565 | } else { 566 | console.log("updated", updatedDocument) 567 | // Document updated successfully, return the updated document as the response 568 | res.status(200).json({message: 'Subscriber saved Successfully'}); 569 | } 570 | }); 571 | } 572 | else { 573 | console.log("Subscriber strategy not found"); 574 | res.status(404).json({message: "Subscriber not founded!"}) 575 | } 576 | } catch (e) { 577 | res.status(500).json({message: 'An Error Occurred', error: e}) 578 | } 579 | } 580 | 581 | 582 | exports.deleteSubscriber = async (req, res) => { 583 | try { 584 | const request = req.body; 585 | const subscriberId = req.params.subscriberId 586 | console.log("Id------------>", subscriberId) 587 | const strategies = await Subscriber.findOne({_id: subscriberId}); 588 | if (strategies) { 589 | console.log('strategies', strategies) 590 | const strategy = await Subscriber.findByIdAndUpdate(subscriberId, {removedState: true}); 591 | return res.status(200).json({message: 'Subscriber deleted Successfully'}); 592 | } 593 | else { 594 | return res.status(404).json({message: "Subscriber not founded."}) 595 | } 596 | } catch (e) { 597 | res.status(500).json({message: 'An Error Occurred', error: e}) 598 | } 599 | } 600 | 601 | 602 | exports.deleteSubscription = async (req, res) => { 603 | try { 604 | const request = req.body; 605 | const subscriberId = req.params.subscriberId; 606 | const strategyId = req.params.strategyId; 607 | console.log("Id------------>", strategyId) 608 | const strategies = await Subscriber.findOne({_id: subscriberId, "subscriptions.strategyId": strategyId, removedState: false}); 609 | if (strategies) { 610 | console.log('strategies', strategies) 611 | const strategy = await Subscriber.findOneAndUpdate({_id: subscriberId, "subscriptions.strategyId": strategyId, removedState: false}, { $set: { 'subscriptions.$.removedState': true } }); 612 | return res.status(200).json({message: 'Subscription deleted Successfully'}); 613 | } 614 | else { 615 | return res.status(404).json({message: "Subscription not founded."}) 616 | } 617 | } catch (e) { 618 | res.status(500).json({message: 'An Error Occurred', error: e}) 619 | } 620 | } -------------------------------------------------------------------------------- /EA_Deploy/socket-library-mt4-mt5.mqh: -------------------------------------------------------------------------------- 1 | /* ******************************************************************************* 2 | 3 | Socket library, for both MT4 and MT5 (32-bit and 64-bit) 4 | 5 | Features: 6 | 7 | * Both client and server sockets 8 | * Both send and receive 9 | * Both MT4 and MT5 (32-bit and 64-bit) 10 | * Optional event-driven handling (EAs only, not scripts or indicators), 11 | offering faster responses to socket events than OnTimer() 12 | * Direct use of Winsock; no need for a custom DLL sitting 13 | between this code and ws2_32.dll 14 | 15 | Based on the following forum posts: 16 | 17 | https://www.mql5.com/en/forum/203049#comment_5232176 18 | https://www.mql5.com/en/forum/160115/page3#comment_3817302 19 | 20 | 21 | CLIENT SOCKETS 22 | -------------- 23 | 24 | You create a connection to a server using one of the following two 25 | constructors for ClientSocket: 26 | 27 | ClientSocket(ushort localport); 28 | ClientSocket(string HostnameOrIPAddress, ushort port); 29 | 30 | The first connects to a port on localhost (127.0.0.1). The second 31 | connects to a remote server, which can be specified either by 32 | passing an IP address such as "123.123.123.123" or a hostname 33 | such as "www.myserver.com". 34 | 35 | After creating the instance of the class, and periodically afterwards, 36 | you should check the value of IsSocketConnected(). If false, then 37 | the connection has failed (or has later been closed). You then need to 38 | destroy the class and create a new connection. One common pattern of usage 39 | therefore looks like the following: 40 | 41 | ClientSocket * glbConnection = NULL; 42 | 43 | void OnTick() 44 | { 45 | // Create a socket if none already exists 46 | if (!glbConnection) glbConnection = new ClientSocket(12345); 47 | 48 | if (glbConnection.IsSocketConnected()) { 49 | // Socket is okay. Do some action such as sending or receiving 50 | } 51 | 52 | // Socket may already have been dead, or now detected as failed 53 | // following the attempt above at sending or receiving. 54 | // If so, delete the socket and try a new one on the next call to OnTick() 55 | if (!glbConnection.IsSocketConnected()) { 56 | delete glbConnection; 57 | glbConnection = NULL; 58 | } 59 | } 60 | 61 | You send data down a socket using the simple Send() method, which takes 62 | a string parameter. Any failure to send returns false, which will 63 | also mean that IsSocketConnected() then returns false. The format 64 | of the data which you are sending to the server is obviously 65 | entirely up to you... 66 | 67 | You can receive pending incoming data on a socket using Receive(), which 68 | returns either the pending data or an empty string. You will normally want 69 | to call Receive() from OnTimer(), or using the event handling described below. 70 | 71 | A non-blank return value from Receive() does not necessarily mean that 72 | the socket is still active. The server may have sent some data *and* closed 73 | the socket. 74 | 75 | string strMessage = MySocket.Receive(); 76 | if (strMessage != "") { 77 | // Process the message 78 | } 79 | 80 | // Regardless of whether there was any data, the socket may 81 | // now be dead. 82 | if (!MySocket.IsSocketConnected()) { 83 | // ... socket has been closed 84 | } 85 | 86 | You can also give Receive() an optional message terminator, such as "\r\n". 87 | It will then store up data, and only return complete messages (minus the 88 | terminator). If you use a terminator then there may have been multiple 89 | complete messages since your last call to Receive(), and you should 90 | keep calling Receive() until it returns an empty string, in order to collect 91 | all the messages. For example: 92 | 93 | string strMessage; 94 | do { 95 | strMessage = MySocket.Receive("\r\n"); 96 | if (strMessage != "") { 97 | // Do something with the message 98 | } 99 | } (while strMessage != ""); 100 | 101 | You close a socket simply by destroying the ClientSocket object. 102 | 103 | 104 | SERVER SOCKETS 105 | -------------- 106 | 107 | For anyone not used to socket programming: the model is that you create 108 | a server socket; you accept connections on it; and each acceptance 109 | creates a new socket for communicating with that client. No data 110 | is sent or received through the server socket itself. 111 | 112 | You create a server socket by telling the constructor a port number, 113 | and whether to accept connections only from the local machine or 114 | from any remote computer (subject to firewall rules etc). 115 | 116 | ServerSocket(ushort ServerPort, bool ForLocalhostOnly); 117 | 118 | You should check the value of Created() after creating the ServerSocket() 119 | object. Any failure will usually be because something is already 120 | listening on your chosen port. 121 | 122 | MyServerSocket = new ServerSocket(12345, true); 123 | if (!MyServerSocket.Created()) { 124 | // Almost certainly because port 12345 is already in use 125 | } 126 | 127 | You must be careful to destroy any server sockets which you 128 | create. If you don't then the port will remain in use and locked 129 | until MT4/5 is shut down, and you (or any other program) will not 130 | be able to create a new socket on that port. The normal way 131 | of handling this is to do destruction in OnDeinit(): 132 | 133 | ServerSocket * glbServerSocket; 134 | ... 135 | 136 | void OnDeinit(const int reason) 137 | { 138 | if (glbServerSocket) delete glbServerSocket; 139 | } 140 | 141 | You accept incoming connections using Accept(), typically from 142 | periodic checks in OnTimer() or using the event handling described below. 143 | Accept() returns either NULL if there is no waiting client, or a new 144 | instance of ClientSocket() which you then use for communicating with 145 | the client. There can be multiple simultaneous new connections, 146 | and you will therefore typically want to keep calling Accept() 147 | until it returns NULL. For example: 148 | 149 | ClientSocket * pNewClient; 150 | do { 151 | pNewClient = MyServerSocket.Accept(); 152 | if (pNewClient) { 153 | // Store client socket for future use. 154 | // Must remember to delete it when finished, to avoid memory leaks. 155 | } 156 | } (while pNewClient != NULL); 157 | 158 | To repeat the overview above: Accept() gives you a new instance of 159 | ClientSocket (which you must later delete). You then communicate with the 160 | client using the Send() and Receive() on ClientSocket. No data is 161 | ever sent or received through the server socket itself. 162 | 163 | 164 | EVENT-DRIVEN HANDLING 165 | --------------------- 166 | 167 | The timing infrastructure in Windows does not normally have millisecond 168 | granularity. EventSetMillisecondTimer(1) is usually in fact equivalent 169 | to EventSetMillisecondTimer(16). Therefore, checks for socket 170 | activity in OnTimer() potentially have a delay of at least 16 milliseconds 171 | before you respond to a new connection or incoming data. 172 | 173 | In an EA (but not a script or indicator) you can achieve <1ms response 174 | times using event-driven handling. The way this works is that the 175 | socket library generates dummy key-down messages to OnChartEvent() 176 | when socket activity occurs. Responding to these events can be 177 | significantly faster than a periodic check in OnTimer(). 178 | 179 | You need to request the event-driven handling by #defining SOCKET_LIBRARY_USE_EVENTS 180 | before including the library. For example: 181 | 182 | #define SOCKET_LIBRARY_USE_EVENTS 183 | #include 184 | 185 | (Note that this has no effect in a custom indicator or script. It only 186 | works with EAs.) 187 | 188 | You then process notifications in OnChartEvent as follows: 189 | 190 | void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) 191 | { 192 | if (id == CHARTEVENT_KEYDOWN) { 193 | // May be a real key press, or a dummy notification 194 | // resulting from socket activity. If lparam matches 195 | // any .GetSocketHandle() then it's from a socket. 196 | // If not, it's a real key press. (If lparam>256 then 197 | // it's also pretty reliably a socket message rather 198 | // than a real key press.) 199 | 200 | if (lparam == MyServerSocket.GetSocketHandle()) { 201 | // Activity on a server socket 202 | } else if (lparam == MyClientSocket.GetSocketHandle()) { 203 | // Activity on a client socket 204 | } else { 205 | // Doesn't match a socket. Assume real key pres 206 | } 207 | } 208 | } 209 | 210 | For a comprehensive example of using the event-driven handling, 211 | see the example socket server code. 212 | 213 | 214 | NOTES ON MT4/5 CROSS-COMPATIBILITY 215 | ---------------------------------- 216 | 217 | It appears to be safe for a 64-bit application to use 4-byte socket 218 | handles, despite the fact that the Win32 SDK defines SOCKET as 8-byte 219 | on x64. Nevertheless, this code uses 8-byte handles when running 220 | on 64-bit MT5. 221 | 222 | The area which definitely does cause problems is gethostbyname(), 223 | because it returns a memory block containing pointers whose size 224 | depends on the environment. (The issue here is 32-bit vs 64-bit, 225 | not MT4 vs MT5.) 226 | 227 | This code not only needs to handle 4-byte vs 8-byte memory pointers. 228 | The further problem is that MQL5 has no integer data type whose 229 | size varies with the environment. Therefore, it's necessary to 230 | have two versions of the #import of gethostbyname(), and to 231 | force the compiler to use the applicable one despite the fact 232 | that they only really vary by their return type. Manipulating 233 | the hostent* returned by gethostbyname() is then very ugly, 234 | made doubly so by the need for different paths of execution 235 | on 32-bit and 64-bit, using a range of different #imports of 236 | the RtlMoveMemory() function which the code uses to 237 | process the pointers. 238 | 239 | ******************************************************************************* */ 240 | 241 | #property strict 242 | 243 | 244 | // ------------------------------------------------------------- 245 | // Winsock constants and structures 246 | // ------------------------------------------------------------- 247 | 248 | #define SOCKET_HANDLE32 uint 249 | #define SOCKET_HANDLE64 ulong 250 | #define AF_INET 2 251 | #define SOCK_STREAM 1 252 | #define IPPROTO_TCP 6 253 | #define INVALID_SOCKET32 0xFFFFFFFF 254 | #define INVALID_SOCKET64 0xFFFFFFFFFFFFFFFF 255 | #define SOCKET_ERROR -1 256 | #define INADDR_NONE 0xFFFFFFFF 257 | #define FIONBIO 0x8004667E 258 | #define WSAWOULDBLOCK 10035 259 | 260 | struct sockaddr { 261 | short family; 262 | ushort port; 263 | uint address; 264 | ulong ignore; 265 | }; 266 | 267 | struct linger { 268 | ushort onoff; 269 | ushort linger_seconds; 270 | }; 271 | 272 | // ------------------------------------------------------------- 273 | // DLL imports 274 | // ------------------------------------------------------------- 275 | 276 | #import "ws2_32.dll" 277 | // Imports for 32-bit environment 278 | SOCKET_HANDLE32 socket(int, int, int); // Artificially differs from 64-bit version based on 3rd parameter 279 | int connect(SOCKET_HANDLE32, sockaddr&, int); 280 | int closesocket(SOCKET_HANDLE32); 281 | int send(SOCKET_HANDLE32, uchar&[],int,int); 282 | int recv(SOCKET_HANDLE32, uchar&[], int, int); 283 | int ioctlsocket(SOCKET_HANDLE32, uint, uint&); 284 | int bind(SOCKET_HANDLE32, sockaddr&, int); 285 | int listen(SOCKET_HANDLE32, int); 286 | SOCKET_HANDLE32 accept(SOCKET_HANDLE32, int, int); 287 | int WSAAsyncSelect(SOCKET_HANDLE32, int, uint, int); 288 | int shutdown(SOCKET_HANDLE32, int); 289 | 290 | // Imports for 64-bit environment 291 | SOCKET_HANDLE64 socket(int, int, uint); // Artificially differs from 32-bit version based on 3rd parameter 292 | int connect(SOCKET_HANDLE64, sockaddr&, int); 293 | int closesocket(SOCKET_HANDLE64); 294 | int send(SOCKET_HANDLE64, uchar&[], int, int); 295 | int recv(SOCKET_HANDLE64, uchar&[], int, int); 296 | int ioctlsocket(SOCKET_HANDLE64, uint, uint&); 297 | int bind(SOCKET_HANDLE64, sockaddr&, int); 298 | int listen(SOCKET_HANDLE64, int); 299 | SOCKET_HANDLE64 accept(SOCKET_HANDLE64, int, int); 300 | int WSAAsyncSelect(SOCKET_HANDLE64, long, uint, int); 301 | int shutdown(SOCKET_HANDLE64, int); 302 | 303 | // gethostbyname() has to vary between 32/64-bit, because 304 | // it returns a memory pointer whose size will be either 305 | // 4 bytes or 8 bytes. In order to keep the compiler 306 | // happy, we therefore need versions which take 307 | // artificially-different parameters on 32/64-bit 308 | uint gethostbyname(uchar&[]); // For 32-bit 309 | ulong gethostbyname(char&[]); // For 64-bit 310 | 311 | // Neutral; no difference between 32-bit and 64-bit 312 | uint inet_addr(uchar&[]); 313 | int WSAGetLastError(); 314 | uint htonl(uint); 315 | ushort htons(ushort); 316 | #import 317 | 318 | // For navigating the Winsock hostent structure, with indescribably horrible 319 | // variation between 32-bit and 64-bit 320 | #import "kernel32.dll" 321 | void RtlMoveMemory(uint&, uint, int); 322 | void RtlMoveMemory(ushort&, uint, int); 323 | void RtlMoveMemory(ulong&, ulong, int); 324 | void RtlMoveMemory(ushort&, ulong, int); 325 | #import 326 | 327 | // ------------------------------------------------------------- 328 | // Forward definitions of classes 329 | // ------------------------------------------------------------- 330 | 331 | class ClientSocket; 332 | class ServerSocket; 333 | 334 | 335 | // ------------------------------------------------------------- 336 | // Client socket class 337 | // ------------------------------------------------------------- 338 | 339 | class ClientSocket 340 | { 341 | private: 342 | // Need different socket handles for 32-bit and 64-bit environments 343 | SOCKET_HANDLE32 mSocket32; 344 | SOCKET_HANDLE64 mSocket64; 345 | 346 | // Other state variables 347 | bool mConnected; 348 | int mLastWSAError; 349 | string mPendingReceiveData; // Backlog of incoming data, if using a message-terminator in Receive() 350 | 351 | // Event handling 352 | bool mDoneEventHandling; 353 | void SetupSocketEventHandling(); 354 | 355 | public: 356 | // Constructors for connecting to a server, either locally or remotely 357 | ClientSocket(ushort localport); 358 | ClientSocket(string HostnameOrIPAddress, ushort port); 359 | 360 | // Constructors used by ServerSocket() when accepting a client connection 361 | ClientSocket(ServerSocket* ForInternalUseOnly, SOCKET_HANDLE32 ForInternalUseOnly_clientsocket32); 362 | ClientSocket(ServerSocket* ForInternalUseOnly, SOCKET_HANDLE64 ForInternalUseOnly_clientsocket64); 363 | 364 | // Destructor 365 | ~ClientSocket(); 366 | 367 | // Simple send and receive methods 368 | bool Send(string strMsg); 369 | bool Send(uchar & callerBuffer[], int startAt = 0, int szToSend = -1); 370 | string Receive(string MessageSeparator = ""); 371 | int Receive(uchar & callerBuffer[]); 372 | 373 | // State information 374 | bool IsSocketConnected() {return mConnected;} 375 | int GetLastSocketError() {return mLastWSAError;} 376 | ulong GetSocketHandle() {return (mSocket32 ? mSocket32 : mSocket64);} 377 | 378 | // Buffer sizes, overwriteable once the class has been created 379 | int ReceiveBufferSize; 380 | int SendBufferSize; 381 | }; 382 | 383 | 384 | // ------------------------------------------------------------- 385 | // Constructor for a simple connection to 127.0.0.1 386 | // ------------------------------------------------------------- 387 | 388 | ClientSocket::ClientSocket(ushort localport) 389 | { 390 | // Default buffer sizes 391 | ReceiveBufferSize = 10000; 392 | SendBufferSize = 999999999; 393 | 394 | // Need to create either a 32-bit or 64-bit socket handle 395 | mConnected = false; 396 | mLastWSAError = 0; 397 | if (TerminalInfoInteger(TERMINAL_X64)) { 398 | uint proto = IPPROTO_TCP; 399 | mSocket64 = socket(AF_INET, SOCK_STREAM, proto); 400 | if (mSocket64 == INVALID_SOCKET64) { 401 | mLastWSAError = WSAGetLastError(); 402 | #ifdef SOCKET_LIBRARY_LOGGING 403 | Print("socket() failed, 64-bit, error: ", mLastWSAError); 404 | #endif 405 | return; 406 | } 407 | } else { 408 | int proto = IPPROTO_TCP; 409 | mSocket32 = socket(AF_INET, SOCK_STREAM, proto); 410 | if (mSocket32 == INVALID_SOCKET32) { 411 | mLastWSAError = WSAGetLastError(); 412 | #ifdef SOCKET_LIBRARY_LOGGING 413 | Print("socket() failed, 32-bit, error: ", mLastWSAError); 414 | #endif 415 | return; 416 | } 417 | } 418 | 419 | // Fixed definition for connecting to 127.0.0.1, with variable port 420 | sockaddr server; 421 | server.family = AF_INET; 422 | server.port = htons(localport); 423 | server.address = 0x100007f; // 127.0.0.1 424 | 425 | // connect() call has to differ between 32-bit and 64-bit 426 | int res; 427 | if (TerminalInfoInteger(TERMINAL_X64)) { 428 | res = connect(mSocket64, server, sizeof(sockaddr)); 429 | } else { 430 | res = connect(mSocket32, server, sizeof(sockaddr)); 431 | } 432 | if (res == SOCKET_ERROR) { 433 | // Ooops 434 | mLastWSAError = WSAGetLastError(); 435 | #ifdef SOCKET_LIBRARY_LOGGING 436 | Print("connect() to localhost failed, error: ", mLastWSAError); 437 | #endif 438 | return; 439 | } else { 440 | mConnected = true; 441 | 442 | // Set up event handling. Can fail if called in OnInit() when 443 | // MT4/5 is still loading, because no window handle is available 444 | #ifdef SOCKET_LIBRARY_USE_EVENTS 445 | SetupSocketEventHandling(); 446 | #endif 447 | } 448 | } 449 | 450 | // ------------------------------------------------------------- 451 | // Constructor for connection to a hostname or IP address 452 | // ------------------------------------------------------------- 453 | 454 | ClientSocket::ClientSocket(string HostnameOrIPAddress, ushort port) 455 | { 456 | // Default buffer sizes 457 | ReceiveBufferSize = 10000; 458 | SendBufferSize = 999999999; 459 | 460 | // Need to create either a 32-bit or 64-bit socket handle 461 | mConnected = false; 462 | mLastWSAError = 0; 463 | if (TerminalInfoInteger(TERMINAL_X64)) { 464 | uint proto = IPPROTO_TCP; 465 | mSocket64 = socket(AF_INET, SOCK_STREAM, proto); 466 | if (mSocket64 == INVALID_SOCKET64) { 467 | mLastWSAError = WSAGetLastError(); 468 | #ifdef SOCKET_LIBRARY_LOGGING 469 | Print("socket() failed, 64-bit, error: ", mLastWSAError); 470 | #endif 471 | return; 472 | } 473 | } else { 474 | int proto = IPPROTO_TCP; 475 | mSocket32 = socket(AF_INET, SOCK_STREAM, proto); 476 | if (mSocket32 == INVALID_SOCKET32) { 477 | mLastWSAError = WSAGetLastError(); 478 | #ifdef SOCKET_LIBRARY_LOGGING 479 | Print("socket() failed, 32-bit, error: ", mLastWSAError); 480 | #endif 481 | return; 482 | } 483 | } 484 | 485 | // Is the host parameter an IP address? 486 | uchar arrName[]; 487 | StringToCharArray(HostnameOrIPAddress, arrName); 488 | ArrayResize(arrName, ArraySize(arrName) + 1); 489 | uint addr = inet_addr(arrName); 490 | 491 | if (addr == INADDR_NONE) { 492 | // Not an IP address. Need to look up the name 493 | // ....................................................................................... 494 | // Unbelievably horrible handling of the hostent structure depending on whether 495 | // we're in 32-bit or 64-bit, with different-length memory pointers. 496 | // Ultimately, we're having to deal here with extracting a uint** from 497 | // the memory block provided by Winsock - and with additional 498 | // complications such as needing different versions of gethostbyname(), 499 | // because the return value is a pointer, which is 4 bytes in x86 and 500 | // 8 bytes in x64. So, we must artifically pass different types of buffer 501 | // to gethostbyname() depending on the environment, so that the compiler 502 | // doesn't treat them as imports which differ only by their return type. 503 | if (TerminalInfoInteger(TERMINAL_X64)) { 504 | char arrName64[]; 505 | ArrayResize(arrName64, ArraySize(arrName)); 506 | for (int i = 0; i < ArraySize(arrName); i++) arrName64[i] = (char)arrName[i]; 507 | ulong nres = gethostbyname(arrName64); 508 | if (nres == 0) { 509 | // Name lookup failed 510 | mLastWSAError = WSAGetLastError(); 511 | #ifdef SOCKET_LIBRARY_LOGGING 512 | Print("Name-resolution in gethostbyname() failed, 64-bit, error: ", mLastWSAError); 513 | #endif 514 | return; 515 | } else { 516 | // Need to navigate the hostent structure. Very, very ugly... 517 | ushort addrlen; 518 | RtlMoveMemory(addrlen, nres + 18, 2); 519 | if (addrlen == 0) { 520 | // No addresses associated with name 521 | #ifdef SOCKET_LIBRARY_LOGGING 522 | Print("Name-resolution in gethostbyname() returned no addresses, 64-bit, error: ", mLastWSAError); 523 | #endif 524 | return; 525 | } else { 526 | ulong ptr1, ptr2, ptr3; 527 | RtlMoveMemory(ptr1, nres + 24, 8); 528 | RtlMoveMemory(ptr2, ptr1, 8); 529 | RtlMoveMemory(ptr3, ptr2, 4); 530 | addr = (uint)ptr3; 531 | } 532 | } 533 | } else { 534 | uint nres = gethostbyname(arrName); 535 | if (nres == 0) { 536 | // Name lookup failed 537 | mLastWSAError = WSAGetLastError(); 538 | #ifdef SOCKET_LIBRARY_LOGGING 539 | Print("Name-resolution in gethostbyname() failed, 32-bit, error: ", mLastWSAError); 540 | #endif 541 | return; 542 | } else { 543 | // Need to navigate the hostent structure. Very, very ugly... 544 | ushort addrlen; 545 | RtlMoveMemory(addrlen, nres + 10, 2); 546 | if (addrlen == 0) { 547 | // No addresses associated with name 548 | #ifdef SOCKET_LIBRARY_LOGGING 549 | Print("Name-resolution in gethostbyname() returned no addresses, 32-bit, error: ", mLastWSAError); 550 | #endif 551 | return; 552 | } else { 553 | int ptr1, ptr2; 554 | RtlMoveMemory(ptr1, nres + 12, 4); 555 | RtlMoveMemory(ptr2, ptr1, 4); 556 | RtlMoveMemory(addr, ptr2, 4); 557 | } 558 | } 559 | } 560 | 561 | } else { 562 | // The HostnameOrIPAddress parameter is an IP address, 563 | // which we have stored in addr 564 | } 565 | 566 | // Fill in the address and port into a sockaddr_in structure 567 | sockaddr server; 568 | server.family = AF_INET; 569 | server.port = htons(port); 570 | server.address = addr; // Already in network-byte-order 571 | 572 | // connect() call has to differ between 32-bit and 64-bit 573 | int res; 574 | if (TerminalInfoInteger(TERMINAL_X64)) { 575 | res = connect(mSocket64, server, sizeof(sockaddr)); 576 | } else { 577 | res = connect(mSocket32, server, sizeof(sockaddr)); 578 | } 579 | if (res == SOCKET_ERROR) { 580 | // Ooops 581 | mLastWSAError = WSAGetLastError(); 582 | #ifdef SOCKET_LIBRARY_LOGGING 583 | Print("connect() to server failed, error: ", mLastWSAError); 584 | #endif 585 | } else { 586 | mConnected = true; 587 | 588 | // Set up event handling. Can fail if called in OnInit() when 589 | // MT4/5 is still loading, because no window handle is available 590 | #ifdef SOCKET_LIBRARY_USE_EVENTS 591 | SetupSocketEventHandling(); 592 | #endif 593 | } 594 | } 595 | 596 | // ------------------------------------------------------------- 597 | // Constructors for internal use only, when accepting connections 598 | // on a server socket 599 | // ------------------------------------------------------------- 600 | 601 | ClientSocket::ClientSocket(ServerSocket* ForInternalUseOnly, SOCKET_HANDLE32 ForInternalUseOnly_clientsocket32) 602 | { 603 | // Constructor ror "internal" use only, when accepting an incoming connection 604 | // on a server socket 605 | mConnected = true; 606 | ReceiveBufferSize = 10000; 607 | SendBufferSize = 999999999; 608 | 609 | mSocket32 = ForInternalUseOnly_clientsocket32; 610 | } 611 | 612 | ClientSocket::ClientSocket(ServerSocket* ForInternalUseOnly, SOCKET_HANDLE64 ForInternalUseOnly_clientsocket64) 613 | { 614 | // Constructor ror "internal" use only, when accepting an incoming connection 615 | // on a server socket 616 | mConnected = true; 617 | ReceiveBufferSize = 10000; 618 | SendBufferSize = 999999999; 619 | 620 | mSocket64 = ForInternalUseOnly_clientsocket64; 621 | } 622 | 623 | 624 | // ------------------------------------------------------------- 625 | // Destructor. Close the socket if created 626 | // ------------------------------------------------------------- 627 | 628 | ClientSocket::~ClientSocket() 629 | { 630 | if (TerminalInfoInteger(TERMINAL_X64)) { 631 | if (mSocket64 != 0) { 632 | shutdown(mSocket64, 2); 633 | closesocket(mSocket64); 634 | } 635 | } else { 636 | if (mSocket32 != 0) { 637 | shutdown(mSocket32, 2); 638 | closesocket(mSocket32); 639 | } 640 | } 641 | } 642 | 643 | // ------------------------------------------------------------- 644 | // Simple send function which takes a string parameter 645 | // ------------------------------------------------------------- 646 | 647 | bool ClientSocket::Send(string strMsg) 648 | { 649 | if (!mConnected) return false; 650 | 651 | // Make sure that event handling is set up, if requested 652 | #ifdef SOCKET_LIBRARY_USE_EVENTS 653 | SetupSocketEventHandling(); 654 | #endif 655 | 656 | int szToSend = StringLen(strMsg); 657 | if (szToSend == 0) return true; // Ignore empty strings 658 | 659 | bool bRetval = true; 660 | uchar arr[]; 661 | StringToCharArray(strMsg, arr); 662 | 663 | while (szToSend > 0) { 664 | int res, szAmountToSend = (szToSend > SendBufferSize ? SendBufferSize : szToSend); 665 | if (TerminalInfoInteger(TERMINAL_X64)) { 666 | res = send(mSocket64, arr, szToSend, 0); 667 | } else { 668 | res = send(mSocket32, arr, szToSend, 0); 669 | } 670 | 671 | if (res == SOCKET_ERROR || res == 0) { 672 | mLastWSAError = WSAGetLastError(); 673 | if (mLastWSAError == WSAWOULDBLOCK) { 674 | // Blocking operation. Retry. 675 | } else { 676 | #ifdef SOCKET_LIBRARY_LOGGING 677 | Print("send() failed, error: ", mLastWSAError); 678 | #endif 679 | 680 | // Assume death of socket for any other type of error 681 | szToSend = -1; 682 | bRetval = false; 683 | mConnected = false; 684 | } 685 | } else { 686 | szToSend -= res; 687 | if (szToSend > 0) { 688 | // If further data remains to be sent, shuffle the array downwards 689 | // by copying it onto itself. Note that the MQL4/5 documentation 690 | // says that the result of this is "undefined", but it seems 691 | // to work reliably in real life (because it almost certainly 692 | // just translates inside MT4/5 into a simple call to RtlMoveMemory, 693 | // which does allow overlapping source & destination). 694 | ArrayCopy(arr, arr, 0, res, szToSend); 695 | } 696 | } 697 | } 698 | 699 | return bRetval; 700 | } 701 | 702 | 703 | // ------------------------------------------------------------- 704 | // Simple send function which takes an array of uchar[], 705 | // instead of a string. Can optionally be given a start-index 706 | // within the array (rather then default zero) and a number 707 | // of bytes to send. 708 | // ------------------------------------------------------------- 709 | 710 | bool ClientSocket::Send(uchar & callerBuffer[], int startAt = 0, int szToSend = -1) 711 | { 712 | if (!mConnected) return false; 713 | 714 | // Make sure that event handling is set up, if requested 715 | #ifdef SOCKET_LIBRARY_USE_EVENTS 716 | SetupSocketEventHandling(); 717 | #endif 718 | 719 | // Process the start-at and send-size parameters 720 | int arraySize = ArraySize(callerBuffer); 721 | if (!arraySize) return true; // Ignore empty arrays 722 | if (startAt >= arraySize) return true; // Not a valid start point; nothing to send 723 | if (szToSend <= 0) szToSend = arraySize; 724 | if (startAt + szToSend > arraySize) szToSend = arraySize - startAt; 725 | 726 | // Take a copy of the array 727 | uchar arr[]; 728 | ArrayResize(arr, szToSend); 729 | ArrayCopy(arr, callerBuffer, 0, startAt, szToSend); 730 | 731 | bool bRetval = true; 732 | 733 | while (szToSend > 0) { 734 | int res, szAmountToSend = (szToSend > SendBufferSize ? SendBufferSize : szToSend); 735 | if (TerminalInfoInteger(TERMINAL_X64)) { 736 | res = send(mSocket64, arr, szToSend, 0); 737 | } else { 738 | res = send(mSocket32, arr, szToSend, 0); 739 | } 740 | 741 | if (res == SOCKET_ERROR || res == 0) { 742 | mLastWSAError = WSAGetLastError(); 743 | if (mLastWSAError == WSAWOULDBLOCK) { 744 | // Blocking operation. Retry. 745 | } else { 746 | #ifdef SOCKET_LIBRARY_LOGGING 747 | Print("send() failed, error: ", mLastWSAError); 748 | #endif 749 | 750 | // Assume death of socket for any other type of error 751 | szToSend = -1; 752 | bRetval = false; 753 | mConnected = false; 754 | } 755 | } else { 756 | szToSend -= res; 757 | if (szToSend > 0) { 758 | // If further data remains to be sent, shuffle the array downwards 759 | // by copying it onto itself. Note that the MQL4/5 documentation 760 | // says that the result of this is "undefined", but it seems 761 | // to work reliably in real life (because it almost certainly 762 | // just translates inside MT4/5 into a simple call to RtlMoveMemory, 763 | // which does allow overlapping source & destination). 764 | ArrayCopy(arr, arr, 0, res, szToSend); 765 | } 766 | } 767 | } 768 | 769 | return bRetval; 770 | } 771 | 772 | 773 | // ------------------------------------------------------------- 774 | // Simple receive function. Without a message separator, 775 | // it simply returns all the data sitting on the socket. 776 | // With a separator, it stores up incoming data until 777 | // it sees the separator, and then returns the text minus 778 | // the separator. 779 | // Returns a blank string once no (more) data is waiting 780 | // for collection. 781 | // ------------------------------------------------------------- 782 | 783 | string ClientSocket::Receive(string MessageSeparator = "") 784 | { 785 | if (!mConnected) return ""; 786 | 787 | // Make sure that event handling is set up, if requested 788 | #ifdef SOCKET_LIBRARY_USE_EVENTS 789 | SetupSocketEventHandling(); 790 | #endif 791 | 792 | string strRetval = ""; 793 | 794 | uchar arrBuffer[]; 795 | ArrayResize(arrBuffer, ReceiveBufferSize); 796 | 797 | uint nonblock = 1; 798 | if (TerminalInfoInteger(TERMINAL_X64)) { 799 | ioctlsocket(mSocket64, FIONBIO, nonblock); 800 | 801 | int res = 1; 802 | while (res > 0) { 803 | res = recv(mSocket64, arrBuffer, ReceiveBufferSize, 0); 804 | if (res > 0) { 805 | StringAdd(mPendingReceiveData, CharArrayToString(arrBuffer, 0, res)); 806 | 807 | } else if (res == 0) { 808 | // Socket closed 809 | #ifdef SOCKET_LIBRARY_LOGGING 810 | Print("Socket closed"); 811 | #endif 812 | mConnected = false; 813 | 814 | } else { 815 | mLastWSAError = WSAGetLastError(); 816 | 817 | if (mLastWSAError != WSAWOULDBLOCK) { 818 | #ifdef SOCKET_LIBRARY_LOGGING 819 | Print("recv() failed, result:, " , res, ", error: ", mLastWSAError, " queued bytes: " , StringLen(mPendingReceiveData)); 820 | #endif 821 | mConnected = false; 822 | } 823 | } 824 | } 825 | } else { 826 | ioctlsocket(mSocket32, FIONBIO, nonblock); 827 | 828 | int res = 1; 829 | while (res > 0) { 830 | res = recv(mSocket32, arrBuffer, ReceiveBufferSize, 0); 831 | if (res > 0) { 832 | StringAdd(mPendingReceiveData, CharArrayToString(arrBuffer, 0, res)); 833 | 834 | } else if (res == 0) { 835 | // Socket closed 836 | #ifdef SOCKET_LIBRARY_LOGGING 837 | Print("Socket closed"); 838 | #endif 839 | mConnected = false; 840 | 841 | } else { 842 | mLastWSAError = WSAGetLastError(); 843 | 844 | if (mLastWSAError != WSAWOULDBLOCK) { 845 | #ifdef SOCKET_LIBRARY_LOGGING 846 | Print("recv() failed, result:, " , res, ", error: ", mLastWSAError, " queued bytes: " , StringLen(mPendingReceiveData)); 847 | #endif 848 | mConnected = false; 849 | } 850 | } 851 | } 852 | } 853 | 854 | if (mPendingReceiveData == "") { 855 | // No data 856 | 857 | } else if (MessageSeparator == "") { 858 | // No requested message separator to wait for 859 | strRetval = mPendingReceiveData; 860 | mPendingReceiveData = ""; 861 | 862 | } else { 863 | int idx = StringFind(mPendingReceiveData, MessageSeparator); 864 | if (idx >= 0) { 865 | while (idx == 0) { 866 | mPendingReceiveData = StringSubstr(mPendingReceiveData, idx + StringLen(MessageSeparator)); 867 | idx = StringFind(mPendingReceiveData, MessageSeparator); 868 | } 869 | 870 | strRetval = StringSubstr(mPendingReceiveData, 0, idx); 871 | mPendingReceiveData = StringSubstr(mPendingReceiveData, idx + StringLen(MessageSeparator)); 872 | } 873 | } 874 | 875 | return strRetval; 876 | } 877 | 878 | // ------------------------------------------------------------- 879 | // Receive function which fills an array, provided by reference. 880 | // Always clears the array. Returns the number of bytes 881 | // put into the array. 882 | // If you send and receive binary data, then you can no longer 883 | // use the built-in messaging protocol provided by this library's 884 | // option to process a message terminator such as \r\n. You have 885 | // to implement the messaging yourself. 886 | // ------------------------------------------------------------- 887 | 888 | int ClientSocket::Receive(uchar & callerBuffer[]) 889 | { 890 | if (!mConnected) return 0; 891 | 892 | ArrayResize(callerBuffer, 0); 893 | int ctTotalReceived = 0; 894 | 895 | // Make sure that event handling is set up, if requested 896 | #ifdef SOCKET_LIBRARY_USE_EVENTS 897 | SetupSocketEventHandling(); 898 | #endif 899 | 900 | uchar arrBuffer[]; 901 | ArrayResize(arrBuffer, ReceiveBufferSize); 902 | 903 | uint nonblock = 1; 904 | if (TerminalInfoInteger(TERMINAL_X64)) { 905 | ioctlsocket(mSocket64, FIONBIO, nonblock); 906 | } else { 907 | ioctlsocket(mSocket32, FIONBIO, nonblock); 908 | } 909 | 910 | int res = 1; 911 | while (res > 0) { 912 | if (TerminalInfoInteger(TERMINAL_X64)) { 913 | res = recv(mSocket64, arrBuffer, ReceiveBufferSize, 0); 914 | } else { 915 | res = recv(mSocket32, arrBuffer, ReceiveBufferSize, 0); 916 | } 917 | 918 | if (res > 0) { 919 | ArrayResize(callerBuffer, ctTotalReceived + res); 920 | ArrayCopy(callerBuffer, arrBuffer, ctTotalReceived, 0, res); 921 | ctTotalReceived += res; 922 | 923 | } else if (res == 0) { 924 | // Socket closed 925 | #ifdef SOCKET_LIBRARY_LOGGING 926 | Print("Socket closed"); 927 | #endif 928 | mConnected = false; 929 | 930 | } else { 931 | mLastWSAError = WSAGetLastError(); 932 | 933 | if (mLastWSAError != WSAWOULDBLOCK) { 934 | #ifdef SOCKET_LIBRARY_LOGGING 935 | Print("recv() failed, result:, " , res, ", error: ", mLastWSAError); 936 | #endif 937 | mConnected = false; 938 | } 939 | } 940 | } 941 | 942 | return ctTotalReceived; 943 | } 944 | 945 | // ------------------------------------------------------------- 946 | // Event handling in client socket 947 | // ------------------------------------------------------------- 948 | 949 | void ClientSocket::SetupSocketEventHandling() 950 | { 951 | #ifdef SOCKET_LIBRARY_USE_EVENTS 952 | if (mDoneEventHandling) return; 953 | 954 | // Can only do event handling in an EA. Ignore otherwise. 955 | if (MQLInfoInteger(MQL_PROGRAM_TYPE) != PROGRAM_EXPERT) { 956 | mDoneEventHandling = true; 957 | return; 958 | } 959 | 960 | long hWnd = ChartGetInteger(0, CHART_WINDOW_HANDLE); 961 | if (!hWnd) return; 962 | mDoneEventHandling = true; // Don't actually care whether it succeeds. 963 | 964 | if (TerminalInfoInteger(TERMINAL_X64)) { 965 | WSAAsyncSelect(mSocket64, hWnd, 0x100 /* WM_KEYDOWN */, 0xFF /* All events */); 966 | } else { 967 | WSAAsyncSelect(mSocket32, (int)hWnd, 0x100 /* WM_KEYDOWN */, 0xFF /* All events */); 968 | } 969 | #endif 970 | } 971 | 972 | 973 | // ------------------------------------------------------------- 974 | // Server socket class 975 | // ------------------------------------------------------------- 976 | 977 | class ServerSocket 978 | { 979 | private: 980 | // Need different socket handles for 32-bit and 64-bit environments 981 | SOCKET_HANDLE32 mSocket32; 982 | SOCKET_HANDLE64 mSocket64; 983 | 984 | // Other state variables 985 | bool mCreated; 986 | int mLastWSAError; 987 | 988 | // Optional event handling 989 | void SetupSocketEventHandling(); 990 | bool mDoneEventHandling; 991 | 992 | public: 993 | // Constructor, specifying whether we allow remote connections 994 | ServerSocket(ushort ServerPort, bool ForLocalhostOnly); 995 | 996 | // Destructor 997 | ~ServerSocket(); 998 | 999 | // Accept function, which returns NULL if no waiting client, or 1000 | // a new instace of ClientSocket() 1001 | ClientSocket * Accept(); 1002 | 1003 | // Access to state information 1004 | bool Created() {return mCreated;} 1005 | int GetLastSocketError() {return mLastWSAError;} 1006 | ulong GetSocketHandle() {return (mSocket32 ? mSocket32 : mSocket64);} 1007 | }; 1008 | 1009 | 1010 | // ------------------------------------------------------------- 1011 | // Constructor for server socket 1012 | // ------------------------------------------------------------- 1013 | 1014 | ServerSocket::ServerSocket(ushort serverport, bool ForLocalhostOnly) 1015 | { 1016 | // Create socket and make it non-blocking 1017 | mCreated = false; 1018 | mLastWSAError = 0; 1019 | if (TerminalInfoInteger(TERMINAL_X64)) { 1020 | // Force compiler to use the 64-bit version of socket() 1021 | // by passing it a uint 3rd parameter 1022 | uint proto = IPPROTO_TCP; 1023 | mSocket64 = socket(AF_INET, SOCK_STREAM, proto); 1024 | 1025 | if (mSocket64 == INVALID_SOCKET64) { 1026 | mLastWSAError = WSAGetLastError(); 1027 | #ifdef SOCKET_LIBRARY_LOGGING 1028 | Print("socket() failed, 64-bit, error: ", mLastWSAError); 1029 | #endif 1030 | return; 1031 | } 1032 | uint nonblock = 1; 1033 | ioctlsocket(mSocket64, FIONBIO, nonblock); 1034 | 1035 | } else { 1036 | // Force compiler to use the 32-bit version of socket() 1037 | // by passing it a int 3rd parameter 1038 | int proto = IPPROTO_TCP; 1039 | mSocket32 = socket(AF_INET, SOCK_STREAM, proto); 1040 | 1041 | if (mSocket32 == INVALID_SOCKET32) { 1042 | mLastWSAError = WSAGetLastError(); 1043 | #ifdef SOCKET_LIBRARY_LOGGING 1044 | Print("socket() failed, 32-bit, error: ", mLastWSAError); 1045 | #endif 1046 | return; 1047 | } 1048 | uint nonblock = 1; 1049 | ioctlsocket(mSocket32, FIONBIO, nonblock); 1050 | } 1051 | 1052 | // Try a bind 1053 | sockaddr server; 1054 | server.family = AF_INET; 1055 | server.port = htons(serverport); 1056 | server.address = (ForLocalhostOnly ? 0x100007f : 0); // 127.0.0.1 or INADDR_ANY 1057 | 1058 | if (TerminalInfoInteger(TERMINAL_X64)) { 1059 | int bindres = bind(mSocket64, server, sizeof(sockaddr)); 1060 | if (bindres != 0) { 1061 | // Bind failed 1062 | mLastWSAError = WSAGetLastError(); 1063 | #ifdef SOCKET_LIBRARY_LOGGING 1064 | Print("bind() failed, 64-bit, port probably already in use, error: ", mLastWSAError); 1065 | #endif 1066 | return; 1067 | 1068 | } else { 1069 | int listenres = listen(mSocket64, 10); 1070 | if (listenres != 0) { 1071 | // Listen failed 1072 | mLastWSAError = WSAGetLastError(); 1073 | #ifdef SOCKET_LIBRARY_LOGGING 1074 | Print("listen() failed, 64-bit, error: ", mLastWSAError); 1075 | #endif 1076 | return; 1077 | 1078 | } else { 1079 | mCreated = true; 1080 | } 1081 | } 1082 | } else { 1083 | int bindres = bind(mSocket32, server, sizeof(sockaddr)); 1084 | if (bindres != 0) { 1085 | // Bind failed 1086 | mLastWSAError = WSAGetLastError(); 1087 | #ifdef SOCKET_LIBRARY_LOGGING 1088 | Print("bind() failed, 32-bit, port probably already in use, error: ", mLastWSAError); 1089 | #endif 1090 | return; 1091 | 1092 | } else { 1093 | int listenres = listen(mSocket32, 10); 1094 | if (listenres != 0) { 1095 | // Listen failed 1096 | mLastWSAError = WSAGetLastError(); 1097 | #ifdef SOCKET_LIBRARY_LOGGING 1098 | Print("listen() failed, 32-bit, error: ", mLastWSAError); 1099 | #endif 1100 | return; 1101 | 1102 | } else { 1103 | mCreated = true; 1104 | } 1105 | } 1106 | } 1107 | 1108 | // Try settig up event handling; can fail here in constructor 1109 | // if no window handle is available because it's being called 1110 | // from OnInit() while MT4/5 is loading 1111 | #ifdef SOCKET_LIBRARY_USE_EVENTS 1112 | SetupSocketEventHandling(); 1113 | #endif 1114 | } 1115 | 1116 | 1117 | // ------------------------------------------------------------- 1118 | // Destructor. Close the socket if created 1119 | // ------------------------------------------------------------- 1120 | 1121 | ServerSocket::~ServerSocket() 1122 | { 1123 | if (TerminalInfoInteger(TERMINAL_X64)) { 1124 | if (mSocket64 != 0) closesocket(mSocket64); 1125 | } else { 1126 | if (mSocket32 != 0) closesocket(mSocket32); 1127 | } 1128 | } 1129 | 1130 | // ------------------------------------------------------------- 1131 | // Accepts any incoming connection. Returns either NULL, 1132 | // or an instance of ClientSocket 1133 | // ------------------------------------------------------------- 1134 | 1135 | ClientSocket * ServerSocket::Accept() 1136 | { 1137 | if (!mCreated) return NULL; 1138 | 1139 | // Make sure that event handling is in place; can fail in constructor 1140 | // if no window handle is available because it's being called 1141 | // from OnInit() while MT4/5 is loading 1142 | #ifdef SOCKET_LIBRARY_USE_EVENTS 1143 | SetupSocketEventHandling(); 1144 | #endif 1145 | 1146 | ClientSocket * pClient = NULL; 1147 | 1148 | if (TerminalInfoInteger(TERMINAL_X64)) { 1149 | SOCKET_HANDLE64 acc = accept(mSocket64, 0, 0); 1150 | if (acc != INVALID_SOCKET64) { 1151 | pClient = new ClientSocket(NULL, acc); 1152 | } 1153 | } else { 1154 | SOCKET_HANDLE32 acc = accept(mSocket32, 0, 0); 1155 | if (acc != INVALID_SOCKET32) { 1156 | pClient = new ClientSocket(NULL, acc); 1157 | } 1158 | } 1159 | 1160 | return pClient; 1161 | } 1162 | 1163 | // ------------------------------------------------------------- 1164 | // Event handling 1165 | // ------------------------------------------------------------- 1166 | 1167 | void ServerSocket::SetupSocketEventHandling() 1168 | { 1169 | #ifdef SOCKET_LIBRARY_USE_EVENTS 1170 | if (mDoneEventHandling) return; 1171 | 1172 | // Can only do event handling in an EA. Ignore otherwise. 1173 | if (MQLInfoInteger(MQL_PROGRAM_TYPE) != PROGRAM_EXPERT) { 1174 | mDoneEventHandling = true; 1175 | return; 1176 | } 1177 | 1178 | long hWnd = ChartGetInteger(0, CHART_WINDOW_HANDLE); 1179 | if (!hWnd) return; 1180 | mDoneEventHandling = true; // Don't actually care whether it succeeds. 1181 | 1182 | if (TerminalInfoInteger(TERMINAL_X64)) { 1183 | WSAAsyncSelect(mSocket64, hWnd, 0x100 /* WM_KEYDOWN */, 0xFF /* All events */); 1184 | } else { 1185 | WSAAsyncSelect(mSocket32, (int)hWnd, 0x100 /* WM_KEYDOWN */, 0xFF /* All events */); 1186 | } 1187 | #endif 1188 | } --------------------------------------------------------------------------------