Automating the DAS Trader Report Export from WSL

Automating the DAS Trader Report Export from WSL

I trade through Cobra Trading, whose build of DAS Trader Pro is the platform this whole post is about. My review pipeline runs off six CSV files that DAS Trader Pro exports at the end of each session: an account report for each account and one for all accounts, then the orders, executions and tickets from the Trade Report dialog. Exporting them by hand means opening two windows, clicking Export, typing a path into a Save As dialog, and clicking OK on a completion box. It takes two minutes, and on the days it did not happen the pipeline had nothing to read. This is how I made it a scheduled job, driven from WSL.

The pieces

DAS offers an add-on feature for running a command API on a local TCP port, sold as DAS Trader DMA FIX/API. The command API manual is not published; it comes with the subscription, because the API is a commercial product, so I quote only the two commands I rely on here and describe the rest in general terms. Among its commands is one that sends a hotkey script to the application, and the hotkey guide includes a command that opens the account report window for a named account:

LOGIN <trader> <password> <account> 1
SCRIPT GLOBALSCRIPT ShowAccount <account>

That gets the first window open without touching the GUI. Everything after it is GUI work, and for that I use PowerShell called from WSL through powershell.exe, with three tools inside it:

  • user32.dll calls through Add-Type P/Invoke, to find windows and controls and send them messages;
  • the Windows Script Host shell COM object (WScript.Shell), to type into the Save As dialog;
  • System.Drawing, once, to screenshot a menu I could not read any other way.

Finding the windows

UI Automation was the first thing I tried, and it saw almost nothing: the DAS main window is an MFC application with owner-drawn control bars, it has no classic menu (GetMenu returns zero), and its children do not enumerate. Raw EnumWindows does work. Filtering by the DAS process id gives every window it owns, visible or not, with class name and title:

$src = @"
using System; using System.Text; using System.Runtime.InteropServices;
using System.Collections.Generic;
public class W {
  public delegate bool EnumProc(IntPtr h, IntPtr l);
  [DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc p, IntPtr l);
  [DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr h, EnumProc p, IntPtr l);
  [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
  [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
  [DllImport("user32.dll")] public static extern int GetClassName(IntPtr h, StringBuilder s, int n);
  [DllImport("user32.dll")] public static extern bool PostMessage(IntPtr h, uint m, IntPtr w, IntPtr l);
  public static IntPtr FindTop(uint pid, string title) {
    IntPtr r = IntPtr.Zero;
    EnumWindows((h, l) => {
      uint p; GetWindowThreadProcessId(h, out p);
      if (p != pid) return true;
      var t = new StringBuilder(256); GetWindowText(h, t, 256);
      if (t.ToString().Contains(title)) { r = h; return false; }
      return true;
    }, IntPtr.Zero);
    return r;
  }
  public static IntPtr FindChild(IntPtr h, string text) {
    IntPtr r = IntPtr.Zero;
    EnumChildWindows(h, (c, l) => {
      var t = new StringBuilder(256); GetWindowText(c, t, 256);
      if (t.ToString() == text) { r = c; return false; }
      return true;
    }, IntPtr.Zero);
    return r;
  }
}
"@
Add-Type -TypeDefinition $src
$pid_ = (Get-Process DasTrader64).Id
$report = [W]::FindTop($pid_, "Account Report  --- $account")
$export = [W]::FindChild($report, "&Export")
[W]::PostMessage($export, 0x00F5, [IntPtr]0, [IntPtr]0)   # BM_CLICK

The account report window is class WndFrm5678 with the account in its title. Its children enumerate normally, so the Export button is found by caption and clicked with a posted BM_CLICK.

The Save As dialog

Export raises a standard Save As dialog. UI Automation could not see its edit controls from across the WSL boundary either, and setting the file name edit by WM_SETTEXT landed in the wrong control twice. Keystrokes through the COM shell were reliable on the first try and every run since:

$ws = New-Object -ComObject WScript.Shell
$ws.AppActivate("Save As") | Out-Null
$ws.SendKeys("%n")        # Alt+N selects the File name box
$ws.SendKeys("^a")
$ws.SendKeys("$ExportRoot\$Month\$Date\Accounts-$Account.csv")
$ws.SendKeys("{ENTER}")

The full path goes into the file name box, so the dialog’s current folder does not matter. $ExportRoot is the Trade_Review folder, passed in from TRADE_REVIEW_PATH in the .env file that tradekit and the rest of my tooling already read, so the script never carries a path of its own. The routine runs this for each account, then once more for the “all accounts” view. That view is a combo box on the same window: CB_SELECTSTRING picks the entry, and posting WM_COMMAND with the CBN_SELCHANGE code to the parent makes the MFC handler refresh the grid before the export.

The Trade Report dialog

The orders, executions and tickets come from Trade → Reports, and there is no script command for it. The Trade menu is a skinned popup: Alt+T opens it, but arrow keys and letters do not select inside it, and the popup’s window class is invisible to UI Automation. I captured the open menu once with System.Drawing.Graphics.CopyFromScreen, read the item positions from the image, and now click the item at a fixed offset from the main window’s top-left corner:

$ws.AppActivate("Cobra Trading Powered by DASTrader.com") | Out-Null
$ws.SendKeys("%t"); Start-Sleep -Milliseconds 800
[W]::SetCursorPos($rect.L + 90, $rect.T + 296) | Out-Null
[W]::mouse_event(2, 0, 0, 0, [UIntPtr]::Zero)   # left button down
[W]::mouse_event(4, 0, 0, 0, [UIntPtr]::Zero)   # left button up

The dialog that opens is titled “Report” and is plain Win32: checkboxes for Orders, Executions, Tickets and the liquidity flags, a CSV radio button, a “Show Training orders/trades” box that must be on for the practice account to be included, a path edit, and Export and Quit buttons. BM_GETCHECK reads each box, BM_SETCHECK fixes any that are wrong, and WM_SETTEXT sets the path. The path edit reads back empty through GetWindowText even after it accepts the value, and the dialog remembers its last path between runs, so the script sets it every time instead of trusting it.

PostMessage, not SendMessage

The Export button pops a modal completion box that lists liquidity by route. A SendMessage click blocks until that box is dismissed, which is never, so the PowerShell process hung until its timeout on the first attempt. The click is posted instead. The script then polls for a new dialog owned by the DAS process, posts a click on its OK button, posts a click on Quit, and sends WM_CLOSE to the report windows it opened, so the desktop looks the way it did before.

Two PowerShell details worth writing down: Write-Output inside a function becomes part of the function’s return value, which turned my log lines into the export’s success flag until I switched to Write-Host; and a P/Invoke alias for SendMessageW needs an explicit EntryPoint, or the runtime looks for a symbol that does not exist.

Scheduling

The script takes a date and an export root, so it can be tested into a scratch folder without touching the real one. A bash wrapper on the WSL side reads the credentials from the local environment file, runs the script, verifies that the six files were refreshed, and hands the folder to the ingest. Task Scheduler runs the wrapper on weekdays after the close:

schtasks /Create /TN FalconDASExport /SC WEEKLY /D MON,TUE,WED,THU,FRI /ST 18:30 ^
  /TR "wsl.exe -d FedoraLinux-42 -u davdunc -- /home/davdunc/.claude/Tools/das-eod-export.sh"

The task is interactive-only because it drives a GUI. DAS has to be running and logged in, and I have to be logged in to Windows. When either is false it does not run, and the older API-capture job takes over.

The export is the only part I have handed to the scheduler. The step after it, the review that reads the ingested session and publishes a report card, still runs when I start it and with me watching. I am not ready to let that publish on its own: it writes to channels other people read, and a wrong grade or a leaked figure is not something a retry fixes. The export produces files on my own disk, which is a different risk, and that is where the automation stops for now.

What will break

The menu click is a pixel offset from the window corner. A DAS update that moves the Reports item, or a layout change that shifts the menu bar, breaks it, and the script reports that the Report dialog never opened rather than clicking blindly. The Save As keystrokes assume the dialog has focus, which the script checks with GetForegroundWindow before typing. Both are the kind of failure that is loud, which is the most I ask of GUI automation.

Windows references

The Microsoft documentation for everything the script touches, in the order it uses them:

Sources: Cobra Trading and its DAS Trader Pro platform page; the DAS Trader command API manual for LOGIN and SCRIPT (supplied with the API subscription, not published), the DAS Advanced Hotkey Guide for ShowAccount, and the Win32 message constants (BM_CLICK 0x00F5, BM_GETCHECK 0x00F0, BM_SETCHECK 0x00F1, WM_SETTEXT 0x000C, WM_COMMAND 0x0111, WM_CLOSE 0x0010, CB_SELECTSTRING 0x014D).