15 KiB
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
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:
#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:
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:
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:
/// <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
- Build:
dotnet build src\NT8.Adapters\NT8.Adapters.csproj --configuration Release
- Deploy:
.\deployment\Deploy-To-NT8.ps1
-
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 === -
Copy JSON from Output window - ready to share!
-
Test Auto-Export:
- Set
AutoExportConfig = true - Re-enable strategy
- Check
Documents\NinjaTrader 8\logs\folder - Should see
SimpleORBNT8_[timestamp]_config.json
- Set
📋 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
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