Files
nt8-sdk/CONFIG_EXPORT_SPEC.md
2026-02-24 15:00:41 -05:00

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

  1. Build:
dotnet build src\NT8.Adapters\NT8.Adapters.csproj --configuration Release
  1. Deploy:
.\deployment\Deploy-To-NT8.ps1
  1. 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 ===
    
  2. Copy JSON from Output window - ready to share!

  3. 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

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