253 lines
6.3 KiB
Markdown
253 lines
6.3 KiB
Markdown
# Coding Patterns — NT8 SDK Required Patterns
|
||
**Last Updated:** 2026-03-27
|
||
|
||
All code in the NT8 SDK MUST follow these patterns without exception.
|
||
|
||
---
|
||
|
||
## 0. C# 5.0 Hard Constraints (NinjaScript Compiler)
|
||
|
||
```csharp
|
||
// ❌ PROHIBITED — compiler will fail silently or error
|
||
$"Hello {name}" // no string interpolation
|
||
obj?.Method() // no null-conditional
|
||
public int Prop => _value; // no expression body
|
||
nameof(SomeClass) // no nameof
|
||
await SomeAsync() // no async/await
|
||
|
||
// ✅ REQUIRED
|
||
string.Format("Hello {0}", name)
|
||
obj != null ? obj.Method() : null
|
||
public int Prop { get { return _value; } }
|
||
"SomeClass" // string literal
|
||
// synchronous only
|
||
```
|
||
|
||
---
|
||
|
||
## 0b. NT8-Specific Critical Rules
|
||
|
||
```csharp
|
||
// SetStopLoss/SetProfitTarget MUST come BEFORE EnterLong/EnterShort
|
||
// Calling them after is silently ignored in backtest
|
||
SetStopLoss(signalName, CalculationMode.Ticks, stopTicks, false); // FIRST
|
||
SetProfitTarget(signalName, CalculationMode.Ticks, targetTicks); // SECOND
|
||
EnterShort(qty, signalName); // THIRD
|
||
|
||
// OnBarUpdate must guard secondary series
|
||
protected override void OnBarUpdate()
|
||
{
|
||
if (BarsInProgress != 0) return; // CRITICAL: ignore daily bar series updates
|
||
// ...
|
||
}
|
||
|
||
// State guard in ProcessStrategyIntent
|
||
// Allows Historical (backtest), blocks Realtime replay burst:
|
||
if (State == State.Realtime && !_realtimeBarSeen)
|
||
return;
|
||
```
|
||
|
||
---
|
||
|
||
## Sizing Formula
|
||
|
||
```
|
||
contracts = floor(RiskPerTrade / (StopTicks × TickValue))
|
||
NQ tick value = $5.00
|
||
$100 / (8 × $5) = 2 contracts
|
||
$200 / (8 × $5) = 5 contracts (capped at MaxContracts)
|
||
Always verify: RiskPerTrade ≤ MaxTradeRisk
|
||
```
|
||
|
||
---
|
||
|
||
## 1. Thread Safety — Lock Everything Shared
|
||
|
||
Every class with shared state must have a lock object:
|
||
```csharp
|
||
private readonly object _lock = new object();
|
||
```
|
||
|
||
Every access to shared `Dictionary`, `List`, `Queue`, or any field touched by multiple threads:
|
||
```csharp
|
||
// ❌ NEVER
|
||
_activeOrders[orderId] = status;
|
||
|
||
// ✅ ALWAYS
|
||
lock (_lock)
|
||
{
|
||
_activeOrders[orderId] = status;
|
||
}
|
||
```
|
||
|
||
### Read-then-write must be atomic
|
||
```csharp
|
||
// ❌ WRONG — race condition between check and write
|
||
if (!_orders.ContainsKey(id))
|
||
_orders[id] = newOrder;
|
||
|
||
// ✅ CORRECT
|
||
lock (_lock)
|
||
{
|
||
if (!_orders.ContainsKey(id))
|
||
_orders[id] = newOrder;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Error Handling — Try-Catch on All Public Methods
|
||
|
||
```csharp
|
||
public ReturnType MethodName(Type parameter)
|
||
{
|
||
// 1. Validate parameters first
|
||
if (parameter == null)
|
||
throw new ArgumentNullException("parameter");
|
||
|
||
// 2. Wrap the main logic
|
||
try
|
||
{
|
||
// Implementation
|
||
return result;
|
||
}
|
||
catch (SpecificException ex)
|
||
{
|
||
_logger.LogError("Specific failure in MethodName: {0}", ex.Message);
|
||
throw;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("Unexpected failure in MethodName: {0}", ex.Message);
|
||
throw;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Logging — Always string.Format, Never $""
|
||
|
||
```csharp
|
||
// ❌ NEVER — C# 6 syntax, breaks NT8 compile
|
||
_logger.LogInformation($"Order {orderId} filled");
|
||
|
||
// ✅ ALWAYS
|
||
_logger.LogInformation("Order {0} filled", orderId);
|
||
_logger.LogWarning("Risk check failed for {0}: {1}", symbol, reason);
|
||
_logger.LogError("Exception in {0}: {1}", "MethodName", ex.Message);
|
||
_logger.LogCritical("Emergency flatten triggered: {0}", reason);
|
||
```
|
||
|
||
### Log level guide
|
||
| Level | When to use |
|
||
|---|---|
|
||
| `LogTrace` | Entering/exiting methods, fine-grained flow |
|
||
| `LogDebug` | State reads, normal data flow |
|
||
| `LogInformation` | Important events: order submitted, filled, cancelled |
|
||
| `LogWarning` | Recoverable issues: validation failed, limit approaching |
|
||
| `LogError` | Failures: exceptions, unexpected states |
|
||
| `LogCritical` | System integrity issues: emergency flatten, data corruption |
|
||
|
||
---
|
||
|
||
## 4. Events — Never Raise Inside Locks
|
||
|
||
Raising events inside a lock causes deadlocks when event handlers acquire other locks.
|
||
|
||
```csharp
|
||
// ❌ DEADLOCK RISK
|
||
lock (_lock)
|
||
{
|
||
_state = newState;
|
||
OrderStateChanged?.Invoke(this, args); // handler may try to acquire _lock
|
||
}
|
||
|
||
// ✅ CORRECT
|
||
OrderState newState;
|
||
lock (_lock)
|
||
{
|
||
newState = CalculateNewState();
|
||
_state = newState;
|
||
}
|
||
// Raise AFTER releasing lock
|
||
RaiseOrderStateChanged(orderId, previousState, newState);
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Constructor — Validate All Dependencies
|
||
|
||
```csharp
|
||
public MyClass(ILogger<MyClass> logger, ISomeDependency dep)
|
||
{
|
||
if (logger == null)
|
||
throw new ArgumentNullException("logger");
|
||
if (dep == null)
|
||
throw new ArgumentNullException("dep");
|
||
|
||
_logger = logger;
|
||
_dep = dep;
|
||
|
||
// Initialize collections
|
||
_activeOrders = new Dictionary<string, OrderStatus>();
|
||
|
||
_logger.LogInformation("MyClass initialized");
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. XML Documentation — Required on All Public Members
|
||
|
||
```csharp
|
||
/// <summary>
|
||
/// Brief one-line description of what this does.
|
||
/// </summary>
|
||
/// <param name="intent">The trading intent to validate.</param>
|
||
/// <param name="context">Current strategy context with account state.</param>
|
||
/// <returns>Risk decision indicating allow or reject.</returns>
|
||
/// <exception cref="ArgumentNullException">Thrown when intent or context is null.</exception>
|
||
public RiskDecision ValidateOrder(StrategyIntent intent, StrategyContext context)
|
||
{
|
||
...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. NT8-Specific Patterns (NinjaScript)
|
||
|
||
When writing code that runs inside NinjaTrader (in `NT8.Adapters/`):
|
||
|
||
```csharp
|
||
// Always guard OnBarUpdate
|
||
protected override void OnBarUpdate()
|
||
{
|
||
if (BarsInProgress != 0) return;
|
||
if (CurrentBar < BarsRequiredToTrade) return;
|
||
// ...
|
||
}
|
||
|
||
// Managed order pattern — set stops BEFORE entry
|
||
SetStopLoss("SignalName", CalculationMode.Ticks, stopTicks, false);
|
||
SetProfitTarget("SignalName", CalculationMode.Ticks, targetTicks);
|
||
EnterLong(contracts, "SignalName");
|
||
|
||
// Use string.Format for Print() too
|
||
Print(string.Format("Order submitted: {0} contracts at {1}", qty, price));
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Checklist Before Marking Any Method Complete
|
||
|
||
- [ ] Parameter null checks at the top
|
||
- [ ] `try-catch` wrapping the body
|
||
- [ ] All `Dictionary`/collection access inside `lock (_lock)`
|
||
- [ ] All logging uses `string.Format()` (no `$""`)
|
||
- [ ] XML `/// <summary>` on every public method, property, class
|
||
- [ ] No C# 6+ syntax
|
||
- [ ] Events raised outside lock blocks
|
||
- [ ] `verify-build.bat` passes
|