Files
Michael Schimmel 06ec0db105 1st commit
2025-05-23 12:51:32 +02:00

464 lines
18 KiB
ObjectPascal

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.