A trial version of an algorithm is often needed for algo creators to pursue an effective distribution strategy in cTrader Store. This article explains how to introduce limitations to trial versions of cBots, indicators and plugins at the code level.
Tip
Duplicate your main algorithm to create a trial version, add limitations to it as explained in this guide and then publish a trial in cTrader Store.
Trial limitations
Trial limitations ensure that potential buyers can test the general behaviour, logic, and quality of your algorithm, but cannot fully replicate the functionality of the paid version.
Common limitations in the code of trial algorithms include:
Demo-only operations
Backtesting-only operations
Daily profit cap
Hardcoded symbol and period
Hardcoded parameters
And more
cBots
The code structure of a trial cBot may resemble this:
1 2 3 4 5 6 7 8 910111213141516171819202122
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{protectedoverridevoidOnStart(){// Trial setup checks (account type, symbol, expiry, etc.)// Restrictions logic for the trial// before or around your normal trading logic.}protectedoverridevoidOnBar(){// Restrictions logic for the trial// before or around your normal trading logic.}privatevoidRunStrategy(){// Normal trading logic goes here// e.g., signal detection, risk management, order placement.}}
1 2 3 4 5 6 7 8 910111213141516171819202122
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():defon_start(self):# Trial setup checks (account type, symbol, expiry, etc.)# Restrictions logic for the trial# before or around your normal trading logic.passdefon_bar(self):# Restrictions logic for the trial# before or around your normal trading logic.passdefrun_strategy(self):# Normal trading logic goes here# e.g., signal detection, risk management, order placement.pass
Demo only
Allow the trial bot to run only on demo accounts. If a user attaches it to a live account, the cBot stops executing.
1 2 3 4 5 6 7 8 9101112131415161718192021
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{protectedoverridevoidOnStart(){if(Account.IsLive){Print("This trial version can be used only on demo accounts.");Stop();// Stops the cBot for real accountsreturn;}RunStrategy();// <<< Actual trading logic}privatevoidRunStrategy(){// Normal trading logic goes here// e.g., signal detection, risk management, order placement.}}
1 2 3 4 5 6 7 8 910111213141516171819
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():defon_start(self):ifapi.Account.IsLive:print("This trial version can be used only on demo accounts.")api.Stop()# Stops the cBot for real accountsreturnself.run_strategy()# <<< Actual trading logicdefrun_strategy(self):# Normal trading logic goes here# e.g., signal detection, risk management, order placement.pass
Backtesting only
Allow the cBot to function only during backtests. If a user tries to use the algorithm to trade in real conditions, nothing happens.
1 2 3 4 5 6 7 8 9101112131415
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{protectedoverridevoidOnStart(){if(RunningMode==RunningMode.RealTime){Print("This trial version can run only in backtesting mode.");Stop();// Stops cBotreturn;}}// Rest of your cBot logic}
1 2 3 4 5 6 7 8 91011121314
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():defon_start(self):ifapi.RunningMode==RunningMode.RealTime:print("This trial version can run only in backtesting mode.")api.Stop()# Stops cBotreturn# Rest of your cBot logic
Daily profit cap
Program the cBot to stop operating once the daily net profit reaches a set value.
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{privateconstdoubleMaxDailyProfit=20.0;protectedoverridevoidOnStart(){StopIfMaxDailyProfitReached();Positions.Closed+=_=>StopIfMaxDailyProfitReached();}privatevoidStopIfMaxDailyProfitReached(){vartodayProfit=History.Where(t=>t.ClosingTime.Date==Server.Time.Date).Sum(t=>t.NetProfit);if(todayProfit<MaxDailyProfit)return;Print("Trial limit reached: daily profit cap.");Stop();// Stops cBot}// Rest of your cBot logic}
1 2 3 4 5 6 7 8 91011121314151617181920212223
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():MaxDailyProfit=20.0defon_start(self):self.stop_if_max_daily_profit_reached()api.Positions.Closed+=lambda_:self.stop_if_max_daily_profit_reached()defstop_if_max_daily_profit_reached(self):todayProfit=[t.NetProfitfortinHistoryift.ClosingTime.Date==api.Server.Time.Date]iftodayProfit<self.MaxDailyProfit:returnprint("Trial limit reached: daily profit cap.")api.Stop();# Stops cBot# Rest of your cBot logic
Hardcoded symbol and period
If a user tries to run the cBot on another symbol or period, nothing happens.
1 2 3 4 5 6 7 8 910111213141516171819202122
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{privateconststringValidSymbolName="EURUSD";privateconststringValidTimeFrame="Hour";protectedoverridevoidOnStart(){StopIfSymbolOrTimeFrameIsInvalid();}privatevoidStopIfSymbolOrTimeFrameIsInvalid(){if(SymbolName==ValidSymbolName&&TimeFrame.Name==ValidTimeFrame)return;Print($"This trial version works only on {ValidSymbolName} {ValidTimeFrame}.");Stop();// Stops cBot}// Rest of your cBot logic}
1 2 3 4 5 6 7 8 9101112131415161718192021
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():ValidSymbolName="EURUSD"ValidTimeFrame="Hour"defon_start(self):self.stop_if_symbol_or_time_frame_is_invalid()defstop_if_symbol_or_time_frame_is_invalid(self):ifapi.SymbolName==self.ValidSymbolNameandapi.TimeFrame.Name==self.ValidTimeFrame:returnprint(f"This trial version works only on {ValidSymbolName}{ValidTimeFrame}.")api.Stop();# Stops cBot# Rest of your cBot logic
Hardcoded parameters
Code the cBot to always use unfavourable internal constants, such as a small volume or lot size.
1 2 3 4 5 6 7 8 910111213
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{// In full version, you might have:// [Parameter("Volume (lots)", DefaultValue = 0.1)]// public double VolumeInLots { get; set; }// In trial version, you can omit parameters entirely// and always use a fixed volume amount when executing trades:privateconststringTrialVolumeInLots=0.01// Rest of your cBot logic}
1 2 3 4 5 6 7 8 9101112131415161718
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():# In full version, you might have:# [Parameter("Volume (lots)", DefaultValue = 0.1)]# public double VolumeInLots { get; set; }# In Python algos you have to define parameters# in C# file of your algo# In trial version, you can omit parameters entirely# and always use a fixed volume amount when executing trades:TrialVolumeInLots=0.01# Rest of your cBot logic
Limited daily trades
Configure the cBot to stop trading for the rest of the day after a given number of trades.
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{privateconstintMaxTradesPerDay=5;protectedoverridevoidOnBar(){if(IsMaxDailyTradeReached())return;// Skip trading logic if max trade number reached}privatevoidIsMaxDailyTradeReached(){vartodayTradesCount=History.Where(t=>t.ClosingTime.Date==Server.Time.Date).Count();if(todayTradesCount<MaxTradesPerDay)returnfalse;Print("Trial limit reached: maximum trades for today.");returntrue;}// Rest of your cBot logic}
1 2 3 4 5 6 7 8 910111213141516171819202122
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():MaxTradesPerDay=5defon_bar(self):ifself.is_max_daily_trade_reached():return# Skip trading logic if max trade number reacheddefis_max_daily_trade_reached(self):todayTradesCount=len([tfortinapi.Historyift.ClosingTime.Date==Server.Time.Date])iftodayTradesCount<self.MaxTradesPerDay:returnFalseprint(f"Trial limit reached: maximum trades for today.")returnTrue# Rest of your cBot logic
Time-limited operations
Code the cBot to send a start date linked to the user's cTID when it is started, and configure it to stop functioning after a fixed period. To prevent users from bypassing the trial start configuration, consider using a remote API service or a similar secure solution.
1 2 3 4 5 6 7 8 910111213141516171819202122232425
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{privateconstintTrialDays=5;protectedoverridevoidOnStart(){// Do the same check on other methods like OnTick, OnBar, etc...if(Server.Time>=GetTrialStartTime().AddDays(TrialDays))OnTrialExpired();}privateDateTimeGetTrialStartTime(){// Add here the logic for getting trial start time from your secure remote service}privatevoidOnTrialExpired(){Print("Your trial period has expired. Please purchase the full version.");Stop();}// Rest of your cBot logic}
1 2 3 4 5 6 7 8 91011121314151617181920212223
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():TrialDays=5defon_start(self):# Do the same check on other methods like on_tick, on_bar, etc...ifapi.Server.Time>=self.get_trial_start_time().AddDays(self.TrialDays):self.on_trial_expired()defget_trial_start_time(self):# Add here the logic for getting trial start time from your secure remote servicepassdefon_trial_expired(self):print("Your trial period has expired. Please purchase the full version.")api.Stop()# Rest of your cBot logic
Session-based limitation
Allow the cBot to trade in only one session.
1 2 3 4 5 6 7 8 91011121314
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{protectedoverridevoidOnBar(){// Do the same check on other methods like OnTick, etc...if(!IsValidSession())return}privateboolIsValidSession()=>MarketSessions.HasFlag(MarketSession.London)// Rest of your cBot logic}
1 2 3 4 5 6 7 8 910111213141516
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():defon_bar(self):# Do the same check on other methods like on_tick, etc...ifself.is_valid_session()==False:returndefis_valid_session(self):returnapi.MarketSessions.HasFlag(MarketSession.London)# Rest of your cBot logic
Simulated trades only
Code the cBot to log where it would trade and not execute trades.
1 2 3 4 5 6 7 8 91011121314151617181920
[Robot(AccessRights = AccessRights.None)]publicclassTest:Robot{protectedoverridevoidOnBar(){// Example: simple signalvarbuySignal=Bars.ClosePrices.Last(1)>Bars.OpenPrices.Last(1);if(buySignal){// In full version, this is where you would execute an order:// ExecuteMarketOrder(TradeType.Buy, SymbolName, 10000);// In the trial: only log the virtual actionPrint($"[TRIAL] Would open BUY position at {Symbol.Ask}");}}// Rest of your cBot logic}
1 2 3 4 5 6 7 8 910111213141516171819
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():defon_bar(self):# Example: simple signalbuySignal=api.Bars.ClosePrices.Last(1)>api.Bars.OpenPrices.Last(1)ifbuySignal:# In full version, this is where you would execute an order:# api.ExecuteMarketOrder(TradeType.Buy, api.SymbolName, 10000);# In the trial: only log the virtual actionprint(f"[TRIAL] Would open BUY position at {api.Symbol.Ask}")# Rest of your cBot logic
Indicators
The code structure of a trial indicator may resemble this:
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():definitialize(self):# Trial setup checks go herepassdefcalculate(self,index):# Trial indicator logic goes herepass
Reduced functionality
The trial indicator executes a basic calculation, while the full version contains more advanced logic.
1 2 3 4 5 6 7 8 9101112131415161718
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]publicclassTest:Indicator{[Output("Result", LineColor = "DodgerBlue")]publicIndicatorDataSeriesResult{get;set;}protectedoverridevoidInitialize(){}publicoverridevoidCalculate(intindex){// Trial build: simplified logic onlyResult[index]=(Bars.HighPrices[index]+Bars.LowPrices[index])/2;// Full version might add more lines, buffers, filters, etc.}}
1 2 3 4 5 6 7 8 9101112131415
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():definitialize(self):passdefcalculate(self,index):# Trial build: simplified logic onlyapi.Result[index]=(api.Bars.HighPrices[index]+api.Bars.LowPrices[index])/2# Full version might add more lines, buffers, filters, etc.
Hardcoded symbol and timeframe
Code the indicator to show outputs only when a certain symbol and timeframe is used.
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]publicclassTest:Indicator{[Output("Result", LineColor = "DodgerBlue")]publicIndicatorDataSeriesResult{get;set;}privateconststringValidSymbolName="EURUSD";privateconststringValidTimeFrame="Hour";protectedoverridevoidInitialize(){if(!IsSymbolAndTimeFrameValid()){Chart.DrawStaticText("symbolLimit","Trial version supports only EURUSD and GBPUSD.",VerticalAlignment.Center,HorizontalAlignment.Center,Color.Yellow);return;}}publicoverridevoidCalculate(intindex){if(!IsSymbolAndTimeFrameValid())return;// Rest of your indicator logic}privatevoidIsSymbolAndTimeFrameValid(){returnSymbolName==ValidSymbolName&&TimeFrame.Name==ValidTimeFrame;}}
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():ValidSymbolName="EURUSD"ValidTimeFrame="Hour"definitialize(self):ifself.is_symbol_and_time_frame_valid()==False:api.Chart.DrawStaticText("symbolLimit","Trial version supports only EURUSD and GBPUSD.",VerticalAlignment.Center,HorizontalAlignment.Center,Color.Yellow)returndefcalculate(self,index):ifself.is_symbol_and_time_frame_valid()==False:return# Rest of your indicator logicdefis_symbol_and_time_frame_valid(self):returnapi.SymbolName==self.ValidSymbolNameandapi.TimeFrame.Name==self.ValidTimeFrame
Time-limited operations
Code the indicator to stop displaying outputs once it expires and show only a message.
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]publicclassTest:Indicator{[Output("Result", LineColor = "DodgerBlue")]publicIndicatorDataSeriesResult{get;set;}protectedoverridevoidInitialize(){}publicoverridevoidCalculate(intindex){if(IsTrialExpired()){Result[index]=double.NaN;return;}// Rest of your indicator logic}privateboolIsTrialExpired(){// Here you can check if trial is expired or not// You have to store trial start time somewhere secure outside// user reach and comapre it with Server.Time// You can also let user know trial is expired by showing// a text message on chart, ex:// Chart.DrawStaticText(// "trial_expired",// "Trial expired. Please purchase the full version.",// VerticalAlignment.Center,// HorizontalAlignment.Center,// Color.Red// );}}
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*importmathclassTest():definitialize(self):passdefcalculate(self,index):ifself.is_trial_expired():api.Result[index]=math.nanreturn# Rest of your indicator logicdefis_trial_expired(self):# Here you can check if trial is expired or not# You have to store trial start time somewhere secure outside# user reach and comapre it with api.Server.Time# You can also let user know trial is expired by showing# a text message on chart, ex:# api.Chart.DrawStaticText(# "trial_expired",# "Trial expired. Please purchase the full version.",# VerticalAlignment.Center,# HorizontalAlignment.Center,# Color.Red# )
Plugins
The code structure of a trial plugin may resemble this:
1 2 3 4 5 6 7 8 9101112
[Plugin(AccessRights = AccessRights.None)]publicclassTest:Plugin{protectedoverridevoidOnStart(){// Trial setup or logic here}protectedoverridevoidOnStop(){}}
1 2 3 4 5 6 7 8 910111213
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():defon_start(self):# Trial setup or logic herepassdefon_stop(self,index):pass
Reduced functionality
The trial version includes only basic features, while the full version provides everything.
[Plugin(AccessRights = AccessRights.None)]publicclassTest:Plugin{protectedoverridevoidOnStart(){// Use same check inside your plugin other methodsif(IsTrialExpired())OnTrialExpired();// You can also run timer and keep checking trial expiryTimer.Start(100)}protectedoverridevoidOnTimer(){if(IsTrialExpired())OnTrialExpired();}privateDateTimeIsTrialExpired(){// Here you have to check if user trial period is expired// or not, for that you have to store the trial start time// somewhere secure outside user access and compare it with// Server.Time}privatevoidOnTrialExpired(){// Do whatever you want when trial is expired hereMessageBox.Show("Trial expired");}}
importclrclr.AddReference("cAlgo.API")# Import cAlgo API typesfromcAlgo.APIimport*# Import trading wrapper functionsfromrobot_wrapperimport*classTest():defon_start(self):# Use same check inside your plugin other methodsifself.is_trial_expired():self.on_trial_expired()# You can also run timer and keep checking trial expiryTimer.Start(100)defon_timer(self,index):ifself.is_trial_expired():self.on_trial_expired()defis_trial_expired(self):# Here you have to check if user trial period is expired# or not, for that you have to store the trial start time# somewhere secure outside user access and compare it with# Server.Timedefon_trial_expired(self):# Do whatever you want when the trial has expired hereapi.MessageBox.Show("Trial expired")