Fix deploy script: add NT8.Strategies.dll to deployment pipeline
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
This commit is contained in:
@@ -124,3 +124,8 @@ Math.Floor(x); // (via standard using System;)
|
||||
- Search for `=>` — if on a property or method, rewrite as full block
|
||||
- Search for `nameof` — replace with string literal
|
||||
- Search for `out var` — split into declaration + assignment
|
||||
|
||||
## NinjaScript attributes
|
||||
|
||||
`[Optimizable]` does not exist in NinjaScript. Use `[NinjaScriptProperty]`
|
||||
and `[Range(min, max)]` instead. See nt8compilespec.md for full NT8 API rules.
|
||||
|
||||
@@ -137,3 +137,126 @@ Compile Checklist (Preflight)
|
||||
""").format(date=datetime.date.today().isoformat())
|
||||
|
||||
files["NT8_Templates.md"] = textwrap.dedent("""
|
||||
|
||||
---
|
||||
|
||||
# NinjaScript Compiler Constraints
|
||||
|
||||
## CRITICAL: Two separate compilers exist in this project
|
||||
|
||||
This project uses TWO compilers with DIFFERENT rules:
|
||||
|
||||
1. **dotnet build** — compiles src/NT8.Core/ and src/NT8.Adapters/ as
|
||||
standard .NET Framework 4.8 assemblies. Errors here show up in the
|
||||
terminal.
|
||||
|
||||
2. **NinjaTrader NinjaScript compiler** — compiles the .cs files deployed
|
||||
to `C:\Users\billy\Documents\NinjaTrader 8\bin\Custom\Strategies\`.
|
||||
Errors here only show up inside the NT8 NinjaScript Editor, NOT in
|
||||
dotnet build output.
|
||||
|
||||
**dotnet build passing does NOT mean NT8 will compile successfully.**
|
||||
Always treat NT8 compilation as a required separate verification step.
|
||||
|
||||
---
|
||||
|
||||
## NinjaScript-specific API rules
|
||||
|
||||
### OnConnectionStatusUpdate — correct signature
|
||||
|
||||
```csharp
|
||||
// CORRECT — single ConnectionStatusEventArgs parameter
|
||||
protected override void OnConnectionStatusUpdate(
|
||||
ConnectionStatusEventArgs connectionStatusUpdate)
|
||||
{
|
||||
connectionStatusUpdate.Status // ConnectionStatus enum
|
||||
connectionStatusUpdate.PriceStatus // ConnectionStatus enum
|
||||
}
|
||||
|
||||
// WRONG — this signature does not exist in NT8
|
||||
protected override void OnConnectionStatusUpdate(
|
||||
Connection connection,
|
||||
ConnectionStatus status,
|
||||
DateTime time) // CS0115 — no suitable method found to override
|
||||
```
|
||||
|
||||
### [Optimizable] attribute — does not exist
|
||||
|
||||
```csharp
|
||||
// WRONG — OptimizableAttribute does not exist in NinjaScript
|
||||
[Optimizable]
|
||||
public int StopTicks { get; set; } // CS0246
|
||||
|
||||
// CORRECT — all [NinjaScriptProperty] params are optimizer-eligible
|
||||
// Use [Range] to set optimizer bounds
|
||||
[NinjaScriptProperty]
|
||||
[Range(1, 50)]
|
||||
public int StopTicks { get; set; }
|
||||
```
|
||||
|
||||
### Attributes that DO exist in NinjaScript
|
||||
|
||||
```csharp
|
||||
[NinjaScriptProperty] // exposes to UI and optimizer
|
||||
[Display(...)] // controls UI label, group, order
|
||||
[Range(min, max)] // sets optimizer/validation bounds
|
||||
[Browsable(false)] // hides from UI
|
||||
[XmlIgnore] // excludes from serialization
|
||||
```
|
||||
|
||||
### Attributes that do NOT exist in NinjaScript
|
||||
|
||||
```
|
||||
[Optimizable] — use [NinjaScriptProperty] + [Range] instead
|
||||
[JsonProperty] — not available
|
||||
[Required] — not available
|
||||
```
|
||||
|
||||
### Method overrides — always verify against NT8 documentation
|
||||
|
||||
Before adding any `protected override` method to NT8StrategyBase.cs or
|
||||
any NinjaScript file, verify the exact signature at:
|
||||
https://developer.ninjatrader.com/docs/desktop
|
||||
|
||||
Common signatures that differ from intuition:
|
||||
|
||||
| Method | Correct NT8 Signature |
|
||||
|---|---|
|
||||
| OnConnectionStatusUpdate | `(ConnectionStatusEventArgs e)` |
|
||||
| OnMarketData | `(MarketDataEventArgs e)` |
|
||||
| OnMarketDepth | `(MarketDepthEventArgs e)` |
|
||||
| OnOrderUpdate | `(Order order, double limitPrice, double stopPrice, int quantity, int filled, double avgFillPrice, OrderState orderState, DateTime time, ErrorCode error, string nativeError)` |
|
||||
| OnExecutionUpdate | `(Execution execution, string executionId, double price, int quantity, MarketPosition marketPosition, string orderId, DateTime time)` |
|
||||
|
||||
---
|
||||
|
||||
## Deployment reminder
|
||||
|
||||
The full deployment sequence is always:
|
||||
|
||||
```
|
||||
1. dotnet build NT8-SDK.sln --configuration Release
|
||||
2. deployment\deploy-to-nt8.bat
|
||||
3. NT8: Tools → NinjaScript Editor → Compile All
|
||||
4. Reload any open Strategy Analyzer or chart instances
|
||||
```
|
||||
|
||||
Steps 1-2 passing does NOT mean step 3 will pass. NT8 compilation is
|
||||
the only authoritative check for NinjaScript-specific code.
|
||||
|
||||
---
|
||||
|
||||
## Files compiled by NT8 (not dotnet build)
|
||||
|
||||
These files are deployed as source and compiled by NT8's embedded
|
||||
compiler. Any NT8-specific API usage in these files is invisible to
|
||||
dotnet build:
|
||||
|
||||
- `src/NT8.Adapters/Strategies/NT8StrategyBase.cs`
|
||||
- `src/NT8.Adapters/Strategies/SimpleORBNT8.cs`
|
||||
- `src/NT8.Adapters/Wrappers/BaseNT8StrategyWrapper.cs`
|
||||
- `src/NT8.Adapters/Wrappers/SimpleORBNT8Wrapper.cs`
|
||||
|
||||
Any new strategy or wrapper file added to these locations inherits the
|
||||
same constraint. When modifying these files, the NT8 compiler is the
|
||||
only valid test.
|
||||
|
||||
@@ -10,6 +10,7 @@ set "NT8_CUSTOM=%USERPROFILE%\Documents\NinjaTrader 8\bin\Custom"
|
||||
set "NT8_STRATEGIES=%NT8_CUSTOM%\Strategies"
|
||||
set "CORE_BIN=%PROJECT_ROOT%\src\NT8.Core\bin\Release\net48"
|
||||
set "ADAPTERS_BIN=%PROJECT_ROOT%\src\NT8.Adapters\bin\Release\net48"
|
||||
set "STRATEGIES_BIN=%PROJECT_ROOT%\src\NT8.Strategies\bin\Release\net48"
|
||||
set "WRAPPERS_SRC=%PROJECT_ROOT%\src\NT8.Adapters\Wrappers"
|
||||
set "BACKUP_ROOT=%SCRIPT_DIR%backups"
|
||||
|
||||
@@ -40,6 +41,13 @@ if not exist "%ADAPTERS_BIN%\NT8.Adapters.dll" (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%STRATEGIES_BIN%\NT8.Strategies.dll" (
|
||||
echo ERROR: Strategies DLL not found: %STRATEGIES_BIN%\NT8.Strategies.dll
|
||||
echo Build release artifacts first:
|
||||
echo dotnet build NT8-SDK.sln --configuration Release
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%NT8_STRATEGIES%" (
|
||||
mkdir "%NT8_STRATEGIES%"
|
||||
)
|
||||
@@ -52,6 +60,7 @@ mkdir "%BACKUP_ROOT%\%STAMP%" >nul 2>&1
|
||||
echo Backing up existing NT8 SDK files...
|
||||
if exist "%NT8_CUSTOM%\NT8.Core.dll" copy /Y "%NT8_CUSTOM%\NT8.Core.dll" "%BACKUP_DIR%\NT8.Core.dll" >nul
|
||||
if exist "%NT8_CUSTOM%\NT8.Adapters.dll" copy /Y "%NT8_CUSTOM%\NT8.Adapters.dll" "%BACKUP_DIR%\NT8.Adapters.dll" >nul
|
||||
if exist "%NT8_CUSTOM%\NT8.Strategies.dll" copy /Y "%NT8_CUSTOM%\NT8.Strategies.dll" "%BACKUP_DIR%\NT8.Strategies.dll" >nul
|
||||
if exist "%NT8_STRATEGIES%\BaseNT8StrategyWrapper.cs" copy /Y "%NT8_STRATEGIES%\BaseNT8StrategyWrapper.cs" "%BACKUP_DIR%\BaseNT8StrategyWrapper.cs" >nul
|
||||
if exist "%NT8_STRATEGIES%\SimpleORBNT8Wrapper.cs" copy /Y "%NT8_STRATEGIES%\SimpleORBNT8Wrapper.cs" "%BACKUP_DIR%\SimpleORBNT8Wrapper.cs" >nul
|
||||
|
||||
@@ -59,6 +68,7 @@ echo Deployment manifest > "%MANIFEST_FILE%"
|
||||
echo Timestamp: %STAMP%>> "%MANIFEST_FILE%"
|
||||
echo Source Core DLL: %CORE_BIN%\NT8.Core.dll>> "%MANIFEST_FILE%"
|
||||
echo Source Adapters DLL: %ADAPTERS_BIN%\NT8.Adapters.dll>> "%MANIFEST_FILE%"
|
||||
echo Source Strategies DLL: %STRATEGIES_BIN%\NT8.Strategies.dll>> "%MANIFEST_FILE%"
|
||||
echo Destination Custom Folder: %NT8_CUSTOM%>> "%MANIFEST_FILE%"
|
||||
echo Destination Strategies Folder: %NT8_STRATEGIES%>> "%MANIFEST_FILE%"
|
||||
|
||||
@@ -75,6 +85,12 @@ if errorlevel 1 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
copy /Y "%STRATEGIES_BIN%\NT8.Strategies.dll" "%NT8_CUSTOM%\NT8.Strategies.dll" >nul
|
||||
if errorlevel 1 (
|
||||
echo ERROR: Failed to copy NT8.Strategies.dll
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Deploying wrapper sources...
|
||||
copy /Y "%WRAPPERS_SRC%\BaseNT8StrategyWrapper.cs" "%NT8_STRATEGIES%\BaseNT8StrategyWrapper.cs" >nul
|
||||
if errorlevel 1 (
|
||||
@@ -112,6 +128,11 @@ if not exist "%NT8_CUSTOM%\NT8.Adapters.dll" (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%NT8_CUSTOM%\NT8.Strategies.dll" (
|
||||
echo ERROR: Verification failed for NT8.Strategies.dll
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%NT8_STRATEGIES%\BaseNT8StrategyWrapper.cs" (
|
||||
echo ERROR: Verification failed for BaseNT8StrategyWrapper.cs
|
||||
exit /b 1
|
||||
@@ -133,4 +154,3 @@ echo 2. Open NinjaScript Editor and press F5 (Compile).
|
||||
echo 3. Verify strategies appear in the Strategies list.
|
||||
|
||||
exit /b 0
|
||||
|
||||
|
||||
@@ -203,9 +203,12 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
{
|
||||
try
|
||||
{
|
||||
InitFileLog();
|
||||
InitializeSdkComponents();
|
||||
_sdkInitialized = true;
|
||||
Print(string.Format("[SDK] {0} initialized successfully", Name));
|
||||
WriteSettingsFile();
|
||||
WriteSessionHeader();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -217,8 +220,7 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
}
|
||||
else if (State == State.Realtime)
|
||||
{
|
||||
InitFileLog();
|
||||
WriteSessionHeader();
|
||||
WriteSettingsFile();
|
||||
}
|
||||
else if (State == State.Terminated)
|
||||
{
|
||||
@@ -229,6 +231,12 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
|
||||
protected override void OnBarUpdate()
|
||||
{
|
||||
// Only process primary bar series — ignore secondary data series updates.
|
||||
// Secondary series (e.g. daily bars for confluence) trigger OnBarUpdate separately
|
||||
// and must never generate strategy signals.
|
||||
if (BarsInProgress != 0)
|
||||
return;
|
||||
|
||||
if (!_sdkInitialized || _sdkStrategy == null)
|
||||
{
|
||||
return;
|
||||
@@ -244,6 +252,9 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
|
||||
_lastBarTime = Time[0];
|
||||
|
||||
// Sync actual open position to portfolio manager on every bar
|
||||
PortfolioRiskManager.Instance.UpdateOpenContracts(Name, Math.Abs(Position.Quantity));
|
||||
|
||||
// Kill switch — checked AFTER bar guards so ExitLong/ExitShort are valid
|
||||
if (EnableKillSwitch)
|
||||
{
|
||||
@@ -272,6 +283,34 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
return;
|
||||
}
|
||||
|
||||
// Hard RTH guard using NT8 bar time converted from CT to ET.
|
||||
// Belt-and-suspenders against SDK session timezone issues.
|
||||
DateTime ntBarTimeEt;
|
||||
try
|
||||
{
|
||||
var centralZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
|
||||
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
|
||||
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(
|
||||
DateTime.SpecifyKind(Time[0], DateTimeKind.Unspecified),
|
||||
centralZone);
|
||||
ntBarTimeEt = TimeZoneInfo.ConvertTimeFromUtc(utcTime, easternZone);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ntBarTimeEt = Time[0];
|
||||
}
|
||||
|
||||
bool isRthBar = ntBarTimeEt.TimeOfDay >= new TimeSpan(9, 30, 0)
|
||||
&& ntBarTimeEt.TimeOfDay < new TimeSpan(16, 0, 0);
|
||||
|
||||
if (!isRthBar)
|
||||
{
|
||||
if (EnableVerboseLogging && CurrentBar % 500 == 0)
|
||||
Print(string.Format("[SDK] Skipping ETH bar {0} at {1:HH:mm} ET",
|
||||
CurrentBar, ntBarTimeEt));
|
||||
return;
|
||||
}
|
||||
|
||||
// Log first processable bar and every 100th bar.
|
||||
if (CurrentBar == BarsRequiredToTrade || CurrentBar % 100 == 0)
|
||||
{
|
||||
@@ -384,34 +423,35 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
/// <summary>
|
||||
/// Handles broker connection status changes. Halts new orders on disconnect,
|
||||
/// logs reconnect, and resets the connection flag when restored.
|
||||
/// NinjaScript signature: single ConnectionStatusEventArgs parameter.
|
||||
/// </summary>
|
||||
protected override void OnConnectionStatusUpdate(
|
||||
Connection connection,
|
||||
ConnectionStatus status,
|
||||
DateTime time)
|
||||
ConnectionStatusEventArgs connectionStatusUpdate)
|
||||
{
|
||||
if (connection == null) return;
|
||||
if (connectionStatusUpdate == null) return;
|
||||
|
||||
if (status == ConnectionStatus.Connected)
|
||||
if (connectionStatusUpdate.Status == ConnectionStatus.Connected)
|
||||
{
|
||||
if (_connectionLost)
|
||||
{
|
||||
_connectionLost = false;
|
||||
Print(string.Format("[NT8-SDK] Connection RESTORED at {0} — trading resumed.",
|
||||
time.ToString("HH:mm:ss")));
|
||||
FileLog(string.Format("CONNECTION RESTORED at {0}", time.ToString("HH:mm:ss")));
|
||||
DateTime.Now.ToString("HH:mm:ss")));
|
||||
FileLog(string.Format("CONNECTION RESTORED at {0}", DateTime.Now.ToString("HH:mm:ss")));
|
||||
}
|
||||
}
|
||||
else if (status == ConnectionStatus.Disconnected ||
|
||||
status == ConnectionStatus.ConnectionLost)
|
||||
else if (connectionStatusUpdate.Status == ConnectionStatus.Disconnected ||
|
||||
connectionStatusUpdate.Status == ConnectionStatus.ConnectionLost)
|
||||
{
|
||||
if (!_connectionLost)
|
||||
{
|
||||
_connectionLost = true;
|
||||
Print(string.Format("[NT8-SDK] Connection LOST at {0} — halting new orders. Status={1}",
|
||||
time.ToString("HH:mm:ss"),
|
||||
status));
|
||||
FileLog(string.Format("CONNECTION LOST at {0} Status={1}", time.ToString("HH:mm:ss"), status));
|
||||
DateTime.Now.ToString("HH:mm:ss"),
|
||||
connectionStatusUpdate.Status));
|
||||
FileLog(string.Format("CONNECTION LOST at {0} Status={1}",
|
||||
DateTime.Now.ToString("HH:mm:ss"),
|
||||
connectionStatusUpdate.Status));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,6 +505,7 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
private void WriteSessionHeader()
|
||||
{
|
||||
FileLog("=== SESSION START " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " ===");
|
||||
FileLog(string.Format("Mode : {0}", State == State.Historical ? "BACKTEST" : "LIVE/SIM"));
|
||||
FileLog(string.Format("Strategy : {0}", Name));
|
||||
FileLog(string.Format("Account : {0}", Account != null ? Account.Name : "N/A"));
|
||||
FileLog(string.Format("Symbol : {0}", Instrument != null ? Instrument.FullName : "N/A"));
|
||||
@@ -566,8 +607,12 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
DateTime etTime;
|
||||
try
|
||||
{
|
||||
var centralZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
|
||||
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
|
||||
etTime = TimeZoneInfo.ConvertTime(Time[0], easternZone);
|
||||
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(
|
||||
DateTime.SpecifyKind(Time[0], DateTimeKind.Unspecified),
|
||||
centralZone);
|
||||
etTime = TimeZoneInfo.ConvertTimeFromUtc(utcTime, easternZone);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -630,16 +675,37 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
DateTime etTime;
|
||||
try
|
||||
{
|
||||
var centralZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
|
||||
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
|
||||
etTime = TimeZoneInfo.ConvertTime(Time[0], easternZone);
|
||||
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(
|
||||
DateTime.SpecifyKind(Time[0], DateTimeKind.Unspecified),
|
||||
centralZone);
|
||||
etTime = TimeZoneInfo.ConvertTimeFromUtc(utcTime, easternZone);
|
||||
}
|
||||
catch
|
||||
{
|
||||
etTime = Time[0];
|
||||
}
|
||||
|
||||
var sessionStart = etTime.Date.AddHours(9).AddMinutes(30);
|
||||
var sessionEnd = etTime.Date.AddHours(16);
|
||||
// Futures trade nearly 24 hours. Bars at/after 17:00 ET belong to the next
|
||||
// calendar day's RTH trading session.
|
||||
DateTime tradingDate;
|
||||
if (etTime.TimeOfDay >= new TimeSpan(17, 0, 0))
|
||||
tradingDate = etTime.Date.AddDays(1);
|
||||
else
|
||||
tradingDate = etTime.Date;
|
||||
|
||||
if (EnableVerboseLogging && (CurrentBar == BarsRequiredToTrade || CurrentBar % 500 == 0))
|
||||
{
|
||||
Print(string.Format("[SDK-TZ] Bar {0}: NT8 Time[0]={1:yyyy-MM-dd HH:mm:ss} | etTime={2:yyyy-MM-dd HH:mm:ss} | isRth={3}",
|
||||
CurrentBar,
|
||||
Time[0],
|
||||
etTime,
|
||||
etTime.TimeOfDay >= TimeSpan.FromHours(9.5) && etTime.TimeOfDay < TimeSpan.FromHours(16.0)));
|
||||
}
|
||||
|
||||
var sessionStart = tradingDate.AddHours(9).AddMinutes(30);
|
||||
var sessionEnd = tradingDate.AddHours(16);
|
||||
var isRth = etTime.TimeOfDay >= TimeSpan.FromHours(9.5)
|
||||
&& etTime.TimeOfDay < TimeSpan.FromHours(16.0);
|
||||
|
||||
@@ -843,5 +909,82 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
return null;
|
||||
return _executionAdapter.GetOrderStatus(orderName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all strategy parameter lines for the settings export file.
|
||||
/// Override in subclasses to append strategy-specific parameters.
|
||||
/// Call base.GetStrategySettingsLines() first then add to the list.
|
||||
/// </summary>
|
||||
protected virtual List<string> GetStrategySettingsLines()
|
||||
{
|
||||
var lines = new List<string>();
|
||||
lines.Add("=== STRATEGY SETTINGS EXPORT ===");
|
||||
lines.Add(string.Format("ExportTime : {0:yyyy-MM-dd HH:mm:ss}", DateTime.Now));
|
||||
lines.Add(string.Format("StrategyName : {0}", Name));
|
||||
lines.Add(string.Format("Description : {0}", Description));
|
||||
lines.Add(string.Format("Account : {0}", Account != null ? Account.Name : "N/A"));
|
||||
lines.Add(string.Format("Instrument : {0}", Instrument != null ? Instrument.FullName : "N/A"));
|
||||
lines.Add(string.Format("BarsPeriod : {0} {1}", BarsPeriod != null ? BarsPeriod.Value.ToString() : "N/A", BarsPeriod != null ? BarsPeriod.BarsPeriodType.ToString() : string.Empty));
|
||||
lines.Add(string.Format("BarsRequiredToTrade: {0}", BarsRequiredToTrade));
|
||||
lines.Add(string.Format("Calculate : {0}", Calculate));
|
||||
lines.Add("--- Risk ---");
|
||||
lines.Add(string.Format("DailyLossLimit : {0:C}", DailyLossLimit));
|
||||
lines.Add(string.Format("MaxTradeRisk : {0:C}", MaxTradeRisk));
|
||||
lines.Add(string.Format("MaxOpenPositions : {0}", MaxOpenPositions));
|
||||
lines.Add(string.Format("RiskPerTrade : {0:C}", RiskPerTrade));
|
||||
lines.Add("--- Sizing ---");
|
||||
lines.Add(string.Format("MinContracts : {0}", MinContracts));
|
||||
lines.Add(string.Format("MaxContracts : {0}", MaxContracts));
|
||||
lines.Add("--- Direction ---");
|
||||
lines.Add(string.Format("EnableLongTrades : {0}", EnableLongTrades));
|
||||
lines.Add(string.Format("EnableShortTrades : {0}", EnableShortTrades));
|
||||
lines.Add("--- Controls ---");
|
||||
lines.Add(string.Format("EnableKillSwitch : {0}", EnableKillSwitch));
|
||||
lines.Add(string.Format("EnableVerboseLogging: {0}", EnableVerboseLogging));
|
||||
lines.Add(string.Format("EnableFileLogging : {0}", EnableFileLogging));
|
||||
lines.Add(string.Format("LogDirectory : {0}", string.IsNullOrEmpty(LogDirectory) ? "(default)" : LogDirectory));
|
||||
lines.Add("--- Portfolio ---");
|
||||
lines.Add(string.Format("PortfolioStatus : {0}", PortfolioRiskManager.Instance.GetStatusSnapshot()));
|
||||
lines.Add("=== END SETTINGS ===");
|
||||
return lines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a settings export file to the same directory as the session log.
|
||||
/// File is named settings_STRATEGYNAME_YYYYMMDD_HHmmss.txt.
|
||||
/// Only writes when EnableVerboseLogging is true.
|
||||
/// </summary>
|
||||
private void WriteSettingsFile()
|
||||
{
|
||||
if (!EnableVerboseLogging) return;
|
||||
|
||||
try
|
||||
{
|
||||
string dir = string.IsNullOrEmpty(LogDirectory)
|
||||
? System.IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||
"NinjaTrader 8", "log", "nt8-sdk")
|
||||
: LogDirectory;
|
||||
|
||||
System.IO.Directory.CreateDirectory(dir);
|
||||
|
||||
string safeName = Name.Replace(" ", "_").Replace("/", "_").Replace("\\", "_");
|
||||
string path = System.IO.Path.Combine(dir,
|
||||
string.Format("settings_{0}_{1}.txt",
|
||||
safeName,
|
||||
DateTime.Now.ToString("yyyyMMdd_HHmmss")));
|
||||
|
||||
var lines = GetStrategySettingsLines();
|
||||
|
||||
System.IO.File.WriteAllLines(path, lines.ToArray());
|
||||
|
||||
Print(string.Format("[NT8-SDK] Settings exported: {0}", path));
|
||||
FileLog(string.Format("SETTINGS FILE: {0}", path));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Print(string.Format("[NT8-SDK] WARNING: Could not write settings file: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
public class SimpleORBNT8 : NT8StrategyBase
|
||||
{
|
||||
[NinjaScriptProperty]
|
||||
[Optimizable]
|
||||
[Display(Name = "Opening Range Minutes", GroupName = "ORB Strategy", Order = 1)]
|
||||
[Range(5, 120)]
|
||||
public int OpeningRangeMinutes { get; set; }
|
||||
@@ -35,13 +34,11 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
public double StdDevMultiplier { get; set; }
|
||||
|
||||
[NinjaScriptProperty]
|
||||
[Optimizable]
|
||||
[Display(Name = "Stop Loss Ticks", GroupName = "ORB Risk", Order = 1)]
|
||||
[Range(1, 50)]
|
||||
public int StopTicks { get; set; }
|
||||
|
||||
[NinjaScriptProperty]
|
||||
[Optimizable]
|
||||
[Display(Name = "Profit Target Ticks", GroupName = "ORB Risk", Order = 2)]
|
||||
[Range(1, 100)]
|
||||
public int TargetTicks { get; set; }
|
||||
@@ -51,7 +48,7 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
if (State == State.SetDefaults)
|
||||
{
|
||||
Name = "Simple ORB NT8";
|
||||
Description = "Opening Range Breakout with NT8 SDK integration";
|
||||
Description = "v0.4.0 | 2026-03-19 | NR7+ORB factors, PortfolioRiskManager, connection recovery, live account balance";
|
||||
|
||||
// Daily bar series is added automatically via AddDataSeries in Configure.
|
||||
|
||||
@@ -139,6 +136,32 @@ namespace NinjaTrader.NinjaScript.Strategies
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends ORB-specific parameters to the base settings export.
|
||||
/// </summary>
|
||||
protected override List<string> GetStrategySettingsLines()
|
||||
{
|
||||
var lines = base.GetStrategySettingsLines();
|
||||
|
||||
// Insert ORB section before the final === END SETTINGS === line
|
||||
int endIdx = lines.Count - 1;
|
||||
lines.Insert(endIdx, "--- ORB Strategy ---");
|
||||
lines.Insert(endIdx + 1, string.Format("OpeningRangeMinutes: {0}", OpeningRangeMinutes));
|
||||
lines.Insert(endIdx + 2, string.Format("StdDevMultiplier : {0:F2}", StdDevMultiplier));
|
||||
lines.Insert(endIdx + 3, string.Format("StopTicks : {0}", StopTicks));
|
||||
lines.Insert(endIdx + 4, string.Format("TargetTicks : {0}", TargetTicks));
|
||||
|
||||
double tickDollarValue = 0.25 * 50.0;
|
||||
if (Instrument != null && Instrument.MasterInstrument != null)
|
||||
tickDollarValue = Instrument.MasterInstrument.TickSize * Instrument.MasterInstrument.PointValue;
|
||||
|
||||
lines.Insert(endIdx + 5, string.Format("StopDollars : {0:C}", StopTicks * tickDollarValue));
|
||||
lines.Insert(endIdx + 6, string.Format("TargetDollars : {0:C}", TargetTicks * tickDollarValue));
|
||||
lines.Insert(endIdx + 7, string.Format("RR_Ratio : {0:F2}:1", (double)TargetTicks / StopTicks));
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a DailyBarContext from the secondary daily bar series.
|
||||
/// Returns a context with Count=0 if fewer than 2 daily bars are available.
|
||||
|
||||
@@ -188,23 +188,34 @@ namespace NT8.Core.Risk
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reports a fill to the portfolio manager. Updates open contract count for the strategy.
|
||||
/// Called from NT8StrategyBase.OnExecutionUpdate() after each fill.
|
||||
/// Reports a fill to the portfolio manager.
|
||||
/// Contract tracking is handled by UpdateOpenContracts().
|
||||
/// </summary>
|
||||
/// <param name="strategyId">Strategy that received the fill.</param>
|
||||
/// <param name="fill">Fill details.</param>
|
||||
public void ReportFill(string strategyId, OrderFill fill)
|
||||
{
|
||||
// Contract tracking is now handled by UpdateOpenContracts() called
|
||||
// from OnBarUpdate with the actual position size.
|
||||
// This method is retained for API compatibility and future P&L attribution use.
|
||||
if (string.IsNullOrEmpty(strategyId) || fill == null) return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the open contract count for a strategy to the actual current
|
||||
/// position size. Called from NT8StrategyBase on each bar close.
|
||||
/// This replaces fill-based inference with authoritative position data.
|
||||
/// </summary>
|
||||
/// <param name="strategyId">Strategy identifier.</param>
|
||||
/// <param name="openContracts">Actual number of open contracts (0 when flat).</param>
|
||||
public void UpdateOpenContracts(string strategyId, int openContracts)
|
||||
{
|
||||
if (string.IsNullOrEmpty(strategyId)) return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_strategyOpenContracts.ContainsKey(strategyId))
|
||||
_strategyOpenContracts[strategyId] = 0;
|
||||
|
||||
_strategyOpenContracts[strategyId] += fill.Quantity;
|
||||
if (_strategyOpenContracts[strategyId] < 0)
|
||||
_strategyOpenContracts[strategyId] = 0;
|
||||
if (openContracts < 0) openContracts = 0;
|
||||
_strategyOpenContracts[strategyId] = openContracts;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,6 +176,16 @@ namespace NT8.Strategies.Examples
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_logger != null && _openingRangeReady)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"ORB ready: High={0:F2} Low={1:F2} Range={2:F2} TradeTaken={3}",
|
||||
_openingRangeHigh,
|
||||
_openingRangeLow,
|
||||
_openingRangeHigh - _openingRangeLow,
|
||||
_tradeTaken);
|
||||
}
|
||||
|
||||
if (_tradeTaken)
|
||||
return null;
|
||||
|
||||
@@ -219,7 +229,8 @@ namespace NT8.Strategies.Examples
|
||||
|
||||
candidate.Confidence = score.WeightedScore;
|
||||
candidate.Reason = string.Format("{0}; grade={1}; mode={2}", candidate.Reason, score.Grade, mode);
|
||||
candidate.Metadata["confluence_score"] = score.WeightedScore;
|
||||
candidate.Metadata["confluence_score"] = score;
|
||||
candidate.Metadata["confluence_weighted_score"] = score.WeightedScore;
|
||||
candidate.Metadata["trade_grade"] = score.Grade.ToString();
|
||||
candidate.Metadata["risk_mode"] = mode.ToString();
|
||||
candidate.Metadata["grade_multiplier"] = gradeMultiplier;
|
||||
|
||||
@@ -52,10 +52,7 @@ namespace NT8.Core.Tests.Risk
|
||||
_manager.RegisterStrategy("strat1", TestDataBuilder.CreateTestRiskConfig());
|
||||
_manager.MaxTotalOpenContracts = 2;
|
||||
|
||||
var fill1 = new OrderFill("ord1", "ES", 1, 5000.0, System.DateTime.UtcNow, 0.0, "exec1");
|
||||
var fill2 = new OrderFill("ord2", "ES", 1, 5001.0, System.DateTime.UtcNow, 0.0, "exec2");
|
||||
_manager.ReportFill("strat1", fill1);
|
||||
_manager.ReportFill("strat1", fill2);
|
||||
_manager.UpdateOpenContracts("strat1", 2);
|
||||
var intent = TestDataBuilder.CreateValidIntent();
|
||||
|
||||
// Act
|
||||
@@ -65,6 +62,26 @@ namespace NT8.Core.Tests.Risk
|
||||
Assert.IsFalse(decision.Allow);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateOpenContracts_WhenPositionCloses_UnblocksTrading()
|
||||
{
|
||||
// Arrange
|
||||
_manager.RegisterStrategy("strat1", TestDataBuilder.CreateTestRiskConfig());
|
||||
_manager.MaxTotalOpenContracts = 6;
|
||||
var intent = TestDataBuilder.CreateValidIntent();
|
||||
|
||||
// Act
|
||||
_manager.UpdateOpenContracts("strat1", 6);
|
||||
var blocked = _manager.ValidatePortfolioRisk("strat1", intent);
|
||||
|
||||
_manager.UpdateOpenContracts("strat1", 0);
|
||||
var unblocked = _manager.ValidatePortfolioRisk("strat1", intent);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(blocked.Allow);
|
||||
Assert.IsTrue(unblocked.Allow);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PortfolioKillSwitch_WhenTrue_BlocksAllOrders()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user