Production hardening: kill switch, circuit breaker, trailing stops, log level, holiday calendar
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:
550
CONFIG_EXPORT_SPEC.md
Normal file
550
CONFIG_EXPORT_SPEC.md
Normal file
@@ -0,0 +1,550 @@
|
||||
# Configuration Export/Import - Implementation Specification
|
||||
|
||||
**For:** Kilocode AI Agent
|
||||
**Priority:** HIGH
|
||||
**Mode:** Code Mode
|
||||
**Estimated Time:** 1.5-2 hours
|
||||
**Files to Edit:** 1 file (NT8StrategyBase.cs)
|
||||
**Files to Create:** 1 file (StrategyConfigExporter.cs)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objective
|
||||
|
||||
Add ability to export NT8 strategy configuration as JSON for:
|
||||
- Easy sharing with support/debugging
|
||||
- Version control of strategy settings
|
||||
- Configuration backup/restore
|
||||
- Reproducible backtests
|
||||
|
||||
---
|
||||
|
||||
## 📋 What We're Adding
|
||||
|
||||
### 1. Export Configuration Button/Method
|
||||
User can click a button (or call a method) to export all strategy settings as JSON file.
|
||||
|
||||
### 2. Import Configuration Method
|
||||
User can load settings from a previously exported JSON file.
|
||||
|
||||
### 3. Automatic Export on Strategy Start
|
||||
Optionally auto-export config to a timestamped file when strategy starts.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Implementation
|
||||
|
||||
### Component 1: StrategyConfigExporter.cs
|
||||
|
||||
**Location:** `src/NT8.Adapters/Strategies/StrategyConfigExporter.cs`
|
||||
|
||||
**Purpose:** Static helper class to serialize/deserialize strategy configurations
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace NT8.Adapters.Strategies
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to export/import NT8 strategy configurations as JSON.
|
||||
/// Enables configuration sharing, backup, and reproducible testing.
|
||||
/// </summary>
|
||||
public static class StrategyConfigExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Export strategy configuration to JSON string.
|
||||
/// </summary>
|
||||
public static string ExportToJson(Dictionary<string, object> config)
|
||||
{
|
||||
if (config == null || config.Count == 0)
|
||||
return "{}";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("{");
|
||||
|
||||
var first = true;
|
||||
foreach (var kvp in config)
|
||||
{
|
||||
if (!first)
|
||||
sb.AppendLine(",");
|
||||
first = false;
|
||||
|
||||
sb.Append(" \"");
|
||||
sb.Append(EscapeJsonString(kvp.Key));
|
||||
sb.Append("\": ");
|
||||
|
||||
AppendValue(sb, kvp.Value);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.Append("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save configuration to JSON file.
|
||||
/// </summary>
|
||||
public static void ExportToFile(Dictionary<string, object> config, string filepath)
|
||||
{
|
||||
var json = ExportToJson(config);
|
||||
File.WriteAllText(filepath, json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import configuration from JSON string.
|
||||
/// Simple parser for basic types (string, int, double, bool).
|
||||
/// </summary>
|
||||
public static Dictionary<string, object> ImportFromJson(string json)
|
||||
{
|
||||
var config = new Dictionary<string, object>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return config;
|
||||
|
||||
// Remove outer braces and whitespace
|
||||
json = json.Trim();
|
||||
if (json.StartsWith("{"))
|
||||
json = json.Substring(1);
|
||||
if (json.EndsWith("}"))
|
||||
json = json.Substring(0, json.Length - 1);
|
||||
|
||||
// Split by commas (simple parser - doesn't handle nested objects)
|
||||
var lines = json.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var colonIndex = line.IndexOf(':');
|
||||
if (colonIndex < 0)
|
||||
continue;
|
||||
|
||||
var key = line.Substring(0, colonIndex).Trim().Trim('"');
|
||||
var valueStr = line.Substring(colonIndex + 1).Trim();
|
||||
|
||||
var value = ParseValue(valueStr);
|
||||
config[key] = value;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import configuration from JSON file.
|
||||
/// </summary>
|
||||
public static Dictionary<string, object> ImportFromFile(string filepath)
|
||||
{
|
||||
if (!File.Exists(filepath))
|
||||
throw new FileNotFoundException("Config file not found", filepath);
|
||||
|
||||
var json = File.ReadAllText(filepath);
|
||||
return ImportFromJson(json);
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static void AppendValue(StringBuilder sb, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
sb.Append("null");
|
||||
}
|
||||
else if (value is string)
|
||||
{
|
||||
sb.Append("\"");
|
||||
sb.Append(EscapeJsonString(value.ToString()));
|
||||
sb.Append("\"");
|
||||
}
|
||||
else if (value is bool)
|
||||
{
|
||||
sb.Append(((bool)value) ? "true" : "false");
|
||||
}
|
||||
else if (value is int || value is long || value is double || value is decimal || value is float)
|
||||
{
|
||||
sb.Append(value.ToString());
|
||||
}
|
||||
else if (value is DateTime)
|
||||
{
|
||||
sb.Append("\"");
|
||||
sb.Append(((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
sb.Append("\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: ToString()
|
||||
sb.Append("\"");
|
||||
sb.Append(EscapeJsonString(value.ToString()));
|
||||
sb.Append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static string EscapeJsonString(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return str;
|
||||
|
||||
return str
|
||||
.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\n", "\\n")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\t", "\\t");
|
||||
}
|
||||
|
||||
private static object ParseValue(string valueStr)
|
||||
{
|
||||
valueStr = valueStr.Trim();
|
||||
|
||||
// Remove trailing comma if present
|
||||
if (valueStr.EndsWith(","))
|
||||
valueStr = valueStr.Substring(0, valueStr.Length - 1).Trim();
|
||||
|
||||
// Null
|
||||
if (valueStr == "null")
|
||||
return null;
|
||||
|
||||
// Boolean
|
||||
if (valueStr == "true")
|
||||
return true;
|
||||
if (valueStr == "false")
|
||||
return false;
|
||||
|
||||
// String (quoted)
|
||||
if (valueStr.StartsWith("\"") && valueStr.EndsWith("\""))
|
||||
{
|
||||
var str = valueStr.Substring(1, valueStr.Length - 2);
|
||||
return UnescapeJsonString(str);
|
||||
}
|
||||
|
||||
// Number - try int, then double
|
||||
int intVal;
|
||||
if (int.TryParse(valueStr, out intVal))
|
||||
return intVal;
|
||||
|
||||
double doubleVal;
|
||||
if (double.TryParse(valueStr, out doubleVal))
|
||||
return doubleVal;
|
||||
|
||||
// Fallback: return as string
|
||||
return valueStr;
|
||||
}
|
||||
|
||||
private static string UnescapeJsonString(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str))
|
||||
return str;
|
||||
|
||||
return str
|
||||
.Replace("\\\"", "\"")
|
||||
.Replace("\\\\", "\\")
|
||||
.Replace("\\n", "\n")
|
||||
.Replace("\\r", "\r")
|
||||
.Replace("\\t", "\t");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Component 2: Add Export Methods to NT8StrategyBase.cs
|
||||
|
||||
**File:** `src/NT8.Adapters/Strategies/NT8StrategyBase.cs`
|
||||
|
||||
**Add these properties and methods:**
|
||||
|
||||
```csharp
|
||||
#region Configuration Export/Import
|
||||
|
||||
[NinjaScriptProperty]
|
||||
[Display(Name = "Auto Export Config", GroupName = "SDK", Order = 10)]
|
||||
public bool AutoExportConfig { get; set; }
|
||||
|
||||
[NinjaScriptProperty]
|
||||
[Display(Name = "Config Export Path", GroupName = "SDK", Order = 11)]
|
||||
public string ConfigExportPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Export current strategy configuration to JSON string.
|
||||
/// Can be called from derived strategies or used for debugging.
|
||||
/// </summary>
|
||||
public string ExportConfigurationJson()
|
||||
{
|
||||
var config = new Dictionary<string, object>();
|
||||
|
||||
// Basic info
|
||||
config["StrategyName"] = Name;
|
||||
config["ExportedAt"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
config["Instrument"] = Instrument != null ? Instrument.FullName : "Not Set";
|
||||
config["BarsPeriod"] = BarsPeriod != null ? BarsPeriod.ToString() : "Not Set";
|
||||
|
||||
// SDK settings
|
||||
config["EnableSDK"] = EnableSDK;
|
||||
config["AutoExportConfig"] = AutoExportConfig;
|
||||
config["ConfigExportPath"] = ConfigExportPath ?? "";
|
||||
|
||||
// Risk settings
|
||||
config["DailyLossLimit"] = DailyLossLimit;
|
||||
config["MaxTradeRisk"] = MaxTradeRisk;
|
||||
config["MaxOpenPositions"] = MaxOpenPositions;
|
||||
|
||||
// Sizing settings
|
||||
config["RiskPerTrade"] = RiskPerTrade;
|
||||
config["MinContracts"] = MinContracts;
|
||||
config["MaxContracts"] = MaxContracts;
|
||||
|
||||
// NT8 settings
|
||||
config["BarsRequiredToTrade"] = BarsRequiredToTrade;
|
||||
config["Calculate"] = Calculate.ToString();
|
||||
config["EntriesPerDirection"] = EntriesPerDirection;
|
||||
config["StartBehavior"] = StartBehavior.ToString();
|
||||
|
||||
return StrategyConfigExporter.ExportToJson(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export configuration to file.
|
||||
/// </summary>
|
||||
public void ExportConfigurationToFile(string filepath)
|
||||
{
|
||||
var config = GetConfigurationDictionary();
|
||||
StrategyConfigExporter.ExportToFile(config, filepath);
|
||||
Print(string.Format("[SDK] Configuration exported to: {0}", filepath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get configuration as dictionary for export.
|
||||
/// </summary>
|
||||
protected Dictionary<string, object> GetConfigurationDictionary()
|
||||
{
|
||||
var config = new Dictionary<string, object>();
|
||||
|
||||
config["StrategyName"] = Name;
|
||||
config["ExportedAt"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
config["Instrument"] = Instrument != null ? Instrument.FullName : "Not Set";
|
||||
config["BarsPeriod"] = BarsPeriod != null ? BarsPeriod.ToString() : "Not Set";
|
||||
|
||||
config["EnableSDK"] = EnableSDK;
|
||||
config["AutoExportConfig"] = AutoExportConfig;
|
||||
config["ConfigExportPath"] = ConfigExportPath ?? "";
|
||||
|
||||
config["DailyLossLimit"] = DailyLossLimit;
|
||||
config["MaxTradeRisk"] = MaxTradeRisk;
|
||||
config["MaxOpenPositions"] = MaxOpenPositions;
|
||||
|
||||
config["RiskPerTrade"] = RiskPerTrade;
|
||||
config["MinContracts"] = MinContracts;
|
||||
config["MaxContracts"] = MaxContracts;
|
||||
|
||||
config["BarsRequiredToTrade"] = BarsRequiredToTrade;
|
||||
config["Calculate"] = Calculate.ToString();
|
||||
config["EntriesPerDirection"] = EntriesPerDirection;
|
||||
config["StartBehavior"] = StartBehavior.ToString();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print configuration to Output window for easy copy/paste.
|
||||
/// </summary>
|
||||
public void PrintConfiguration()
|
||||
{
|
||||
var json = ExportConfigurationJson();
|
||||
Print("=== Strategy Configuration ===");
|
||||
Print(json);
|
||||
Print("=== End Configuration ===");
|
||||
}
|
||||
|
||||
#endregion
|
||||
```
|
||||
|
||||
**Update OnStateChange() to handle auto-export:**
|
||||
|
||||
Find the `State.DataLoaded` section and add auto-export:
|
||||
|
||||
```csharp
|
||||
else if (State == State.DataLoaded)
|
||||
{
|
||||
if (EnableSDK)
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeSdkComponents();
|
||||
_sdkInitialized = true;
|
||||
|
||||
Print(string.Format("[SDK] {0} initialized successfully", Name));
|
||||
|
||||
// Auto-export configuration if enabled
|
||||
if (AutoExportConfig)
|
||||
{
|
||||
var exportPath = ConfigExportPath;
|
||||
|
||||
// Default path if not specified
|
||||
if (string.IsNullOrEmpty(exportPath))
|
||||
{
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
|
||||
var filename = string.Format("{0}_{1}_config.json", Name, timestamp);
|
||||
exportPath = System.IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||
"NinjaTrader 8",
|
||||
"logs",
|
||||
filename
|
||||
);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ExportConfigurationToFile(exportPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Print(string.Format("[SDK] Failed to export config: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
// Print config to Output window for easy access
|
||||
PrintConfiguration();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Print(string.Format("[SDK ERROR] Initialization failed: {0}", ex.Message));
|
||||
Log(string.Format("[SDK ERROR] {0}", ex.ToString()), LogLevel.Error);
|
||||
_sdkInitialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Update State.SetDefaults to include new properties:**
|
||||
|
||||
```csharp
|
||||
if (State == State.SetDefaults)
|
||||
{
|
||||
// ... existing code ...
|
||||
|
||||
// SDK configuration export
|
||||
AutoExportConfig = false; // Off by default
|
||||
ConfigExportPath = ""; // Empty = auto-generate path
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Component 3: Update SimpleORBNT8.cs
|
||||
|
||||
**Add SimpleORB-specific configuration export:**
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Export SimpleORB-specific configuration.
|
||||
/// Overrides base to include ORB parameters.
|
||||
/// </summary>
|
||||
protected new Dictionary<string, object> GetConfigurationDictionary()
|
||||
{
|
||||
var config = base.GetConfigurationDictionary();
|
||||
|
||||
// Add ORB-specific settings
|
||||
config["OpeningRangeMinutes"] = OpeningRangeMinutes;
|
||||
config["StdDevMultiplier"] = StdDevMultiplier;
|
||||
config["StopTicks"] = StopTicks;
|
||||
config["TargetTicks"] = TargetTicks;
|
||||
|
||||
return config;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification
|
||||
|
||||
### Manual Test
|
||||
|
||||
1. **Build:**
|
||||
```bash
|
||||
dotnet build src\NT8.Adapters\NT8.Adapters.csproj --configuration Release
|
||||
```
|
||||
|
||||
2. **Deploy:**
|
||||
```powershell
|
||||
.\deployment\Deploy-To-NT8.ps1
|
||||
```
|
||||
|
||||
3. **Test in NT8:**
|
||||
- Add SimpleORBNT8 to Strategy Analyzer
|
||||
- Enable strategy
|
||||
- Check Output window - should see:
|
||||
```
|
||||
[SDK] Simple ORB NT8 initialized successfully
|
||||
=== Strategy Configuration ===
|
||||
{
|
||||
"StrategyName": "Simple ORB NT8",
|
||||
"ExportedAt": "2026-02-17 14:30:00",
|
||||
"Instrument": "ES 03-26",
|
||||
"BarsPeriod": "5 Minute",
|
||||
"EnableSDK": true,
|
||||
"DailyLossLimit": 1000,
|
||||
...
|
||||
}
|
||||
=== End Configuration ===
|
||||
```
|
||||
|
||||
4. **Copy JSON from Output window** - ready to share!
|
||||
|
||||
5. **Test Auto-Export:**
|
||||
- Set `AutoExportConfig = true`
|
||||
- Re-enable strategy
|
||||
- Check `Documents\NinjaTrader 8\logs\` folder
|
||||
- Should see `SimpleORBNT8_[timestamp]_config.json`
|
||||
|
||||
---
|
||||
|
||||
## 📋 Success Criteria
|
||||
|
||||
- [ ] StrategyConfigExporter.cs created
|
||||
- [ ] Export methods added to NT8StrategyBase
|
||||
- [ ] Auto-export on strategy start works
|
||||
- [ ] PrintConfiguration() shows JSON in Output window
|
||||
- [ ] SimpleORBNT8 includes ORB-specific parameters
|
||||
- [ ] JSON format is valid and readable
|
||||
- [ ] Zero compilation errors
|
||||
- [ ] All 319 existing tests still pass
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Constraints
|
||||
|
||||
- C# 5.0 syntax only (no modern JSON libraries)
|
||||
- Simple manual JSON serialization (no Newtonsoft.Json dependency)
|
||||
- Thread-safe (no async file I/O)
|
||||
- Minimal allocations
|
||||
- Clear error messages
|
||||
|
||||
---
|
||||
|
||||
## 📋 Git Commit
|
||||
|
||||
```bash
|
||||
git add src/NT8.Adapters/Strategies/StrategyConfigExporter.cs
|
||||
git add src/NT8.Adapters/Strategies/NT8StrategyBase.cs
|
||||
git add src/NT8.Adapters/Strategies/SimpleORBNT8.cs
|
||||
git commit -m "feat: Add configuration export/import
|
||||
|
||||
- Add StrategyConfigExporter helper class
|
||||
- Add ExportConfigurationJson() method
|
||||
- Add PrintConfiguration() to Output window
|
||||
- Add auto-export on strategy start
|
||||
- Add AutoExportConfig property
|
||||
- Simple JSON serialization (C# 5.0 compatible)
|
||||
|
||||
Enables easy configuration sharing for debugging"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**READY FOR KILOCODE - CODE MODE** ✅
|
||||
|
||||
**Time: 1.5-2 hours**
|
||||
Reference in New Issue
Block a user