├── MQL4 ├── Scripts │ ├── _CloseAll.mq4 │ ├── _DeleteAllPendings.mq4 │ └── mql4-mysql_trade_replicator_drop_tables.mq4 ├── Include │ ├── logging_basic.mqh │ ├── string_toolbox.mqh │ ├── volume_sizing.mqh │ ├── mql4-mysql_config.mqh │ ├── display_price_volume.mqh │ ├── orders_to_send_manager.mqh │ ├── mql4-mysql_trade_replicator.mqh │ ├── mql4-mysql_trade_replicator_master.mqh │ ├── mql4-mysql_trade_replicator_slave.mqh │ ├── mql4-mysql_toolbox.mqh │ └── mql4-mysql.mqh └── Experts │ ├── mql4-mysql_trade_replicator_master.mq4 │ └── mql4-mysql_trade_replicator_slave.mq4 ├── _Models ├── trade_replicator.mwb └── trade_replicator.png ├── Python ├── trade_replicator_master.bat ├── trade_replicator_create.bat ├── trade_replicator_analyst.bat ├── trade_replicator_drop.bat ├── trade_replicator_manage.py ├── trade_replicator_master.py └── trade_replicator_toolbox.py ├── JForex ├── Strategies │ ├── files │ │ └── mysql-connector-java-5.1.30-bin.jar │ └── JForexMySQLTradeReplicatorMaster.java └── README.TXT ├── .gitignore ├── LICENSE └── README.md /MQL4/Scripts/_CloseAll.mq4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Scripts/_CloseAll.mq4 -------------------------------------------------------------------------------- /_Models/trade_replicator.mwb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/_Models/trade_replicator.mwb -------------------------------------------------------------------------------- /_Models/trade_replicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/_Models/trade_replicator.png -------------------------------------------------------------------------------- /MQL4/Include/logging_basic.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/logging_basic.mqh -------------------------------------------------------------------------------- /MQL4/Include/string_toolbox.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/string_toolbox.mqh -------------------------------------------------------------------------------- /MQL4/Include/volume_sizing.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/volume_sizing.mqh -------------------------------------------------------------------------------- /MQL4/Include/mql4-mysql_config.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/mql4-mysql_config.mqh -------------------------------------------------------------------------------- /MQL4/Scripts/_DeleteAllPendings.mq4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Scripts/_DeleteAllPendings.mq4 -------------------------------------------------------------------------------- /MQL4/Include/display_price_volume.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/display_price_volume.mqh -------------------------------------------------------------------------------- /MQL4/Include/orders_to_send_manager.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/orders_to_send_manager.mqh -------------------------------------------------------------------------------- /Python/trade_replicator_master.bat: -------------------------------------------------------------------------------- 1 | python trade_replicator_master.py --host 127.0.0.1 --database test --user root --password 123456 --port 3306 2 | pause -------------------------------------------------------------------------------- /MQL4/Include/mql4-mysql_trade_replicator.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/mql4-mysql_trade_replicator.mqh -------------------------------------------------------------------------------- /MQL4/Experts/mql4-mysql_trade_replicator_master.mq4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Experts/mql4-mysql_trade_replicator_master.mq4 -------------------------------------------------------------------------------- /MQL4/Experts/mql4-mysql_trade_replicator_slave.mq4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Experts/mql4-mysql_trade_replicator_slave.mq4 -------------------------------------------------------------------------------- /MQL4/Include/mql4-mysql_trade_replicator_master.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/mql4-mysql_trade_replicator_master.mqh -------------------------------------------------------------------------------- /MQL4/Include/mql4-mysql_trade_replicator_slave.mqh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/MQL4/Include/mql4-mysql_trade_replicator_slave.mqh -------------------------------------------------------------------------------- /Python/trade_replicator_create.bat: -------------------------------------------------------------------------------- 1 | python trade_replicator_manage.py --host 127.0.0.1 --database test --user root --password 123456 --port 3306 --action create 2 | pause -------------------------------------------------------------------------------- /JForex/Strategies/files/mysql-connector-java-5.1.30-bin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/femtotrader/mysql-trade-replicator/HEAD/JForex/Strategies/files/mysql-connector-java-5.1.30-bin.jar -------------------------------------------------------------------------------- /Python/trade_replicator_analyst.bat: -------------------------------------------------------------------------------- 1 | python trade_replicator_manage.py --host 127.0.0.1 --database test --user root --password 123456 --port 3306 --masterid_algo 11:algo1,12:algo1,13:algo1,14:algo1,15:algo2,16:algo2,17:algo2,18:algo2 --action analyst 2 | pause -------------------------------------------------------------------------------- /Python/trade_replicator_drop.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo ARE YOU SURE YOU REALLY WANT TO DROP TABLES? 3 | echo Close this window or press CTRL+C to CANCEL 4 | echo Press ENTER to accept 5 | pause 6 | python trade_replicator_manage.py --host 127.0.0.1 --database test --user root --password 123456 --port 3306 --action drop 7 | REM --tablesprefix 8 | pause -------------------------------------------------------------------------------- /JForex/README.TXT: -------------------------------------------------------------------------------- 1 | Prerequisites 2 | JForex 3 | JForex JLNP https://www.dukascopy.com/client/demo/jclient/jforex.jnlp 4 | Open demo account http://www.dukascopy.com/swiss/fr/forex/demo_fx_account/ 5 | 6 | mysql-connector-java-gpl-5.1.30 7 | https://dev.mysql.com/downloads/connector/j/ 8 | 9 | Copy 10 | mysql-connector-java-5.1.30-bin.jar 11 | in \Documents\JForex\Strategies\files 12 | 13 | mql4-mysql_trade_replicator_master_jforex.java 14 | in \Documents\JForex\Strategies\ 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, femtotrader 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /MQL4/Scripts/mql4-mysql_trade_replicator_drop_tables.mq4: -------------------------------------------------------------------------------- 1 | //+------------------------------------------------------------------+ 2 | //| mql4-mysql_trade_replicator_drop_tables.mq4 | 3 | //| Copyright 2014, MetaQuotes Software Corp. | 4 | //| http://www.mql5.com | 5 | //+------------------------------------------------------------------+ 6 | #property copyright "Copyright 2014, MetaQuotes Software Corp." 7 | #property link "http://www.mql5.com" 8 | #property version "1.00" 9 | #property strict 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | extern string g_tablePrefix = ""; //table prefix 17 | extern int g_logging_level = 0; //level of logging (0 display all - see toolbox code) 18 | 19 | //+------------------------------------------------------------------+ 20 | //| Script program start function | 21 | //+------------------------------------------------------------------+ 22 | void OnStart() 23 | { 24 | //--- 25 | 26 | init_table_names(g_tablePrefix); 27 | 28 | if (connect_db(g_db_connect_id, g_db_host, g_db_user, g_db_pass, g_db_name, g_db_port, g_db_socket, g_db_client)) { 29 | if (!drop_table_replicator(g_db_connect_id, g_db_name, g_tablePrefix)) { 30 | Print("Can't drop tables"); 31 | } 32 | } else { 33 | Print("Can't connect to DB"); 34 | } 35 | } 36 | //+------------------------------------------------------------------+ 37 | 38 | -------------------------------------------------------------------------------- /MQL4/Include/mql4-mysql_toolbox.mqh: -------------------------------------------------------------------------------- 1 | //+------------------------------------------------------------------+ 2 | //| mql4-mysql_toolbox.mq4 | 3 | //| | 4 | //| Sergey Lukin | 5 | //| contact@sergeylukin.com | 6 | //| | 7 | //| Toolbox by FemtoTrader | 8 | //| femto.trader@gmail.com | 9 | //+------------------------------------------------------------------+ 10 | #property copyright "FemtoTrader" 11 | #property link "https://sites.google.com/site/femtotrader/" 12 | 13 | #include 14 | //+------------------------------------------------------------------+ 15 | //| | 16 | //+------------------------------------------------------------------+ 17 | string create_db_timestamp(datetime dt) // no timezone 18 | { 19 | string s_timestamp; 20 | if(dt!=0) 21 | { 22 | s_timestamp = TimeToStr(dt, TIME_DATE | TIME_SECONDS); 23 | s_timestamp = StringSetChar(s_timestamp, 4, '-'); 24 | s_timestamp = StringSetChar(s_timestamp, 7, '-'); 25 | //time = time + " " + g_timezone_setting; 26 | } else { 27 | s_timestamp="0000-00-00 00:00:00"; 28 | } 29 | return (s_timestamp); 30 | } 31 | //+------------------------------------------------------------------+ 32 | //| | 33 | //+------------------------------------------------------------------+ 34 | string timezone_to_string(int timezone) 35 | { 36 | string sTimezone; 37 | 38 | sTimezone=IntegerToString(MathAbs(timezone)); 39 | 40 | while(StringLen(sTimezone)<2) 41 | sTimezone=("0"+sTimezone); 42 | 43 | if(timezone>=0) 44 | { 45 | sTimezone="+"+sTimezone; 46 | } else { 47 | sTimezone="-"+sTimezone; 48 | } 49 | return(sTimezone); 50 | } 51 | //+------------------------------------------------------------------+ 52 | 53 | void disconnect_db(int db_connect_id) { 54 | deinit_MySQL(db_connect_id); 55 | } 56 | 57 | /* 58 | bool reconnect(int db_connect_id) 59 | { 60 | disconnect_db(db_connect_id); 61 | return (connect_db(db_connect_id)); 62 | } 63 | */ -------------------------------------------------------------------------------- /Python/trade_replicator_manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | about = """ 5 | Python script to manage trade_replicator MySQL database 6 | 7 | Copyright (C) 2014 "FemtoTrader" 8 | 9 | This program is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program. If not, see 21 | 22 | I'm developer and I provide under free software license some softwares that can be useful 23 | for currencies users/traders. 24 | If you consider that what I'm doing is valuable 25 | you can send me some crypto-coins. 26 | https://sites.google.com/site/femtotrader/donate 27 | """ 28 | 29 | import argparse 30 | import logging 31 | import sys 32 | from trade_replicator_toolbox import * 33 | 34 | # mysql-connector-python-1.1.6-py2.7.msi 35 | 36 | def manage(args): 37 | database = args.database 38 | tables_prefix = args.tablesprefix 39 | tables_names = get_tables_names(tables_prefix) 40 | conn = get_db_conn(args) 41 | 42 | if args.action in ACTIONS_ALLOWED: 43 | logging.info("Run '%s'" % args.action) 44 | if args.action == 'analyst': 45 | tables_analyst(conn, tables_names, args) 46 | 47 | elif args.action == 'create': 48 | tables_create(conn, database, tables_prefix, tables_names) 49 | 50 | elif args.action == 'drop': 51 | tables_drop(conn, database, tables_prefix, tables_names) 52 | 53 | elif args.action == 'truncate': 54 | tables_truncate(conn, database, tables_prefix, tables_names) 55 | 56 | else: 57 | raise(NotImplementedError) 58 | 59 | else: 60 | raise(NotImplementedError) 61 | 62 | conn.close() 63 | logging.info("database connection closed") 64 | 65 | def cast_args_manage(args): 66 | args.port = int(args.port) 67 | args.action = args.action.lower() 68 | 69 | return(args) 70 | 71 | if __name__ == '__main__': 72 | logger = logging.getLogger() 73 | #logger = logging.getLogger(__name__) 74 | 75 | logging.basicConfig(level=logging.DEBUG) 76 | 77 | # File handler 78 | fh = logging.FileHandler('trade_replicator_manage.log') 79 | fh.setLevel(logging.INFO) 80 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') 81 | fh.setFormatter(formatter) 82 | logger.addHandler(fh) 83 | 84 | logging.info('Manage trade_replicator') 85 | 86 | parser = argparse.ArgumentParser() 87 | #parser.add_argument("--dbengine", help="DB engine", action='store', default='mysql') 88 | parser.add_argument("--host", help="DB hostname", action='store', default='127.0.0.1') 89 | parser.add_argument("--database", help="DB name", action='store', default='test') 90 | parser.add_argument("--user", help="DB username", action='store', default='root') 91 | parser.add_argument("--password", help="DB user password", action='store', default='123456') 92 | parser.add_argument("--port", help="DB port", action='store', default='3306') 93 | parser.add_argument("--tablesprefix", help="tables prefix", action='store', default='trade_replicator_') 94 | #parser.add_argument("--master_id", help="master_id", action='store', default='n9o816RTuaxJt99WSfD8') 95 | #parser.add_argument("--master_deposit_currency", help="master_id", action='store', default='USD') 96 | parser.add_argument("--masterid_algo", help="masterid1:algoA,masterid2:algoA,masterid3:algoB,masterid4:algoB", action='store', default='11:algo1,12:algo2') 97 | parser.add_argument("--slavetradesclosed", help="only closed slave trades", action='store_true') 98 | parser.add_argument("--slavetradesopened", help="only opened slave trades", action='store_true') 99 | parser.add_argument("--about", help="about", action='store_true') 100 | parser.add_argument("--action", help="action to do: " + "'" + "', '".join(ACTIONS_ALLOWED) + "'", action="store", default='analyst') 101 | args = parser.parse_args() 102 | 103 | args = cast_args_manage(args) 104 | args.dbengine = 'mysql' 105 | 106 | if args.about: 107 | print(about) 108 | else: 109 | manage(args) 110 | -------------------------------------------------------------------------------- /Python/trade_replicator_master.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | about = """ 5 | Python script to send order (master) via trade_replicator MySQL database 6 | 7 | Copyright (C) 2014 "FemtoTrader" 8 | 9 | This program is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program. If not, see 21 | 22 | I'm developer and I provide under free software license some softwares that can be useful 23 | for currencies users/traders. 24 | If you consider that what I'm doing is valuable 25 | you can send me some crypto-coins. 26 | https://sites.google.com/site/femtotrader/donate 27 | """ 28 | 29 | import argparse 30 | import logging 31 | import traceback 32 | from trade_replicator_toolbox import * 33 | 34 | import datetime 35 | import os, random, string 36 | import time 37 | 38 | def random_alphanumeric_id(length): 39 | chars = string.ascii_letters + string.digits # + '!@#$%^&*()' 40 | random.seed = (os.urandom(1024)) 41 | return(''.join(random.choice(chars) for i in range(length))) 42 | 43 | def random_numeric_id(length): 44 | return(random.randint(10**8, 10**9)) 45 | 46 | def quote(s): 47 | return("'" + s + "'") 48 | 49 | def create_db_timestamp(dt): 50 | if dt is not None and dt!=0: 51 | return(dt.strftime("%Y-%m-%d %H:%M:%S")) 52 | else: 53 | return("0000-00-00 00:00:00") 54 | 55 | def execute_query(conn, query): 56 | cursor = conn.cursor() 57 | try: 58 | logging.info("send query to DB") 59 | logging.info(query) 60 | cursor.execute(query) 61 | conn.commit() 62 | logging.info("DONE") 63 | return(True) 64 | 65 | except mysql.connector.Error as err: 66 | logging.error(err.msg) 67 | return(False) 68 | 69 | def register_terminal(conn, table_name, terminal_type, terminal_id, deposit_currency, comment): 70 | query = "INSERT INTO %s (%s_id, deposit_currency, comment, created, updated) VALUES ('%s', '%s', '%s', NULL, NULL) ON DUPLICATE KEY UPDATE comment=VALUES(comment), deposit_currency=VALUES(deposit_currency), updated=VALUES(updated);" % (table_name, 'master', terminal_id, deposit_currency, comment) 71 | return(execute_query(conn, query)) 72 | 73 | def register_terminal_master(conn, tables_names, master_id, deposit_currency, comment): 74 | return(register_terminal(conn, tables_names['terminals_master'], 'master', master_id, deposit_currency, comment)) 75 | 76 | def create_db_double(x): 77 | if x is None: 78 | return("NULL") 79 | else: 80 | return(str(x)) 81 | 82 | def send_trades_master_query(conn, tables_names, master_id, instrument, direction, volume, open_price, open_time, 83 | close_time, close_price, trade_id, stop_loss, take_profit, 84 | commission, profit, swaps, comment, magic_number): 85 | 86 | 87 | values = ", ".join([quote(master_id), quote(instrument), str(direction), str(volume), create_db_double(open_price), quote(create_db_timestamp(open_time)), quote(create_db_timestamp(close_time)), create_db_double(close_price), quote(trade_id), str(stop_loss), str(take_profit), str(commission), create_db_double(profit), create_db_double(swaps), quote(comment), str(magic_number), 88 | "NULL", "NULL"]) 89 | query = "INSERT INTO %s VALUES (%s);" % (tables_names['trades_master'], values) 90 | execute_query(conn, query) 91 | return(trade_id) 92 | 93 | def order_send(conn, tables_names, master_id, instrument, direction, volume, price, 94 | stop_loss=0.0, take_profit=0.0, comment="", magic_number=0): 95 | 96 | direction = direction.index 97 | open_price = price 98 | open_time = datetime.datetime.utcnow() 99 | close_time = 0 100 | close_price = None 101 | trade_id = random_alphanumeric_id(25) 102 | commission = 0.0 103 | profit = None 104 | swaps = None 105 | 106 | result = send_trades_master_query(conn, tables_names, master_id, instrument, direction, volume, open_price, open_time, 107 | close_time, close_price, trade_id, stop_loss, take_profit, 108 | commission, profit, swaps, comment, magic_number) 109 | 110 | return(result) 111 | 112 | def order_modify(): 113 | raise(NotImplementedError) 114 | 115 | def order_close(conn, tables_names, master_id, trade_id): 116 | close_time = datetime.datetime.utcnow() 117 | query = "UPDATE %s SET close_time=%s, close_price=0 WHERE master_id=%s AND trade_id=%s" % (tables_names['trades_master'], quote(create_db_timestamp(close_time)), quote(master_id), quote(trade_id)) 118 | execute_query(conn, query) 119 | return(True) 120 | 121 | # ToDo: partial close 122 | 123 | def main(args): 124 | database = args.database 125 | tables_prefix = args.tablesprefix 126 | tables_names = get_tables_names(tables_prefix) 127 | master_id = args.masterid 128 | conn = get_db_conn(args) 129 | deposit_currency = args.deposit_currency 130 | comment = args.comment 131 | register_terminal_master(conn, tables_names, master_id, deposit_currency, comment) 132 | 133 | logging.info("open order") 134 | ticket = order_send(conn, tables_names, master_id, "EURUSD", ORDER_TYPE.SELL, 0.01, 1.34, 0.0, 0.0, "order send with python", 42) 135 | logging.info("trade open as #%s" % ticket) 136 | 137 | delay = 5 138 | logging.info("waiting %d s" % delay) 139 | time.sleep(delay) 140 | 141 | logging.info("close order") 142 | b_result = order_close(conn, tables_names, master_id, ticket) 143 | 144 | conn.close() 145 | 146 | 147 | 148 | if __name__ == '__main__': 149 | logger = logging.getLogger() 150 | logging.basicConfig(level = logging.DEBUG, format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s') 151 | 152 | # File handler 153 | fh = logging.FileHandler('log_py_master.log') 154 | fh.setLevel(logging.INFO) 155 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') 156 | fh.setFormatter(formatter) 157 | logger.addHandler(fh) 158 | 159 | parser = argparse.ArgumentParser() 160 | parser.add_argument("--dbengine", help="DB engine", action='store', default='mysql') 161 | parser.add_argument("--host", help="DB hostname", action='store', default='127.0.0.1') 162 | parser.add_argument("--database", help="DB name", action='store', default='test') 163 | parser.add_argument("--user", help="DB username", action='store', default='root') 164 | parser.add_argument("--password", help="DB user password", action='store', default='123456') 165 | parser.add_argument("--port", help="DB port", action='store', default='3306') 166 | parser.add_argument("--tablesprefix", help="tables prefix", action='store', default='trade_replicator_') 167 | parser.add_argument("--masterid", help="masterid", action='store', default='n9o816RTuaxJt99WSfD8') 168 | parser.add_argument("--deposit_currency", help="deposit_currency prefix", action='store', default='USD') 169 | parser.add_argument("--comment", help="comment (terminal master)", action='store', default='master01') 170 | parser.add_argument("--about", help="about", action='store_true') 171 | args = parser.parse_args() 172 | 173 | if args.about: 174 | print(about) 175 | else: 176 | main(args) 177 | -------------------------------------------------------------------------------- /MQL4/Include/mql4-mysql.mqh: -------------------------------------------------------------------------------- 1 | //+----------------------------------------------------------------------------+ 2 | //| mql4-mysql.mqh | 3 | //+----------------------------------------------------------------------------+ 4 | //| Built by Sergey Lukin | 5 | //| contact@sergeylukin.com | 6 | //| | 7 | //| This libarry is highly based on following: | 8 | //| | 9 | //| - MySQL wrapper by "russel": http://codebase.mql4.com/5040 | 10 | //| - MySQL wrapper modification by "vedroid": http://codebase.mql4.com/8122 | 11 | //| - EAX Mysql: http://www.mql5.com/en/code/855 | 12 | //| - This thread: http://forum.mql4.com/60708 (Cheers to user "gchrmt4" for | 13 | //| expanded explanations on how to deal with ANSI <-> UNICODE hell in MQL4 | 14 | //| | 15 | //+----------------------------------------------------------------------------+ 16 | #property copyright "Unlicense" 17 | #property link "http://unlicense.org/" 18 | 19 | #import "kernel32.dll" 20 | int lstrlenA(int); 21 | void RtlMoveMemory(uchar & arr[], int, int); 22 | int LocalFree(int); // May need to be changed depending on how the DLL allocates memory 23 | #import 24 | 25 | #import "msvcrt.dll" 26 | // TODO extend/handle 32/64 bit codewise 27 | int memcpy(char &Destination[], int Source, int Length); 28 | int memcpy(char &Destination[], long Source, int Length); 29 | int memcpy(int &dst, int src, int cnt); 30 | int memcpy(long &dst, long src, int cnt); 31 | #import 32 | 33 | #import "libmysql.dll" 34 | int mysql_init (int dbConnectId); 35 | int mysql_errno (int dbConnectId); 36 | int mysql_error (int dbConnectId); 37 | int mysql_real_connect (int dbConnectId, uchar & host[], uchar & user[], uchar & password[], uchar & db[], int port, int socket, int clientflag); 38 | int mysql_real_query (int dbConnectId, uchar & query[], int length); 39 | int mysql_query (int dbConnectId, uchar & query[]); 40 | void mysql_close (int dbConnectId); 41 | int mysql_store_result (int dbConnectId); 42 | int mysql_use_result (int dbConnectId); 43 | int mysql_insert_id (int dbConnectId); 44 | 45 | int mysql_fetch_row (int resultStruct); 46 | int mysql_fetch_field (int resultStruct); 47 | int mysql_fetch_lengths (int resultStruct); 48 | int mysql_num_fields (int resultStruct); 49 | int mysql_num_rows (int resultStruct); 50 | void mysql_free_result (int resultStruct); 51 | #import 52 | 53 | //+----------------------------------------------------------------------------+ 54 | //| Connect to MySQL and write connection ID to the first argument | 55 | //| Probably not the most elegant way but it works well for simple purposes | 56 | //| and is flexible enough to allow multiple connections | 57 | //+----------------------------------------------------------------------------+ 58 | bool init_MySQL(int & dbConnectId, string host, string user, string pass, string dbName, int port = 3306, int socket = 0, int client = 0) { 59 | dbConnectId = mysql_init(dbConnectId); 60 | 61 | if ( dbConnectId == 0 ) { 62 | Print("init_MySQL: mysql_init failed. There was insufficient memory to allocate a new object"); 63 | return (false); 64 | } 65 | 66 | // Convert the strings to uchar[] arrays 67 | uchar hostChar[]; 68 | StringToCharArray(host, hostChar); 69 | uchar userChar[]; 70 | StringToCharArray(user, userChar); 71 | uchar passChar[]; 72 | StringToCharArray(pass, passChar); 73 | uchar dbNameChar[]; 74 | StringToCharArray(dbName, dbNameChar); 75 | 76 | int result = mysql_real_connect(dbConnectId, hostChar, userChar, passChar, dbNameChar, port, socket, client); 77 | 78 | if ( result != dbConnectId ) { 79 | int errno = mysql_errno(dbConnectId); 80 | string error = mql4_mysql_ansi2unicode(mysql_error(dbConnectId)); 81 | 82 | Print("init_MySQL: mysql_errno: ", errno,"; mysql_error: ", error); 83 | return (false); 84 | } 85 | return (true); 86 | } 87 | 88 | //+----------------------------------------------------------------------------+ 89 | //| | 90 | //+----------------------------------------------------------------------------+ 91 | void deinit_MySQL(int dbConnectId){ 92 | mysql_close(dbConnectId); 93 | } 94 | 95 | //+----------------------------------------------------------------------------+ 96 | //| Check whether there was an error with last query | 97 | //| | 98 | //| return (true): no error; (false): there was an error; | 99 | //+----------------------------------------------------------------------------+ 100 | bool MySQL_NoError(int dbConnectId) { 101 | int errno = mysql_errno(dbConnectId); 102 | string error = mql4_mysql_ansi2unicode(mysql_error(dbConnectId)); 103 | 104 | if ( errno > 0 ) { 105 | Print("MySQL_NoError: mysql_errno: ", errno,"; mysql_error: ", error); 106 | return (false); 107 | } 108 | return (true); 109 | } 110 | 111 | //+----------------------------------------------------------------------------+ 112 | //| Simply run a query, perfect for actions like INSERTs, UPDATEs, DELETEs | 113 | //+----------------------------------------------------------------------------+ 114 | bool MySQL_Query(int dbConnectId, string query) { 115 | uchar queryChar[]; 116 | StringToCharArray(query, queryChar); 117 | 118 | mysql_query(dbConnectId, queryChar); 119 | if ( MySQL_NoError(dbConnectId) ) { 120 | return (true); 121 | } 122 | return (false); 123 | } 124 | 125 | //+----------------------------------------------------------------------------+ 126 | //| Fetch row(s) in a 2-dimansional array | 127 | //| | 128 | //| return (-1): error; (0): 0 rows selected; (1+): some rows selected; | 129 | //+----------------------------------------------------------------------------+ 130 | int MySQL_FetchArray(int dbConnectId, string query, string & data[][]){ 131 | 132 | if ( !MySQL_Query(dbConnectId, query) ) { 133 | return (-1); 134 | } 135 | 136 | int resultStruct = mysql_store_result(dbConnectId); 137 | 138 | if ( !MySQL_NoError(dbConnectId) ) { 139 | Print("mysqlFetchArray: resultStruct: ", resultStruct); 140 | return (-1); 141 | } 142 | int num_rows = mysql_num_rows(resultStruct); 143 | int num_fields = mysql_num_fields(resultStruct); 144 | 145 | char byte[]; 146 | 147 | if ( num_rows == 0 ) { // 0 rows selected; 148 | return (0); 149 | } 150 | 151 | ArrayResize(data, num_rows); 152 | 153 | for ( int i = 0; i < num_rows; i++ ) { 154 | 155 | int row_ptr = mysql_fetch_row(resultStruct); 156 | int len_ptr = mysql_fetch_lengths(resultStruct); 157 | 158 | for ( int j = 0; j < num_fields; j++ ) { 159 | int leng; 160 | memcpy(leng, len_ptr + j*sizeof(int), sizeof(int)); 161 | 162 | ArrayResize(byte,leng+1); 163 | ArrayInitialize(byte,0); 164 | 165 | int row_ptr_pos; 166 | memcpy(row_ptr_pos, row_ptr + j*sizeof(int), sizeof(int)); 167 | memcpy(byte, row_ptr_pos, leng); 168 | 169 | string s = CharArrayToString(byte); 170 | data[i][j] = s; 171 | 172 | LocalFree(leng); 173 | LocalFree(row_ptr_pos); 174 | } 175 | } 176 | 177 | mysql_free_result(resultStruct); 178 | 179 | if ( MySQL_NoError(dbConnectId) ) { 180 | return (num_rows); 181 | } 182 | return (-1); 183 | } 184 | 185 | //+----------------------------------------------------------------------------+ 186 | //| Lovely function that helps us to get ANSI strings from DLLs to our UNICODE | 187 | //| format | 188 | //| http://forum.mql4.com/60708 | 189 | //+----------------------------------------------------------------------------+ 190 | string mql4_mysql_ansi2unicode(int ptrStringMemory) 191 | { 192 | int szString = lstrlenA(ptrStringMemory); 193 | uchar ucValue[]; 194 | ArrayResize(ucValue, szString + 1); 195 | RtlMoveMemory(ucValue, ptrStringMemory, szString + 1); 196 | string str = CharArrayToString(ucValue); 197 | LocalFree(ptrStringMemory); 198 | return str; 199 | } 200 | //+----------------------------------------------------------------------------+ 201 | 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MySQL Trade Replicator 2 | ====================== 3 | 4 | MySQL Trade Replicator enables you to copy trades between separate accounts, possibly located 5 | at different brokerages. Trades are copied from "master" account to a number of "slave" 6 | accounts, volume scaling and trade direction inversion are supported on slave side. 7 | 8 | Trades are copied via database, so Trade Replicator scripts both master and slave 9 | versions use mql4-mysql https://github.com/sergeylukin/mql4-mysql 10 | to communicate with MySQL database server. 11 | 12 | http://dev.mysql.com/downloads/ 13 | 14 | Why you might want to try this? 15 | ------------------------------- 16 | 17 | * you are a successful trader and would like to offer signal providing service so 18 | your clients could subscribe to your trading for a monthly fee and you do not 19 | want to do this via Zulu or eToro for your own reasons; 20 | 21 | * you are a very consistent Forex looser wishing to use trade inversion 22 | in order to turn your loosing trades on one account into the winning trades 23 | on another account. 24 | 25 | Some remarks to the above proposals: in the first case your client base should be 26 | rather small and you have to give each person a different DB user/password to make 27 | it possible to terminate service access for a given client if need be. 28 | For bigger client base it is hardly possible to manage all clients manually. 29 | 30 | As for the second case, what I mean by consistent looser is that you have to loose 31 | significantly more than 50% of the trades and loses should be definitely way bigger 32 | than the spread and than your average win. 33 | It turns out that consistent losers are as rare as the consistent winners, so it is more 34 | of a joke but of course you are welcome to try that and see :) 35 | 36 | 37 | Supported Platforms 38 | ------------------- 39 | Windows 7+ and any version of MT4 terminal running on it (including build>=600). 40 | 41 | 42 | Original work 43 | ------------- 44 | This MySQL trade replicator is based on 45 | 46 | [https://github.com/kr0st/trade_replicator/](https://github.com/kr0st/trade_replicator/) 47 | 48 | 49 | License 50 | ------- 51 | BSD 2-Clause (http://opensource.org/licenses/BSD-2-Clause). 52 | 53 | 54 | Install 55 | ------- 56 | Install MySQL free community edition from http://dev.mysql.com/downloads/ 57 | 58 | Set password for user "root" (123456 by example) 59 | 60 | You should have a database called "test" 61 | 62 | Master script is used at the signal provider side and slave version is used 63 | at the signal subscriber side. 64 | 65 | * Install - MT4 master and MT4 slave 66 | 67 | Copy files from MQL4 directory into MT4 directory of master and slave 68 | 69 | * Install - JForex master and MT4 slave 70 | 71 | Copy files from JForex directory into your JForex directory `C:\Users\\Documents\JForex\Strategies` 72 | JForex master needs a database driver to work which you should obtain from MySQL site at https://dev.mysql.com/downloads/connector/j/ 73 | The file is called `mysql-connector-java-5.1.30-bin.jar`. 74 | This file should be placed into "Documents\JForex\Strategies\files". 75 | `JForexMySQLTradeReplicatorMaster.java` file must be placed in `Documents\JForex\Strategies` 76 | When this is done just compile this java file from Dukascopy trading platform and start it. 77 | 78 | Important Notes 79 | --------------- 80 | If you opened some trades and then run the master script it will detect that some 81 | new trades appeared and will write them to DB for further copying to slave accounts. 82 | Your master account should be used only for trading that you want to be copied. 83 | 84 | On start script will try to create all needed DB tables, so at least on the first start 85 | you should give script’s DB user rights to create tables. 86 | 87 | Many masters and slaves could use the same database as long as the rule of uniqueness 88 | is not violated for master ids and for slave ids. 89 | 90 | It is possible to copy from many masters at once in a single running slave script instance. 91 | 92 | This project is still in alpha stage so be very careful because some issues could occur 93 | and I will not be responsible of damage (particularly the loss of money). 94 | 95 | 96 | How it works 97 | ------------ 98 | Each master is assigned a random-generated id, when master script is running each trade 99 | that appears on master account will be placed in the database (trades_master table) 100 | identified uniquely by master id and MT4 order ticket. 101 | 102 | At client side slave version of the script is running and scanning DB every second 103 | for new master trades and also for trades that have been closed by master. 104 | 105 | If new master trade is found it is opened in client MT4 terminal and 106 | is written to DB (trades_slave table) uniquely identified by slave id and 107 | new order ticket. In case trade copy failed, status column will have the error number 108 | that has been reported by the MT4 server. When there is no error status column is NULL. 109 | 110 | When master closes the trade it is also going to be closed at the client side and 111 | information in DB will be updated with the time and profit of the closed trade. 112 | 113 | This is how it works in short, you are encouraged to view the database schema because 114 | it contains a lot of useful information that can be used to prepare the summary 115 | of trading for masters and slaves. 116 | 117 | 118 | Quick Start MT4 master - MT4 slave 119 | ---------------------------------- 120 | Before running the scripts they have to be properly configured. You can do it every time 121 | when you run the script or you can write values you need right into mq4 script file 122 | and compile it. 123 | 124 | The first important thing to understand and configure is master and slave ids. 125 | The best way is to use some password generating software and generate 20-symbol ids 126 | such as http://strongpasswordgenerator.com/ 127 | However it should be clear that slave script should contain the existing master id 128 | because slave script will be copying trades only from the master with the given id. 129 | 130 | When setting up a slave side script, please note that it can copy trades from many 131 | masters therefore you can supply multiple space-separated masters ids 132 | to the slave script, here is how to configure masters ids in a slave-side scrip: 133 | 134 | #### Database settings 135 | 136 | Open `MQL4/Include/mql4-mysql_config.mqh` and edit it 137 | 138 | extern string g_db_host_setting = "localhost"; //DB hostname ('localhost' or '127.0.0.1') 139 | extern string g_db_user_setting = "root"; //DB user ('root') 140 | extern string g_db_pass_setting = "123456"; //DB password ('123456') 141 | extern string g_db_name_setting = "test"; //DB name 142 | 143 | extern int g_db_port_setting = 3306; //DB port 144 | extern int g_db_socket_setting = 0; //DB socket 145 | extern int g_db_client_setting = 0; //DB client 146 | 147 | 148 | #### Edit master expert advisor 149 | 150 | Set `g_master_id_setting` 151 | 152 | extern string g_master_id_setting = "n9o816RTuaxJt99WSfD8"; //master_id 153 | 154 | You can put an other value if you want. 155 | At first startup you will also have to create database and register this new master. 156 | It will create a new row in table terminals_master 157 | 158 | extern bool g_create_tables_setting = true; //create tables (set to True only first time to create database tables) 159 | extern bool g_register_master_setting = true; //register master to DB (set to True only first time you use a new master) 160 | 161 | Compile master script and drag this EA on a chart. 162 | 163 | #### Edit slave expert advisor 164 | 165 | Set `g_slave_id_setting` 166 | 167 | extern string g_slave_id_setting = "fVB472V9x8i4RN081pg8"; //slave_id - a string of 20 symbols recommended id, use some passwords generator to obtain it http://strongpasswordgenerator.com/ 168 | 169 | You can put an other value if you want. 170 | 171 | A slave EA need to subscribe to master so you need to set 172 | 173 | extern string g_subscribed_masters_setting = "n9o816RTuaxJt99WSfD8"; //subscribed_masters is a list of masters in the form "master1 master2 master3" etc. (blank space between each master) 174 | 175 | If you want your slave subscribe to several slave just use a blank space between each master_id 176 | 177 | At first startup of a new slave you have to register this new slave. 178 | 179 | extern bool g_register_slave_setting = true; //register slave to DB (set to True only first time you use a new slave) 180 | 181 | Compile slave script and drag this EA on a chart. 182 | 183 | #### Recompile the scripts. 184 | 185 | When ids are configured it is time to setup the database access. Parameters are pretty 186 | much self-explanatory, the only thing to remember here is that read/write access is 187 | required both for master and slave scripts. 188 | 189 | Please see other parameters in the script file, all of them are easy to understand and 190 | all of them need to be configured properly. 191 | 192 | #### Test on demo account 193 | 194 | You should only use this software with demo account in order to test it. 195 | 196 | Other features 197 | -------------- 198 | * JForex master 199 | 200 | You can send orders from JForex to MT4 (or any other slave) using JForex master 201 | 202 | * Python master 203 | 204 | You can send orders from a Python script to MT4 (or any other slave) using Python master 205 | 206 | See example in Python directory 207 | 208 | * Account analyst 209 | 210 | A Python Pandas script is also given to compare trades from `trades_master` 211 | and `trades_slave` 212 | 213 | DB Schema 214 | --------- 215 | 216 | ![DB schema image](_Models/trade_replicator.png) 217 | 218 | 219 | ToDo 220 | ---- 221 | * JForex slave 222 | * Support of modification of SL / TP 223 | * Support of pending orders 224 | 225 | Contact 226 | ------- 227 | 228 | * Mail: femto dot trader at g mail dot com 229 | * Twitter: [@FemtoTrader](https://twitter.com/FemtoTrader) 230 | * Website: https://sites.google.com/site/femtotrader/ 231 | * Donate: https://sites.google.com/site/femtotrader/donate 232 | -------------------------------------------------------------------------------- /Python/trade_replicator_toolbox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Python toolbox to manage trade_replicator MySQL database 6 | 7 | Copyright (C) 2014 "FemtoTrader" 8 | 9 | This program is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program. If not, see 21 | 22 | I'm developer and I provide under free software license some softwares that can be useful 23 | for currencies users/traders. 24 | If you consider that what I'm doing is valuable 25 | you can send me some crypto-coins. 26 | https://sites.google.com/site/femtotrader/donate 27 | """ 28 | 29 | import traceback 30 | import logging 31 | from collections import OrderedDict 32 | 33 | import mysql.connector 34 | 35 | ACTIONS_ALLOWED = ['create', 'drop', 'truncate', 'analyst'] 36 | 37 | 38 | def pip_position(symbol): 39 | if len(symbol) == 6: 40 | cur1 = symbol[0:3] 41 | cur2 = symbol[3:6] 42 | 43 | if cur2 == "JPY": 44 | return(2) 45 | else: 46 | return(4) 47 | 48 | else: 49 | raise(NotImplementedError) 50 | 51 | """ 52 | print 10**(-pip_position("EURUSD")) 53 | print 10**(-pip_position("EURJPY")) 54 | """ 55 | 56 | d_order_type_str = { 57 | 0: 'BUY', 58 | 1: 'SELL', 59 | 2: 'BUYLIMIT', 60 | 3: 'BUYSTOP', 61 | 4: 'SELLLIMIT', 62 | 5: 'SELLSTOP', 63 | } 64 | 65 | from enum import Enum 66 | ORDER_TYPE = Enum('BUY', 'SELL', 'BUYLIMIT', 'BUYSTOP', 'SELLLIMIT', 'SELLSTOP') 67 | 68 | d_order_type_sign = { 69 | 0: 1, # BUY 70 | 1: -1, # SELL 71 | 2: 1, # BUYLIMIT 72 | 3: 1, # BUYSTOP 73 | 4: -1, # SELLLIMIT 74 | 5: -1, # SELLSTOP 75 | } 76 | 77 | # ??? 78 | #d_copy = { 79 | # 0: 4, # BUY(Ask)->SELLLIMIT(Ask) 80 | # 1: 2 # SELL(Bid)->BUYLIMIT(Bid) 81 | #} 82 | 83 | def title_string(s): 84 | return("="*5 + " " + s + " " + "="*5) 85 | 86 | def get_db_conn(args): 87 | if args.dbengine.lower() == 'pgsql': 88 | import psycopg2 89 | logging.info("Connecting to PostgreSQL database") 90 | conn = psycopg2.connect(host=args.host, database=args.database, 91 | user=args.user, password=args.password) 92 | elif args.dbengine.lower() == 'mysql': 93 | logging.info("Connecting to MySQL database") 94 | import mysql.connector 95 | conn = mysql.connector.connect(host=args.host, database=args.database, 96 | user=args.user, password=args.password) 97 | else: 98 | raise(NotImplementedError) 99 | return(conn) 100 | 101 | def tables_analyst(conn, tables_names, args): 102 | import pandas as pd 103 | 104 | masterid_algo = args.masterid_algo 105 | 106 | df_trades_master = pd.io.sql.read_sql("SELECT * FROM %s" % tables_names['trades_master'], conn) 107 | df_trades_slave = pd.io.sql.read_sql("SELECT * FROM %s" % tables_names['trades_slave'], conn) 108 | 109 | logging.info(title_string("trades_master")) 110 | df_trades_master['open_time'] = pd.to_datetime(df_trades_master['open_time']) 111 | df_trades_master['close_time'] = pd.to_datetime(df_trades_master['close_time']) 112 | df_trades_master['trade_duration'] = df_trades_master['close_time'] - df_trades_master['open_time'] 113 | df_trades_master['pip_position'] = df_trades_master['instrument'].map(pip_position) 114 | df_trades_master['direction_str'] = df_trades_master['direction'].map(d_order_type_str) 115 | df_trades_master['direction_sign'] = df_trades_master['direction'].map(d_order_type_sign) 116 | #df_trades_master = df_trades_master.sort(columns=['open_time'], ascending=[True]) 117 | #logging.info(df_trades_master.dtypes) 118 | cols = list(df_trades_master.columns) 119 | for i, col in enumerate(cols): 120 | cols[i] = "master" + '_' + col 121 | 122 | df_trades_master.columns = cols 123 | #df_trades_master = df_trades_master[::-1] # reverse order (last at head) 124 | #print(df_trades_master) 125 | df_trades_master = df_trades_master.sort(columns=['master_trade_id'], ascending=[True]) 126 | logging.info("len: %d" % len(df_trades_master)) 127 | logging.info("profit (master): %.2f" % df_trades_master['master_profit'].sum()) 128 | #df_trades_master = df_trades_master.set_index('master_trade_id') 129 | df_trades_master.to_csv("trades_master.csv", sep=";") 130 | df_trades_master.to_excel("trades_master.xls") 131 | 132 | 133 | 134 | logging.info(title_string("trades_slave")) 135 | df_trades_slave['open_time'] = pd.to_datetime(df_trades_slave['open_time']) 136 | df_trades_slave['close_time'] = pd.to_datetime(df_trades_slave['close_time']) 137 | df_trades_slave['trade_duration'] = df_trades_slave['close_time'] - df_trades_slave['open_time'] 138 | df_trades_slave['pip_position'] = df_trades_slave['instrument'].map(pip_position) 139 | df_trades_slave['direction_str'] = df_trades_slave['direction'].map(d_order_type_str) 140 | df_trades_slave['direction_sign'] = df_trades_slave['direction'].map(d_order_type_sign) 141 | #df_trades_slave = df_trades_slave.sort(columns=['open_time'], ascending=[True]) 142 | #logging.info(df_trades_slave.dtypes) 143 | cols = list(df_trades_slave.columns) 144 | for i, col in enumerate(cols): 145 | if col != u"master_trade_id": 146 | cols[i] = 'slave' + '_' + col 147 | df_trades_slave.columns = cols 148 | #df_trades_slave = df_trades_slave[::-1] # reverse order (last at head) 149 | #print(df_trades_slave) 150 | df_trades_slave = df_trades_slave.sort(columns=['master_trade_id'], ascending=[True]) 151 | logging.info("len: %d" % len(df_trades_slave)) 152 | logging.info("profit (slave): %.2f" % df_trades_slave['slave_profit'].sum()) 153 | #df_trades_slave = df_trades_slave.set_index('master_trade_id') 154 | #df_trades_slave.to_csv("trades_slave.csv", sep=";") 155 | df_trades_slave.to_excel("trades_slave.xls") 156 | 157 | df_merged = pd.merge(df_trades_master, df_trades_slave, on='master_trade_id', how='outer') 158 | df_merged = df_merged.sort(columns=['master_open_time'], ascending=[True]) 159 | 160 | df_merged['copy_dir'] = (1 + df_merged['master_direction_sign'] * df_merged['slave_direction_sign']) / 2 161 | 162 | for col in ['open_price', 'close_price', 'volume']: # 'open_time', 'close_time' 163 | df_merged['diff' + '_' + col] = df_merged['slave' + '_' + col] - df_merged['master' + '_' + col] 164 | 165 | # display pips 166 | df_merged['pip_position'] = df_merged['master_instrument'].map(pip_position) 167 | df_merged['pip_multiplier'] = 10**(df_merged['pip_position']) 168 | 169 | for col in ['open_price', 'close_price']: 170 | df_merged["diff_{col}_pips".format(col=col)] = df_merged["diff_{col}".format(col=col)] * df_merged['pip_multiplier'] 171 | 172 | 173 | # ToFix: we should add a column named "comment" into master table instead of this ugly hack 174 | d_masterid_algo = dict(key_val.split(':') for key_val in masterid_algo.split(',')) 175 | df_merged['master_comment'] = '' 176 | try: 177 | df_merged['master_comment'] = df_merged['master_master_id'].map(d_masterid_algo) 178 | except: 179 | logging.error(traceback.format_exc()) 180 | 181 | 182 | cols = ['master_master_id', #'slave_master_id', 183 | 'slave_slave_id', 184 | 'master_comment', 185 | 'master_instrument', #'slave_instrument', 186 | 'master_direction', 'slave_direction', 187 | 'master_direction_str', 'slave_direction_str', 188 | 'copy_dir', 189 | 'master_volume', 'slave_volume', 'diff_volume', 190 | 'diff_open_price_pips', #'master_open_price', 'slave_open_price', 'diff_open_price_pips', 191 | 'master_open_time', 'slave_open_time', #'diff_open_time', 192 | 'master_close_time', 'slave_close_time', #'diff_close_time', 193 | 'diff_close_price_pips', #'master_close_price', 'slave_close_price', 'diff_close_price_pips', 194 | 'master_stop_loss', 'slave_stop_loss', 195 | 'master_take_profit', 'slave_take_profit', 196 | 'master_commission', 'slave_commission', 197 | 'master_profit', 'slave_profit', 198 | 'master_swaps', 'slave_swaps', 199 | 'master_trade_duration', 'slave_trade_duration', 200 | 'slave_slave_trade_id', 201 | 'master_trade_id', 202 | #'slave_slave_id', 203 | 'slave_status'] 204 | 205 | df_merged = df_merged[cols] 206 | 207 | if args.slavetradesclosed: 208 | df_merged = df_merged[df_merged['slave_close_time'].notnull()] 209 | 210 | if args.slavetradesopened: 211 | df_merged = df_merged[df_merged['slave_close_time'].isnull()] 212 | 213 | #logging.info(df_merged) 214 | #df_merged.to_csv("merged.csv", sep=";") 215 | df_merged.to_excel("merged.xls") 216 | 217 | 218 | df_merged_grp_algo = pd.DataFrame(df_merged.groupby(['master_comment'])['slave_profit'].sum()) 219 | logging.info(title_string("slave stats per algo (master_comment)")+"\n"+str(df_merged_grp_algo)) 220 | df_merged_grp_algo.to_excel("merged_grp_algo.xls") 221 | 222 | logging.info(title_string("slave stats per algo and per trader (master_comment, trades_master_comment)")+"\n"+"ToDo") 223 | # ToDo: when we will have a column into trades_master with trader name (comment in trades_master) we will be able 224 | # to make df_merged.groupby(['master_comment', 'trades_master_comment'])['slave_profit'] 225 | 226 | def tables_create(conn, database, tables_prefix, tables_names): 227 | logging.info("Create tables") 228 | tables_queries = OrderedDict() 229 | 230 | tables_queries['terminals_master'] = """CREATE TABLE IF NOT EXISTS `{database}`.`{table}` ( 231 | `master_id` varchar(25) NOT NULL, 232 | `deposit_currency` varchar(10) NOT NULL, 233 | `comment` varchar(25) NOT NULL DEFAULT '', 234 | `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 235 | `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 236 | PRIMARY KEY (`master_id`) 237 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;""".format(database=database, table=tables_names['terminals_master']) 238 | 239 | #--- 240 | 241 | tables_queries['terminals_slave'] = """CREATE TABLE IF NOT EXISTS `{database}`.`{table}` ( 242 | `slave_id` varchar(25) NOT NULL, 243 | `deposit_currency` varchar(10) NOT NULL, 244 | `comment` varchar(25) NOT NULL DEFAULT '', 245 | `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 246 | `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 247 | PRIMARY KEY (`slave_id`) 248 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;""".format(database=database, table=tables_names['terminals_slave']) 249 | 250 | #--- 251 | 252 | tables_queries['trades_master'] = """CREATE TABLE IF NOT EXISTS `{database}`.`{table}` ( 253 | `master_id` varchar(25) NOT NULL, 254 | `instrument` varchar(12) NOT NULL, 255 | `direction` int(11) NOT NULL, 256 | `volume` decimal(10,5) NOT NULL, 257 | `open_price` decimal(10,5) NOT NULL, 258 | `open_time` timestamp NOT NULL DEFAULT 0, 259 | `close_time` timestamp NOT NULL DEFAULT 0, 260 | `close_price` decimal(10,5) DEFAULT NULL, 261 | `trade_id` varchar(25) NOT NULL, 262 | `stop_loss` decimal(10,5) DEFAULT NULL, 263 | `take_profit` decimal(10,5) DEFAULT NULL, 264 | `commission` decimal(10,5) DEFAULT NULL, 265 | `profit` decimal(10,5) DEFAULT NULL, 266 | `swaps` decimal(10,5) DEFAULT NULL, 267 | `comment` varchar(25) NOT NULL DEFAULT '', 268 | `magic_number` int(11) NOT NULL DEFAULT 0, 269 | `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 270 | `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 271 | PRIMARY KEY (`master_id`,`trade_id`), 272 | CONSTRAINT `{table}_master_id_fkey` FOREIGN KEY (`master_id`) REFERENCES `{database}`.`{table_masters}` (`master_id`) 273 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;""".format(database=database, table=tables_names['trades_master'], table_masters=tables_names['terminals_master']) 274 | 275 | #--- 276 | 277 | tables_queries['trades_slave'] = """CREATE TABLE IF NOT EXISTS `{database}`.`{table}` ( 278 | `master_id` varchar(25) NOT NULL, 279 | `instrument` varchar(12) NOT NULL, 280 | `direction` int(11) NOT NULL, 281 | `volume` decimal(10,5) NOT NULL, 282 | `open_price` decimal(10,5) NOT NULL, 283 | `open_time` timestamp NOT NULL DEFAULT 0, 284 | `close_time` timestamp NOT NULL DEFAULT 0, 285 | `close_price` decimal(10,5) DEFAULT NULL, 286 | `master_trade_id` varchar(25) NOT NULL, 287 | `stop_loss` decimal(10,5) DEFAULT NULL, 288 | `take_profit` decimal(10,5) DEFAULT NULL, 289 | `slave_id` varchar(25) NOT NULL, 290 | `slave_trade_id` varchar(25) NOT NULL, 291 | `commission` decimal(10,5) DEFAULT NULL, 292 | `profit` decimal(10,5) DEFAULT NULL, 293 | `swaps` decimal(10,5) DEFAULT NULL, 294 | `comment` varchar(25) NOT NULL DEFAULT '', 295 | `magic_number` int(11) NOT NULL DEFAULT 0, 296 | `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 297 | `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 298 | `status` text, 299 | PRIMARY KEY (`slave_id`,`slave_trade_id`), 300 | KEY `{table}_master_id_fkey1` (`master_id`,`master_trade_id`), 301 | CONSTRAINT `{table}_master_id_fkey` FOREIGN KEY (`master_id`) REFERENCES `{database}`.`{table_masters}` (`master_id`), 302 | CONSTRAINT `{table}_master_id_fkey1` FOREIGN KEY (`master_id`, `master_trade_id`) REFERENCES `{database}`.`{table_trades_master}` (`master_id`, `trade_id`), 303 | CONSTRAINT `{table}_slave_id_fkey` FOREIGN KEY (`slave_id`) REFERENCES `{database}`.`{table_slaves}` (`slave_id`) 304 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;""".format(database=database, 305 | table=tables_names['trades_slave'], 306 | table_trades_master=tables_names['trades_master'], 307 | table_masters=tables_names['terminals_master'], 308 | table_slaves=tables_names['terminals_slave']) 309 | 310 | #--- 311 | 312 | #logging.info("First insert into tables") 313 | #query = "INSERT IGNORE INTO " + g_tableName_masters + " VALUES ('" + g_master_id_setting + "', '" + g_deposit_currency + "');"; # + comment 314 | 315 | #--- 316 | 317 | cursor = conn.cursor() 318 | 319 | for (name, query) in tables_queries.items(): 320 | #print(name , query) 321 | 322 | try: 323 | logging.info(" "*2 + "Creating table '{}': ".format(name)) 324 | logging.info(" "*4 + name.rjust(14) + ": " + tables_queries[name]) 325 | cursor.execute(query) 326 | except mysql.connector.Error as err: 327 | logging.error(query) 328 | logging.error(err.msg) 329 | 330 | def tables_drop_or_truncate(conn, database, tables_prefix, tables_names, action): 331 | logging.info("{action} tables".format(action=action)) 332 | tables_queries = OrderedDict() 333 | 334 | tables_queries['terminals_master'] = "{action} TABLE `{database}`.`{table}`;".format( 335 | action=action, database=database, table=tables_names['terminals_master']) 336 | 337 | tables_queries['terminals_slave'] = "{action} TABLE `{database}`.`{table}`;".format( 338 | action=action, database=database, table=tables_names['terminals_slave']) 339 | 340 | tables_queries['trades_master'] = "{action} TABLE `{database}`.`{table}`;".format( 341 | action=action, database=database, table=tables_names['trades_master']) 342 | 343 | tables_queries['trades_slave'] = "{action} TABLE `{database}`.`{table}`;".format( 344 | action=action, database=database, table=tables_names['trades_slave']) 345 | 346 | cursor = conn.cursor() 347 | 348 | for (name, query) in tables_queries.items()[::-1]: 349 | #print(name , query) 350 | 351 | try: 352 | logging.info(" "*2 + "Droping table '{}': ".format(name)) 353 | logging.info(" "*4 + name.rjust(14) + ": " + tables_queries['trades_slave']) 354 | cursor.execute(query) 355 | except mysql.connector.Error as err: 356 | logging.error(query) 357 | logging.error(err.msg) 358 | 359 | def tables_truncate(conn, database, tables_prefix, tables_names): 360 | return tables_drop_or_truncate(conn, database, tables_prefix, tables_names, 'TRUNCATE') 361 | 362 | def tables_drop(conn, database, tables_prefix, tables_names): 363 | return tables_drop_or_truncate(conn, database, tables_prefix, tables_names, 'DROP') 364 | 365 | def get_tables_names(tables_prefix): 366 | tables_names = OrderedDict() 367 | tables_names['terminals_master'] = tables_prefix + "terminals_master" 368 | tables_names['terminals_slave'] = tables_prefix + "terminals_slave" 369 | tables_names['trades_master'] = tables_prefix + "trades_master" 370 | tables_names['trades_slave'] = tables_prefix + "trades_slave" 371 | return(tables_names) 372 | 373 | -------------------------------------------------------------------------------- /JForex/Strategies/JForexMySQLTradeReplicatorMaster.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JForexMySQLTradeReplicatorMaster.java 3 | * Trades replicator using MySQL database 4 | * JForex master 5 | * 6 | * Copyright © 2014, FemtoTrader 7 | * https://sites.google.com/site/femtotrader/ 8 | * 9 | * Distributed under the BSD license 10 | * http://opensource.org/licenses/BSD-2-Clause 11 | * 12 | * Inspired from trade_replicator 13 | * https://github.com/kr0st/trade_replicator 14 | * 15 | * Dependencies: 16 | * mysql-connector-java-5.1.30-bin.jar 17 | */ 18 | 19 | package jforex; 20 | 21 | import java.util.*; 22 | 23 | import com.dukascopy.api.*; 24 | 25 | import java.sql.Connection; 26 | import java.sql.DriverManager; 27 | import java.sql.SQLException; 28 | import java.sql.Statement; 29 | import java.sql.ResultSet; 30 | import java.text.SimpleDateFormat; 31 | import java.sql.ResultSetMetaData; 32 | 33 | @Library("mysql-connector-java-5.1.30-bin.jar") 34 | 35 | @RequiresFullAccess 36 | public class JForexMySQLTradeReplicatorMaster implements IStrategy { 37 | 38 | public class DB_Trade 39 | { 40 | public class General_Exception extends Exception 41 | { 42 | private String description; 43 | public General_Exception(String s) 44 | { 45 | description = s; 46 | } 47 | 48 | public String toString() 49 | { 50 | return description; 51 | } 52 | } 53 | 54 | public String master_id; 55 | public String instrument; 56 | public int direction; 57 | public double volume; 58 | public double open_price; 59 | public Date open_time; 60 | public Date close_time; 61 | public double close_price; 62 | public String trade_id; 63 | public double stop_loss; 64 | public double take_profit; 65 | public double commission; 66 | public double profit; 67 | public double swaps; 68 | public String comment; 69 | public int magic_number; 70 | //public Date created; 71 | //public Date updated; 72 | 73 | public DB_Trade(ResultSet rs) throws SQLException, General_Exception 74 | { 75 | ResultSetMetaData meta = rs.getMetaData(); 76 | int cols = meta.getColumnCount(); 77 | if (cols != 18) 78 | throw new General_Exception("Unexpected number of columns in master trades table, check your DB schema."); 79 | 80 | master_id = rs.getString(1); 81 | instrument = rs.getString(2); 82 | direction = rs.getInt(3); 83 | volume = rs.getDouble(4); 84 | open_price = rs.getDouble(5); 85 | open_time = rs.getDate(6); 86 | try { 87 | close_time = rs.getDate(7); 88 | } catch (Exception e) { 89 | close_time = null; 90 | } 91 | 92 | close_price = rs.getDouble(8); 93 | trade_id = rs.getString(9); 94 | stop_loss = rs.getDouble(10); 95 | take_profit = rs.getDouble(11); 96 | commission = rs.getDouble(12); 97 | profit = rs.getDouble(13); 98 | swaps = rs.getDouble(14); 99 | comment = rs.getString(15); 100 | magic_number = rs.getInt(16); 101 | //created = 102 | //updated = 103 | } 104 | 105 | public String toString() 106 | { 107 | String res = ""; 108 | 109 | SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-DD hh:mm:ss.SSSZ"); 110 | 111 | try 112 | { 113 | if (open_time == null) 114 | open_time = date.parse("1970-01-01 00:00:00.000+0000"); 115 | 116 | if (close_time == null) 117 | close_time = date.parse("1970-01-01 00:00:00.000+0000"); 118 | } 119 | catch (Exception e) 120 | { 121 | console.getOut().println("ERROR:" + e.toString()); 122 | return ""; 123 | } 124 | 125 | res = "master_id=" + master_id + " instrument=" + instrument + " direction=" + direction + " volume=" + volume + 126 | " open_price=" + open_price + " open_time=" + date.format(open_time) + " close_time=" + date.format(close_time) + " close_price=" + close_price + 127 | " trade_id=" + trade_id + " stop_loss=" + stop_loss + " take_profit=" + take_profit + " commission=" + commission + " profit=" + profit + " swaps=" + swaps; 128 | 129 | return res; 130 | } 131 | } 132 | 133 | @Configurable("DB IP/host") 134 | public String db_ip_setting = "127.0.0.1"; 135 | @Configurable("DB port") 136 | public String db_port_setting = "3306"; 137 | @Configurable("DB login") 138 | public String db_user_setting = "root"; 139 | @Configurable("DB password") 140 | public String db_password_setting = "123456"; 141 | @Configurable("DB name") 142 | public String db_name_setting = "test"; 143 | @Configurable("DB table prefix") 144 | public String db_table_prefix_setting = "trade_replicator_"; 145 | @Configurable("Master ID") 146 | public String master_id_setting = "n9o816RTuaxJt99WSfD8"; //20 symbols recommended id, use some passwords generator to obtain it 147 | @Configurable("Deposit currency") 148 | public String deposit_currency_setting = "USD"; 149 | @Configurable("Master comment") 150 | public String master_comment_setting = "master01"; 151 | @Configurable("Drop tables") 152 | public boolean drop_tables = false; 153 | @Configurable("Create tables") 154 | public boolean create_tables = false; 155 | @Configurable("Register master") 156 | public boolean register_master = false; 157 | 158 | 159 | private IEngine engine; 160 | private IConsole console; 161 | private IHistory history; 162 | private IContext context; 163 | private IIndicators indicators; 164 | private IUserInterface userInterface; 165 | 166 | private Connection db_conn = null; 167 | 168 | private String g_table_name_terminals_master; 169 | private String g_table_name_terminals_slave; 170 | private String g_table_name_trades_master; 171 | private String g_table_name_trades_slave; 172 | 173 | private long prev_time_; 174 | 175 | private void init_table_names(String tableprefix) { 176 | g_table_name_terminals_master = tableprefix + "terminals_master"; 177 | g_table_name_terminals_slave = tableprefix + "terminals_slave"; 178 | g_table_name_trades_master = tableprefix + "trades_master"; 179 | g_table_name_trades_slave = tableprefix + "trades_slave"; 180 | } 181 | 182 | private Connection db_connect(String db_name, String host, String port, String user, String pass) 183 | { 184 | Connection connection = null; 185 | try 186 | { 187 | //Class.forName("com.mysql.jdbc.Driver").newInstance(); 188 | String url = "jdbc:mysql://" + host + ":" + port + "/" + db_name; 189 | console.getOut().println(url); 190 | connection = DriverManager.getConnection(url, user, pass); 191 | } 192 | catch (SQLException e) 193 | { 194 | console.getOut().println("DB connection failed!!!"); 195 | return null; 196 | } 197 | 198 | if (connection != null) 199 | { 200 | console.getOut().println("DB connection established"); 201 | } 202 | else 203 | { 204 | console.getOut().println("Failed to make a connection!!!"); 205 | } 206 | 207 | return connection; 208 | } 209 | 210 | public String InstrumentToString(Instrument instrument) { 211 | String s = instrument.toString(); 212 | s = s.replace("/", ""); 213 | s = s.replace("\\", ""); 214 | return(s); 215 | } 216 | 217 | public String quote(String s) { 218 | return("'" + s + "'"); 219 | } 220 | 221 | public String backquote(String s) { 222 | return("`" + s + "`"); 223 | } 224 | 225 | void drop_table_replicator(String db_name, String tablePrefix) { 226 | action_table_replicator(db_name, tablePrefix, "DROP"); 227 | } 228 | 229 | void truncate_table_replicator(String db_name, String tablePrefix) { 230 | action_table_replicator(db_name, tablePrefix, "TRUNCATE"); 231 | } 232 | 233 | void action_table_replicator(String db_name, String tablePrefix, String action) 234 | { 235 | String query; 236 | 237 | ArrayList a_tablename = new ArrayList(); 238 | 239 | a_tablename.add(g_table_name_trades_slave); 240 | a_tablename.add(g_table_name_trades_master); 241 | a_tablename.add(g_table_name_terminals_slave); 242 | a_tablename.add(g_table_name_terminals_master); 243 | 244 | Statement statement; 245 | 246 | for (String tablename : a_tablename) 247 | { 248 | try { 249 | statement = db_conn.createStatement(); 250 | query = action + " TABLE " + backquote(db_name) + "." + backquote(tablename) + ";"; 251 | console.getOut().println(query); 252 | statement.execute(query); 253 | } catch (SQLException e) { 254 | console.getOut().println("ERROR: " + e.toString()); 255 | } 256 | } 257 | } 258 | 259 | boolean create_table_replicator(String db_name, String tablePrefix) 260 | { 261 | boolean b_result; 262 | String query; 263 | Statement statement; 264 | 265 | //--- 266 | 267 | query = 268 | "CREATE TABLE IF NOT EXISTS `" + db_name + "`.`" + g_table_name_terminals_master + "` (" + 269 | " `master_id` varchar(25) NOT NULL," + 270 | " `deposit_currency` varchar(10) NOT NULL," + 271 | " `comment` varchar(25) NOT NULL DEFAULT ''," + 272 | " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + 273 | " `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," + 274 | " PRIMARY KEY (`master_id`)" + 275 | ") ENGINE=InnoDB DEFAULT CHARSET=utf8;"; 276 | try { 277 | statement = db_conn.createStatement(); 278 | console.getOut().println(query); 279 | b_result = statement.execute(query); 280 | } catch (SQLException e) { 281 | console.getOut().println("ERROR: " + e.toString()); 282 | } 283 | 284 | //--- 285 | 286 | query = 287 | "CREATE TABLE IF NOT EXISTS `" + db_name + "`.`" + g_table_name_terminals_slave + "` (" + 288 | " `slave_id` varchar(25) NOT NULL," + 289 | " `deposit_currency` varchar(10) NOT NULL," + 290 | " `comment` varchar(25) NOT NULL DEFAULT ''," + 291 | " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + 292 | " `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," + 293 | " PRIMARY KEY (`slave_id`)" + 294 | ") ENGINE=InnoDB DEFAULT CHARSET=utf8;"; 295 | try { 296 | statement = db_conn.createStatement(); 297 | console.getOut().println(query); 298 | b_result = statement.execute(query); 299 | } catch (SQLException e) { 300 | console.getOut().println("ERROR: " + e.toString()); 301 | } 302 | 303 | //--- 304 | 305 | query = 306 | "CREATE TABLE IF NOT EXISTS `" + db_name + "`.`" + g_table_name_trades_master + "` (" + 307 | " `master_id` varchar(25) NOT NULL," + 308 | " `instrument` varchar(12) NOT NULL," + 309 | " `direction` int(11) NOT NULL," + 310 | " `volume` decimal(10,5) NOT NULL," + 311 | " `open_price` decimal(10,5) NOT NULL," + 312 | " `open_time` timestamp NOT NULL DEFAULT 0," + 313 | " `close_time` timestamp NOT NULL DEFAULT 0," + 314 | " `close_price` decimal(10,5) DEFAULT NULL," + 315 | " `trade_id` varchar(25) NOT NULL," + 316 | " `stop_loss` decimal(10,5) DEFAULT NULL," + 317 | " `take_profit` decimal(10,5) DEFAULT NULL," + 318 | " `commission` decimal(10,5) DEFAULT NULL," + 319 | " `profit` decimal(10,5) DEFAULT NULL," + 320 | " `swaps` decimal(10,5) DEFAULT NULL," + 321 | " `comment` varchar(25) NOT NULL DEFAULT ''," + 322 | " `magic_number` int(11) NOT NULL DEFAULT 0," + 323 | " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + 324 | " `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," + 325 | " PRIMARY KEY (`master_id`,`trade_id`)," + 326 | " CONSTRAINT `" + g_table_name_trades_master + "_master_id_fkey` FOREIGN KEY (`master_id`) REFERENCES `" + db_name + "`.`" + g_table_name_terminals_master + "` (`master_id`)" + 327 | ") ENGINE=InnoDB DEFAULT CHARSET=utf8;"; 328 | try { 329 | statement = db_conn.createStatement(); 330 | console.getOut().println(query); 331 | b_result = statement.execute(query); 332 | } catch (SQLException e) { 333 | console.getOut().println("ERROR: " + e.toString()); 334 | } 335 | 336 | //--- 337 | 338 | query = 339 | "CREATE TABLE IF NOT EXISTS `" + db_name + "`.`" + g_table_name_trades_slave + "` (" + 340 | " `master_id` varchar(25) NOT NULL," + 341 | " `instrument` varchar(12) NOT NULL," + 342 | " `direction` int(11) NOT NULL," + 343 | " `volume` decimal(10,5) NOT NULL," + 344 | " `open_price` decimal(10,5) NOT NULL," + 345 | " `open_time` timestamp NOT NULL DEFAULT 0," + 346 | " `close_time` timestamp NOT NULL DEFAULT 0," + 347 | " `close_price` decimal(10,5) DEFAULT NULL," + 348 | " `master_trade_id` varchar(25) NOT NULL," + 349 | " `stop_loss` decimal(10,5) DEFAULT NULL," + 350 | " `take_profit` decimal(10,5) DEFAULT NULL," + 351 | " `slave_id` varchar(25) NOT NULL," + 352 | " `slave_trade_id` varchar(25) NOT NULL," + 353 | " `commission` decimal(10,5) DEFAULT NULL," + 354 | " `profit` decimal(10,5) DEFAULT NULL," + 355 | " `swaps` decimal(10,5) DEFAULT NULL," + 356 | " `comment` varchar(25) NOT NULL DEFAULT ''," + 357 | " `magic_number` int(11) NOT NULL DEFAULT 0," + 358 | " `status` text," + 359 | " `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + 360 | " `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," + 361 | " PRIMARY KEY (`slave_id`,`slave_trade_id`)," + 362 | " KEY `" + g_table_name_trades_slave + "_master_id_fkey1` (`master_id`,`master_trade_id`)," + 363 | " CONSTRAINT `" + g_table_name_trades_slave + "_master_id_fkey` FOREIGN KEY (`master_id`) REFERENCES `" + db_name + "`.`" + g_table_name_terminals_master + "` (`master_id`)," + 364 | " CONSTRAINT `" + g_table_name_trades_slave + "_master_id_fkey1` FOREIGN KEY (`master_id`, `master_trade_id`) REFERENCES `" + db_name + "`.`" + g_table_name_trades_master + "` (`master_id`, `trade_id`)," + 365 | " CONSTRAINT `" + g_table_name_trades_slave + "_slave_id_fkey` FOREIGN KEY (`slave_id`) REFERENCES `" + db_name + "`.`" + g_table_name_terminals_slave + "` (`slave_id`)" + 366 | ") ENGINE=InnoDB DEFAULT CHARSET=utf8;"; 367 | try { 368 | statement = db_conn.createStatement(); 369 | console.getOut().println(query); 370 | b_result = statement.execute(query); 371 | } catch (SQLException e) { 372 | console.getOut().println("ERROR: " + e.toString()); 373 | } 374 | 375 | //--- 376 | 377 | return(true); 378 | } 379 | 380 | public boolean register(String table_name, String terminal_type, String terminal_id, String deposit_currency, String comment) { 381 | try { 382 | Statement statement = db_conn.createStatement(); 383 | String sep = ", "; 384 | String query = "INSERT INTO " + table_name + "(" + terminal_type + "_id, deposit_currency, comment, created, updated) VALUES (" + quote(master_id_setting) + sep + quote(deposit_currency_setting) 385 | + sep + quote(master_comment_setting) + sep + "NULL" + sep + "NULL" + ") ON DUPLICATE KEY UPDATE comment=VALUES(comment), deposit_currency=VALUES(deposit_currency), updated=VALUES(updated);"; 386 | console.getOut().println(query); 387 | return(statement.execute(query)); 388 | } catch (SQLException e) { 389 | console.getOut().println("ERROR: " + e.toString()); 390 | return(false); 391 | } 392 | 393 | } 394 | 395 | public boolean register_master(String table_name, String terminal_id, String deposit_currency, String comment) { 396 | return(register(table_name, "master", terminal_id, deposit_currency, comment)); 397 | } 398 | 399 | 400 | public void onStart(IContext context) throws JFException { 401 | this.engine = context.getEngine(); 402 | this.console = context.getConsole(); 403 | this.history = context.getHistory(); 404 | this.context = context; 405 | this.indicators = context.getIndicators(); 406 | this.userInterface = context.getUserInterface(); 407 | 408 | db_conn = db_connect(db_name_setting, db_ip_setting, db_port_setting, db_user_setting, db_password_setting); 409 | 410 | init_table_names(db_table_prefix_setting); 411 | 412 | if (drop_tables) { 413 | try { 414 | drop_table_replicator(db_name_setting, db_table_prefix_setting); 415 | } catch (Exception e) { 416 | console.getOut().println("Can't drop tables"); 417 | } 418 | } 419 | 420 | if (create_tables) { 421 | try { 422 | create_table_replicator(db_name_setting, db_table_prefix_setting); 423 | } catch (Exception e) { 424 | console.getOut().println("Can't drop tables"); 425 | } 426 | } 427 | 428 | if (register_master) { 429 | try { 430 | register_master(g_table_name_terminals_master, master_id_setting, deposit_currency_setting, master_comment_setting); 431 | } catch (Exception e) { 432 | console.getOut().println("Can't register master to trade replicator (this master_id maybe ever exists)"); 433 | } 434 | } 435 | 436 | prev_time_ = 0; 437 | 438 | } 439 | 440 | public void onAccount(IAccount account) throws JFException { 441 | } 442 | 443 | public void onMessage(IMessage message) throws JFException { 444 | } 445 | 446 | public void onStop() throws JFException 447 | { 448 | try 449 | { 450 | if (db_conn != null) { 451 | db_conn.close(); 452 | console.getOut().println("DB connection closed"); 453 | } 454 | } 455 | catch (SQLException e) 456 | { 457 | console.getOut().println("DB connection failed to close!!!"); 458 | return; 459 | } 460 | } 461 | 462 | private void onTime(long time) throws JFException 463 | { 464 | //console.getOut().println("--> onTime"); 465 | try 466 | { 467 | ArrayList trades; 468 | 469 | trades = get_open_trades_from_db(); 470 | find_closed_trades(trades); 471 | 472 | trades = get_open_trades_from_db(); 473 | find_new_trades(trades); 474 | } 475 | catch (Exception e) 476 | { 477 | console.getOut().println(e); 478 | return; 479 | } 480 | //console.getOut().println("<-- onTime"); 481 | } 482 | 483 | public void onTick(Instrument instrument, ITick tick) throws JFException { 484 | //console.getOut().println("new tick " + InstrumentToString(instrument)); 485 | long cur_time = tick.getTime(); 486 | if (cur_time - prev_time_ >= 1000) 487 | { 488 | prev_time_ = cur_time; 489 | onTime(cur_time); 490 | } 491 | } 492 | 493 | public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException { 494 | } 495 | 496 | 497 | private ArrayList get_open_trades_from_db() 498 | { 499 | //console.getOut().println("--> get_open_trades_from_db"); 500 | ArrayList open_trades = new ArrayList(); 501 | 502 | try 503 | { 504 | Statement query = db_conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); 505 | ResultSet rs = query.executeQuery("SELECT * FROM " + g_table_name_trades_master + " WHERE close_time=0 AND master_id = " + quote(master_id_setting) + ";"); 506 | 507 | //print_result_set(rs); 508 | 509 | rs.last(); 510 | int rows = rs.getRow(); 511 | rs.beforeFirst(); 512 | 513 | for (int i = 0; i < rows; ++i) 514 | { 515 | rs.next(); 516 | DB_Trade open_trade = new DB_Trade(rs); 517 | open_trades.add(open_trade); 518 | 519 | //console.getOut().println("trade#" + i + " = " + open_trade); 520 | } 521 | } 522 | catch (SQLException e) 523 | { 524 | console.getOut().println("ERROR: " + e.toString()); 525 | return open_trades; 526 | } 527 | catch (DB_Trade.General_Exception e) 528 | { 529 | console.getOut().println("ERROR: " + e.toString()); 530 | return open_trades; 531 | } 532 | 533 | //console.getOut().println("<-- get_open_trades_from_db"); 534 | return open_trades; 535 | } 536 | 537 | private IOrder is_trade_closed(String id) throws JFException 538 | { 539 | //console.getOut().println("--> is_trade_closed"); 540 | 541 | IOrder order = history.getHistoricalOrderById(id); 542 | if (order != null) 543 | { 544 | if (order.getClosePrice() != 0) 545 | { 546 | //console.getOut().println("pos.id = " + order.getId() + "close_time = " + order.getCloseTime() + " close_price = " + order.getClosePrice()); 547 | //console.getOut().println("<-- is_trade_closed result non-null"); 548 | return order; 549 | } 550 | } 551 | 552 | //console.getOut().println("<-- is_trade_closed result null"); 553 | return null; 554 | } 555 | 556 | private void on_trade_close(IOrder closed_order) throws JFException 557 | { 558 | //console.getOut().println("--> on_trade_close"); 559 | if (closed_order == null) 560 | return; 561 | 562 | String master_id = master_id_setting; 563 | double close_price = closed_order.getClosePrice(); 564 | SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); //new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSSZ"); 565 | Date current = new Date(); 566 | String close_time = date_format.format(current); 567 | String trade_id = closed_order.getId(); 568 | double stop_loss = closed_order.getStopLossPrice(); 569 | double take_profit = closed_order.getTakeProfitPrice(); 570 | double profit = closed_order.getProfitLossInAccountCurrency(); 571 | double swaps = 0; 572 | 573 | String query = "UPDATE " + g_table_name_trades_master + " SET stop_loss=" + stop_loss + ", take_profit=" + take_profit + ", close_time='" + close_time + "', close_price=" + close_price + ", profit=" + profit + ", swaps=" + swaps + " WHERE (master_id='" + master_id + "') AND (trade_id='" + trade_id + "');"; 574 | console.getOut().println("Master closed trade: " + query); 575 | 576 | try 577 | { 578 | Statement statement = db_conn.createStatement(); 579 | statement.execute(query); 580 | } 581 | catch (SQLException e) 582 | { 583 | console.getOut().println("ERROR: " + e.toString()); 584 | return; 585 | } 586 | 587 | //console.getOut().println("<-- on_trade_close"); 588 | } 589 | 590 | private void find_closed_trades(ArrayList trades) throws JFException 591 | { 592 | //console.getOut().println("--> find_closed_trades"); 593 | for (DB_Trade trade : trades) 594 | { 595 | on_trade_close(is_trade_closed(trade.trade_id)); 596 | } 597 | //console.getOut().println("<-- find_closed_trades"); 598 | } 599 | 600 | private void on_new_trade(IOrder order) 601 | { 602 | //console.getOut().println("--> on_new_trade"); 603 | String master_id = master_id_setting; 604 | String instrument = order.getInstrument().toString(); 605 | instrument = instrument.replace("/", ""); 606 | instrument = instrument.replace("\\", ""); 607 | 608 | int direction = 0; 609 | if (!order.isLong()) direction = 1; // ToFix: pending orders 610 | 611 | double volume = order.getAmount() * 10; //convert into mt4 lots (from fractions of million to fractions of 100K) 612 | double open_price = order.getOpenPrice(); 613 | 614 | //SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSSZ"); 615 | SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS"); 616 | Date current = new Date(); 617 | String open_time = date_format.format(current); 618 | String close_time = "0000-00-00 00:00:00.000"; 619 | String trade_id = order.getId(); 620 | double stop_loss = order.getStopLossPrice(); 621 | double take_profit = order.getTakeProfitPrice(); 622 | 623 | double com = order.getCommission(); 624 | String comment = order.getLabel(); //"order sent with JForex"; 625 | int magic_number = 0; 626 | String commission = "" + com; 627 | String label = order.getLabel(); 628 | 629 | String sep = ", "; 630 | String query = "INSERT INTO " + g_table_name_trades_master + " VALUES (" + quote(master_id) + sep 631 | + quote(instrument) + sep + direction + sep + volume + sep 632 | + open_price + sep + quote(open_time) + sep + quote(close_time) + sep + "NULL" + sep 633 | + quote(trade_id) + sep + stop_loss + sep 634 | + take_profit + sep + commission + sep + "NULL" + sep 635 | + "NULL" + sep + quote(comment) + sep + magic_number + sep + "NULL" + sep + "NULL" + ");"; 636 | 637 | console.getOut().println("Master opened a new trade: " + query); 638 | 639 | try 640 | { 641 | Statement statement = db_conn.createStatement(); 642 | statement.execute(query); 643 | } 644 | catch (SQLException e) 645 | { 646 | console.getOut().println("ERROR: " + e.toString()); 647 | return; 648 | } 649 | 650 | //console.getOut().println("<-- on_new_trade"); 651 | } 652 | private void find_new_trades(ArrayList trades) throws JFException 653 | { 654 | //console.getOut().println("--> find_new_trades"); 655 | List orders = engine.getOrders(); 656 | for (IOrder order : orders) 657 | { 658 | if (order.getState() != IOrder.State.FILLED) 659 | continue; 660 | 661 | Boolean found = false; 662 | for (DB_Trade trade : trades) 663 | { 664 | String pos_id = order.getId(); 665 | pos_id.trim(); 666 | trade.trade_id.trim(); 667 | 668 | //console.getOut().println("trade_id = " + trade.trade_id + ", pos id = " + pos_id); 669 | if (trade.trade_id.equals(pos_id)) 670 | { 671 | found = true; 672 | break; 673 | } 674 | } 675 | 676 | if (!found) 677 | on_new_trade(order); 678 | } 679 | 680 | //console.getOut().println("<-- find_new_trades"); 681 | } 682 | } --------------------------------------------------------------------------------