スキル一覧に戻る
mkalhitti-cloud

live-price-tracking

by mkalhitti-cloud

NinjaTrader 8 automated trading system for MES/MGC

0🍴 0📅 2026年1月22日
GitHubで見るManusで実行

SKILL.md


name: live-price-tracking description: Critical guide for implementing live price tracking in NinjaTrader strategies. Use when fixing the Close[0] bug, implementing OnMarketData hooks, adding tick-level price tracking, or debugging trailing stops that only update at bar close.

CRITICAL: Live Price Tracking vs. Bar Close Data

Severity: CRITICAL - Impacts real-time trading performance Discovered: V5.3 development (multi-AI code review) Status: FIXED in V5.3.1 Impact: 50-90% improvement in trailing stop execution


The Problem: Close[0] Bug

Symptom

Trailing stops not updating between bar closes. Position hits profit target intra-bar but order doesn't execute until next bar, losing significant profit potential.

Root Cause

// ❌ CRITICAL BUG - Only evaluates at bar close
protected override void OnBarUpdate()
{
    if (Close[0] > highestPrice)
    {
        highestPrice = Close[0];
        double newStop = highestPrice - atrDistance;
        SetStopLoss(newStop);  // Order update delayed!
    }
}

Why This Fails:

  • Close[0] only updates when bar closes
  • OnBarUpdate() only fires at bar close (default Calculate mode)
  • Intra-bar price movements ignored completely

Real-World Impact Example

10:05:00 - Enter long @ 4500
10:05:15 - Price hits 4510 (10 point profit)
10:05:30 - Price reverses to 4505
10:06:00 - Bar closes @ 4505
          → Trailing stop ONLY NOW updates to 4495
10:06:15 - Price drops to 4495, stopped out

Result: 5 point loss instead of 10 point profit
Lost:   15 points due to delayed stop update

The Solution: OnMarketData Pattern

How It Works

protected override void OnMarketData(MarketDataEventArgs e)
{
    // Filter: Only actual trades, not bid/ask/volume
    if (e.MarketDataType != MarketDataType.Last)
        return;

    // Filter: Only this instrument
    if (e.Instrument != Instrument)
        return;

    // Live price available tick-by-tick
    double livePrice = e.Price;
    lastTickTime = DateTime.Now;

    // Update trailing stop in real-time
    if (Position.MarketPosition == MarketPosition.Long && livePrice > highestPrice)
    {
        highestPrice = livePrice;
        double newStop = highestPrice - atrDistance;

        // Rate-limited modification (Apex compliance)
        if (CanModifyOrder())
            SetStopLoss(newStop);
    }
}

Key Points

  1. e.MarketDataType == MarketDataType.Last - Only real trades (not bid/ask updates)
  2. e.Instrument == Instrument - Only this chart's instrument
  3. e.Price - Live tick-by-tick price
  4. Fires on every tick, not just bar close

GetLivePrice() Helper (Fallback Chain)

Purpose

Provides live price even when OnMarketData not firing (rare cases)

Implementation

private double GetLivePrice()
{
    // Priority 1: Bid/Ask midpoint (most accurate)
    if (Ask > 0 && Bid > 0)
        return (Bid + Ask) / 2.0;

    // Priority 2: Ask alone
    if (Ask > 0)
        return Ask;

    // Priority 3: Bid alone
    if (Bid > 0)
        return Bid;

    // Last resort: Last bar close (stale data warning)
    return Close[0];
}

Usage

protected override void OnBarUpdate()
{
    // Use GetLivePrice() instead of Close[0]
    double currentPrice = GetLivePrice();

    if (currentPrice > sessionHigh + TickSize)
    {
        EnterLong(qty, "ORB_Long");
        SetStopLoss(currentPrice - (atr * 2.0));
    }
}

Rithmic-Specific Considerations

Tick Frequency

RTH (9:30-16:00 ET):   50-200 ticks/min
Pre-market:             5-20 ticks/min
Overnight:              5-50 ticks/min

Testing OnMarketData

private int tickCount = 0;

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType != MarketDataType.Last)
        return;

    tickCount++;

    // Log every 100 ticks to verify firing
    if (tickCount % 100 == 0)
        Print($"Ticks received: {tickCount}");
}

Expected Output During RTH:

Should see 100 ticks every 30-120 seconds
If gap > 2 minutes, Rithmic may have disconnected

Disconnect Detection

private DateTime lastTickTime = DateTime.Now;

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType == MarketDataType.Last)
        lastTickTime = DateTime.Now;
}

protected override void OnBarUpdate()
{
    // Detect stale data (> 5 seconds without ticks during RTH)
    if ((DateTime.Now - lastTickTime).TotalSeconds > 5 && IsRTH())
    {
        Print("WARNING: Rithmic disconnect detected");

        // Pause trading or close positions
        if (Position.MarketPosition != MarketPosition.Flat)
            FlattenAll("Data disconnect");
    }
}

Rate-Limiting Order Modifications (Apex Compliance)

The Rule

Maximum 1 order modification per second (Apex account requirement)

Implementation

private DateTime lastModTime = DateTime.MinValue;
private const int MOD_DELAY_MS = 1000;

private bool CanModifyOrder()
{
    TimeSpan elapsed = DateTime.Now - lastModTime;

    if (elapsed.TotalMilliseconds < MOD_DELAY_MS)
        return false;  // Blocked - too soon

    lastModTime = DateTime.Now;
    return true;  // Allowed
}

Usage in OnMarketData

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType != MarketDataType.Last)
        return;

    double livePrice = e.Price;

    // Calculate new stop
    double newStop = livePrice - (cachedATR * 2.0);

    // Only modify if:
    // 1. Stop moved in favorable direction
    // 2. Rate-limit allows modification
    if (newStop > currentStop && CanModifyOrder())
    {
        ChangeOrder(stopOrder, stopOrder.Quantity, 0, newStop);
        currentStop = newStop;
    }
}

Why Rate-Limiting Matters:

  • Without it: 50-200 modifications per minute (1 per tick)
  • Violates Apex rules → Account warning or closure
  • With rate-limiting: Max 60 modifications per minute (safe)

Complete Implementation Example

Full Trailing Stop Pattern

// Variables
private double highestPrice = 0;
private double lowestPrice = double.MaxValue;
private double cachedATR = 0;
private double currentStop = 0;
private DateTime lastModTime = DateTime.MinValue;
private DateTime lastTickTime = DateTime.Now;
private const int MOD_DELAY_MS = 1000;

// Cache ATR in OnBarUpdate (don't recalculate every tick)
protected override void OnBarUpdate()
{
    if (CurrentBar < BarsRequiredToPlot)
        return;

    cachedATR = ATR(14)[0];

    // Check for data staleness
    if ((DateTime.Now - lastTickTime).TotalSeconds > 5 && IsRTH())
    {
        Print("WARNING: No ticks for 5+ seconds");
    }
}

// Update trailing stops tick-by-tick
protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType != MarketDataType.Last)
        return;

    if (e.Instrument != Instrument)
        return;

    double livePrice = e.Price;
    lastTickTime = DateTime.Now;

    // Long position trailing
    if (Position.MarketPosition == MarketPosition.Long)
    {
        if (livePrice > highestPrice)
        {
            highestPrice = livePrice;
            double newStop = highestPrice - (cachedATR * 2.0);

            if (newStop > currentStop && CanModifyOrder())
            {
                ChangeOrder(stopOrder, stopOrder.Quantity, 0, newStop);
                currentStop = newStop;
                Print($"Stop updated: {currentStop:F2}");
            }
        }
    }
    // Short position trailing
    else if (Position.MarketPosition == MarketPosition.Short)
    {
        if (livePrice < lowestPrice)
        {
            lowestPrice = livePrice;
            double newStop = lowestPrice + (cachedATR * 2.0);

            if (newStop < currentStop && CanModifyOrder())
            {
                ChangeOrder(stopOrder, stopOrder.Quantity, 0, newStop);
                currentStop = newStop;
                Print($"Stop updated: {currentStop:F2}");
            }
        }
    }
}

// Rate-limiting function
private bool CanModifyOrder()
{
    if ((DateTime.Now - lastModTime).TotalMilliseconds < MOD_DELAY_MS)
        return false;

    lastModTime = DateTime.Now;
    return true;
}

// Fallback price helper
private double GetLivePrice()
{
    if (Ask > 0 && Bid > 0)
        return (Bid + Ask) / 2.0;

    if (Ask > 0) return Ask;
    if (Bid > 0) return Bid;

    return Close[0];
}

Testing & Verification

Verification Checklist

  1. OnMarketData Firing:

    - [ ] Add Print($"Tick: {e.Price}") to OnMarketData
    - [ ] Open Output window
    - [ ] Should see 50+ prints per minute during RTH
    
  2. Trailing Stop Updates:

    - [ ] Enter position
    - [ ] Watch price move in favor
    - [ ] WITHOUT bar closing, check if stop updates
    - [ ] Stop line should move on chart between bars
    
  3. Rate-Limiting Works:

    - [ ] Monitor Print output for "Stop updated"
    - [ ] Should see max 1 update per second
    - [ ] NOT 50+ updates per minute
    
  4. Disconnect Detection:

    - [ ] Pause data feed (Control Center → Connection)
    - [ ] Should see "WARNING: No ticks" after 5 seconds
    - [ ] Resume feed, verify recovery
    

Migration Checklist

When updating old code:

  • Search entire file for Close[0]
  • Check each occurrence - is it for real-time decisions?
  • Replace with OnMarketData hook or GetLivePrice()
  • Add rate-limiting to order modifications
  • Implement disconnect detection
  • Test with 1-min, 5-min, and 15-min charts
  • Verify memory doesn't leak (1+ hour test)
  • Check no recursive OnMarketData calls
  • Test Rithmic disconnect/reconnect

Common Mistakes

❌ Processing ALL MarketDataTypes

// WRONG - Fires on bid, ask, volume updates too
protected override void OnMarketData(MarketDataEventArgs e)
{
    UpdateTrailingStop(e.Price);  // Executes 200+ times per minute!
}

CORRECT:

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType != MarketDataType.Last)
        return;  // Only actual trades

    UpdateTrailingStop(e.Price);
}

❌ Not Checking Instrument

// WRONG - Processes other charts' ticks
protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType == MarketDataType.Last)
        UpdateTrailingStop(e.Price);
}

CORRECT:

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType != MarketDataType.Last)
        return;

    if (e.Instrument != Instrument)
        return;  // Filter by instrument

    UpdateTrailingStop(e.Price);
}

❌ Calling Indicators in OnMarketData

// WRONG - Slow indicator recalculation every tick
protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType != MarketDataType.Last)
        return;

    double atr = ATR(14)[0];  // ❌ Expensive!
    double newStop = e.Price - atr;
}

CORRECT:

// Cache in OnBarUpdate
private double cachedATR = 0;

protected override void OnBarUpdate()
{
    cachedATR = ATR(14)[0];
}

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType != MarketDataType.Last)
        return;

    double newStop = e.Price - cachedATR;  // ✅ Fast
}

Performance Impact

Before Fix (Using Close[0])

Memory:         +2-5 KB per position
CPU:            < 1% (OnBarUpdate only)
Order Latency:  500ms - 60 seconds (depends on bar size)
Profit Impact:  -50% to -90% on trailing stop exits

After Fix (Using OnMarketData)

Memory:         +2-5 KB per position (same)
CPU:            < 1% per 1000 ticks (negligible)
Order Latency:  < 50ms (sub-second)
Profit Impact:  50-90% improvement on trailing exits

Summary

Critical Rules

  1. NEVER use Close[0] for real-time decisions
  2. ALWAYS use OnMarketData for trailing stops
  3. ALWAYS rate-limit order modifications (1/second)
  4. ALWAYS filter MarketDataType.Last only
  5. ALWAYS check e.Instrument == Instrument
  6. ALWAYS cache indicators (don't recalculate every tick)

Quick Audit

Search your code for: "Close[0]"
If found in context of:
- Trailing stop updates → FIX REQUIRED
- Live price decisions → FIX REQUIRED
- Entry/exit logic → FIX REQUIRED

Replace with OnMarketData pattern shown above

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です