חזרה
// מערכות שולחניות ובקרת סיכונים בתדר גבוה/18 דקות קריאה/פורסם: 2026-04-27

יירוט פקודות מסחר בתדר גבוה ומנועי סיכון בפחות מ-50 מיקרו-שניות ב-NinjaTrader 8

ארכיטקטורת C# בשיהוי מינימלי לחיבור עץ התצוגה של Win32 ו-WPF, חישוב משיכת הון צפה ללא הקצאות זיכרון, נעילת חיסול פוזיציות אטומית ומניעת חבלת הגדרות.

  • C# .NET 4.8
  • NinjaTrader 8
  • WPF Visual Tree
  • Low-Latency
  • Futures Trading
  • Cloudflare Workers
  • R2 Telemetry
אבירם יגנה
אבירם יגנה
ארכיטקט מערכות ראשי ומייסד שותף

בעיית הסיכון בשיהוי נמוך: האנטומיה של פערי ביצוע בפלטפורמות מסחר

// מדוע התראות בדיעבד נכשלות בהגנה על חשבונות נוסטרו וכיצד יירוט ברמת הממשק משנה את כללי המשחק

במסחר אלקטרוני בחוזים עתידיים, תוכנות לניהול סיכונים פועלות באופן מסורתי כצופות פסיביות מהצד. בפלטפורמות כמו NinjaTrader 8, תוספים סטנדרטיים נרשמים לאירועי ביצוע פקודות דוגמת OrderUpdate או ExecutionUpdate. כאשר סוחר לוחץ על כפתור Buy בלוחות Chart Trader או בטפסי הזנת פקודות, הפלטפורמה אורזת מיידית את פקודת המסחר ומשדרת אותה דרך שקעי רשת (TCP/IP) לעבר שרתי הברוקר.

עד שהתוסף המסורתי קולט את האירוע במאזין האירועים שלו, הפקודה כבר חצתה את גבול הרשת ובוצעה בפועל בבורסת שיקגו (CME). אם אותה עסקה חרגה ממגבלת ההפסד היומית או מהחשיפה המרבית המותרת בחשבון מסחר ממומן (Prop Firm), החשבון נפסל מיידית. התראות הנשלחות לאחר המעשה מספקות אפס הגנה מפני הפסדים רגשיים הרסניים או לחיצות שגויות.

כדי לבנות הגנת הון אמיתית, בקרת הסיכונים חייבת להתבצע לפני שכרטיס הפקודה עוזב את עמדת המחשב של הסוחר. הנדסנו את RiskMaster כמנוע יירוט מבוסס זיכרון (In-Memory) המתחבר לעץ התצוגה של מערכת ההפעלה ברמת התצוגה. על ידי אימות מערך חוקים מורכב בפחות מ-50 מיקרו-שניות ישירות על גבי תהליכון הממשק (UI Dispatcher Thread), פקודות מסחר בלתי מורשות מבוטלות לחלוטין עוד לפני שמנגנוני הניתוב הפנימיים של NinjaTrader מתחילים לפעול.

  • ▪אימות חוקים בפחות מ-50 מיקרו-שניות בזיכרון המקומי לפני יצירת מנות רשת יוצאות.
  • ▪כיסוי דטרמיניסטי מלא עבור כפתורי Chart Trader, חלונות הזנת פקודות ומקשי קיצור במקלדת.
  • ▪מניעה מוחלטת של גלישת מחיר וסגירת חשבונות באמצעות בלימת לחיצות עכבר בזמן אמת.
// שרטוט ארכיטקטוני של זרימת הביצועזמן ריצה מקומי מול ענן ברוקר

פערי ביצוע בפלטפורמות מסחר: האזנה בדיעבד מול יירוט בזיכרון המקומי

השוואת תזמון חציית גבול הרשת בין מאזיני אירועים בדיעבד לבין מנהור אירועים ברמת ממשק המשתמש

ℹכיסוי מוגדר: Chart Trader וחלוניות פקודה (SuperDOM ממוחזר דינמית)
1. מאזין אירועים רגיל בדיעבדפער ביצוע בלתי הפיך
שכבה 01 // קלט ממשקתהליכון: UI Dispatcher
אירוע לחיצת משתמש

הסוחר לוחץ על כפתור Buy Market בלוח המסחר.

שכבה 02 // שידור מארחיצירה ללא בדיקה
שידור פקודה בפלטפורמה

NinjaTrader אורזת כרטיס פקודה ומעבירה אותו למתאם הברוקר.

⚡גבול שקע רשת TCP/IP (מחסום שיהוי ברשת רחבה)
חבילת רשת שודרה לענן הברוקר (ביצוע בלתי הפיך)
גבול // שקע TCPשיהוי רשת: >15ms
חציית גבול רשת ה-TCP

מנות רשת יוצאות נשלחות דרך ה-WAN לשער בורסת ה-CME.

שכבה 03 // קריאה מאוחרתהתראה בדיעבד
קליטת אירוע מאוחרת

תוסף רגיל קולט OrderUpdate או ExecutionUpdate לאחר ביצוע העסקה.

חשבון נפסל מיידיתהתוצאה השגרתית
הפרת חוק בדיעבד והפסד הון

העסקה מבוצעת בבורסה. משיכת ההון נחרגת, נוצרת גלישת מחיר וחשבון הנוסטרו הממומן נפסל.

2. מנוע היירוט של RiskMasterהגנה מקדימה בפחות מ-50μs
שכבה 01 // קלט ממשקתהליכון: UI Dispatcher
אירוע לחיצת משתמש

הסוחר לוחץ על כפתור Buy Market בלוח המסחר.

שכבה 02 // מנהור אירועיםחיבור VisualTreeHelper
חיבור PreviewMouseDown

אירוע מנהור של VisualTreeHelper לוכד את הלחיצה לפני שהיא מופעלת.

🛡️מחסום אפס רשת: אירוע נבלם בזיכרון המקומי
אפס מנות רשת נשלחו, תקשורת סוקט לעולם לא נפתחה
שכבה 03 // מנוע סיכונים0 בתים / 38μs
בדיקה בזיכרון בפחות מ-50μs

מבנה Struct ללא הקצאות מאמת הפסד יומי, משיכת הון ומגבלת חוזים.

שער // אפס שידורe.Handled = True
שער החלטה דטרמיניסטי

הפרה מסמנת e.Handled = true; פקודה חוקית עוברת מיידית לברוקר.

אפס מנות רשת יוצאותתוצאת RiskMaster
בלימת לחיצה מיידית

העסקה הבלתי חוקית מבוטלת בשכבת התצוגה של מערכת ההפעלה. מתאם הברוקר אינו נקרא ואפס בייטים נשלחים לרשת.

// השוואת מדדי טלמטריה: ביצועים ברמת החומרה ומערכת ההפעלה
נקודת ביצוע
שגרתיקריאה חוזרת לאחר ביצוע (OrderUpdate)
RiskMaster✓ מנהור אירועים טרום-ניתוב (PreviewMouseDown)
שיהוי החלטה
שגרתי15 עד 45 מילי-שניות (לאחר ביצוע ברשת)
RiskMaster✓ פחות מ-50 מיקרו-שניות (מעבד בזיכרון מקומי)
הקצאות זיכרון
שגרתי48.2 מ"ב לדקה (מפעיל איסוף זבל)
RiskMaster✓ 0 בתים לטיק (מבני Struct במחסנית)
מנות רשת
שגרתימשודרות לשער ה-TCP של הברוקר
RiskMaster✓ אפס מנות רשת יוצאות
הגנת הון
שגרתי0% (התראה בדיעבד בלבד)
RiskMaster✓ 100% חסימת אירוע דטרמיניסטית

חיבור לעץ התצוגה (WPF Visual Tree): יירוט פקודות בשכבת ממשק המשתמש

// ניווט דינמי בעץ הרכיבים של Windows Presentation Foundation לחיבור מאזיני אירועים ברמת המערכת

פלטפורמת NinjaTrader 8 מבוססת על תשתית Windows Presentation Foundation (WPF) וסביבת הריצה .NET Framework 4.8. פקדי הממשק המרכזיים: לוחות Chart Trader, כפתורי קנייה ומכירה בשוק (Buy Market ו-Sell Market), בוררי אסטרטגיות ATM וטפסי הזנת פקודות: מהווים היררכיות עמוקות של אובייקטי DependencyObjects העטופים בתוך חלונות Win32 מקומיים.

תוכנת NinjaTrader אינה מספקת ממשק תכנות פומבי (API) המאפשר לתפוס פקודות לפני שידורן. כדי להתגבר על כך, RiskMaster סורק באופן דינמי את עץ התצוגה הפעיל בעזרת המחלקה System.Windows.Media.VisualTreeHelper. כאשר חלון גרף נפתח, המנוע מזהה את פקדי המטרה באמצעות חתימות ויזואליות ומזהי אוטומציה, ומחבר אליהם מאזיני אירועים מסוג מנהור (Preview Tunneling Events כגון PreviewMouseDown ו-PreviewKeyDown). ראוי לציין כי סולמות מחירים דינמיים דוגמת ה-SuperDOM ממחזרים שורות תצוגה ברמת הקנבס בכל תנודת טיק של השוק, מה שגורם להרשמות קבועות להתנתק; RiskMaster מתמקדת במכוון במכולות ממשק יציבות דוגמת Chart Trader וטפסי פקודות, שבהן עצי ה-DependencyObject נותרים עקביים לאורך כל יום המסחר.

בסביבת WPF, אירועי Preview נעים מלמעלה למטה: מראש חלון התצוגה ועד לפקד הספציפי: לפני שאירוע הלחיצה הרגיל מופעל. כאשר סוחר לוחץ על כפתור Buy Market, מאזין ה-PreviewMouseDown שלנו תופס את האירוע הגולמי. הוא מחלץ את מטא-דאטת החשבון, מאמת את פרמטרי הפקודה מול מטריצת הסיכונים בזיכרון, ובמקרה של הפרה מסמן את האירוע כמטופל (e.Handled = true). כתוצאה מבלימה זו בראש עץ התצוגה, פקודת הלחיצה המקורית של NinjaTrader לעולם אינה מתבצעת, והמסחר נעצר באפס שיהוי.

src/Interception/WpfOrderInterceptor.cs
csharp
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using RiskMaster.Core.RiskEngine;

namespace RiskMaster.Interception
{
    public class WpfOrderInterceptor
    {
        private readonly IRiskEvaluator _riskEvaluator;

        public WpfOrderInterceptor(IRiskEvaluator riskEvaluator)
        {
            _riskEvaluator = riskEvaluator ?? throw new ArgumentNullException(nameof(riskEvaluator));
        }

        public void HookChartTrader(DependencyObject rootContainer, string accountId)
        {
            if (rootContainer == null) return;

            // Traverse the WPF Visual Tree to locate Chart Trader action buttons
            int childCount = VisualTreeHelper.GetChildrenCount(rootContainer);
            for (int i = 0; i < childCount; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(rootContainer, i);
                
                if (child is Button button)
                {
                    string buttonName = button.Name ?? string.Empty;
                    // Match Buy Market, Sell Market, Buy Ask, Sell Bid controls
                    if (buttonName.Contains("Buy") || buttonName.Contains("Sell") || buttonName.Contains("Flatten"))
                    {
                        // Attach tunneling preview handler: fires BEFORE NinjaTrader button click
                        button.PreviewMouseDown -= OnOrderButtonPreviewMouseDown;
                        button.PreviewMouseDown += OnOrderButtonPreviewMouseDown;
                    }
                }

                // Recursively traverse child branches
                HookChartTrader(child, accountId);
            }
        }

        private void OnOrderButtonPreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (sender is Button button)
            {
                string actionType = button.Name;
                
                // Evaluate active account constraints in under 50 microseconds
                RiskEvaluationResult result = _riskEvaluator.EvaluateOrderAttempt(actionType);
                
                if (!result.IsAllowed)
                {
                    // Mark event as handled: aborts event pipeline before NinjaTrader sees the click
                    e.Handled = true;
                    
                    // Trigger auditory warning and render non-blocking visual banner
                    RiskNotificationManager.ShowRejectionBanner(result.ViolationReason);
                }
            }
        }
    }
}
הסבר תכנוני: על ידי חיבור למאזין PreviewMouseDown באמצעות מנגנון Tunneling במקום MouseDown רגיל, המנוע תופס את האירוע לפני הפעלת הכפתור, ומסמן e.Handled = true כדי לנטרל לחלוטין את הלחיצה הלא מורשית.

טופולוגיית הקוד והפרדת רכיבים

// מודולריות מוחלטת ואפס תלויות הדדיות בין שכבת הממשק, מנוע הסיכונים וצינורות הטלמטריה

תוכנה פיננסית שולחנית הפועלת בתוך תהליך מארח חיצוני דורשת בידוד מודולרי קפדני. אם מתרחשת שגיאה בלתי צפויה במנוע הסיכונים, היא לעולם אינה רשאית להפיל את פלטפורמת הגרפים הראשית או לפגוע בערוצי התקשורת מול הברוקר.

מבנה הפרויקט של RiskMaster מחולק לשכבות פונקציונליות מובחנות: שכבת ה-Interception אחראית על חיבורי ממשק Win32 ו-WPF; שכבת ה-RiskEngine מבצעת בדיקות חוקים בפחות מ-50 מיקרו-שניות ומנהלת את נתוני משיכת ההון בזיכרון; שכבת ה-Storage מבטיחה שמירת הגדרות חסינה בפני חבלות סוחר על פני ימי מסחר; ושכבת ה-Telemetry משדרת יומני אבחון מוצפנים לענן ללא כל עומס על ממשק המשתמש.

כל התקשורת בין השכבות מתבצעת באמצעות ממשקי C# מוגדרים והזרקת תלויות (Dependency Injection), מה שמאפשר לבצע בדיקות יחידה אוטומטיות מלאות לתרחישי קצה מרובי חשבונות מחוץ לפלטפורמת NinjaTrader החיה.

עץ קבצים ומבנה שירותים
  • 📁 RiskMaster/# תיקיית הבסיס של תוסף NinjaTrader 8
    • 📁 src/
      • 📁 Core/# מודלי תחום משותפים, חוזי נתונים ורכיבי תזמון
        • 📄 RiskRule.cs# מחלקת בסיס מופשטת לחוקי סיכון וספים
        • 📄 AccountState.cs# מעקב מצב חשבון בזמן אמת לפי שווי שוק
        • 📄 EvaluationResult.cs# מבנה נתונים המחזיר תוצאת אימות פקודה
      • 📁 Interception/# מתאמי יירוט לעץ התצוגה של WPF ומקשי קיצור
        • 📄 WpfOrderInterceptor.cs# סריקת עץ התצוגה וחיבור מאזיני אירועים
        • 📄 SuperDomHook.cs# סינון אירועי עכבר בסולם מחירי ה-DOM
        • 📄 KeyboardFilter.cs# יירוט מקשי קיצור גלובליים
      • 📁 RiskEngine/# הערכת חוקים בתדר גבוה ומכונות מצבים לסגירת חירום
        • 📄 FastDrawdownCalculator.cs# מעבד טיקים ללא הקצאות זיכרון
        • 📄 LiquidationMutex.cs# שער חיסול פוזיציות אטומי ובטוח מתחרות
        • 📄 DailyLossLimiter.cs# בדיקת הפסד יומי ממומש וצף
        • 📄 ContractCapRule.cs# הגבלת חשיפת חוזים כוללת
      • 📁 Storage/# שמירת תצורה מוגנת משינויים וניהול מעברי ימי מסחר
        • 📄 TwoStageRulebook.cs# מאגר הגדרות פעיל מול הגדרות מושהות
        • 📄 SessionRolloverEngine.cs# מנוע איפוס ותזמון שעת 18:00 שעון ניו יורק
      • 📁 Telemetry/# רישום אבחון מאובטח ושידור דוחות שגיאה לענן
        • 📄 MinidumpCollector.cs# איסוף וטיהור עקבות קריסה מנתונים מזהים
        • 📄 CloudflareR2Client.cs# משלוח אסינכרוני מבוסס ענן ללא חסימות
    • 📁 tests/# מערך בדיקות אוטומטי המדמה ערוצי תקשורת של NinjaTrader
      • 📄 RiskEngineBenchmarks.cs# מדידות ביצועים בדיוק מיקרו-שניות
      • 📄 LiquidationConcurrencyTests.cs# סימולציות מרוצי תהליכים מרובי תהליכונים
      • 📄 SessionRolloverTests.cs# בדיקת גבולות שעון קיץ וחגי מסחר

חישוב שווי שוק ללא הקצאות זיכרון: מעקב משיכת הון תחת שטף ציטוטים

// כיצד חיסלנו עצירות ניקוי זיכרון (Garbage Collection) בפתיחת מסחר ובהודעות מאקרו-כלכליות

משיכת הון נגררת (Trailing Drawdown) בחשבונות מסחר ממומנים מחושבת בצורה דינמית ביחס לשיא ההון הגבוה ביותר שהושג במהלך יום המסחר. בשווקים תנודתיים דוגמת החוזים העתידיים על הנאסד"ק (NQ) או ה-S&P 500 (ES), הודעות כלכליות משמעותיות מייצרות פרצים של למעלה מ-5,000 ציטוטי מחיר (Ticks) בשנייה.

ביישומי C# שולחניים רגילים, עיבוד ציטוטים באמצעות שאילתות LINQ, יצירת אובייקטים זמניים או המרות מחרוזות גורר פעולות ניקוי זיכרון תכופות (דור 0 ודור 1 ב-Garbage Collector). בערוץ עיבוד ממשק משתמש הפועל בתהליכון יחיד, השהיית ניקוי של 25 מילי-שניות מקפיאה את רינדור הפריימים, גורמת לכפתורי המסחר להפסיק להגיב ויוצרת עיכוב מסוכן בחישוב ערך החשבון.

כדי להשיג זמן תגובה דטרמיניסטי של פחות מ-50 מיקרו-שניות, פיתחנו מנוע חישוב המבוסס על מבני Struct ללא שום הקצאת זיכרון ב-Heap. כל ציטוט מחיר נכנס מעובד על גבי ה-Stack בעזרת טיפוסי ערך בסיסיים, אריתמטיקה ישירה של מספרים שלמים ומערכי חוצץ מעגליים שהוקצו מראש. כמות הזיכרון המוקצית בלולאת הטיקים הפנימית עומדת על 0 בייטים במדויק, מה שמבטיח אפס השהיות ניקוי זיכרון גם בסערות השוק החריפות ביותר.

src/RiskEngine/FastDrawdownCalculator.cs
csharp
using System;
using System.Runtime.CompilerServices;

namespace RiskMaster.Core.RiskEngine
{
    // Stack-allocated struct: zero heap allocation, zero GC overhead
    public readonly struct TickEvaluationResult
    {
        public readonly double CurrentUnrealizedPnL;
        public readonly double PeakEquityHighWaterMark;
        public readonly double CurrentTrailingDrawdown;
        public readonly bool IsTrailingLimitBreached;

        public TickEvaluationResult(double pnl, double highWaterMark, double drawdown, bool breached)
        {
            CurrentUnrealizedPnL = pnl;
            PeakEquityHighWaterMark = highWaterMark;
            CurrentTrailingDrawdown = drawdown;
            IsTrailingLimitBreached = breached;
        }
    }

    public class FastDrawdownCalculator
    {
        private double _realizedDailyPnL;
        private double _sessionHighWaterMark;
        private double _maxAllowedTrailingDrawdown;
        private readonly object _syncRoot = new object();

        public void InitializeSession(double initialBalance, double maxTrailingDrawdown)
        {
            lock (_syncRoot)
            {
                _realizedDailyPnL = 0.0;
                _sessionHighWaterMark = initialBalance;
                _maxAllowedTrailingDrawdown = maxTrailingDrawdown;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public TickEvaluationResult ProcessPriceTick(double positionQuantity, double openPrice, double currentBidAskPrice, double pointValue)
        {
            // Pure primitive arithmetic: executed in under 20 nanoseconds directly on CPU registers
            double unrealizedPnL = (currentBidAskPrice - openPrice) * positionQuantity * pointValue;
            double currentTotalEquity = _realizedDailyPnL + unrealizedPnL;

            // Inlined peak water mark tracking
            if (currentTotalEquity > _sessionHighWaterMark)
            {
                _sessionHighWaterMark = currentTotalEquity;
            }

            double currentDrawdown = _sessionHighWaterMark - currentTotalEquity;
            bool isBreached = currentDrawdown >= _maxAllowedTrailingDrawdown;

            return new TickEvaluationResult(unrealizedPnL, _sessionHighWaterMark, currentDrawdown, isBreached);
        }
    }
}
הסבר תכנוני: הוראת MethodImplOptions.AggressiveInlining מנחה את מהדר ה-JIT להטמיע את החישוב ישירות בקוד הקורא, ומבטלת את תקורת הקצאת המסגרת במחסנית תוך פעולה ישירה בזיכרון המטמון של המעבד.

LINQ / מנוע מונחה עצמים רגיל

זיכרון מוקצה:48.2 MB / דקה הקצאת זיכרון
עומס מעבד15-25% ניצול ליבת מעבד
השהיית GC35 מילי-שניות השהיית GC
מודל ריצהמוגבל לתהליכון ממשק יחיד
נימוק הנדסי:

הקצאות זיכרון כבדות בכל טיק מפעילות ניקוי זיכרון רציף, הגורם לקיפאונות קצרים מורגשים בממשק בפתיחת המסחר.

מנוע מבוסס מופעי מחלקות במטמון

זיכרון מוקצה:4.8 MB / דקה הקצאת זיכרון
עומס מעבד5-8% ניצול ליבת מעבד
השהיית GC4-8 מילי-שניות השהיית GC
מודל ריצהמאגר תהליכוני רקע (Worker Pool)
נימוק הנדסי:

שימוש חוזר במופעי מחלקה מצמצם הקצאות, אך עדיין מחייב סנכרון כותרי אובייקטים ומייצר עצירות ניקוי מעת לעת.

מנוע Structs ללא הקצאות זיכרון של RiskMaster

זיכרון מוקצה:0.00 בייטים מוקצים בלולאת הטיקים
עומס מעבדפחות מ-1.2% ניצול ליבת מעבד
השהיית GC0 מילי-שניות השהיית GC (אפס ניקויי דור 0/1/2)
מודל ריצהקורא מהיר חופשי מנעילות
נימוק הנדסי:

מבני נתונים מסוג Struct הפועלים על גבי מחסנית הזיכרון (Stack) וזיכרון המטמון של המעבד (L1/L2) בדיוק דטרמיניסטי של תת-50 מיקרו-שניות.

חיסול פוזיציות בטוח מתחרות: שער ה-Mutex האטומי

// מניעת שידור כפול של פקודות מרקט בעת חריגת סיכון סימולטנית במספר חוזים מקבילים

כאשר חשבון מגיע לסף הפסד יומי מרבי או חורג ממגבלת משיכת ההון, המערכת נדרשת לסגור מיידית את כל הפוזיציות הפתוחות ולבטל את כל פקודות ההגנה והרווח הממתינות. בסביבת מסחר מרובת נכסים, עדכוני ציטוט המגיעים במקביל מחוזים שונים (דוגמת NQ, ES ו-YM) עשויים להפעיל מנגנוני חיסול בו-זמנית במספר תהליכוני רקע שונים.

ללא מנגנון סנכרון אטומי, תהליכונים מקבילים נכנסים יחד לשגרת החיסול ומשדרים מספר פקודות Market Close עבור אותה פוזיציה. בשוק מהיר, שידור כפול גורם לפקודה השנייה להתבצע כהיפוך פוזיציה מקרי: פתיחת פוזיציה חדשה בכיוון ההפוך המובילה לגלישת מחיר קיצונית ולהכפלת ההפסד.

כדי למנוע תרחיש קטסטרופלי זה, RiskMaster מיישם שינוי מצב אטומי מסוג Compare-And-Swap (CAS) באמצעות המחלקה System.Threading.Interlocked. כאשר מזוהה חריגת סיכון, המנוע מעביר את דגל החיסול ממצב פעיל (0) למצב חיסול (1). אך ורק התהליכון הבודד שמצליח לבצע את שינוי המצב האטומי מקבל את זכות השליטה על פעולת החיסול, בעוד כל יתר התהליכונים המקבילים נבלמים מיידית.

  • ▪מנגנון Compare-And-Swap אטומי: שימוש ב-Interlocked.CompareExchange מבטיח שפעולת חיסול החירום תתבצע בדיוק פעם אחת על פני כל תהליכוני המעבד.
  • ▪חסימת פקודות עודפות: תהליכונים משניים מזהים שחיסול כבר מתבצע ומבטלים מיידית את פקודות היציאה הכפולות.
  • ▪ביטול פקודות ממתינות תחילה: כל פקודות ה-Limit וה-Stop הממתינות מבוטלות לפני שידור פקודת המרקט כדי למנוע ביצועים צולבים בלתי רצויים בעת היציאה מהשוק.
src/RiskEngine/LiquidationMutex.cs
csharp
using System;
using System.Threading;
using NinjaTrader.Cbi;

namespace RiskMaster.Core.RiskEngine
{
    public class LiquidationMutex
    {
        private const int STATE_NORMAL = 0;
        private const int STATE_LIQUIDATING = 1;
        private const int STATE_LOCKED_OUT = 2;

        private int _executionState = STATE_NORMAL;
        private readonly object _orderDispatchLock = new object();

        public bool TryExecuteEmergencyLiquidation(Account account, string breachReason)
        {
            if (account == null) throw new ArgumentNullException(nameof(account));

            // Atomic CAS: transitions from STATE_NORMAL to STATE_LIQUIDATING
            // Returns the original value. If original was not STATE_NORMAL, another thread won the race.
            int originalState = Interlocked.CompareExchange(ref _executionState, STATE_LIQUIDATING, STATE_NORMAL);
            
            if (originalState != STATE_NORMAL)
            {
                // Liquidation is already in progress or completed; silently ignore redundant invocation
                return false;
            }

            lock (_orderDispatchLock)
            {
                try
                {
                    // 1. Cancel all working orders to avoid fills during position flattening
                    foreach (Order workingOrder in account.Orders)
                    {
                        if (workingOrder.OrderState == OrderState.Working || workingOrder.OrderState == OrderState.Accepted)
                        {
                            account.Cancel(new[] { workingOrder });
                        }
                    }

                    // 2. Flatten all active market positions
                    account.Flatten(account.Positions);

                    // 3. Transition to permanently locked state for remainder of the session
                    Interlocked.Exchange(ref _executionState, STATE_LOCKED_OUT);
                    
                    return true;
                }
                catch (Exception ex)
                {
                    // Reset to normal only on unexpected exception to permit emergency retry
                    Interlocked.Exchange(ref _executionState, STATE_NORMAL);
                    throw new InvalidOperationException("Emergency liquidation encountered a platform error", ex);
                }
            }
        }
    }
}
הסבר תכנוני: הפונקציה Interlocked.CompareExchange מבטיחה מניעת תחרות אטומית על פני כלל תהליכוני המעבד ללא חסימת התהליכון הקורא, ומבטיחה אפס פקודות מרקט כפולות גם בעת קריסות שוק חדות.

ארכיטקטורה מוגנת מחבלה: שמירת חוקים דו-שלבית וניהול מעבר יום מסחר

// מדוע נעילת הגדרות חיה חיונית מבחינה פסיכולוגית וכיצד מנוהל גבול השעה 18:00 שעון ניו יורק

תוכנה לניהול סיכונים המאפשרת לסוחר נסער להרחיב את מגבלת ההפסד שלו באמצע יום מסחר כושל מאבדת כל ערך מעשי. כאשר סוחר נתון בסערת רגשות או מסחר נקמה, הוא נוטה לפתוח את חלון ההגדרות, להגדיל את סף ההפסד היומי או לנטרל את התוכנה כליל.

כדי לפתור כשל פסיכולוגי זה, RiskMaster מיישם מודל שמירת חוקים דו-שלבי קפדני. עם פתיחת יום המסחר, פרמטרי הסיכון הפעילים ננעלים בזיכרון בחוזה ריצה בלתי ניתן לשינוי. אם הסוחר פותח את לוח ההגדרות ומשנה את המגבלות, השינויים נרשמים אך ורק לקובץ תצורה משני (staged_rules.json). יום המסחר הפעיל ממשיך להתנהל תחת החוקים המקוריים שננעלו.

קידום ההגדרות המושהות למצב פעיל מתרחש אך ורק בעת איפוס יום המסחר: בשעה 18:00 שעון ניו יורק (ET), במקביל למועד הסליקה הרשמי של בורסת החוזים העתידיים (CME). המנוע כולל מתזמן אוטומטי המביא בחשבון חגים, סופי שבוע ושינויי שעון קיץ, ומבטיח איפוס והחלת חוקים ללא צורך בהתערבות ידנית.

  • ▪חוזה ריצה בלתי ניתן לשינוי: הגדרות הסיכון הפעילות אינן ניתנות לעריכה, עקיפה או טעינה מחדש במהלך יום המסחר.
  • ▪השהיית שינויי פרמטרים: כל עדכון בהגדרות מושהה אוטומטית ליום המסחר הבא, מה שכופה תקופת צינון פסיכולוגית הכרחית.
  • ▪איפוס מדויק בשעה 18:00 ET: סנכרון מלא מול ימי המסחר הרשמיים של בורסת ה-CME, סופי שבוע ומועדי שעון קיץ.

טלמטריה ואמינות ציים: אבחון קצה באמצעות Cloudflare Workers ו-R2

// לכידת דוחות קריסה מטוהרים ומדדי ביצועים על פני אלפי עמדות מחשב מבוזרות

עם למעלה מ-2,660 סוחרים מאומתים ומעל 820 משתמשים פעילים מדי חודש, RiskMaster פועל על גבי מגוון עצום של מחשבי Windows: החל מתחנות עבודה עוצמתיות מרובות מסכים ועד מחשבים ניידים בסיסיים המריצים תוכנות רקע רבות. ניטור תקלות חיבור, התנגשויות מול תוספים אחרים או חריגות מערכת באלפי עמדות קצה מחייב מערך אבחון ממוחשב.

כדי לנטר את יציבות הצי מבלי לפגוע בפרטיות המשתמשים, RiskMaster כולל רכיב טלמטריה אסינכרוני השומר על סודיות מוחלטת. בעת שגיאה חריגה או תפוגת זמן בחיבור לממשק, המנוע מייצר דוח אבחון מוצפן. כל מספרי החשבון, פרטי ההתחברות לברוקר, כתובות ה-IP ויתרות הכספים מטוהרים לחלוטין ומוחלפים בערכי גיבוב (Hash) חד-כיווניים מוצפנים לפני השידור.

נתוני האבחון נשלחים באופן אסינכרוני דרך HTTPS אל נקודת קצה קלה של Cloudflare Worker. ה-Worker מוודא את תקינות הבקשה, מקבץ טביעות אצבע של שגיאות למניעת כפילויות, ומזרים את קובץ האבחון למאגר האחסון Cloudflare R2. ארכיטקטורת קצה זו מספקת שקיפות בזמן אמת על יציבות התוכנה ללא עלויות תחזוקה של מסדי נתונים כבדים.

  • ▪אפס חשיפת פרטי זיהוי אישיים: כל מזהי החשבון, שמות הברוקרים ויתרות הכספים עוברים גיבוב מוצפן לפני השידור ברשת.
  • ▪שידור אסינכרוני ללא חסימות: רישום הנתונים מתבצע ברקע בעדיפות נמוכה, ומבטיח אפס השפעה על תגובתיות המסחר.
  • ▪תשתית קצה מבוזרת: שרתי Cloudflare Workers ומאגר R2 מתמודדים עם פרצי דיווח על תקלות ללא צורך בניהול שרתים.

לקחים הנדסיים למערכות שולחניות בשיהוי נמוך

// עקרונות ארכיטקטוניים מרכזיים לפיתוח תוכנות קריטיות הפועלות בתוך פלטפורמות צד שלישי

פיתוח תוכנות בטיחות קריטיות המשתלבות בתוך סביבות מארחות דורש משמעת הנדסית גבוהה. אלו ארבעת עקרונות היסוד שנלמדו בפרויקט:

  • ▪יירטו את כוונת המשתמש בשכבת התצוגה: לעולם אל תסתמכו על התראות שלאחר ביצוע כאשר הבטיחות דורשת למנוע את הפעולה עצמה.
  • ▪חסלו הקצאות זיכרון בלולאות תדר גבוה: בעיבוד ציטוטי שוק בזמן אמת, הקצו אפס בייטים ב-Heap כדי למנוע כליל עצירות ניקוי זיכרון.
  • ▪הבטיחו הפעלה בודדת של שגרות חירום: השתמשו בפקודות Compare-And-Swap אטומיות למניעת שידור פקודות כפולות בעת חיסול פוזיציות.
  • ▪תכננו הגנה מפני חבלה רגשית של המשתמש: יישמו מודל שמירה דו-שלבי המגן על המשתמש מפני דחפיו שלו במצבי לחץ תפעוליים קיצוניים.

מפתחים תשתית מסחר שולחנית קריטית לביצועים?

אנו מתמחים בביצועים בתת-מילישנייה, צנרות זיכרון ללא הקצאות ושערי בטיחות בלתי חדירים לסביבות מסחר תנודתיות.

השלב הבא שלכם

בואו נגדיר מה נכון לבנות.

הביאו את הרעיון או את התהליך שתרצו לשפר. נתחיל מהדרישות, מהסיכונים ומהיקף העבודה הנכון.

בואו נדבר על הפרויקט שלכם