I dati sono racchiusi in una struttura DATI RCV , dove m_pReportV3 punta a un array di record RCV_REPORT_STRUCTExV3 . JSL può caricare la DLL e analizzare i dati binari usando Sbirciare , mentre Python può gestire il ciclo di messaggi di Windows tramite tipi di c .
Challenge
JSL da solo non può creare un ciclo di messaggi di Windows, quindi ho integrato Python per gestirlo. La DLL viene inizializzata con un handle di finestra e un ID messaggio, quindi invia dati a quella finestra. Devo analizzare questi dati in JSL e popolare una tabella JMP con i campi specificati.
My Proposed Solution
Di seguito è riportata la mia implementazione. Utilizza JSL per definire la tabella e analizzare i dati, e Python per gestire il message loop. Ho incluso commenti per spiegare ogni passaggio. Potresti rivederla e suggerire miglioramenti?
// StockReceiver.jsl
// Create a JMP data table with required columns
dt = New Table("Stock Data",
New Column("Market", Character, Width(4)), // Market identifier (e.g., 'HS')
New Column("Code", Character, Width(10)), // Stock code
New Column("Name", Character, Width(32)), // Stock name
New Column("Time", Numeric, Format("yyyy-mm-dd hh:mm:ss")), // Transaction time
New Column("LastClose", Numeric, Format("Fixed Dec", 12, 2)), // Previous close
New Column("Open", Numeric, Format("Fixed Dec", 12, 2)), // Opening price
New Column("High", Numeric, Format("Fixed Dec", 12, 2)), // Highest price
New Column("Low", Numeric, Format("Fixed Dec", 12, 2)), // Lowest price
New Column("NewPrice", Numeric, Format("Fixed Dec", 12, 2)), // Latest price
New Column("Volume", Numeric, Format("Fixed Dec", 12, 2)), // Volume
New Column("Amount", Numeric, Format("Fixed Dec", 12, 2)), // Amount
New Column("BuyPrice1", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 1
New Column("BuyVolume1", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 1
New Column("SellPrice1", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 1
New Column("SellVolume1", Numeric, Format("Fixed Dec", 12, 2)),// Ask volume 1
New Column("BuyPrice2", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 2
New Column("BuyVolume2", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 2
New Column("SellPrice2", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 2
New Column("SellVolume2", Numeric, Format("Fixed Dec", 12, 2)),// Ask volume 2
New Column("BuyPrice3", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 3
New Column("BuyVolume3", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 3
New Column("SellPrice3", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 3
New Column("SellVolume3", Numeric, Format("Fixed Dec", 12, 2)),// Ask volume 3
New Column("BuyPrice4", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 4
New Column("BuyVolume4", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 4
New Column("SellPrice4", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 4
New Column("SellVolume4", Numeric, Format("Fixed Dec", 12, 2)),// Ask volume 4
New Column("BuyPrice5", Numeric, Format("Fixed Dec", 12, 3)), // Bid price 5
New Column("BuyVolume5", Numeric, Format("Fixed Dec", 12, 2)),// Bid volume 5
New Column("SellPrice5", Numeric, Format("Fixed Dec", 12, 3)),// Ask price 5
New Column("SellVolume5", Numeric, Format("Fixed Dec", 12, 2)) // Ask volume 5
);
// Global variables for managing the receiver
global:hwnd = 0; // Window handle
global:dll = 0; // DLL object
global:running = 1; // Flag to control the message loop
// Function to parse RCV_DATA and extract RCV_REPORT_STRUCTExV3 fields
ParseStockData = Function({lParam},
// Check the data type (RCV_REPORT = 0x3f001234)
dataType = Peek(lParam, 0, "int");
If(dataType == 1061153620, // RCV_REPORT
// Get number of records and pointer to report data
packetNum = Peek(lParam, 4, "int"); // m_nPacketNum
reportPtr = Peek(lParam, 276, "int"); // m_pReportV3 offset
// Process each record
For(i = 0, i < packetNum, i++,
baseOffset = reportPtr + i * Peek(reportPtr, 0, "short"); // Offset based on m_cbSize
// Extract fields from RCV_REPORT_STRUCTExV3
time = Peek(baseOffset, 2, "int"); // m_time (time_t)
market = Peek(baseOffset, 6, "short"); // m_wMarket
code = Peek(baseOffset, 8, "char[10]"); // m_szLabel
name = Peek(baseOffset, 18, "char[32]"); // m_szName
lastClose = Peek(baseOffset, 50, "float"); // m_fLastClose
open = Peek(baseOffset, 54, "float"); // m_fOpen
high = Peek(baseOffset, 58, "float"); // m_fHigh
low = Peek(baseOffset, 62, "float"); // m_fLow
newPrice = Peek(baseOffset, 66, "float"); // m_fNewPrice
volume = Peek(baseOffset, 70, "float"); // m_fVolume
amount = Peek(baseOffset, 74, "float"); // m_fAmount
buyPrice1 = Peek(baseOffset, 78, "float"); // m_fBuyPrice[0]
buyPrice2 = Peek(baseOffset, 82, "float"); // m_fBuyPrice[1]
buyPrice3 = Peek(baseOffset, 86, "float"); // m_fBuyPrice[2]
buyPrice4 = Peek(baseOffset, 126, "float"); // m_fBuyPrice4
buyPrice5 = Peek(baseOffset, 134, "float"); // m_fBuyPrice5
buyVolume1 = Peek(baseOffset, 90, "float"); // m_fBuyVolume[0]
buyVolume2 = Peek(baseOffset, 94, "float"); // m_fBuyVolume[1]
buyVolume3 = Peek(baseOffset, 98, "float"); // m_fBuyVolume[2]
buyVolume4 = Peek(baseOffset, 130, "float"); // m_fBuyVolume4
buyVolume5 = Peek(baseOffset, 138, "float"); // m_fBuyVolume5
sellPrice1 = Peek(baseOffset, 102, "float"); // m_fSellPrice[0]
sellPrice2 = Peek(baseOffset, 106, "float"); // m_fSellPrice[1]
sellPrice3 = Peek(baseOffset, 110, "float"); // m_fSellPrice[2]
sellPrice4 = Peek(baseOffset, 142, "float"); // m_fSellPrice4
sellPrice5 = Peek(baseOffset, 150, "float"); // m_fSellPrice5
sellVolume1 = Peek(baseOffset, 114, "float"); // m_fSellVolume[0]
sellVolume2 = Peek(baseOffset, 118, "float"); // m_fSellVolume[1]
sellVolume3 = Peek(baseOffset, 122, "float"); // m_fSellVolume[2]
sellVolume4 = Peek(baseOffset, 146, "float"); // m_fSellVolume4
sellVolume5 = Peek(baseOffset, 154, "float"); // m_fSellVolume5
// Convert time_t to readable format (milliseconds for JMP)
timeStr = Format(DateTime(time * 1000), "yyyy-mm-dd hh:mm:ss");
// Add row to table
dt << Add Rows(1);
row = N Rows(dt);
:Market[row] = Char(market); // Convert market code to string
:Code[row] = code;
:Name[row] = name;
:Time[row] = timeStr;
:LastClose[row] = lastClose;
:Open[row] = open;
:High[row] = high;
:Low[row] = low;
:NewPrice[row] = newPrice;
:Volume[row] = volume;
:Amount[row] = amount;
:BuyPrice1[row] = buyPrice1;
:BuyPrice2[row] = buyPrice2;
:BuyPrice3[row] = buyPrice3;
:BuyPrice4[row] = buyPrice4;
:BuyPrice5[row] = buyPrice5;
:BuyVolume1[row] = buyVolume1;
:BuyVolume2[row] = buyVolume2;
:BuyVolume3[row] = buyVolume3;
:BuyVolume4[row] = buyVolume4;
:BuyVolume5[row] = buyVolume5;
:SellPrice1[row] = sellPrice1;
:SellPrice2[row] = sellPrice2;
:SellPrice3[row] = sellPrice3;
:SellPrice4[row] = sellPrice4;
:SellPrice5[row] = sellPrice5;
:SellVolume1[row] = sellVolume1;
:SellVolume2[row] = sellVolume2;
:SellVolume3[row] = sellVolume3;
:SellVolume4[row] = sellVolume4;
:SellVolume5[row] = sellVolume5;
);
Return(1); // Message processed
,
Return(0); // Unknown data type, not processed
);
);
// Load the DLL
dll = Load DLL("I:\DZH\HailiLive\StockDrv.dll");
If(Is Empty(dll),
Throw("Failed to load StockDrv.dll");
);
global:dll = dll;
// Run Python code to handle Windows message loop
Run Python(
"
import ctypes
from ctypes import wintypes
import jmp
# Constants for message handling
WM_APP = 0x8000
My_Msg_StkData = WM_APP + 1
RCV_REPORT = 0x3f001234
# Windows API type definitions
HWND = wintypes.HWND
UINT = wintypes.UINT
WPARAM = wintypes.WPARAM
LPARAM = wintypes.LPARAM
WNDPROC = ctypes.WINFUNCTYPE(ctypes.c_long, HWND, UINT, WPARAM, LPARAM)
# Load user32.dll for Windows API calls
user32 = ctypes.WinDLL('user32')
# Define WNDCLASSEX structure for window registration
class WNDCLASSEX(ctypes.Structure):
_fields_ = [
('cbSize', UINT),
('style', UINT),
('lpfnWndProc', WNDPROC),
('cbClsExtra', ctypes.c_int),
('cbWndExtra', ctypes.c_int),
('hInstance', wintypes.HINSTANCE),
('hIcon', wintypes.HICON),
('hCursor', wintypes.HCURSOR),
('hbrBackground', wintypes.HBRUSH),
('lpszMenuName', wintypes.LPCWSTR),
('lpszClassName', wintypes.LPCWSTR),
('hIconSm', wintypes.HICON)
]
# Window procedure to handle messages
@WNDPROC
def WndProc(hWnd, msg, wParam, lParam):
if msg == My_Msg_StkData and wParam == RCV_REPORT:
# Call JSL to process the data
result = jmp.run_jsl('ParseStockData(' + str(lParam) + ');')
return result
return user32.DefWindowProcW(hWnd, msg, wParam, lParam)
# Get global variables from JSL
hInstance = ctypes.windll.kernel32.GetModuleHandleW(None) # Current module handle
dll = jmp.get_global('dll') # DLL object
running = jmp.get_global('running') # Loop control flag
# Register window class
wcex = WNDCLASSEX()
wcex.cbSize = ctypes.sizeof(WNDCLASSEX)
wcex.style = 0x3 # CS_HREDRAW | CS_VREDRAW
wcex.lpfnWndProc = WNDPROC(WndProc)
wcex.cbClsExtra = 0
wcex.cbWndExtra = 0
wcex.hInstance = hInstance
wcex.hIcon = 0
wcex.hCursor = 0
wcex.hbrBackground = 0
wcex.lpszMenuName = None
wcex.lpszClassName = 'StockDataWindow'
wcex.hIconSm = 0
if not user32.RegisterClassExW(ctypes.byref(wcex)):
raise Exception('Failed to register window class')
# Create window
hWnd = user32.CreateWindowExW(
0, 'StockDataWindow', 'Stock Receiver', 0,
0, 0, 100, 100, 0, 0, hInstance, None
)
if not hWnd:
raise Exception('Failed to create window')
jmp.set_global('hwnd', hWnd) # Store window handle in JSL
# Initialize the DLL
stock_init = dll.Stock_Init
stock_init.argtypes = [HWND, UINT, ctypes.c_int]
stock_init.restype = ctypes.c_int
result = stock_init(hWnd, My_Msg_StkData, 4) # 4 = RCV_WORK_SENDMSG
if result != 1:
raise Exception('Stock_Init failed: ' + str(result))
# Message loop
msg = ctypes.wintypes.MSG()
while running[0]:
if user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) > 0:
user32.TranslateMessage(ctypes.byref(msg))
user32.DispatchMessageW(ctypes.byref(msg))
else:
break
# Cleanup
stock_quit = dll.Stock_Quit
stock_quit.argtypes = [HWND]
stock_quit.restype = ctypes.c_int
stock_quit(hWnd)
user32.DestroyWindow(hWnd)
user32.UnregisterClassW('StockDataWindow', hInstance)
print('Stock data receiver stopped')
",
"Stock data receiver started" // Output when Python starts
);
// Function to stop the receiver
StopReceiver = Function({},
global:running = 0; // Signal Python to exit loop
If(global:hwnd != 0,
Call DLL("Stock_Quit", dll, global:hwnd); // Cleanup DLL
global:hwnd = 0;
);
Write("Stock data receiver stopped.\!n");
);
// Display startup message
Write("Stock data receiver initialized. Run StopReceiver() to stop.\!n");Dettagli chiave
- Funzioni DLL :
- Stock_Init(HWND hWnd, UINT Messaggio, int nWorkMode) : Inizializza la DLL, restituisce 1 in caso di successo.
- Stock_Quit(HWND hWnd) : Interrompe l'invio del messaggio, restituisce 1 in caso di successo.
- Offset della struttura dati :
- DATI RCV : m_pReportV3 al byte 276 (dopo m_wTipo di dati , m_nNumeroPacchetto , m_File , m_bDISCO ).
- RCV_REPORT_STRUCTExV3 : Campi allineati con #pacchettopragma(1) , ad esempio, m_tempo a 2, m_fPrezzoAcquista5 a 134, m_fVendiVolume5 a 154.
- Esecuzione :
- JSL carica la DLL e definisce la tabella.
- Python crea una finestra, inizializza la DLL ed esegue il ciclo di messaggi.
- I messaggi attivano Analizza i dati di magazzino per popolare la tabella.
Domande per te
- L'analisi binaria tramite Sbirciare è corretta per gli offset? Ho dato per scontato che non ci fosse alcun padding a causa di #pacchettopragma(1) .
- Il ciclo dei messaggi potrebbe essere ottimizzato per dati ad alta frequenza?
- Dovrei aggiungere il buffering per ridurre gli aggiornamenti della tabella (ad esempio, ogni 100 righe)?
Vi sarei grato per il vostro feedback su potenziali problemi, miglioramenti delle prestazioni o approcci alternativi. Grazie in anticipo per la vostra competenza!
Cordiali saluti, Grazie Esperti!