Quick Answer
CS0165: Use of unassigned local variable 'x' means you read a local variable on a path where the compiler cannot prove that it already has a value. C# does not give a local variable a default value. Before you read it, every path through the method that reaches that line must have assigned it. If one path (for example the case where an if is false) skips the assignment, the compiler stops with CS0165.
In a cBot this usually happens with a value that is set inside an if, a loop or a try block and used afterwards. The fix is to give the variable a value where you declare it (double stop = 0;), or to restructure the code so that every path assigns it. The first is the simplest, but pick the value with care: a default of 0 can have a meaning in trading code, and a wrong default can place a wrong order.
From the author: I build cBots in cTrader Automate and convert strategies from Pine Script and MQL5 to C#. In MQL5 a local
doublethat you never set can hold a leftover value, and a Pine variable always has a value orna. C# is stricter and catches the problem when you build, which is better than a random number in a trade, and this guide shows each case.
Key Facts
| What you see | Usual cause | Fix |
|---|---|---|
| Variable set only inside if | The if is false on some bars | Assign a value at the declaration |
| Variable set only inside a loop | The loop can run zero times | Assign before the loop |
| Variable set in try, used after it | The try can fail before the assignment | Assign before the try, or in the catch |
| switch without default | A value that no case covers | Add a default that assigns |
| out variable read after a failed call | The call did not set it | Check the return value, or set a default |
| Variable assigned, but only in a lambda | The compiler cannot prove the lambda ran | Assign outside the lambda |
What the Compiler Is Telling You
CS0165: Use of unassigned local variable 'stopLoss'
The name is the variable that may have no value. The line is the place where you read it, not the place where you forgot to assign it. Look at the lines above it and follow every path that reaches the line.
The Broken cBot
This cBot decides a stop-loss distance from the trend, and prints it. It does not place trades. Three variables are read before every path has assigned them:
using cAlgo.API;
using cAlgo.API.Indicators;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class BrokenBot : Robot
{
private ExponentialMovingAverage _ema;
protected override void OnStart()
{
_ema = Indicators.ExponentialMovingAverage(Bars.ClosePrices, 20);
}
protected override void OnBar()
{
double close = Bars.ClosePrices.Last(1);
double ema = _ema.Result.Last(1);
double stopPips;
if (close > ema)
stopPips = 20;
else if (close < ema)
stopPips = 30;
// close == ema: no assignment
double highest;
for (int i = 1; i <= 5; i++)
{
double high = Bars.HighPrices.Last(i);
if (i == 1 || high > highest) // reads highest
highest = high;
}
double ratio;
try
{
ratio = close / ema;
}
catch (System.Exception)
{
Print("Error");
}
Print("Stop {0}, highest {1}, ratio {2}", stopPips, highest, ratio); // CS0165 for all three
}
}
}
On a bar where close == ema, stopPips has no value. If the loop did not run, highest has no value. And if the try fails, ratio has no value. The compiler does not wait for that bar to happen: it reports the problem when you build.
Cause 1: A Value Set Only Inside an if
The compiler follows the branches. With if and else if and no final else, the case "neither" has no assignment. Add a final else, or a default at the declaration:
double stopPips = 25; // default for close == ema
if (close > ema)
stopPips = 20;
else if (close < ema)
stopPips = 30;
Or make all cases explicit with an else, so that no default is needed:
double stopPips;
if (close > ema)
stopPips = 20;
else if (close < ema)
stopPips = 30;
else
stopPips = 25;
The second form is better when the three cases all mean something different, because the compiler then also warns you if you add a fourth case and forget one path. A method with a missing return has a similar problem, which the compiler reports as CS0161.
Cause 2: A Value Set Only Inside a Loop
A loop can run zero times, so the compiler does not accept an assignment inside it as proof. Give the variable a starting value that is correct for "nothing found". For a maximum, start with the first element or with a value that any real value beats:
double highest = Bars.HighPrices.Last(1);
for (int i = 2; i <= 5; i++)
{
double high = Bars.HighPrices.Last(i);
if (high > highest)
highest = high;
}
Starting from the first value is better than starting from 0, because a maximum that starts at 0 would be wrong for a price series with negative values, and a minimum that starts at 0 would always stay 0. A starting value of double.MinValue works for a maximum, and double.MaxValue for a minimum, if you prefer those.
The loop reads bars from the past, so check Bars.Count before it, as the fixed cBot below does.
Cause 3: A Value Set Inside try
If the code in the try block throws before the assignment, the assignment never happens, and the compiler knows it. Assign a value before the try, or assign one in the catch:
double ratio = 0;
try
{
ratio = close / ema;
}
catch (System.Exception ex)
{
Print("Error: {0}", ex.Message);
}
A division of a double by zero does not throw in C#, it gives infinity or NaN, so for a ratio a plain check is better than a try:
double ratio = ema != 0 ? close / ema : 0;
Decide what 0 means here. A ratio of 0 that later feeds a signal can look like a real value, so use double.NaN and check it with double.IsNaN if you need to tell "no value" from a real zero.
Cause 4: A switch Without a default
A switch that assigns in every case still needs a default for values that you did not list:
double distance;
switch (mode)
{
case 1: distance = 10; break;
case 2: distance = 20; break;
default: distance = 15; break;
}
Without the default line, a value other than 1 and 2 leaves distance without a value.
Cause 5: out Variables and TryParse
A method with an out parameter always assigns it, so a variable that you pass as out has a value after the call. The problem is the meaning of that value when the method reports a failure. For TryParse the value is a default when the text is not a number:
double risk;
if (!double.TryParse(text, System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out risk))
{
Print("Risk is not a number: {0}", text);
return;
}
// here risk is assigned and the parse worked
Always check the return value first, and return or use a safe default when it is false. A type problem in the same code gives a different error, CS0029.
Why a Default of 0 Needs Care
A default makes the error go away, but it does not make the code right. Think about what the value means when the variable keeps the default:
| Variable | Default | Risk |
|---|---|---|
| stopPips = 0 | No stop | An order with no stop loss, if the code passes 0 on |
| volume = 0 | No volume | The order call fails, which is safe |
| highest = 0 | A fake maximum | A wrong breakout level |
| ratio = 0 | A fake ratio | A wrong signal |
If a missing value should stop the trade, use a marker value (double.NaN, or a bool such as hasStop) and check it before the order. This is the same idea as checking the result of an order, which is part of the pre-live checklist.
The Fixed cBot
using cAlgo.API;
using cAlgo.API.Indicators;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class FixedBot : Robot
{
private ExponentialMovingAverage _ema;
protected override void OnStart()
{
_ema = Indicators.ExponentialMovingAverage(Bars.ClosePrices, 20);
}
protected override void OnBar()
{
if (Bars.Count < 10)
return;
double close = Bars.ClosePrices.Last(1);
double ema = _ema.Result.Last(1);
if (double.IsNaN(close) || double.IsNaN(ema))
return;
double stopPips;
if (close > ema)
stopPips = 20;
else if (close < ema)
stopPips = 30;
else
stopPips = 25;
double highest = Bars.HighPrices.Last(1);
for (int i = 2; i <= 5; i++)
{
double high = Bars.HighPrices.Last(i);
if (high > highest)
highest = high;
}
double ratio = ema != 0 ? close / ema : double.NaN;
Print("Stop {0}, highest {1}, ratio {2}", stopPips, highest, ratio);
}
}
}
The bot only prints, so you can compare the values with the chart before you use them in an order.
Similar Errors
| Error | Message | What it means | |---|---|---| | CS0165 | Use of unassigned local variable 'x' | A path reads a variable that has no value | | CS0161 | Not all code paths return a value | A path ends without a return | | CS0136 | A local variable cannot be declared in this scope | A nested block reuses an outer name | | CS0103 | The name 'x' does not exist in the current context | A name is unknown | | CS0029 | Cannot implicitly convert type 'A' to 'B' | No automatic conversion exists |
Checklist: What to Check When You See CS0165
- Read the variable name and the line where it is read.
- Find every place where the variable gets a value.
- Follow the paths:
ifwithoutelse, a loop that may not run, atrythat may fail, aswitchwithoutdefault. - Assign at the declaration, or make every path assign.
- Choose the default with care:
0can mean "no stop" or "no volume". - Use
NaNor aboolwhen "no value" must stay different from a real zero. - Build again, print the values and compare them with the chart.
- Test on a demo account before the cBot trades with real money.
Common Mistakes
- Assigning only inside an
if. - Assigning only inside a loop that can run zero times.
- Assigning only inside a
tryblock. - A
switchwithoutdefault. - A default of
0for a stop loss. - Reading an
outvalue without checking the return value.
FAQ
Related Guides
- cTrader C# errors CS7036 and CS1501: the wrong number of arguments in a call.
- cTrader C# error CS0266: a
doublestored in anint. - cTrader C# error CS0103: the name does not exist in the current context.
- cTrader C# error CS1061: the object is found, the member is not.
- cTrader C# error CS1503: an argument of the wrong type in a call.
- Pine Script strategy.exit to cTrader C#: stop loss and take profit values in pips.
- What to check in a trading bot before going live: a pre-live checklist for stops, lot size, spread and trade results.
- cTrader C# error CS0161: a method that does not return on every path.
- cTrader C# error CS0136: a name that is already used in the method.
- cTrader C# error CS0029: a value of the wrong type.
- cTrader cBot crash: index was out of range: a runtime error after the cBot builds.
- cTrader C# error CS0120: a member that needs a cBot object is used without one.
Fix Compile Errors Without Guessing
If a cBot or an Expert Advisor does not build, paste the code and the error message into the CodeFlowOS Error Fixer. It suggests a minimal fix, explains each error and shows what changed. A fix uses 1 credit, with up to 3 attempts included, and the compiler check is optional. Outputs are not guaranteed, and code that compiles can still have wrong trading logic, so always review the result. Before a bot goes live, the Code Review is free during the early-bird period. CodeFlowOS also converts strategies between Pine Script, MQL5 and cTrader C#: try the converter page.
This article is educational. It does not describe trading performance and is not financial advice. Always build the code in cTrader Automate and backtest it before using it on a live account.