Skip to main content

NinjaTrader Vendor Licensing & Subscription Management Guide

This guide will help you quickly integrate Pulse Billing's licensing & subscription management system with your NinjaTrader indicator or strategy to manage licenses and validate customer subscriptions.

Prerequisites

  • Active Pulse Billing account at Pulse Billing
  • NinjaTrader development environment
  • Product code from your Pulse Billing product catalog

Step 1: Get Your Product Code

Prerequisite: If you haven't created a product yet, follow the instructions in Product Management to create your product and pricing plan.

  1. Log into your Pulse Billing dashboard
  2. Navigate to the Products section
  3. Find your NinjaTrader product and copy the Product Code (e.g., P_0000)
  4. You'll need this code to configure your indicator/strategy

Step 2: Create Your Licensed Indicator/Strategy

Use this complete template to create a licensed NinjaTrader indicator:

#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml.Serialization;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.SuperDom;
using NinjaTrader.Gui.Tools;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.Core.FloatingPoint;
using NinjaTrader.NinjaScript.DrawingTools;
using System.Net.Http;
using System.IO;
using System.Windows.Controls;
#endregion

//This namespace holds Indicators in this folder and is required. Do not change it.
namespace NinjaTrader.NinjaScript.Indicators
{
public class MyLicensedIndicator : Indicator
{
// Replace P_0000 with your actual product code from Pulse Billing
private string[] productCodes = new string[] { "P_0000" };
private bool licensed = false;

protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"Enter the description for your new custom Indicator here.";
Name = "MyLicensedIndicator";
Calculate = Calculate.OnBarClose;
IsOverlay = false;
DisplayInDataBox = true;
DrawOnPricePanel = true;
DrawHorizontalGridLines = true;
DrawVerticalGridLines = true;
PaintPriceMarkers = true;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;
//Disable this property if your indicator requires custom values that cumulate with each new market data event.
//See Help Guide for additional information.
IsSuspendedWhileInactive = true;
BarsAgo = 0;
AddPlot(Brushes.Orange, "MyPlot");
}
else if (State == State.Configure)
{
}
else if (State == State.DataLoaded)
{
// Validate license when indicator loads
licensed = new AgileLicensing(this).ValidateLicense(productCodes);
}
}

protected override void OnBarUpdate()
{
// Only execute indicator logic if licensed
if(!this.licensed)
return;

if(CurrentBar < BarsAgo + 1)
return;

// Your indicator logic here
Values[0][BarsAgo] = (Close[BarsAgo] > Close[(BarsAgo + 1)]) ? (High[BarsAgo] + (5 * TickSize)) : (Low[BarsAgo] - (5 * TickSize));
}

#region Properties
[Range(0, int.MaxValue)]
[NinjaScriptProperty]
[Display(Name="BarsAgo", Description="How many bars ago to use for the plot value", Order=1)]
public int BarsAgo
{ get; set; }

[Browsable(false)]
[XmlIgnore]
public Series<double> MyPlot
{
get { return Values[0]; }
}
#endregion

#region Agile.net licensing
internal class AgileLicensing
{
static readonly HttpClient client = new HttpClient();
private bool isValid;
private NinjaScriptBase ninjaScriptBase;

public AgileLicensing(NinjaScriptBase ninjaScriptBase)
{
this.ninjaScriptBase = ninjaScriptBase;
}

public bool ValidateLicense(string[] productCodes)
{
this.isValid = ValidateLicenseHelper(productCodes).Result;
string name = this.ninjaScriptBase.Name;
if (!isValid)
{
this.ninjaScriptBase.Dispatcher.Invoke(new Action(() =>
{
NTMessageBox.Show(name + ": License not found", name, MessageBoxImage.Error);
}));
}
return this.isValid;
}

async Task<bool> ValidateLicenseHelper(string[] productCodes)
{
string apiUrl = "https://dcm-be.secureteam.net/licensing/validate";
bool requested = false;

if (!TryGetLicenseToken(out string licenseToken))
{
RequestLicenseCode(GetLicenseFilePath());
requested = true;
}

if (TryGetLicenseToken(out licenseToken))
{
isValid = await ValidateLicenseToken(apiUrl, licenseToken, productCodes);
if (!isValid && !requested)
{
RequestLicenseCode(GetLicenseFilePath());
isValid = await ValidateLicenseToken(apiUrl, licenseToken, productCodes);
}
}

return isValid;
}

private async Task<bool> ValidateLicenseToken(string apiUrl, string licenseToken, string[] productCodes)
{
string content = string.Format("{{\"licenseToken\": \"{0}\", \"productCodes\": [{1}] }}",
licenseToken,
string.Join(",", productCodes.Select(x => string.Format("\"{0}\"", x))));

StringContent jsonContent = new StringContent(
content,
Encoding.UTF8,
"application/json");

HttpResponseMessage response = await client.PostAsync(apiUrl, jsonContent);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
bool isValid = bool.Parse(responseBody);
return isValid;
}

private bool TryGetLicenseToken(out string licenseToken)
{
licenseToken = null;
var licenseFilePath = GetLicenseFilePath();
var licenseFolderPath = Path.GetDirectoryName(licenseFilePath);
Directory.CreateDirectory(licenseFolderPath);

if (!File.Exists(licenseFilePath) || String.IsNullOrWhiteSpace(File.ReadAllText(licenseFilePath)))
{
return false;
}
else
{
licenseToken = File.ReadAllText(licenseFilePath);
return true;
}
}

private string GetLicenseFilePath()
{
string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var licenseFolderPath = Path.Combine(documentsPath, ninjaScriptBase.Name);
var licenseFilePath = Path.Combine(licenseFolderPath, "license.txt");
return licenseFilePath;
}

private string ShowLicenseDialog()
{
string licenseKey = string.Empty;
this.ninjaScriptBase.Dispatcher.Invoke(new Action(() => {

LicenseKeyDialog dialog = new LicenseKeyDialog();
if (ninjaScriptBase.Owner != null)
{
dialog.Owner = ninjaScriptBase.Owner;
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
bool? result = dialog.ShowDialog();
if (result == true)
{
licenseKey = dialog.LicenseKey;
}
}));
return licenseKey;
}

private string RequestLicenseCode(string filePath)
{
// Prompt the user to enter their license code
string licenseCode = ShowLicenseDialog();
if (!string.IsNullOrWhiteSpace(licenseCode))
{
// Save the license code to the file
File.WriteAllText(filePath, licenseCode);
}

return licenseCode;
}

public class LicenseKeyDialog : Window
{
private TextBox licenseKeyTextBox;
private Button okButton;
private Button cancelButton;

public string LicenseKey { get; private set; }

public LicenseKeyDialog()
{
// Set up the window
Title = "Enter License Key";
Height = 200;
Width = 400;
ResizeMode = ResizeMode.NoResize;
WindowStartupLocation = WindowStartupLocation.CenterOwner;

// Create a stack panel for layout
StackPanel panel = new StackPanel { Margin = new Thickness(20) };
Content = panel;

// Add a label
Label label = new Label { Content = "Please enter your license key:" };
panel.Children.Add(label);

// Add a textbox for input
licenseKeyTextBox = new TextBox { Margin = new Thickness(0, 10, 0, 0), Height = 23 };
panel.Children.Add(licenseKeyTextBox);

// Create a panel for buttons
StackPanel buttonPanel = new StackPanel { Margin = new Thickness(0, 10, 0, 0), Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right };
panel.Children.Add(buttonPanel);

// Add OK button
okButton = new Button { Content = "OK", Width = 80 };
okButton.Click += OkButton_Click;
buttonPanel.Children.Add(okButton);

// Add Cancel button
cancelButton = new Button { Content = "Cancel", Width = 80, Margin = new Thickness(10, 0, 0, 0) };
cancelButton.Click += CancelButton_Click;
buttonPanel.Children.Add(cancelButton);
}

private void OkButton_Click(object sender, RoutedEventArgs e)
{
LicenseKey = licenseKeyTextBox.Text;
DialogResult = true;
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
}
#endregion
}
}

Step 3: Customize Your Implementation

1. Replace Product Code

// Replace P_0000 with your actual product code from Pulse Billing
private string[] productCodes = new string[] { "P_0000" };

2. Add Your Indicator Logic

Replace the sample logic in OnBarUpdate() with your actual indicator calculations:

protected override void OnBarUpdate()
{
// Only execute if licensed
if(!this.licensed)
return;

// Your custom indicator logic here
// Example: Calculate moving average, RSI, etc.
}

3. Customize License Dialog (Optional)

You can modify the LicenseKeyDialog class to match your branding or add additional fields.

Step 4: How It Works

License Validation Flow

  1. Indicator Loads: When the indicator is added to a chart, it automatically validates the license
  2. License Check: The system checks for a stored license token in the user's Documents folder
  3. Token Validation: If found, the token is validated against Pulse Billing's API
  4. User Prompt: If no valid license is found, the user is prompted to enter their license key
  5. Feature Access: Only licensed users can use the indicator's features

License Storage

  • License tokens are stored in: Documents\[IndicatorName]\license.txt
  • The license is automatically saved after successful validation
  • The system handles license renewal and updates automatically

Step 5: Testing Your Integration

Test License Validation

  1. Create Test Product: Set up a test product in your Pulse Billing dashboard
  2. Generate Test License: Create a trial invite and copy the license token. See Trial Management for instructions on how to create a trial invitation.
  3. Test Indicator: Add your indicator to a chart and verify license validation works
  4. Test Dialog: Verify the license dialog appears when no valid license is found

Test Error Handling

  • Test with invalid license tokens
  • Test with expired subscriptions
  • Test network connectivity issues
  • Verify proper error messages are displayed

Step 6: Deploy to Production

Pre-Deployment Checklist

  • Replace test product code with production product code
  • Test with real customer license tokens
  • Verify license validation works correctly
  • Test the complete user experience flow

Your NinjaTrader indicator is now fully integrated with Pulse Billing's licensing system! 🎉