unit MainUnit; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdGlobal, IdException, System.DateUtils, System.StrUtils; const FIX_SEPARATOR = #1; // SOH character for FIX messages // Broker specific connection details - SET TO DEMO & PLAIN TEXT BROKER_HOST = 'demo-uk-eqx-01.p.c-trader.com'; BROKER_PLAINTEXT_PORT = 5201; // Plain text port TARGET_COMP_ID_CONST = 'CSERVER'; SENDER_SUB_ID_CONST = 'QUOTE'; // For Price Connection // Based on your observation that "NO exception" occurs with 57=QUOTE // Standard cTrader examples often use 'TRADE' here. TARGET_SUB_ID_CONST = 'QUOTE'; // << ACHTUNG: Testwert basierend auf Ihrer letzten Beobachtung type TForm1 = class(TForm) ConnectButton: TButton; PasswordEdit: TEdit; LogMemo: TMemo; Label1: TLabel; SenderCompIDEdit: TEdit; Label2: TLabel; DisconnectButton: TButton; UserIdEdit: TEdit; Label3: TLabel; procedure ConnectButtonClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure DisconnectButtonClick(Sender: TObject); private FIdTCPClient: TIdTCPClient; FMsgSeqNum: Integer; function CreateFixTag(Tag: Integer; Value: string): string; function CalculateChecksum(const MessageBody: string): string; function GetUtcDateTimeString: string; procedure Log(const Text: string); procedure SendFixMessage(const MessageType: string; const AdditionalBodyFields: TStrings); procedure SendMarketDataRequest(const Symbol: string; const MDReqID: string); // NEU public end; var Form1: TForm1; implementation {$R *.dfm} function TForm1.CreateFixTag(Tag: Integer; Value: string): string; begin Result := IntToStr(Tag) + '=' + Value + FIX_SEPARATOR; end; function TForm1.GetUtcDateTimeString: string; var nowUtc: TDateTime; begin nowUtc := TTimeZone.Local.ToUniversalTime(Now); Result := FormatDateTime('yyyymmdd-hh:nn:ss.zzz', nowUtc); end; function TForm1.CalculateChecksum(const MessageBody: string): string; var sum: Cardinal; i: Integer; checkSumVal: Integer; begin sum := 0; for i := 1 to Length(MessageBody) do begin sum := sum + Ord(MessageBody[i]); end; checkSumVal := sum mod 256; Result := Format('%.3d', [checkSumVal]); end; procedure TForm1.Log(const Text: string); begin if Assigned(LogMemo) then begin LogMemo.Lines.Add(FormatDateTime('[hh:nn:ss.zzz] ', Time) + Text); end; end; procedure TForm1.FormCreate(Sender: TObject); begin FMsgSeqNum := 1; Log('FormCreate: Initializing components for PLAIN TEXT connection.'); try FIdTCPClient := TIdTCPClient.Create(nil); Log('FormCreate: TCPClient created.'); except on E: Exception do begin Log('CRITICAL ERROR during FormCreate TCPClient creation: ' + E.Message); ShowMessage('CRITICAL ERROR during FormCreate: ' + E.Message); FIdTCPClient := nil; Exit; end; end; if Assigned(FIdTCPClient) then begin FIdTCPClient.Host := BROKER_HOST; FIdTCPClient.Port := BROKER_PLAINTEXT_PORT; FIdTCPClient.ReadTimeout := 10000; // 10 Sekunden Timeout für Leseoperationen FIdTCPClient.ConnectTimeout := 10000; Log('FormCreate: TCPClient configured for PLAIN TEXT - Host: ' + FIdTCPClient.Host + ' Port: ' + IntToStr(FIdTCPClient.Port)); end else begin Log('FormCreate: FIdTCPClient is NIL after creation attempt. Network functions disabled.'); end; // --- WICHTIG: DIESE MIT IHREN EXAKTEN DEMO-ZUGANGSDATEN ERSETZEN --- SenderCompIDEdit.Text := 'demo.pepperstoneuk.4132560'; UserIdEdit.Text := '4132560'; PasswordEdit.Text := 'testing'; // BITTE DAS VON PEPPERSTONE BESTÄTIGTE DEMO-PASSWORT VERWENDEN! Log('FormCreate: Default credentials set. SenderCompID: ' + SenderCompIDEdit.Text + ', UserID: ' + UserIdEdit.Text); end; procedure TForm1.FormDestroy(Sender: TObject); var logoutFields: TStringList; currentSenderCompID: string; begin Log('FormDestroy: Cleaning up.'); if Assigned(FIdTCPClient) and FIdTCPClient.Connected then begin Log('FormDestroy: Still connected. Sending Logout message...'); currentSenderCompID := Trim(SenderCompIDEdit.Text); if currentSenderCompID <> '' then begin logoutFields := TStringList.Create; try logoutFields.Add(CreateFixTag(49, currentSenderCompID)); logoutFields.Add(CreateFixTag(56, TARGET_COMP_ID_CONST)); logoutFields.Add(CreateFixTag(34, IntToStr(FMsgSeqNum))); logoutFields.Add(CreateFixTag(52, GetUtcDateTimeString)); SendFixMessage('5', logoutFields); finally FreeAndNil(logoutFields); end; end else begin Log('FormDestroy: Cannot send Logout - SenderCompID is empty on form.'); end; FIdTCPClient.Disconnect; Log('FormDestroy: Disconnected.'); end; FreeAndNil(FIdTCPClient); Log('FormDestroy: Components freed.'); end; procedure TForm1.SendFixMessage(const MessageType: string; const AdditionalBodyFields: TStrings); var headerBuilder: TStringBuilder; bodyProperBuilder: TStringBuilder; fullMessageBuilder: TStringBuilder; actualBodyLength: Integer; checksum: string; i: Integer; rawMessage: string; begin if not (Assigned(FIdTCPClient) and FIdTCPClient.Connected) then begin Log('Error: Not connected to server (SendFixMessage for MsgType: ' + MessageType + ').'); Exit; end; headerBuilder := TStringBuilder.Create; bodyProperBuilder := TStringBuilder.Create; fullMessageBuilder := TStringBuilder.Create; try bodyProperBuilder.Append(CreateFixTag(35, MessageType)); if Assigned(AdditionalBodyFields) then begin for i := 0 to AdditionalBodyFields.Count - 1 do begin bodyProperBuilder.Append(AdditionalBodyFields[i]); end; end; actualBodyLength := Length(bodyProperBuilder.ToString); Log('SendFixMessage: MsgType=' + MessageType + ', Calculated actualBodyLength (Tag 9 Value) = ' + IntToStr(actualBodyLength)); headerBuilder.Append(CreateFixTag(8, 'FIX.4.4')); headerBuilder.Append(CreateFixTag(9, IntToStr(actualBodyLength))); fullMessageBuilder.Append(headerBuilder.ToString); fullMessageBuilder.Append(bodyProperBuilder.ToString); checksum := CalculateChecksum(fullMessageBuilder.ToString); fullMessageBuilder.Append(CreateFixTag(10, checksum)); rawMessage := fullMessageBuilder.ToString; Log('Sending (MsgType ' + MessageType + '): ' + StringReplace(rawMessage, FIX_SEPARATOR, '|', [rfReplaceAll, rfIgnoreCase])); try FIdTCPClient.IOHandler.Write(rawMessage, IndyTextEncoding_UTF8); FIdTCPClient.IOHandler.WriteBufferFlush(); except on E: EIdException do begin Log('Error sending message (MsgType ' + MessageType + ', Indy Exception): ' + E.ClassName + ': ' + E.Message); Exit; end; on E: Exception do begin Log('Error sending message (MsgType ' + MessageType + ', General Exception): ' + E.Message); Exit; end; end; Inc(FMsgSeqNum); finally FreeAndNil(headerBuilder); FreeAndNil(bodyProperBuilder); FreeAndNil(fullMessageBuilder); end; end; // NEUE Prozedur für Market Data Request procedure TForm1.SendMarketDataRequest(const Symbol: string; const MDReqID: string); var mdFields: TStringList; tempFieldValue: string; begin if not (Assigned(FIdTCPClient) and FIdTCPClient.Connected) then begin Log('Error: Not connected to server (SendMarketDataRequest).'); Exit; end; Log('SendMarketDataRequest: Preparing for Symbol: ' + Symbol + ', MDReqID: ' + MDReqID); mdFields := TStringList.Create; try // Tag 262: MDReqID mdFields.Add(CreateFixTag(262, MDReqID)); Log('SendMarketDataRequest: Tag 262 Value="' + MDReqID + '" (Length=' + IntToStr(Length(MDReqID)) + ')'); // Tag 263: SubscriptionRequestType (0=Snapshot, 1=Snapshot+Updates, 2=Disable previous) tempFieldValue := '1'; // Snapshot + Updates mdFields.Add(CreateFixTag(263, tempFieldValue)); Log('SendMarketDataRequest: Tag 263 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); // Tag 264: MarketDepth (0=Full Book, 1=Top of Book) tempFieldValue := '1'; // Top of Book mdFields.Add(CreateFixTag(264, tempFieldValue)); Log('SendMarketDataRequest: Tag 264 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); // Tag 267: NoMDEntryTypes (Anzahl der MDEntryType-Gruppen) tempFieldValue := '2'; // Für Bid und Offer mdFields.Add(CreateFixTag(267, tempFieldValue)); Log('SendMarketDataRequest: Tag 267 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); // Tag 269: MDEntryType (0=Bid) tempFieldValue := '0'; mdFields.Add(CreateFixTag(269, tempFieldValue)); Log('SendMarketDataRequest: Tag 269 (Bid) Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); // Tag 269: MDEntryType (1=Offer) tempFieldValue := '1'; mdFields.Add(CreateFixTag(269, tempFieldValue)); Log('SendMarketDataRequest: Tag 269 (Offer) Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); // Tag 146: NoRelatedSym (Anzahl der Instrumente) tempFieldValue := '1'; // Für ein Instrument mdFields.Add(CreateFixTag(146, tempFieldValue)); Log('SendMarketDataRequest: Tag 146 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); // Tag 55: Symbol // WICHTIG: Ersetzen Sie '1' durch eine gültige cTrader Symbol ID für die Demo-Umgebung! // Dies ist oft eine numerische ID (z.B. "1" für EURUSD, "22" für GER30). // Fragen Sie Ihren Broker (Pepperstone) nach den korrekten IDs für die Demo. mdFields.Add(CreateFixTag(55, Symbol)); Log('SendMarketDataRequest: Tag 55 Value="' + Symbol + '" (Length=' + IntToStr(Length(Symbol)) + ')'); SendFixMessage('V', mdFields); // 'V' ist MsgType für Market Data Request finally FreeAndNil(mdFields); end; end; procedure TForm1.ConnectButtonClick(Sender: TObject); var logonFields: TStringList; currentSenderCompID: string; currentPasswordValue: string; currentUsernameFromEdit: string; sendingTime: string; response: string; tempFieldValue: string; // Für Market Data Request symbolToRequest: string; marketDataRequestID: string; begin Log('ConnectButtonClick: Initiated for PLAIN TEXT.'); if not Assigned(FIdTCPClient) then begin Log('ConnectButtonClick: TCP Client not initialized. Check FormCreate logs.'); Exit; end; if FIdTCPClient.Connected then begin Log('ConnectButtonClick: Already connected. Please disconnect first.'); Exit; end; currentSenderCompID := Trim(SenderCompIDEdit.Text); currentPasswordValue := PasswordEdit.Text; currentUsernameFromEdit := Trim(UserIdEdit.Text); Log('ConnectButtonClick: Input Values - SenderCompID="' + currentSenderCompID + '" (Length=' + IntToStr(Length(currentSenderCompID)) + '), UserID="' + currentUsernameFromEdit + '" (Length=' + IntToStr(Length(currentUsernameFromEdit)) + '), Password="' + currentPasswordValue + '" (Length=' + IntToStr(Length(currentPasswordValue)) + ')'); if (currentSenderCompID = '') or (currentPasswordValue = '') or (currentUsernameFromEdit = '') then begin Log('ConnectButtonClick: SenderCompID, UserID, and Password are required.'); Exit; end; FMsgSeqNum := 1; Log('Attempting to connect to DEMO (PLAIN TEXT): ' + FIdTCPClient.Host + ':' + IntToStr(FIdTCPClient.Port) + '...'); try FIdTCPClient.Connect; Log('Connected successfully (PLAIN TEXT).'); except on E: EIdException do begin Log('Connection failed (Indy Exception): ' + E.ClassName + ': ' + E.Message); Exit; end; on E: Exception do begin Log('Connection failed (General Exception): ' + E.ClassName + ': ' + E.Message); Exit; end; end; logonFields := TStringList.Create; try sendingTime := GetUtcDateTimeString; Log('ConnectButtonClick: SendingTime (Tag 52 Value)="' + sendingTime + '" (Length=' + IntToStr(Length(sendingTime)) + ')'); logonFields.Add(CreateFixTag(49, currentSenderCompID)); logonFields.Add(CreateFixTag(56, TARGET_COMP_ID_CONST)); Log('ConnectButtonClick: Tag 56 Value="' + TARGET_COMP_ID_CONST + '" (Length=' + IntToStr(Length(TARGET_COMP_ID_CONST)) + ')'); logonFields.Add(CreateFixTag(50, SENDER_SUB_ID_CONST)); // 'QUOTE' für Preis-Feed Log('ConnectButtonClick: Tag 50 Value="' + SENDER_SUB_ID_CONST + '" (Length=' + IntToStr(Length(SENDER_SUB_ID_CONST)) + ')'); logonFields.Add(CreateFixTag(57, TARGET_SUB_ID_CONST)); // 'QUOTE' oder 'TRADE' - Testen Sie, was funktioniert! Log('ConnectButtonClick: Tag 57 Value="' + TARGET_SUB_ID_CONST + '" (Length=' + IntToStr(Length(TARGET_SUB_ID_CONST)) + ')'); tempFieldValue := IntToStr(FMsgSeqNum); logonFields.Add(CreateFixTag(34, tempFieldValue)); Log('ConnectButtonClick: Tag 34 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); logonFields.Add(CreateFixTag(52, sendingTime)); tempFieldValue := '0'; logonFields.Add(CreateFixTag(98, tempFieldValue)); Log('ConnectButtonClick: Tag 98 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); tempFieldValue := '30'; logonFields.Add(CreateFixTag(108, tempFieldValue)); Log('ConnectButtonClick: Tag 108 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); tempFieldValue := 'Y'; // ResetSeqNumFlag=Y logonFields.Add(CreateFixTag(141, tempFieldValue)); Log('ConnectButtonClick: Tag 141 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); logonFields.Add(CreateFixTag(553, currentUsernameFromEdit)); logonFields.Add(CreateFixTag(554, currentPasswordValue)); SendFixMessage('A', logonFields); // Logon senden // Kurze Pause und dann Market Data Request senden //Sleep(500); // 0.5 Sekunden warten // WICHTIG: Ersetzen Sie '1' durch eine gültige cTrader Symbol ID für Pepperstone Demo // und "MDReq_001" durch eine eindeutige ID für Ihren Request. // symbolToRequest := '1'; // Beispiel: EURUSD (numerische ID von Pepperstone erfragen!) // marketDataRequestID := currentUsernameFromEdit + '_' + symbolToRequest + '_' + IntToStr(FMsgSeqNum); // Eindeutige MDReqID // Log('ConnectButtonClick: Proceeding to send Market Data Request.'); // SendMarketDataRequest(symbolToRequest, marketDataRequestID); // Auf Antwort warten try Log('Waiting for response after Logon and MD Request (PLAIN TEXT)...'); repeat response := FIdTCPClient.IOHandler.ReadLn(FIX_SEPARATOR, FIdTCPClient.ReadTimeout, -1, IndyTextEncoding_UTF8); // response := FIdTCPClient.IOHandler.AllData(IndyTextEncoding_UTF8); Log('Received (PLAIN TEXT): ' + StringReplace(response, FIX_SEPARATOR, '|', [rfReplaceAll, rfIgnoreCase])); until (response = '') or (LeftStr(response, 3)='10='); // Hier Parsing-Logik für die Antwort einfügen // z.B. auf 35=W (Snapshot), 35=X (Incremental), 35=Y (Reject) prüfen except on E: EIdException do begin Log('Error reading response or timeout (PLAIN TEXT - Indy Exception): ' + E.ClassName + ': ' + E.Message); end; on E: Exception do begin Log('Error reading response (PLAIN TEXT - General Exception): ' + E.ClassName + ': ' + E.Message); end; end; finally FreeAndNil(logonFields); end; end; procedure TForm1.DisconnectButtonClick(Sender: TObject); var logoutFields: TStringList; currentSenderCompID: string; begin Log('DisconnectButtonClick: Initiated.'); if Assigned(FIdTCPClient) and FIdTCPClient.Connected then begin Log('DisconnectButtonClick: Sending Logout message...'); currentSenderCompID := Trim(SenderCompIDEdit.Text); if currentSenderCompID <> '' then begin logoutFields := TStringList.Create; try logoutFields.Add(CreateFixTag(49, currentSenderCompID)); logoutFields.Add(CreateFixTag(56, TARGET_COMP_ID_CONST)); logoutFields.Add(CreateFixTag(34, IntToStr(FMsgSeqNum))); logoutFields.Add(CreateFixTag(52, GetUtcDateTimeString)); SendFixMessage('5', logoutFields); finally FreeAndNil(logoutFields); end; end else begin Log('DisconnectButtonClick: Cannot send Logout - SenderCompID is empty.'); end; FIdTCPClient.Disconnect; Log('DisconnectButtonClick: Disconnected from server.'); end else begin Log('DisconnectButtonClick: Not connected.'); end; end; end.