Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions V12_002.AccountUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using NinjaTrader.Cbi;

namespace NinjaTrader.NinjaScript.Strategies
{
public partial class V12_002
{
// Placeholder for missing AccountUpdate logic.
public class AccountUpdate
{
public string AccountName { get; set; }
public double Equity { get; set; }
public double DailyPnL { get; set; }
public DateTime Timestamp { get; set; }
}
}
}
18 changes: 18 additions & 0 deletions V12_002.Atm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using NinjaTrader.Cbi;

namespace NinjaTrader.NinjaScript.Strategies
{
public partial class V12_002
{
// Placeholder for missing Atm logic.
public static class Atm
{
public enum AtmStrategyMode
{
Standard,
Custom
}
}
}
}
15 changes: 15 additions & 0 deletions V12_002.Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace NinjaTrader.NinjaScript.Strategies
{
public partial class V12_002
{
// Placeholder for missing Constants logic.
// These are likely used in SignalBroadcaster or UI paths.
public static class Constants
{
public const string StrategyName = "V12_002";
Comment on lines +10 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale version string in placeholder file

Version = "Build 936" here contradicts the BUILD_TAG = "963" in src/V12_002.cs. If this constant is ever surfaced in logs or the panel UI, it would display an incorrect build number. Consider updating to match the canonical BUILD_TAG, or removing this placeholder constant entirely.

Suggested change
{
public const string StrategyName = "V12_002";
public const string Version = "Build 963";

public const string Version = "Build 936";
}
}
}
11 changes: 11 additions & 0 deletions V12_002.Data.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace NinjaTrader.NinjaScript.Strategies
{
public partial class V12_002
{
// Placeholder for missing Data logic.
public static class Data
{
// Add static data members here if needed.
}
}
}
497 changes: 497 additions & 0 deletions V12_002.Entries.FFMA.cs

Large diffs are not rendered by default.

202 changes: 202 additions & 0 deletions V12_002.Entries.MOMO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// V12.Phase7 MODULAR: MOMO Entry Node (Split from Entries.cs -- Phase 7 Partition)
// Contains: ExecuteMOMOEntry, ActivateMOMOMode, DeactivateMOMOMode
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.Tools;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.NinjaScript.DrawingTools;
using NinjaTrader.NinjaScript.Indicators;
using NinjaTrader.NinjaScript.Strategies;
using System.Net;
using System.Net.Sockets;

namespace NinjaTrader.NinjaScript.Strategies
{
public partial class V12_002 : Strategy
{
#region MOMO Entry Logic (V8.6)

/// <summary>
/// V8.6: Execute MOMO (Momentum) trade using Stop Market orders
/// OPPOSITE direction from RMA:
/// - Click ABOVE price = Stop Market LONG (buy when price rises to click level)
/// - Click BELOW price = Stop Market SHORT (sell when price drops to click level)
/// Uses same targets/trails as RMA but with fixed 0.5pt stop
/// </summary>
private void ExecuteMOMOEntry(double clickPrice, int contracts)
{
// V12.Phase7 [C-09]: Compliance enforcement gate.
if (!IsOrderAllowed()) return;
// V12.Phase6 [FLATTEN-GUARD]: Prevent order submission during active flatten
if (isFlattenRunning) return;

if (!MOMOEnabled)
{
Print("MOMO mode is disabled");
return;
}

if (currentATR <= 0)
{
Print("Cannot execute MOMO entry - ATR not available yet");
return;
}

if (contracts <= 0)
{
Print(string.Format("[MOMO] ExecuteMOMOEntry received invalid contracts={0}. Aborting entry.", contracts));
return;
}

try
{
// Use last known price from OnBarUpdate (Close[0] may be stale in UI events)
double currentPrice = lastKnownPrice > 0 ? lastKnownPrice : Close[0];

// MOMO Direction: OPPOSITE from RMA!
// Click ABOVE current price = LONG (stop buy triggers when price rises)
// Click BELOW current price = SHORT (stop sell triggers when price drops)
MarketPosition direction;
if (clickPrice > currentPrice)
{
direction = MarketPosition.Long;
Print(string.Format("MOMO: Click above price ({0:F2} > {1:F2}) = LONG stop entry", clickPrice, currentPrice));
}
else
{
direction = MarketPosition.Short;
Print(string.Format("MOMO: Click below price ({0:F2} < {1:F2}) = SHORT stop entry", clickPrice, currentPrice));
}

// MOMO uses FIXED 0.5pt stop (not ATR-based)
double stopDistance = Math.Min(MOMOStopPoints, MaximumStop); // V8.31: Use MaximumStop

double entryPrice = Instrument.MasterInstrument.RoundToTickSize(clickPrice);
// V12.Phase6 [TICK-01]: All prices rounded to valid tick increments
double stopPrice = Instrument.MasterInstrument.RoundToTickSize(direction == MarketPosition.Long
? entryPrice - stopDistance
: entryPrice + stopDistance);

// Universal Ladder: T(n)Type dropdown drives all target pricing.
double target1Price = CalculateTargetPrice(direction, entryPrice, 1);
double target2Price = CalculateTargetPrice(direction, entryPrice, 2);
double target3Price = CalculateTargetPrice(direction, entryPrice, 3);
double target4Price = CalculateTargetPrice(direction, entryPrice, 4);
double target5Price = CalculateTargetPrice(direction, entryPrice, 5);

int t1Qty, t2Qty, t3Qty, t4Qty, t5Qty;
GetTargetDistribution(contracts, out t1Qty, out t2Qty, out t3Qty, out t4Qty, out t5Qty);

string signalName = direction == MarketPosition.Long ? "MOMOLong" : "MOMOShort";
string timestamp = DateTime.Now.ToString("HHmmssffff");
string entryName = signalName + "_" + timestamp;

PositionInfo pos = new PositionInfo
{
SignalName = entryName,
Direction = direction,
TotalContracts = contracts,
T1Contracts = t1Qty,
T2Contracts = t2Qty,
T3Contracts = t3Qty,
T4Contracts = t4Qty,
T5Contracts = t5Qty,
RemainingContracts = contracts,
EntryPrice = entryPrice,
InitialStopPrice = stopPrice,
CurrentStopPrice = stopPrice,
Target1Price = target1Price,
Target2Price = target2Price,
Target3Price = target3Price,
Target4Price = target4Price,
Target5Price = target5Price,
EntryFilled = false,
T1Filled = false,
T2Filled = false,
T3Filled = false,
BracketSubmitted = false,
ExtremePriceSinceEntry = entryPrice,
CurrentTrailLevel = 0,
EntryOrderType = OrderType.StopMarket,
IsRMATrade = false,
IsMOMOTrade = true, // V8.6: Mark as MOMO trade
// Build 936 [FIX-2]: Deterministic OCO group ID for broker-native bracket protection.
OcoGroupId = "V12_" + GetStableHash(entryName)
};
ApplyTargetLadderGuard(pos);

// Build 1102Y-V3 [MS-06]: Register Master expected BEFORE StopMarket entry.
int masterDeltaMOMO = (direction == MarketPosition.Long) ? contracts : -contracts;
{ var _aek966 = ExpKey(Account.Name); var _aed966 = (masterDeltaMOMO); Enqueue(ctx => ctx.AddExpectedPositionDeltaLocked(_aek966, _aed966)); }

// V12.Hardening: Use StopMarket (was StopLimit with limitPrice==stopPrice -- never fills on fast breakouts)
Order entryOrder = direction == MarketPosition.Long
? SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.StopMarket, contracts, 0, entryPrice, "", entryName)
: SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.StopMarket, contracts, 0, entryPrice, "", entryName);

// A1-1/A2-1: Null-abort rollback + stateLock wrap (Build 960 audit fix)
if (entryOrder == null)
{
{ var _aek966 = ExpKey(Account.Name); var _aed966 = (-masterDeltaMOMO); Enqueue(ctx => ctx.AddExpectedPositionDeltaLocked(_aek966, _aed966)); }
Print("[ENTRY_ABORT] MOMO SubmitOrderUnmanaged returned null for " + entryName + ". Rolling back.");
return;
}
{ var _en966ap = entryName; var _p966ap = pos; Enqueue(ctx => { ctx.activePositions[_en966ap] = _p966ap; }); }
{ var _en966 = entryName; var _eo966 = entryOrder; Enqueue(ctx => { ctx.entryOrders[_en966] = _eo966; }); }
Comment on lines +146 to +163
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don't push MOMO's reservation and tracking to the next drain cycle.

Line 148 and Lines 162-163 are now asynchronous actor work, so SubmitOrderUnmanaged at Lines 151-153 can happen first. If the stop triggers quickly, fill/update callbacks see a live MOMO position before its expected-position and tracking state exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@V12_002.Entries.MOMO.cs` around lines 146 - 163, The expected-position delta
and MOMO tracking are being enqueued asynchronously (via Enqueue(ctx =>
ctx.AddExpectedPositionDeltaLocked(...)) and Enqueue to set
activePositions/entryOrders) which can allow SubmitOrderUnmanaged to run and
trigger fills before those states exist; move the registration and tracking to
execute synchronously before calling SubmitOrderUnmanaged by invoking
AddExpectedPositionDeltaLocked immediately (using ExpKey(Account.Name) and
masterDeltaMOMO) and setting ctx.activePositions[entryName] and
ctx.entryOrders[entryName] synchronously (or use an immediate/synchronous
enqueue helper if available) so the expected-position and tracking exist prior
to SubmitOrderUnmanaged; also ensure the rollback that undoes the delta
(currently Enqueue(... -masterDeltaMOMO)) is likewise done synchronously if
SubmitOrderUnmanaged returns null.


Print(string.Format("MOMO ENTRY ORDER: {0} {1}@{2:F2} STOP MKT | Stop: {3:F2}pt", signalName, contracts, entryPrice, stopDistance));
Print(string.Format("MOMO TARGETS: T1:{0}@{1:F2}(+{2:F2}pt) | T2:{3}@{4:F2} | T3:{5}@{6:F2} | T4:{7}@{8:F2} | T5:{9}@{10:F2} (Runner targets trail-only)",
t1Qty, target1Price, target1Price - entryPrice,
t2Qty, target2Price, t3Qty, target3Price, t4Qty, target4Price, t5Qty, target5Price));

// V12 SIMA: Dispatch to fleet (replaces legacy slave broadcast)
if (EnableSIMA)
{
ExecuteSmartDispatchEntry("MOMO", direction == MarketPosition.Long ? OrderAction.Buy : OrderAction.SellShort, contracts, entryPrice, OrderType.StopMarket, entryName);
}

// Deactivate MOMO mode after entry (one-shot)
DeactivateMOMOMode();
}
catch (Exception ex)
{
Print("ERROR ExecuteMOMOEntry: " + ex.Message);
}
}

private void ActivateMOMOMode()
{
// Deactivate RMA if active (mutually exclusive)
if (isRMAModeActive)
{
DeactivateRMAMode();
}
isMOMOModeActive = true;
}

private void DeactivateMOMOMode()
{
isMOMOModeActive = false;
}

#endregion
}
}
Loading
Loading