Skip to content
Closed
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
45 changes: 35 additions & 10 deletions src/V12_002.Entries.FFMA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,26 @@ private void ExecuteFFMAEntry(MarketPosition direction)
// Build 936 [FIX-2]: Deterministic OCO group ID for broker-native bracket protection.
OcoGroupId = "V12_" + entryName.GetHashCode().ToString("X8")
};
activePositions[entryName] = pos;

// V12.13-D: Notify connected panel clients of position entry
string syncMsg = string.Format("POSITION_ENTERED|FFMA|{0}", contracts);
SendResponseToRemote(syncMsg);


// Submit MARKET order (immediate execution)
Order entryOrder = direction == MarketPosition.Long
? SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Market, contracts, 0, 0, "", entryName)
: SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Market, contracts, 0, 0, "", entryName);

entryOrders[entryName] = entryOrder;
// A1-1/A2-1: Null-abort rollback + stateLock wrap (Build 960 audit fix)
if (entryOrder == null)
{
Print("[ENTRY_ABORT] FFMA SubmitOrderUnmanaged returned null for " + entryName + ". Rolling back.");
return;
Comment on lines 172 to +185
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 | 🟠 Major

Delay the POSITION_ENTERED notification until the order is actually created.

SendResponseToRemote("POSITION_ENTERED|FFMA|...") fires before SubmitOrderUnmanaged. On the new abort path, the panel still gets a false-positive entry event even though no order exists.

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

In `@src/V12_002.Entries.FFMA.cs` around lines 172 - 185, The POSITION_ENTERED
notification is sent before the order is created, causing false-positive panel
events; move the SendResponseToRemote("POSITION_ENTERED|FFMA|{0}", contracts)
call to after SubmitOrderUnmanaged and after the null-check of entryOrder (i.e.,
only call SendResponseToRemote when entryOrder != null), referencing the
SubmitOrderUnmanaged call, the entryOrder variable and entryName so the
notification is sent only on successful order creation/assignment.

}
lock (stateLock)
{
activePositions[entryName] = pos;
entryOrders[entryName] = entryOrder;
}

Print(string.Format("FFMA MARKET ORDER: {0} {1}@MARKET | Stop: {2:F2} (candle {3})",
signalName, contracts, stopPrice, direction == MarketPosition.Long ? "low" : "high"));
Expand Down Expand Up @@ -304,13 +311,22 @@ private void ExecuteFFMALimitEntry(double manualPrice, MarketPosition direction)
// Build 936 [FIX-2]: Deterministic OCO group ID for broker-native bracket protection.
OcoGroupId = "V12_" + entryName.GetHashCode().ToString("X8")
};
activePositions[entryName] = pos;

// V12.27: Submit LIMIT order (not Market like standard FFMA)
Order entryOrder = direction == MarketPosition.Long
? SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Limit, contracts, entryPrice, 0, "", entryName)
: SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Limit, contracts, entryPrice, 0, "", entryName);
entryOrders[entryName] = entryOrder;

// A1-1/A2-1: Null-abort rollback + stateLock wrap (Build 960 audit fix)
if (entryOrder == null)
{
Print("[ENTRY_ABORT] FFMA_LIMIT SubmitOrderUnmanaged returned null for " + entryName + ". Rolling back.");
return;
}
lock (stateLock)
{
activePositions[entryName] = pos;
entryOrders[entryName] = entryOrder;
}

Print(string.Format("V12.27 FFMA_LIMIT: {0} {1}@{2:F2} LIMIT | Stop: {3:F2} | ATR-based",
direction, contracts, entryPrice, stopPrice));
Expand Down Expand Up @@ -443,13 +459,22 @@ private void ExecuteFFMAManualMarketEntry()
// Build 936 [FIX-2]: Deterministic OCO group ID for broker-native bracket protection.
OcoGroupId = "V12_" + entryName.GetHashCode().ToString("X8")
};
activePositions[entryName] = pos;

// Submit MARKET order (immediate execution)
Order entryOrder = direction == MarketPosition.Long
? SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Market, contracts, 0, 0, "", entryName)
: SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Market, contracts, 0, 0, "", entryName);
entryOrders[entryName] = entryOrder;

// A1-1/A2-1: Null-abort rollback + stateLock wrap (Build 960 audit fix)
if (entryOrder == null)
{
Print("[ENTRY_ABORT] FFMA_MANUAL_MARKET SubmitOrderUnmanaged returned null for " + entryName + ". Rolling back.");
return;
}
lock (stateLock)
{
activePositions[entryName] = pos;
entryOrders[entryName] = entryOrder;
}

Print(string.Format("V12.27 FFMA_MANUAL_MARKET: {0} {1}@MARKET | Stop: {2:F2} (candle {3}) | Toward EMA9={4:F2}",
direction, contracts, stopPrice, direction == MarketPosition.Long ? "low" : "high", ema9Value));
Expand Down
13 changes: 8 additions & 5 deletions src/V12_002.Entries.MOMO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ private void ExecuteMOMOEntry(double clickPrice, int contracts)
};
ApplyTargetLadderGuard(pos);

activePositions[entryName] = pos;

// Build 1102Y-V3 [MS-06]: Register Master expected BEFORE StopMarket entry.
int masterDeltaMOMO = (direction == MarketPosition.Long) ? contracts : -contracts;
AddExpectedPositionDeltaLocked(ExpKey(Account.Name), masterDeltaMOMO);
Expand All @@ -154,13 +152,18 @@ private void ExecuteMOMOEntry(double clickPrice, int contracts)
? 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)
{
AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaMOMO);
Print("[ERROR][1102Y-V3] MOMO SubmitOrderUnmanaged NULL for " + entryName + " -- rolled back.");
Print("[ENTRY_ABORT] MOMO SubmitOrderUnmanaged returned null for " + entryName + ". Rolling back.");
return;
}
lock (stateLock)
{
activePositions[entryName] = pos;
entryOrders[entryName] = entryOrder;
}

entryOrders[entryName] = entryOrder;

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)",
Expand Down
15 changes: 7 additions & 8 deletions src/V12_002.Entries.OR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,6 @@ private void EnterORPosition(MarketPosition direction, double entryPrice, double
};
ApplyTargetLadderGuard(pos);

activePositions[entryName] = pos;

// V12.13-D: Notify connected panel clients of position entry
string syncMsg = string.Format("POSITION_ENTERED|OR|{0}", contracts);
SendResponseToRemote(syncMsg);
Expand All @@ -218,18 +216,19 @@ private void EnterORPosition(MarketPosition direction, double entryPrice, double
? 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)
{
// Build 1102Y-V3 [MS-03 ROLLBACK]: Submit failed -- undo Order Ledger reservation.
AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaOR);
Print("[ERROR][1102Y-V3] OR SubmitOrderUnmanaged returned NULL for " + entryName + " -- Master expected rolled back. Fleet dispatch aborted.");
// [FIX-OR-E]: Must abort here. Without an early return, ExecuteSmartDispatchEntry
// dispatches fleet followers for a master entry that does not exist -> phantom fleet entries.
activePositions.TryRemove(entryName, out _);
Print("[ENTRY_ABORT] OR SubmitOrderUnmanaged returned NULL for " + entryName + " -- Master expected rolled back. Fleet dispatch aborted.");
return;
}

entryOrders[entryName] = entryOrder;
lock (stateLock)
{
activePositions[entryName] = pos;
entryOrders[entryName] = entryOrder;
}

Print(string.Format("OR ENTRY ORDER: {0} {1}@{2:F2} | Stop: {3:F2} | OR Range: {4:F2}",
signalName, contracts, entryPrice, stopPrice, sessionRange));
Expand Down
59 changes: 40 additions & 19 deletions src/V12_002.Entries.RMA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,30 +90,45 @@ private void ExecuteTrendSplitEntry()
double stop1Price = Instrument.MasterInstrument.RoundToTickSize(
direction == MarketPosition.Long ? e9 - stop9Dist : e9 + stop9Dist);
PositionInfo pos1 = CreateTRENDPosition(entry1Name, direction, e9, stop1Price, qty9, true, trendGroupId, true);
activePositions[entry1Name] = pos1;

List<string> masterEntryNames = new List<string> { entry1Name };

Order entryOrder1 = direction == MarketPosition.Long
? SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Limit, qty9, e9, 0, "", entry1Name)
: SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Limit, qty9, e9, 0, "", entry1Name);
entryOrders[entry1Name] = entryOrder1;

// A1-1/A2-1: Null-abort + stateLock wrap for E1 (Build 960 audit fix)
if (entryOrder1 == null)
{
Print("[ENTRY_ABORT] TrendSplit E1 SubmitOrderUnmanaged returned null for " + entry1Name + ". Rolling back.");
return;
Comment on lines +101 to +104
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

This log message says "Rolling back" but there is no rollback action in this branch (no expectedPositions reservation and no state to undo). Either remove the rollback wording or add the actual rollback/cleanup that’s intended.

Copilot uses AI. Check for mistakes.
}
lock (stateLock) { activePositions[entry1Name] = pos1; entryOrders[entry1Name] = entryOrder1; }

if (qty15 > 0)
{
double stop2Price = Instrument.MasterInstrument.RoundToTickSize(
direction == MarketPosition.Long ? e15 - stop15Dist : e15 + stop15Dist);
PositionInfo pos2 = CreateTRENDPosition(entry2Name, direction, e15, stop2Price, qty15, false, trendGroupId, true);
activePositions[entry2Name] = pos2;

linkedTRENDEntries[entry1Name] = entry2Name;
linkedTRENDEntries[entry2Name] = entry1Name;

Order entryOrder2 = direction == MarketPosition.Long
? SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Limit, qty15, e15, 0, "", entry2Name)
: SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Limit, qty15, e15, 0, "", entry2Name);
entryOrders[entry2Name] = entryOrder2;
masterEntryNames.Add(entry2Name);

// A1-1/A2-1: Null-abort + stateLock wrap for E2 (Build 960 audit fix)
if (entryOrder2 == null)
{
Print("[ENTRY_ABORT] TrendSplit E2 SubmitOrderUnmanaged returned null for " + entry2Name + ". Rolling back.");
// E1 already submitted -- log but continue without E2 tracking
}
else
{
lock (stateLock) { activePositions[entry2Name] = pos2; entryOrders[entry2Name] = entryOrder2; }
masterEntryNames.Add(entry2Name);
}
Comment on lines +121 to +131
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

entryOrder2 == null is logged but the method continues using the original qty15/finalTotalQty and can still call ExecuteSmartDispatchEntry(...). This can dispatch follower quantity that includes the missing EMA15 leg, creating a master/follower mismatch and potential REAPER repair loop. On E2 submit failure, either (a) cancel/tear down E1 and abort, or (b) adjust quantities and skip SIMA dispatch for the missing leg (and avoid registering/linking E2 metadata).

Copilot uses AI. Check for mistakes.
}

double weightedEntryPrice = ((e9 * qty9) + (e15 * qty15)) / Math.Max(1, finalTotalQty);
Expand Down Expand Up @@ -289,22 +304,23 @@ private void ExecuteRMAEntry(double clickPrice, MarketPosition? forcedDirection
? SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Limit, contracts, entryPrice, 0, "", entryName)
: SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Limit, contracts, entryPrice, 0, "", entryName);

if (entryOrder != null)
{
entryOrders[entryName] = entryOrder;
activePositions[entryName] = pos; // Only add to panel if order submitted

// DEBUG: Visual Confirmation
Draw.Text(this, "Debug_" + entryName, "ORDER SUBMITTED", 0, entryPrice, Brushes.Yellow);
Draw.Line(this, "Line_" + entryName, 0, entryPrice, 10, entryPrice, Brushes.Yellow);
}
else
// A1-1/A2-1: Null-abort rollback + stateLock wrap (Build 960 audit fix)
if (entryOrder == null)
{
// Build 1102Y-V3 [MS-01 ROLLBACK]: Submit failed -- undo reservation to prevent ghost position.
AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaRMA);
Print("[ERROR][1102Y-V3] SubmitOrderUnmanaged returned NULL for " + entryName + " -- Master expected rolled back.");
Print("[ENTRY_ABORT] RMA SubmitOrderUnmanaged returned NULL for " + entryName + " -- Master expected rolled back.");
Draw.Text(this, "Debug_Fail_" + entryName, "ORDER FAILED", 0, entryPrice, Brushes.Red);
return;
}
lock (stateLock)
{
activePositions[entryName] = pos;
entryOrders[entryName] = entryOrder;
}
// DEBUG: Visual Confirmation
Draw.Text(this, "Debug_" + entryName, "ORDER SUBMITTED", 0, entryPrice, Brushes.Yellow);
Draw.Line(this, "Line_" + entryName, 0, entryPrice, 10, entryPrice, Brushes.Yellow);

Print(string.Format("RMA ENTRY ORDER: {0} {1}@{2:F2} | ATR: {3:F2}", signalName, contracts, entryPrice, currentATR));
Print(string.Format("RMA 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)",
Expand Down Expand Up @@ -397,8 +413,6 @@ private void ExecuteRMAEntryCustom(double price, MarketPosition direction)
};
ApplyTargetLadderGuard(pos);

activePositions[entryName] = pos;

// Build 1102Y-V3 [MS-02]: Register Master's expected position in the Order Ledger BEFORE submit.
int masterDeltaRMACustom = (direction == MarketPosition.Long) ? contracts : -contracts;
AddExpectedPositionDeltaLocked(ExpKey(Account.Name), masterDeltaRMACustom);
Expand All @@ -408,11 +422,18 @@ private void ExecuteRMAEntryCustom(double price, MarketPosition direction)
? SubmitOrderUnmanaged(0, OrderAction.Buy, OrderType.Market, contracts, 0, 0, "", entryName)
: SubmitOrderUnmanaged(0, OrderAction.SellShort, OrderType.Market, contracts, 0, 0, "", entryName);

// A1-1/A2-1: Null-abort rollback + stateLock wrap (Build 960 audit fix)
if (entryOrderCustom == null)
{
// Build 1102Y-V3 [MS-02 ROLLBACK]: Submit failed -- undo reservation.
AddExpectedPositionDeltaLocked(ExpKey(Account.Name), -masterDeltaRMACustom);
Print("[ERROR][1102Y-V3] RMACustom SubmitOrderUnmanaged returned NULL for " + entryName + " -- Master expected rolled back.");
Print("[ENTRY_ABORT] RMACustom SubmitOrderUnmanaged returned NULL for " + entryName + " -- Master expected rolled back.");
return;
}
lock (stateLock)
{
activePositions[entryName] = pos;
entryOrders[entryName] = entryOrderCustom;
}

Print(string.Format("IPC EXEC: {0} {1} contracts at MKT (Ref: {2:F2})", direction, contracts, entryPrice));
Expand Down
Loading
Loading