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.
- Log into your Pulse Billing dashboard
- Navigate to the Products section
- Find your NinjaTrader product and copy the Product Code (e.g.,
P_0000) - 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
- Indicator Loads: When the indicator is added to a chart, it automatically validates the license
- License Check: The system checks for a stored license token in the user's Documents folder
- Token Validation: If found, the token is validated against Pulse Billing's API
- User Prompt: If no valid license is found, the user is prompted to enter their license key
- 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
- Create Test Product: Set up a test product in your Pulse Billing dashboard
- Generate Test License: Create a trial invite and copy the license token. See Trial Management for instructions on how to create a trial invitation.
- Test Indicator: Add your indicator to a chart and verify license validation works
- 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! 🎉