551 lines
15 KiB
Markdown
551 lines
15 KiB
Markdown
# 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**
|