How to connect to DDE server? (MT4)

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
serg
Posts: 56
Joined: 21 Mar 2015, 05:33

How to connect to DDE server? (MT4)

07 Jun 2017, 06:05

Hi folks!
Im trying to connect to DDE sevrver using AHK.
Server that Im trying to work with is from MT4 (forex charting/data software), but I guess it doesn't matter, since all DDE servers probably work in same format.

So, I found 2 DDE libraries here on AHK forum (attached below), and tried to use them to do the job, but with no success so far.

Could anyone familiar with DDE point me in the right direction?

Any advise is greatly appreciated!
---------------------------------------------------------------

From MT4 documentation:
"Quotes are given through DDE only at incoming of new ticks (ADVISE mode), but not immediately on request (REQUEST mode) where the latest price is shown. N/A is shown on the first REQUEST, and after the new price has been income, quotes will appear."

Request format:
MT4|BID!EURUSD

---------------------------------------------------------------

First attempt using DDE lib by Joy2DWorld
(https://autohotkey.com/board/topic/2017 ... ti-channel)

Code: Select all

connect :=	DDE_Connect("MT4","QUOTE") 
try1 :=	DDE_Execute("BID!GBPUSD")
try2 :=	DDE_Execute("GBPUSD")
try3 :=	DDE_Request("BID!GBPUSD")
try4 :=	DDE_Request("GBPUSD")
try5 :=	DDE_Poke("GBPUSD", "QUOTE")
try6 :=	DDE_Poke("GBPUSD")
try=
Loop, 6
 try .= "`n" try%a_index%
msgbox connect = %connect%`n`n%try%
DDE_KILL()
DDE lib by Joy2DWorld

Code: Select all

/* ======================================================
Step 1:  Connect to a DDE Server.

DDE_connection_Number1 := DDE_Connect("NAME_OF_DDE_SERVER","TOPIC_REQUESTED", OPTIONAL_CONNECTION_NUMBER_1_TO_20, OPTIONAL_HANDLE_OF_DDE_SERVER_WINDOW ) 

if you have more than one connection,  you use the "DDE_connection_Number1", "DDE_connection_Number2"  var to designate in Requests, Pokes, etc..

You can have up to 20 connections WITH THE SAME SERVER ON THE SAME TOPIC.

You do not need a new connection# for different Servers or Different topics!!!!


Step 2:  Send a command:

result := DDE_Execute("[COMMAND YOU WANT TO SEND]", OPTIONAL_CONNECTION_ID_EG_DDE_connection_Number, OPTIONAL_DELAY_NUMBER) 

note:   you do not need to pay attention or use connection ID's unless you have multiple connections.  THE LAST ACCESSED CONNECTION IS THE DEFAULT,  SO DOES NOT NEED TO BE REDESIGNATED, UNLESS YOU WANT TO CHANGE IT.

also can:

result := DDE_Request("[COMMAND]", OPTIONAL_connection_ID, OPTIONAL_DELAY) 
usually_not_one := DDE_Poke("[COMMAND]", "[DATASET FOR COMMAND]",  OPTIONAL_connection_ID, OPTIONAL_DELAY) 


Step 3: (when you're all done!)   Terminate Connection

DDE_KILL("SERVER_NAME","TOPIC", OPTIONAL_CONNECTION_NUMBER) 

DDE_KILL() = kills all open connectinos.



Super-Powers:  (in additional to multiple connections to same server&topic)

1. You don't need to track open servers & topics,  all done for you.
DDE_Connect(Server,Topic) to existing open topic on that server returns you the Conversation_ID  contained in the "DDE_connection_Number1"  variable in example above.  ie.  Connection_X := DDE_Connect(Server,Topic)  fills Connection_X with the conversation ID for taht conversation.   Can have multiple conversationID's for same topic/server if made on different DDE connection ports:  eg.  DDE_Connect(Server,Topic,2), etc.


2.  BATCH MODE!

uses following Global Bars:

__DDE_Answer_Table  = holds your batch responses.  (You can clear & modify it.. but best to do it in a CRITICAL segment!!  so  not get accidental conflict with new data recepiet)

__DDE_BatchMode = Set this to use batch mode!!

if set,  you do not get Answer := Request('[xxxx')  instead answer goes only to answer table.  IE NO DELAY WAITING FOR ANSWER!!!!!!

__DDE_Use_Answer_Table  =  you DO get an answer, and you ALSO build an answer table

__DDE_SafeMode = for multiple connection  used at same time, etc.  if set TRACKS MESSAGE INCOMING DATA AGAINST INTENDED CONNECTION and SERVER.   This is **IMPORTANT** where you have data comming in from multiple servers and/or connections in NON-BATCH mode.

it makes sure that the answer you get actually comes from the CONNECTION and the SERVER you are expecting it to be from.


usage note:  [and this is well beyond basic use.. if you want to control word, etc.  likely this is not something you even need to read]   __DDE_Use_Answer_Table  and __DDE_SafeMode  should be normal operation mode for multiple active servers & connections with much async data exchange.

if you seek specific response to command X...  all other's will be either qued,  or saved in the answer table....





3. Debug AHK stuff:

__DDE_UseAHKMessages  set to use AHK sendmessage in place of DLL,

will cause crash (eventually)....

option to allow debugging... or 

for those bored with too much stability....



*/   ;======================================================

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;  HERE STARTS THE ACTUAL DDE WRAPPER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;------------------  DDE CLIENT WRAPPER ---------------------------------------

DDE_Connect(DDE_Server,DDE_Topic = "", DDEConnection_ = 0, DDE_Init_To = 0xFFFF )  { 
		; DDE_Init_To = 0 to test to see if connection exists & activate as default
	global
	
	static  DDE_Assigned_Messages
	
	
	local nDDE_Topic, nAppli, Conversation, hAH , sDDE_Topic,sDDE_Server ; , hAHK

	DDE_Connection_ReNumber(DDEConnection_)
	
	if (DDE_Topic = "")
		DDE_Topic := DDE_Server	
		
	if InStr(DDE_Topic . DDE_Server, "\E" , 1) {
		sDDE_Topic := regexreplace(DDE_Topic,"\\E","\E\\E\Q")
		sDDE_Server := regexreplace(DDE_Server,"\\E","\E\\E\Q")
	} else {
		sDDE_Topic = %DDE_Topic%
		sDDE_Server = %DDE_Server%
	}
	
	DDE_Topic_List_Matchme := 
				
	if regexmatch( __DDEM_DDE_Topic_List
				 ,  "\n\Q" 
				. sDDE_Topic 
				. "|" 
				. sDDE_Server 
				.  "|" 
				. DDEConnection_ 
				. "|\E" 
				. "(?<DDE_Server_ID>[^|]++)" 
				. "\|" 
				.  "(?<Client_ID>[^`n]++)", __DDEM_ )  {
				
			__Conversation_BASE := DDEConnection_
							. "|" 
							.  DDE_HEX(__DDEM_DDE_Server_ID)
							. "|" 
							. DDE_HEX(__DDEM_Client_ID)
							
			return __Conversation_BASE
		
	} else if !DDE_Init_To
		return


	if __DDE_UseAHKMessages
		DetectHiddenWindows, On  

	if !DDE_Assigned_Messages {
		CF_TEXT := 1
		WM_DDE_INITIATE   := 0x3E0
		WM_DDE_TERMINATE:= 0x3E1
		WM_DDE_ADVISE   := 0x3E2
		WM_DDE_UNADVISE   := 0x3E3
		WM_DDE_ACK   := 0x3E4
		WM_DDE_DATA   := 0x3E5
		WM_DDE_REQUEST   := 0x3E6
		WM_DDE_POKE   := 0x3E7
		WM_DDE_EXECUTE   := 0x3E8	
		
		__DDE_TableDiv :=  "`n$[_DDE_TABLE_DIV_]$" 
		__DDE_NULL :=  "$[_DDE_NULL_]$"
		
		OnMessage(WM_DDE_ACK , "DDE_ACK")
		OnMessage(WM_DDE_DATA, "DDE_DATA")
		DDE_Assigned_Messages = 1
		
	}


	if !regexmatch(__DDEConnection_Table , "x)\n\Q" 
								. DDEConnection_ 
								. "|\E" 
								. "(?<K>[^`n]++)", hAH ) 
	{
		Gui, %DDEConnection_%: +LastFound 
		Gui, %DDEConnection_%: Show, hide w1 h1 ,__DDEConnection_%DDEConnection_%
		hAHK := WinExist()
		__DDEConnection_Table .= "`n" . DDEConnection_ . "|" . DDE_HEX(hAHK)  ; msgbox % 
	}  	
	
	Ack_W = 

	nAppli := DllCall("GlobalAddAtom", "str", DDE_Server, "Ushort")
	nDDE_Topic := DllCall("GlobalAddAtom", "str", DDE_Topic, "Ushort")

	initWait := 120000
	
	loop 10 {
		sleep 500
		if !__DDE_UseAHKMessages 
			DllCall("SendMessage", "UInt", DDE_Init_To, "UInt", WM_DDE_INITIATE , "UInt", hAHK , "UInt", nAppli | nDDE_Topic << 16) 		
		else
			SendMessage, WM_DDE_INITIATE, hAHK, nAppli | nDDE_Topic << 16,, ahk_id %DDE_Init_To%
		;;
		if (__DDEM_DDE_Server_ID := DDE_WaitA(hAHK, initWait)) 
			break
	}
	
	DllCall("DeleteAtom", "Ushort", nAppli)
	DllCall("DeleteAtom", "Ushort", nDDE_Topic)

	if !(__DDEM_DDE_Server_ID)
		return ( DDE__Tooltips("  ++DDE INITIALIZATION ERROR ".  DDE_Topic . hAHK . "++ " , 6000, 4) * 0)

	if !(__DDEM_Client_ID := Ack_Hwnd)  {  ; hAHK ; Ack_Hwnd 
		__DDEM_Client_ID := DDE_HEX(hAHK)
		DDE__Tooltips("++DDE CLIENT HANDLE INITIALIZATION ERROR ". hAHK . " ++" , 3000, 3) 

	}
	
	Conversation :=DDEConnection_  
				. "|" 
				. DDE_HEX(__DDEM_DDE_Server_ID)
				. "|" 
				. DDE_HEX(__DDEM_Client_ID)
	
	__DDEM_DDE_Topic_List .= "`n"  				 
					. DDE_Topic 
					. "|" 
					. DDE_Server 
					. "|" 
					. Conversation
	
	Return (__Conversation_BASE := Conversation ) 
	
}
; :::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	
DDE_Select( byref Conversation) {

	global   
			
	if ( Conversation or (Conversation := __Conversation_BASE) ) and 	Regexmatch(Conversation, "^(?<Connection>[^|]++)\|(?<__DDE_Server_ID>[^|]++)\|(?<Client_ID>.++)$", __DDEM_)
		return 1
		
}

DDE_Connection_ReNumber(byref DDEConnection) {

	if !DDEConnection or (DDEConnection < 1) or (DDEConnection > 21)
		DDEConnection := 20
	else
		DDEConnection += 20
		
}



DDE_Wait(byRef Var, max = 120000) {

	loop  %max% { 
		if (Var <> "") 
			break
		AX++
	}
	if ( AX < MAX  )  ; ie. loop count
		return Var  ; A
}

DDE_WaitS(byRef VarKey, KeyValue, max = 120000) {

	loop  %max% { 
		if (VarKey = KeyValue) 
			break
		AX++
	}
	
	if ( AX < MAX  )  ; ie. loop count
		return 1 
}


DDE_WaitA(Var, max = 60000) {
	global Ack_Hwnd,Ack_W
	; max =60000
	loop  %max% {  ; 1200000 {
		if  (Var = Ack_Hwnd) 
			break
		AX++
	}
	if ( AX < MAX  )  ; ie. loop count
		return Ack_W
}

DDE_Request(Item, Conversation = "", delay = 90000) {

	local hItem 
	
	if !DDE_Select( Conversation)
		return DDE__Tooltips("  DDE UNDEFINED DDE REQUEST ERROR ". Conversation . " ** "  , 6000, 3) 
	
	hItem := DllCall("GlobalAddAtom", "str", Item, "Ushort")	
	DDE__myAnswer =
	DDE__myAnswerKey =
	DDE__Data_Title = 

	if !__DDE_UseAHKMessages
		DllCall("PostMessage", "UInt", __DDEM___DDE_Server_ID, "UInt", WM_DDE_REQUEST , "UInt", __DDEM_Client_ID , "UInt", CF_TEXT | hItem << 16) 
	else
		PostMessage, WM_DDE_REQUEST, __DDEM_Client_ID, CF_TEXT | hItem << 16,, ahk_id %__DDEM___DDE_Server_ID%
	DllCall("DeleteAtom", "Ushort", hItem)	
	
	if __DDE_BatchMode  ; ie. no waiting... just  >>  __DDE_Use_Answer_Table 
		return
		
	If DDE_SafeMode 
		return DDE_WaitS(DDE__myAnswerKey, __DDEM_Client_ID . "|" . __DDEM___DDE_Server_ID, delay) ? DDE__myAnswer : ""
	else if  (DDE_Wait(DDE__myAnswer, delay) = "$[_~~::NULL::~~_]$")
		return
		
	return DDE__myAnswer
}



DDE_Poke(item= "", data = "", Conversation = "", delay = 20000 ) {  

	Global  
	
	local hItem,hData,pData, lParam
	
	if !DDE_Select( Conversation) 
		return DDE__Tooltips("  DDE UNDEFINED DDE POKE  ERROR ". Conversation . " ** "  , 6000, 3) 

	
	DDE__myAnswer =
	Ack_Hwnd =
	 DDE__Data_Title =
	
	If SubStr(Data, -1) <> "`r`n"
	      Data .= "`r`n"

	   hItem := DllCall("GlobalAddAtom", "str", Item, "Ushort")
	   hData := DllCall("GlobalAlloc", "Uint", 0x0002, "Uint", 2+2+StrLen(Data)+1)   
	   pData := DllCall("GlobalLock" , "Uint", hData)
	   
	   
	   DllCall("ntdll\RtlFillMemoryUlong", "Uint", pData, "Uint", 4, "Uint", 1<<13|1<<16) 
	   DllCall("lstrcpy", "Uint", pData+4, "Uint",&Data)
	   DllCall("GlobalUnlock", "Uint", hData)
	   
	   
	   lParam := DllCall("PackDDElParam", "Uint", WM_DDE_POKE, "Uint", hData, "Uint", hItem)
	   
	if !__DDE_UseAHKMessages
		DllCall("PostMessage", "UInt", __DDEM___DDE_Server_ID, "UInt", WM_DDE_POKE , "UInt", __DDEM_Client_ID , "UInt", lParam) 
	else
		PostMessage, WM_DDE_POKE, __DDEM_Client_ID, lParam,, ahk_id %__DDEM___DDE_Server_ID%

	  If ErrorLevel
	   {
	      DllCall("DeleteAtom", "Ushort", hItem)
	   ;   DllCall("GlobalFree", "Uint"  , hData)
	   ;   DllCall("FreeDDElParam", "Uint", WM_DDE_POKE, "Uint", lParam)
	   }
   

	return DDE_WaitA(__DDEM_Client_ID, delay)

}





DDE_Execute(item, Conversation = "", delay = 60000) {

	Global  
	
	local hCmd, pCmd
	
	; WM_DDE_EXECUTE,__DDEM_Client_ID,__DDEM___DDE_Server_ID, DDE__myAnswer, Ack_Hwnd 
	
	
	if !DDE_Select( Conversation) 
		return DDE__Tooltips("  UNDEFINED DDE EXECUTE  ERROR ". Conversation . " ** "  , 6000, 3) 

	
	   hCmd := DllCall("GlobalAlloc", "Uint", 0x0002, "Uint", StrLen(item)+1)
	   pCmd := DllCall("GlobalLock" , "Uint", hCmd)
	   DllCall("lstrcpy", "Uint", pCmd, "str",item)
	   DllCall("GlobalUnlock", "Uint", hCmd)
	   
	DDE__myAnswer =
	Ack_Hwnd =
	 DDE__Data_Title =
	
	if !__DDE_UseAHKMessages
		DllCall("PostMessage", "UInt", __DDEM___DDE_Server_ID, "UInt", WM_DDE_EXECUTE , "UInt", __DDEM_Client_ID , "UInt", hCmd) 
	else
		PostMessage, WM_DDE_EXECUTE, __DDEM_Client_ID, hCmd,, ahk_id %__DDEM___DDE_Server_ID%
	;If ErrorLevel
		 ;  DllCall("GlobalFree", "Uint", hCmd)
	   
	return DDE_WaitA(__DDEM_Client_ID, delay )
	   
}


DDE_ACK(wParam, LParam, MsgID, hWnd) {
	Critical 24
	global Ack_W,Ack_L,Ack_Hwnd,Ack_MsgID
	
	Ack_W := wParam
	Ack_L := LParam
	Ack_MsgID := MsgID
	Ack_Hwnd := hWnd

}


DDE_DATA(wParam, lParam, MsgID, hWnd) {

	Critical 250
	
	Global WM_DDE_ACK, DDE__myAnswer , DDE__myAnswerKey, DDE_SafeMode,__DDE_BatchMode, Data_MsgID, DDE__Data_Title , __DDE_NULL, __DDE_Answer_Table,__DDE_Use_Answer_Table, __DDE_TableDiv,__DDE_UseAHKMessages ; , sInfo
   
					
	nITem = "Defined!"
   
	DllCall("UnpackDDElParam", "Uint", MsgID, "Uint", lParam, "UintP", hData, "UINTP", nItem)
	DllCall(  "FreeDDElParam", "Uint", MsgID, "Uint", lParam)

	pData := DllCall("GlobalLock", "Uint", hData)
	VarSetCapacity(sInfo, DllCall("lstrlen", "Uint", pData+4))
	DllCall("lstrcpy", "str", sInfo, "Uint", pData+4)
	DllCall("GlobalUnlock", "Uint", hData)
	If (*(pData+1) & 0x20)
	DllCall("GlobalFree", "Uint", hData)
	If (*(pData+1) & 0x80) {
		if !__DDE_UseAHKMessages
			DllCall("PostMessage", "UInt", wParam, "UInt", WM_DDE_ACK , "UInt",  hWnd , "UInt", 0x80 << 8 | nItem << 16) 
		else {
			DetectHiddenWindows, On  
			PostMessage, WM_DDE_ACK, hWnd, 0x80 << 8 | nItem << 16,, ahk_id %wParam%
		}
	}
	Data_MsgID := MsgID
	DDE__Data_Title := "Undefined!"
    
	DllCall("GlobalGetAtomName", "UintP", nItem, "STR", DDE__Data_Title, "SHORT", 512)


	DDE__myAnswer := sInfo
	if (DDE__myAnswer = "") and !DDE_SafeMode
		DDE__myAnswer := __DDE_NULL
	DDE__myAnswerKey := DDE_HEX(hWnd) . "|" . DDE_HEX(wParam)  
	
	if ( __DDE_BatchMode or __DDE_Use_Answer_Table )
		__DDE_Answer_Table .= __DDE_TableDiv 
					. DDE__myAnswerKey 
					. "|" 
					.  DDE__Data_Title 
					.  "|" 
					. DDE__myAnswer
					
	return 0

}


DDE_KILL(__DDE_Server="",__DDE_Topic = "", __DDE_DDEConnection_ = 0) {

	local s__DDE_Topic,s__DDE_Server

	DDE_Connection_ReNumber( __DDE_DDEConnection_)
	if (__DDE_Server = "") {
		Loop, Parse, __DDEM_DDE_Topic_List ,`n
		{
			if !A_loopfield
				continue
			 if regexmatch(A_Loopfield, "(?<__DDE_Topic>[^|]++)" 
									. "\|" 
									. "(?<__DDE_Server>[^|]++)" 
									. "\|" 
									. "(?<DDEConnection>[^|]++)",Kill_) and (!__DDE_No_mass_Termination)
				DDE_KILL(Kill___DDE_Server,Kill___DDE_Topic, Kill_DDEConnection - 20)
		}
		
		__DDEM_DDE_Topic_List = 
		__DDEConnection_Table = 
		
		return
	}
	
	if (__DDE_Topic = "")
		__DDE_Topic := __DDE_Server	
	
	if InStr(__DDE_Topic . __DDE_Server, "\E" , 1) {
		s__DDE_Topic := regexreplace(__DDE_Topic,"\\E","\E\\E\Q")
		s__DDE_Server := regexreplace(__DDE_Server,"\\E","\E\\E\Q")
	} else {
		s__DDE_Topic = %__DDE_Topic%
		s__DDE_Server = %__DDE_Server%
	}

	if regexmatch(__DDEM_DDE_Topic_List
				, "\n\Q" 
				. s__DDE_Topic 
				. "|" 
				. s__DDE_Server 
				.  "|" 
				. __DDE_DDEConnection_ 
				. "|\E" 
				. "(?<__DDE_Server_ID>[^|]++)" 
				. "\|" 
				. "(?<Client_ID>[^`n]++)", __DDEM_)  

		__DDEM_DDE_Topic_List := regexreplace(__DDEM_DDE_Topic_List,"x)" 
										. "\n"
										. "[^|]+"  ; __DDE_Topic
										. "\|" 
										. "[^|]+"  ; __DDE_Server
										.  "\|" 
										. "[^|]+"  ; any connection.. (is same id#s!!!)
										. "\Q|" 
										. __DDEM___DDE_Server_ID
										. "|" 
										. __DDEM_Client_ID 
										. "\E(?=\n|$)" , "")  

	if __DDE_Kill_window_on_Kill and  !regexmatch(__DDEM_DDE_Topic_List, "x)"
										. "\n" 
										. "[^|]+" 
										. "\|" 
										. "[^|]+" 
										.  "\|\Q" 
										. __DDE_DDEConnection_ 
										. "|\E" , __DDEM_)   
	{
		__DDEConnection_Table := regexreplace(__DDEConnection_Table,"\n\Q" . __DDE_DDEConnection_ . "|\E[^`n]++","")
		if ( __DDE_DDEConnection_ > 20) and ( __DDE_DDEConnection_ < 51) 
			Gui, %__DDE_DDEConnection_%: destroy
	}
	
	if __DDEM___DDE_Server_ID {
		if !__DDE_UseAHKMessages
			DllCall("PostMessage", "UInt", __DDEM___DDE_Server_ID, "UInt", WM_DDE_TERMINATE , "UInt", hAHK , "Int", 0) 
		else {
			DetectHiddenWindows, On  
			PostMessage,   WM_DDE_TERMINATE,  hAHK  , 0,,ahk_id %__DDEM___DDE_Server_ID%
		}
	} else
	
		return  DDE__Tooltips( " ++DDE UNDEFINED TERMINATION ERROR ". __DDE_Topic . "|" . __DDE_Server . "  " . __DDE_DDEConnection_ " ** "  , 4000,3) 

	if __DDEM_DDE_Topic_List 
		regexmatch(__DDEM_DDE_Topic_List, "\n" 
					. "[^|]++"					
					. "\|" 
					. "[^|]++" 
					. "\|" 
					.  "(?<Connection_>[^|]++)"
					. "\|" 
					.  "(?<M___DDE_Server_ID>[^|]++)"
					 .  "\|" 
					. "(?<M_Client_ID>[^|]++)"
					. "$", DDE)
	
	if __DDEM___DDE_Server_ID 
		__Conversation_BASE := __DDE_DDEConnection_  
							. "|" 
							.  hex ( __DDEM___DDE_Server_ID )
							. "|" 
							. hex ( __DDEM_Client_ID )

}


DDE_exitapp() {
	DDE_KILL() 
	exitapp
}

DDE__Tooltips(text = "", delay = 3000, id = 1, x = "", y = "") {

	if (id < 1) or (id > 5)
		id := 1
	delay := (delay) ? delay : 3000
	ToolTip, %text%,%x%,%y%,%id%
	if text
		SetTimer, DDE__RemoveToolTip%id%, %delay%
	return 1  ; ie. is set
}
DDE__RemoveToolTips(id = 1) {
	if (id < 1) or (id > 5)
		id := 1
	SetTimer, DDE__RemoveToolTip%id%, Off
	ToolTip,,,,%id%
return


DDE__RemoveToolTip1:
	DDE__RemoveToolTips(2)
return


DDE__RemoveToolTip2:
	DDE__RemoveToolTips(2)
return

DDE__RemoveToolTip3:
	DDE__RemoveToolTips(3)
return

DDE__RemoveToolTip4:
	DDE__RemoveToolTips(4)
return

DDE__RemoveToolTip5:
	DDE__RemoveToolTips(5)
return

}

DDE_hex(HEXME_number) {

	if ((format := A_FormatInteger) != "H") {
		SetFormat Integer, Hex  
		HEXME_number := round(HEXME_number) + 0
		SetFormat Integer, %format%  
		return HEXME_number
	} else 
		return HEXME_number + 0
}
; v1.08  [3/20/10]
; Copr. 2007,2010.  See License.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; END OF DDE CLIENT WRAPPER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


Second attempt using DDE lib by Majkinetor

Code: Select all

idInst := DDE_Initialize(0, RegisterCallback("DDE_Callback")) 
hConv := DDE_Connect(idInst,"MT4","QUOTE") 
tick := DDE_ClientTransaction(idInst, hConv, "EXECUTE")
; not sure where "BID!GBPUSD" request goes, maybe in DDE_AccessData()?
w := DDE_AccessData("BID!GBPUSD")		
DDE_Uninitialize(idInst) 
msgbox idInst = %idInst%`nhConv = %hConv%`ntick`n`nw = %w%
DDE lib by Majkinetor:

Code: Select all

;By majkinetor/   http://multi-rename-script.googlecode.com/svn/trunk/inc/m/DDE.ahk
DDE_Initialize(idInst = 0, pCallback = 0, nFlags = 0)
{
	If	DllCall("DdeInitialize", "UintP", idInst, "Uint", pCallback, "Uint", nFlags, "Uint", 0)=0
	Return	idInst
}

DDE_Uninitialize(idInst)
{
	Return	DllCall("DdeUninitialize", "Uint", idInst)
}

DDE_GetLastError(idInst)
{
	Return	DllCall("DdeGetLastError", "Uint", idInst)
}

DDE_NameService(idInst, sServ = "", nCmd = 1)
{
	Return	DllCall("DdeNameService", "Uint", idInst, "Uint", hServ:=sServ="" ? 0 : DDE_CreateStringHandle(idInst,sServ), "Uint", 0, "Uint", nCmd), hServ ? DDE_FreeStringHandle(idInst,hServ) : ""
}

DDE_EnableCallback(idInst, hConv = 0, nCmd = 0)
{
	Return	DllCall("DdeEnableCallback", "Uint", idInst, "Uint", hConv, "Uint", nCmd)
}

DDE_PostAdvise(idInst, sTopic = "", sItem = "")
{
	Return	DllCall("DdePostAdvise", "Uint", idInst, "Uint", hTopic:=sTopic="" ? 0 : DDE_CreateStringHandle(idInst,sTopic), "Uint", hItem:=sItem="" ? 0 : DDE_CreateStringHandle(idInst,sItem))
	,	hTopic ? DDE_FreeStringHandle(idInst,hTopic) : ""
	,	hItem  ? DDE_FreeStringHandle(idInst,hItem)  : ""
}

DDE_ClientTransaction(idInst, hConv, sType = "EXECUTE", sItem = "", pData = 0, cbData = 0, CF_Format = 1, bAsync = False){
	static  XTYP_ADVSTART := 0x1030,  XTYP_ADVSTOP := 0x8040, XTYP_EXECUTE := 0x4050, XTYP_POKE	:= 0x4090, XTYP_REQUEST	:= 0x20B0
	Return	DllCall("DdeClientTransaction", "Uint", pData, "Uint", cbData, "Uint", hConv, "Uint", hItem:=sItem="" ? 0 : DDE_CreateStringHandle(idInst,sItem), "Uint", CF_Format, "Uint", XTYP_%sType%, "Uint", bAsync ? TIMEOUT_ASYNC:=-1 : 10000, "UintP", nResult), hItem ? DDE_FreeStringHandle(idInst,hItem) : ""
}

DDE_AbandonTransaction(idInst, hConv = 0, idTransaction = 0)
{
	Return	DllCall("DdeAbandonTransaction", "Uint", idInst, "Uint", hConv, "Uint", idTransaction)
}

DDE_Connect(idInst, sServ = "", sTopic = "", pCC = 0)
{
	Return	DllCall("DdeConnect", "Uint", idInst, "Uint", hServ:=sServ="" ? 0 : DDE_CreateStringHandle(idInst,sServ), "Uint", hTopic:=sTopic="" ? 0 : DDE_CreateStringHandle(idInst,sTopic), "Uint", pCC)
	,	hTopic ? DDE_FreeStringHandle(idInst,hTopic) : ""
	,	hServ  ? DDE_FreeStringHandle(idInst,hServ)  : ""
}

DDE_Reconnect(hConv)
{
	Return	DllCall("DdeReconnect", "Uint", hConv)
}

DDE_Disconnect(hConv)
{
	Return	DllCall("DdeDisconnect", "Uint", hConv)
}

DDE_ConnectList(idInst, sServ = "", sTopic = "", hConvList = 0, pCC = 0)
{
	Return	DllCall("DdeConnectList", "Uint", idInst, "Uint", hServ:=sServ="" ? 0 : DDE_CreateStringHandle(idInst,sServ), "Uint", hTopic:=sTopic="" ? 0 : DDE_CreateStringHandle(idInst,sTopic), "Uint", hConvList, "Uint", pCC)
	,	hTopic ? DDE_FreeStringHandle(idInst,hTopic) : ""
	,	hServ  ? DDE_FreeStringHandle(idInst,hServ)  : ""
}

DDE_DisconnectList(hConvList)
{
	Return	DllCall("DdeDisconnectList", "Uint", hConvList)
}

DDE_QueryNextServer(hConvList, hConvPrev = 0)
{
	Return	DllCall("DdeQueryNextServer", "Uint", hConvList, "Uint", hConvPrev)
}

DDE_QueryConvInfo(hConv, idTransaction = -1, ByRef ci = "")	; QID_SYNC=-1
{
	Return	DllCall("DdeQueryConvInfo", "Uint", hConv, "Uint", idTransaction, "Uint", NumPut(VarSetCapacity(ci,64,0),ci)-4)
}

DDE_AccessData(hData, ByRef cbData = "")
{
	Return	DllCall("DdeAccessData", "Uint", hData, "UintP", cbData)
}

DDE_UnaccessData(hData)
{
	Return	DllCall("DdeUnaccessData", "Uint", hData)
}

DDE_AddData(hData, pData, cbData, cbOff = 0)
{
	Return	DllCall("DdeAddData", "Uint", hData, "Uint", pData, "Uint", cbData, "Uint", cbOff)
}

DDE_GetData(hData, ByRef sData = "", cbOff = 0)
{
	cb  :=	DllCall("DdeGetData", "Uint", hData, "Uint", 0, "Uint", 0, "Uint", cbOff)
	VarSetCapacity(sData, cb)
	If	DllCall("DdeGetData", "Uint", hData, "str", sData, "Uint", cb, "Uint", cbOff)
	Return	sData
}

DDE_QueryString(idInst, hString, nCodePage = 1004)	; CP_WINANSI = 1004, CP_WINUNICODE = 1200
{
	cch :=	DllCall("DdeQueryString", "Uint", idInst, "Uint", hString, "Uint", 0, "Uint", 0, "int", nCodePage)
	VarSetCapacity(sString, cch)
	If	DllCall("DdeQueryString", "Uint", idInst, "Uint", hString, "str", sString, "Uint", cch+1, "int", nCodePage)
	Return	sString
}

DDE_CreateDataHandle(idInst, sItem = "", pData = 0, cbData = 0, cbOff = 0, CF_Format = 1, bOwned = True)
{
	Return	DllCall("DdeCreateDataHandle", "Uint", idInst, "Uint", pData, "Uint", cbData, "Uint", cbOff, "Uint", hItem:=sItem="" ? 0 : DDE_CreateStringHandle(idInst,sItem), "Uint", CF_Format, "Uint", bOwned ? HDATA_APPOWNED:=1 : 0), hItem ? DDE_FreeStringHandle(idInst,hItem) : ""
}

DDE_FreeDataHandle(hData)
{
	Return	DllCall("DdeFreeDataHandle", "Uint", hData)
}

DDE_CreateStringHandle(idInst, sString, nCodePage = 1004)
{
	Return	DllCall("DdeCreateStringHandle", "Uint", idInst, "Uint", &sString, "int", nCodePage)
}

DDE_KeepStringHandle(idInst, hString)
{
	If	DllCall("DdeKeepStringHandle", "Uint", idInst, "UintP", hString)
	Return	hString
}

DDE_FreeStringHandle(idInst, hString)
{
	Return	DllCall("DdeFreeStringHandle", "Uint", idInst, "Uint", hString)
}

DDE_CmpStringHandles(hString1, hString2)
{
	Return	DllCall("DdeCmpStringHandles", "Uint", hString1, "Uint", hString2)
}

DDE_SetUserHandle(hConv, hUser)
{
	Return	DllCall("DdeSetUserHandle", "Uint", hConv, "Uint", -1, "Uint", hUser)
}
User avatar
tomoe_uehara
Posts: 213
Joined: 05 Oct 2013, 12:37
Contact:

Re: How to connect to DDE server? (MT4)

07 Jun 2017, 07:26

Hmm I think it will be easier to write an EA directly rather than using AHK, but I'm not sure what you're going to do though
serg
Posts: 56
Joined: 21 Mar 2015, 05:33

Re: How to connect to DDE server? (MT4)

07 Jun 2017, 09:39

tomoe_uehara wrote:it will be easier to write an EA directly rather than using AHK
I need to get quotes for my existing AHK script. And MQL doesn't have all the functionality that I need.

For now Im using Excel to get access to MT4 DDE server, but trying to get direct access.

As a workaround, I wrote MQL indicator that was posting system messages with quotes every tick, and I was catching those messages with AHK, but this method turned out to be not very reliable - some ticks were lost. Also writing ticks to HD and reading them with AHK is not the optimal solution. DDE seems like the best solution for getting live quotes, I just can not make it work ))
serg
Posts: 56
Joined: 21 Mar 2015, 05:33

Re: How to connect to DDE server? (MT4)

10 Jun 2017, 06:42

I was confused about DDE protocol, due to my ignorance, I thought it was something else.
After reading MSDN, looks like DDE is nothing more than just exchanging system messages (same as PostMsg + OnMessage). So one can write MT4 indicator that would post messages with last ticks directly to AHK script, without needing to use DDE.

From MSDN:
"Dynamic Data Exchange Protocol:
Because Windows has a message-based architecture, passing messages is the most appropriate method for automatically transferring information between applications. However, messages contain only two parameters (wParam and lParam) for passing data. As a result, these parameters must refer indirectly to other pieces of data when more than a few words of information pass between applications. The DDE protocol defines exactly how applications should use the wParam and lParam parameters to pass larger pieces of data by means of global atoms and shared memory handles. The DDE protocol has specific rules for allocating and deleting global atoms and shared memory objects."

For everyone else here, who wants to receive live data from MT4/MT5 directly to your AHK script, you can this:
1) Get handle of the AHK script that will receive data from MT4 and store it in a separate file

Code: Select all

DetectHiddenWindows On
AHK_HWND := winexist("[name of the AHK script] ahk_class AutoHotkey")
FileDelete, [location of MT4 folder]\MT4\experts\include\ahk_hwnd.mqh
fileappend, #define AHK_HWND  %AHK_HWND%, [location of MT4 folder]\MT4\experts\include\ahk_hwnd.mqh
DetectHiddenWindows Off
2) Create MT4 indicator that would send data to AHK script:

Code: Select all

#property indicator_chart_window
#include <WinUser32.mqh>
#import "user32.dll"
  int    PostMessageA(int hWnd,int Msg,int wParam,int lParam);
#import
#include <ahk_hwnd.mqh>             // AHK_HWND is stored in ahk_hwnd.mqh

int init()  {
}
int start() {
   string SymbolName = Symbol();
   int PeriodName    = Period();
   string ChartName = StringConcatenate(SymbolName,PeriodName);
   int msg;
   if (SymbolName == "GBPUSD") msg=30001;	// For this example I use just 2 pairs: GBPUSD and EURUSD
   if (SymbolName == "EURUSD") msg=30002;	// I chose msg numbers 30001/30002 randomly, just use number>4096 not to interfere with system messages

   int close = Close[0]*100000;				// Since GBPUSD quote is ~1.5555 - need to convert it to integer without loosing information
   int time = Time[0];						// time is in MT4 format - later will need to convert it to normal format
			//  Alert("Close  ",close," Time ",time," ChartName ",ChartName," msg ",msg," hwnd ",AHK_HWND);
   PostMessageA(AHK_HWND,msg,close,time);

   Sleep(100);			//add more sleep if this script takes too much CPU
}
3) Put this indicator in MT4 "Indicators" folder, open MT4, attach this indicator to relevant charts (I used 2 pairs in this example, so use 2 relevant charts)
4) In AHK sript add:

Code: Select all

OnMessage(30001, "GBPUSD_tick")
OnMessage(30002, "EURUSD_tick")

GBPUSD_tick(tick, time){
tick := tick / 100000		; since price was mulitplied *100000 in Mql code
	rt = 19700101000000	; MT4 time - is number of seconds from 1970.01.01 00:00
	EnvAdd, rt, time, Seconds
	FormatTime, rt, %rt%, MM.dd.yyyy  H:mm
msgbox GBPUSD last price = %tick% `nLast Updated at %rt%
}
EURUSD_tick(tick, time){
; same way as above
}

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot], hedehede81, OrangeCat and 314 guests