I am writing a program for my trading application. Over the months the program has been growing steadily, and what is used to be a small program now I would call a medium size program (about 1000-2000 lines in total), and it will keep growing as I add more features to it. It has grown to a point where it start to get difficult to read my code.
Below is a small part of the code from my over program. It is particularly nasty because I have a lot of calculations involved. What I think the following code is especially hard to read is because there are many similarities in blocks but differ from a plus (+) or minus (-) sign, so I cannot easily chunk it to new methods (refactoring). Should I just group the code into like 20-30 lines or so together and declare a new method? Then I will have many small methods which it is only be called in one place, which I think it defeat the purpose of a new method.
I have been reading extension method or so but I don't know if it is easy to apply to my program.
public class CVB
{
public Bar bar = new Bar();
public class Bar
{
public DateTime startTime;
public DateTime endTime;
public double open;
public double high;
public double low;
public double close;
public double bestBid;
public double bestAsk;
public int volume;
public int delta;
public int accVolume;
public DateTime lastPrintTime;
public DateTime lastMarcoTime;
public string writeLine = null;
public List<string> outputString = new List<string>();
public Boolean validBarWrite = false;
}
public Info info = new Info();
public class Info
{
public string symbol = null;
public string exchange = null;
public string finalSymbol = null;
public int CVBSize = 200;
public double tickSize;
public int largeSizeLimit = 50;
public Boolean realtimeChart = false;
public int realtimeChartTimer = 2000;
}
// more variables not included here....
public Boolean initialise = false;
public List<Level2> ask = new List<Level2>();
public List<Level2> bid = new List<Level2>();
public List<Level2> largeSize = new List<Level2>();
public class Level2
{
public double price { get; set; }
public int size { get; set; }
public Level2(double price, int size)
{
this.price = price;
this.size = size;
}
}
class SortAscending : IComparer<Level2>
{
public int Compare(Level2 x, Level2 y)
{
if (x.price > y.price) return 1;
else if (x.price < y.price) return -1;
else return 0;
}
}
class SortDecending : IComparer<Level2>
{
public int Compare(Level2 x, Level2 y)
{
if (x.price < y.price) return 1;
else if (x.price > y.price) return -1;
else return 0;
}
}
public static void processLine(ref Output output, ref CVB[] cvb, int c)
{
double bidDistance;
double askDistance;
int oldProcessVolume; // use to hold the previous volume, use to calculate the overflow when the CVB limit is reached
int temp;
double outputPrice = output.price;
cvb[c].bar.writeLine = null;
//////////convert the tick file into constant volume
if (cvb[c].initialise == false) //dont worry about the first bar, it is bugged, the number can be comeing from anywhere, not just trade
{
cvb[c].bar.startTime = output.time;
cvb[c].bar.open = output.price;
cvb[c].bar.high = output.price;
cvb[c].bar.low = output.price;
cvb[c].initialise = true;
}
if (output.price > 0 || output.type == "Volume")
{
switch (output.type)
{
case "Trade":
Beginning:
oldProcessVolume = cvb[c].bar.volume;
cvb[c].bar.volume = cvb[c].bar.volume + output.volume;
if (output.price > cvb[c].bar.high)
cvb[c].bar.high = output.price;
if (output.price < cvb[c].bar.low)
cvb[c].bar.low = output.price;
cvb[c].bar.close = output.price;
if (cvb[c].bar.bestAsk != 0 || cvb[c].bar.bestBid != 0) //not at the start of the program
{
bidDistance = output.price - cvb[c].bar.bestBid;
askDistance = cvb[c].bar.bestAsk - output.price;
if (bidDistance - askDistance > 0.00000001) //trade closer to ask
{
cvb[c].bar.delta = cvb[c].bar.delta + output.volume;
if (output.volume > cvb[c].info.largeSizeLimit)
{
temp = cvb[c].largeSize.Where(x => Math.Abs(x.price - outputPrice) < 0.00000001).Sum(y => y.size); //extract the last volume
cvb[c].largeSize.RemoveAll(x => Math.Abs(x.price - outputPrice) < 0.00000001);
cvb[c].largeSize.Add(new Level2(output.price, temp + output.volume));
}
if (cvb[c].bar.volume >= cvb[c].info.CVBSize) // volume is filled
{
output.volume = cvb[c].bar.volume - cvb[c].info.CVBSize; // cap the volume of the bar to the size of CVB, any overflow is put into the next bar
cvb[c].bar.delta = cvb[c].bar.delta - output.volume;
temp = cvb[c].ask.Where(x => Math.Abs(x.price - outputPrice) < 0.0000001).Sum(yield => yield.size); //extract the last size on the ask, because we want accurate bid size and ask size when the bar is finish
cvb[c].ask.RemoveAll(x => Math.Abs(x.price - outputPrice) < 0.00000001);
temp = temp - (cvb[c].info.CVBSize - oldProcessVolume);
cvb[c].bar.accVolume = cvb[c].bar.accVolume + (cvb[c].info.CVBSize - oldProcessVolume);
if (temp > 0)
cvb[c].ask.Add(new Level2(output.price, temp));
cvb[c].bar.validBarWrite = true;
writeToString(ref output, ref cvb, c);
goto Beginning;
}
}
if (askDistance - bidDistance > 0.00000001) //trade closer to bid
{
cvb[c].bar.delta = cvb[c].bar.delta - output.volume;
if (output.volume > cvb[c].info.largeSizeLimit)
{
temp = cvb[c].largeSize.Where(x => Math.Abs(x.price - outputPrice) < 0.00000001).Sum(y => y.size);
cvb[c].largeSize.RemoveAll(x => Math.Abs(x.price - outputPrice) < 0.00000001);
cvb[c].largeSize.Add(new Level2(output.price, temp - output.volume));
}
if (cvb[c].bar.volume >= cvb[c].info.CVBSize) // volume is filled
{
output.volume = cvb[c].bar.volume - cvb[c].info.CVBSize;
cvb[c].bar.delta = cvb[c].bar.delta + output.volume;
temp = cvb[c].bid.Where(x => Math.Abs(x.price - outputPrice) < 0.0000001).Sum(yield => yield.size); //extract the last size on the ask
cvb[c].bid.RemoveAll(x => Math.Abs(x.price - outputPrice) < 0.00000001);
temp = temp - (cvb[c].info.CVBSize - oldProcessVolume);
cvb[c].bar.accVolume = cvb[c].bar.accVolume + (cvb[c].info.CVBSize - oldProcessVolume);
if (temp > 0)
cvb[c].bid.Add(new Level2(output.price, temp));
cvb[c].bar.validBarWrite = true;
writeToString(ref output, ref cvb, c);
goto Beginning;
}
}
if (Math.Abs(bidDistance - askDistance) < 0.00000001)
{
if (cvb[c].bar.volume >= cvb[c].info.CVBSize) // volume is filled
{
output.volume = cvb[c].bar.volume - cvb[c].info.CVBSize;
cvb[c].bar.accVolume = cvb[c].bar.accVolume + (cvb[c].info.CVBSize - oldProcessVolume);
cvb[c].bar.validBarWrite = true;
writeToString(ref output, ref cvb, c);
goto Beginning;
}
}
} //end if (output.price != 0)
break;
case "Ask":
cvb[c].ask.RemoveAll(x => Math.Abs(x.price - outputPrice) < 0.00000001);
cvb[c].bid.RemoveAll(x => x.price >= outputPrice - 0.00000001);
if (output.volume > 0)
cvb[c].ask.Add(new Level2(output.price, output.volume));
break;
case "BestAsk":
cvb[c].bar.bestAsk = output.price;
cvb[c].ask.RemoveAll(x => x.price <= outputPrice + 0.000000001);
cvb[c].bid.RemoveAll(x => x.price >= outputPrice - 0.00000001);
if (output.volume > 0)
cvb[c].ask.Add(new Level2(output.price, output.volume));
break;
case "Bid":
cvb[c].bid.RemoveAll(x => Math.Abs(x.price - outputPrice) < 0.00000001);
cvb[c].ask.RemoveAll(x => x.price <= outputPrice + 0.000000001); //eliminate the chance the bid is higher than the old ask size
if (output.volume > 0)
cvb[c].bid.Add(new Level2(output.price, output.volume));
break;
case "BestBid":
cvb[c].bar.bestBid = output.price;
cvb[c].bid.RemoveAll(x => x.price >= outputPrice - 0.00000001);
cvb[c].ask.RemoveAll(x => x.price <= outputPrice + 0.000000001); //eliminate the chance the bid is higher than the old ask size
if (output.volume > 0)
cvb[c].bid.Add(new Level2(output.price, output.volume));
break;
case "Volume":
cvb[c].bar.accVolume = output.volume;
break;
} //switch (output.type)
}
if (cvb[c].info.realtimeChart == true && output.time > cvb[c].bar.lastPrintTime.AddMilliseconds(cvb[c].info.realtimeChartTimer)) //update the string for the last bar
{
cvb[c].bar.validBarWrite = false;
writeToString(ref output, ref cvb, c);
}
cvb[c].bar.lastPrintTime = output.time;
}
// more methods below
}
Nearly every variable I have starts with cvb[c].something....
and is is hurting my readability for the code. I came from a VB background which I have with / end with
to improves my readability. There is nothing in C# that is similar to VB with / end with
. I read about other threads where they use
var p = this.StatusProgressBar;
p.IsIndeterminate = false;
p.Visibility = Visibility.Visible;
...
but you can't limit the scope of p
within a certain place in the function, which I don't like.
The code for write to string
public static void writeToString(ref Output output, ref CVB[] cvb, int c)
{
string bidString = null;
string askString = null;
string largeSizeString = null;
double bestAsk = 0;
double bestBid = 0;
double processTickSize = cvb[c].info.tickSize;
string timeFmt = "yyyy/MM/dd HH:mm:ss.fff";
int currentBarBidAskSize = cvb[c].minimumWallSize;
cvb[c].wallPrice[0] = 0;
cvb[c].bar.endTime = output.time;
//output all the info into the text file
SortDecending sortDecending = new SortDecending();
cvb[c].bid.Sort(sortDecending);
SortAscending sortAscending = new SortAscending();
cvb[c].ask.Sort(sortAscending);
cvb[c].largeSize.Sort(sortAscending);
for (int i = 0; i < 10; i++)
{
if (i < cvb[c].ask.Count && cvb[c].ask[i].price < cvb[c].ask[0].price + (20 * cvb[c].info.tickSize))
{
askString = askString + "," + cvb[c].ask[i].price.ToString() + "," + cvb[c].ask[i].size.ToString();
}
else
{
askString = askString + ",0,0"; //pad it until 10 size is shown
}
}
for (int i = 0; i < 10; i++)
{
if (i < cvb[c].bid.Count && cvb[c].bid[i].price > cvb[c].bid[0].price - (20 * cvb[c].info.tickSize))
{
bidString = bidString + "," + cvb[c].bid[i].price.ToString() + "," + cvb[c].bid[i].size.ToString();
}
else
{
bidString = bidString + ",0,0"; //pad it until 10 size is shown
}
}
for (int i = 0; i < cvb[c].largeSize.Count; i++)
{
largeSizeString = largeSizeString + "," + cvb[c].largeSize[i].price.ToString() + "," + cvb[c].largeSize[i].size.ToString();
}
cvb[c].largeSize.Clear();
if (cvb[c].ask.Count > 0)
bestAsk = cvb[c].ask[0].price;
if (cvb[c].bid.Count > 0)
bestBid = cvb[c].bid[0].price;
int askSum5 = cvb[c].ask.Where(x => x.price >= bestAsk && x.price < bestAsk + (5 * processTickSize)).Sum(y => y.size);
int bidSum5 = cvb[c].bid.Where(x => x.price <= bestBid && x.price > bestBid - (5 * processTickSize)).Sum(y => y.size);
int askSum10 = cvb[c].ask.Where(x => x.price >= bestAsk && x.price < bestAsk + (10 * processTickSize)).Sum(y => y.size);
int bidSum10 = cvb[c].bid.Where(x => x.price <= bestBid && x.price > bestBid - (10 * processTickSize)).Sum(y => y.size);
cvb[c].bar.writeLine = string.Format("`{0},{1},{2},{3},{4},{5},ASK:{6},BID:{7},{8:0.##},{9:0.##},{10:0.##},{11:0.##},largeSize:{12}",
cvb[c].bar.endTime.ToString(timeFmt),
cvb[c].bar.open,
cvb[c].bar.high,
cvb[c].bar.low,
cvb[c].bar.close,
cvb[c].bar.accVolume,
askString,
bidString,
(double)cvb[c].bar.delta / cvb[c].info.CVBSize, //delta ratio
(double)Math.Log10(Math.Abs((cvb[c].bar.endTime.AddMilliseconds(10) - cvb[c].bar.startTime).TotalSeconds)), //bar duration in seconds, TAKE LOG, when the HH:mm time has fixed, then no need to get the abs value
(double)Math.Sign(askSum5 - bidSum5) * (Math.Max(askSum5, bidSum5) / (Math.Min(askSum5, bidSum5) + 0.0001)) - (Math.Sign(askSum5 - bidSum5)), //size ratio price5
(double)Math.Sign(askSum10 - bidSum10) * (Math.Max(askSum10, bidSum10) / (Math.Min(askSum10, bidSum10) + 0.0001)) - (Math.Sign(askSum10 - bidSum10)),
largeSizeString);
if (cvb[c].info.realtimeChart == false || cvb[c].bar.validBarWrite == true)
{
cvb[c].bar.open = cvb[c].bar.close;
cvb[c].bar.high = cvb[c].bar.open;
cvb[c].bar.low = cvb[c].bar.open;
cvb[c].bar.volume = 0;
cvb[c].bar.delta = 0;
cvb[c].bar.startTime = cvb[c].bar.endTime;
}
}
Any suggestions on improving the readability of my code? Should I add more comment in my code? It looks like a complete mess for anyone who doesn't know the function of the code. I have read somewhere that you should not need to add comments on the code if your code is structure well, just a header. My code is definitely not structured well but I don't know where to start improving it. Now I understand my code, but 6 months from now it will be a complete mess for me to look at the code again.