// The top of every source code file must include this line
#include "sierrachart.h"

/*****************************************************************************

	For reference, please refer to the Advanced Custom Study Interface
	and Language documentation on the Sierra Chart website. 
	
*****************************************************************************/

// Name of the custom study.
SCDLLName("Custom Stock Index Creator")

#ifndef PDStrSymbolListLength
#define PDStrSymbolListLength 10240
#endif
#ifndef PDSymbolIndexNodeStrLen
#define PDSymbolIndexNodeStrLen 32
#endif

//Simple struct to hold symbol data.
struct s_StockIndexNode {
	char Symbol[PDSymbolIndexNodeStrLen+1];
	float Weight; //how much it contributes to the generated index.
	int ChartNumber;
	float PriceOffset; //lowest non-zero price in setup search range.
	float Multiplier; //scales the symbol to a 0-100 range for equal weighting.
	//Constructor
	s_StockIndexNode() {
		Symbol[0] = '\0';
		Weight = 1.0;
		ChartNumber = 0;
		PriceOffset = 0.0;
		Multiplier = 1.0;
		}
	//Reset
	void Reset(void) {*this = s_StockIndexNode();}
}; //end struct s_StockIndexNode


//Cleans and copies the SymbolList before processing.
int CleanStockIndexItemListStr(const char *StrIn, char *StrOut, int StrLen) {
int i, j, RecordCount, FieldPosition, CharCount;
i=0;
j=0;
StrLen -= 2; //adjust for array indexing and final NULL terminator.
if(StrLen <= 0) {return -1;} //safety
RecordCount = 0;
CharCount = 0;
FieldPosition = 1; //start with first field position.
StrOut[0] = '\0'; //initial termination to avoid random garbage during tests
//handle any garbage at the front of the string.
while( (StrIn[i]==' ') || (StrIn[i]==',') || (StrIn[i]==';') || (StrIn[i]=='=') ||
       (StrIn[i]=='\t') || (StrIn[i]=='\r') || (StrIn[i]=='\n') ) {
   i++;
   if(i >= StrLen) {return -1;} //safety
   }
//first char setup
if(StrIn[i] != '\0') {StrOut[j] = StrIn[i]; i++; RecordCount++; CharCount++;}
//walk through and copy the string.
for(; j<StrLen; i++) {
	//check for end of string
	if( (StrIn[i] == '\0') || (i >= StrLen) ) {break;}
	//potential garbage characters and extra separators
	if(FieldPosition == 1) {
		//if this is a separator
		if( (StrIn[i]==' ') || (StrIn[i]==',') || (StrIn[i]=='=') || (StrIn[i]=='\t') || (StrIn[i]=='\r') || (StrIn[i]=='\n') ) {
			if(StrOut[j]==' ') {continue;} //already have a field separator
			if(StrOut[j]==',') {continue;} //duplicate record separator
			if(StrIn[i]==',') {
				//Hit a short record. Still in FieldPosition 1.
				j++; StrOut[j] = ',';
				CharCount = 0;
				continue;
				}
			else {
				//add a space separator
				j++; StrOut[j] = ' ';
				FieldPosition = 2;
				continue;
				}
			}
		}
	if(FieldPosition == 2) {
		//ignore garbage
		if( (StrIn[i]==' ') || (StrIn[i]=='=') || (StrIn[i]=='\t') || (StrIn[i]=='\r') || (StrIn[i]=='\n') ) {continue;}
		//ignore non-numbers in numbers, keep the comma separator
		if( !( (StrIn[i]==',') || (StrIn[i]=='.') || ((StrIn[i]>='0') && (StrIn[i]<='9')) ) ) {continue;}
		//found a record separator
		if(StrIn[i]==',') {
			//add a comma separator
			if(StrOut[j]==' ') {j--;} //remove trailing space
			j++; StrOut[j] = ',';
			FieldPosition = 1;
			CharCount = 0;
			continue;
			}
		}
	//valid character, copy it.
	CharCount++;
	if(CharCount == 1) {RecordCount++;}
	j++; StrOut[j] = StrIn[i];
	} //end for()
//properly terminate string
if( (StrOut[j]==' ') || (StrOut[j]==',') ) {j--;} //remove the trailing separator
j++; StrOut[j] = '\0';
return RecordCount;
} //end char CleanStockIndexItemListStr


//Extract the SymbolList into the stock array.
int LoadStockIndexArray(const char *InStr, s_StockIndexNode *StockArray, const int IndexCount) {
int i, j, Index, FieldPosition;
char StrWeight[PDSymbolIndexNodeStrLen+1];
Index = 0;
FieldPosition = 1;
StrWeight[PDSymbolIndexNodeStrLen]='\0'; //safety
//extract values
for(i=0,j=0; i<PDStrSymbolListLength; i++) {
	//record separator
	if(InStr[i]==',') {
		if(FieldPosition == 1) {StockArray[Index].Symbol[j]='\0';}
		if(FieldPosition == 2) {
			StrWeight[j]='\0';
			StockArray[Index].Weight = (float)atof(StrWeight); //typecast to avoid obnoxious M$VC++ warnings
			}
		Index++;
		FieldPosition = 1;
		j = 0;
		continue;
		}
	//field separator
	if(InStr[i]==' ') {
		if(FieldPosition == 1) {StockArray[Index].Symbol[j]='\0';} //safety if there's an extra space.
		FieldPosition = 2;
		j = 0;
		continue;
		}
	//End of input string.
	if(InStr[i]=='\0') {
		if(FieldPosition == 1) {StockArray[Index].Symbol[j]='\0';}
		if(FieldPosition == 2) {
			StrWeight[j]='\0';
			StockArray[Index].Weight = (float)atof(StrWeight); //typecast to avoid obnoxious M$VC++ warnings
			}
		break;
		}
	//Boundary test. Skip the rest until a separator is hit.
	if(j >= PDStrSymbolListLength) {j = PDStrSymbolListLength; continue;}
	//copy character over
	if(FieldPosition == 1) {StockArray[Index].Symbol[j] = InStr[i];}
	if(FieldPosition == 2) {StrWeight[j] = InStr[i];}
	j++;
	} //end for()
return Index + 1;
} //end int LoadStockIndexArray


//Add a large positive number to the index to help keep the line above zero.
#ifndef PDStockIndexOffset
#define PDStockIndexOffset 10000
#endif

//Custom study starts here.
SCSFExport scsf_CustomStockIndexCreator(SCStudyGraphRef sc)
{

//Hopefully SC will add these back into the main source code. Until then, manually define.
#ifndef MAX_STUDY_SUBGRAPHS
#define MAX_STUDY_SUBGRAPHS 60
#endif
#ifndef MAX_STUDY_EXTRA_ARRAYS
#define MAX_STUDY_EXTRA_ARRAYS 12
#endif

	//Aliases to make things easier to read and maintain.
	SCSubgraphRef Graph_Index = sc.Subgraph[0];
	//Start out of the way in case Graph_Index needs lower numbered arrays.
	SCFloatArrayRef SummationArray = Graph_Index.Arrays[MAX_STUDY_EXTRA_ARRAYS-2];
	SCFloatArrayRef ActiveArrays = Graph_Index.Arrays[MAX_STUDY_EXTRA_ARRAYS-1];
	SCSubgraphRef Graph_Volume = sc.Subgraph[1];

	SCInputRef In_Enabled = sc.Input[0];
	SCInputRef In_SymbolList = sc.Input[1];
	SCInputRef In_NormalizeBars = sc.Input[2];
	SCInputRef In_InputData = sc.Input[3];
	SCInputRef In_VolumeSelection = sc.Input[4];
	SCInputRef In_IntradayLoadWeekendData = sc.Input[5];
	SCInputRef In_LimitChartNumber = sc.Input[6];
	SCInputRef In_CloseCharts = sc.Input[7];
	SCInputRef In_WindowState = sc.Input[8];
	SCInputRef InFlag_SymbolsFound = sc.Input[9];
	
	SCGraphData RemoteBaseData;
	SCFloatArray RemoteBaseArray; //This becomes a pointer the way they are wrapped.

	//remember local variables are not persistent.
	int i, bars, index, RemoteIndex, IndexEnd, RemoteIndexEnd;
	float high, low;
	s_ACSOpenChartParameters OpenChartParameters;
	
	//persistent variables.
	int& pInputDataIndex = sc.GetPersistentInt(0);
	int& pChartDataType = sc.GetPersistentInt(1);
	int& pBarPeriod = sc.GetPersistentInt(2);
	int& pLoadWeekendData = sc.GetPersistentInt(3);
	int& pStockIndexNodeCount = sc.GetPersistentInt(4);
	int& pResetIssued = sc.GetPersistentInt(5);
	int& pLimitNumberOfOpenCharts = sc.GetPersistentInt(6);
	int& pVolumeSelection = sc.GetPersistentInt(7);
	//Persistent pointers. Remember these have to be saved manually and the DLL cannot be unloaded.
	char *pStrSymbolList = (char*)sc.GetPersistentPointer(0);
	s_StockIndexNode *pStockNodeArray = (s_StockIndexNode*)sc.GetPersistentPointer(1);
	
	
	// Section 1 - Set the configuration variables and defaults
	if (sc.SetDefaults)
	{
		//Long descriptive name.
		sc.GraphName = "Custom Stock Index Creator";
		
		sc.StudyVersion = 2022071001; //uint, YYYYMMDDxx
		
		// During development set this flag to 1, so the DLL can be rebuilt without restarting Sierra Chart. When development is completed, set it to 0 to improve performance.
		sc.FreeDLL = 0; // must be set to 0 when using dynamic memory allocation.
		
		sc.AutoLoop = 1;  //Auto looping is enabled.
		
		sc.CalculationPrecedence = STD_PREC_LEVEL; //STD_PREC_LEVEL (default), LOW_PREC_LEVEL, VERY_LOW_PREC_LEVEL
		
		sc.GraphRegion = 0; //main price graph
		sc.ValueFormat = 1; //Set output decimal places: 0-6. Also sc.BaseGraphValueFormat, VALUEFORMAT_INHERITED.
		sc.ScaleRangeType = SCALE_INDEPENDENT; //separate graph
		//options: SCALE_AUTO, SCALE_USERDEFINED, SCALE_INDEPENDENT, SCALE_SAMEASREGION, SCALE_CONSTRANGE, SCALE_CONSTRANGECENTER, SCALE_ZEROBASED
		
		sc.AlertOnlyOncePerBar = true; //Keep alerts from going crazy.
		
		//Data for the "Display Study Documentation" button
sc.StudyDescription = "Custom Stock Index Creator. This program takes a list of stocks and creates a custom index from them. (Note: Slow loading charts may not draw the index line correctly. Go into and exit settings to force a redraw, do a <i>Chart >> Recalculate</i>, or CTRL-INSERT.) I originally created this program for two reasons: (1) Existing sector indexes/ETF's contained far too many stocks and tended to all look like the S&P 500 index, which isn't very useful. (2) I wanted to create my own leading indicator index based on my own criteria explained below. This program is primarily intended to use daily bars, but intraday bar functionality has also been added.\
<p>The symbol list is a symbol separated by a space followed by a weight and a separating comma and then repeats. Example: <i>sym1 1.0, sym2 1.0, sym3 0.7, sym4 0.3</i>. A weight of 1.0 corresponds to 100%. A weight of 0.5 corresponds to 50%. If no weight is given, it will default to 100%. A malformed symbol list will have an undefined behavior. The symbol list was chosen to be a text string for ease of use, backup, and sharing. Once the graph loads, you can go back into settings and copy out a cleaned symbol list to a text editor to save as a backup or as a different version of an index you are working on. If you create an interesting index, you are encouraged to share it by copying and pasting the symbol list to a message board. If someone else posts an interesting symbol list, copy and paste it into the symbol list box in the settings window to see the new index.\
<p>Calculation. Normalization of each symbol is done by looking a number of bars back, finding the high and low, and converting that to a 0-100 range internally. The weight value is then multiplied against the calculated value. Each symbol is added up this way and then divided by the number of active symbols creating a kind of average. That number is then added to a large buffer number to help keep the index positive. If the symbol data does not go back as far as some of the others, it is taken out of the summation and active symbol count when its data runs out. The numbers graphed on the chart are somewhat relative and not that meaningful outside the chart. What is meaningful is the pattern the index creates. Since absolute weights are not used by internal normalization, the graph points may change slightly from day to day. This is noticeable on a 1 minute chart but less so on higher time frames.\
<p>The volume function is enabled by default but set to not draw. This is because a study is confined to a single Chart Region and volume bars would squash the index line. To copy the volume bars to another Chart Region, use the 'Overlay (Single Line)' study with the following settings: <i>Based On: Custom Stock Index Creator, Chart Region 2, Scale: Independent, Chart Number: this one, Input Data: VolumeIdx, Multiplier: 1, Display Zero Values: Yes; Subgraphs Tab: Yellow, Bar, Solid, 2.</i>\
<p>The volume function has 4 modes: 1) 'Price*Volume (Weighted)' (recommended setting) multiplies Typical Price times Volume times Weight to get a uniform unit of movement across all source data charts and sums it with the other source data charts. This will show total amounts spent per bar. 2) 'Price*Volume Avg' takes 'Price*Volume (Weighted)' and divides it by the total number of active stocks. 3) 'Volume (Weighted)' multiplies the Volume times Weight and sums it with the other source data charts. 4) 'Volume (Straight)' just sums all the Volumes of the source data charts. In general, use 'Price*Volume' when building an index of many different types of symbols. Use one of the 'Volume' options when comparing different versions of the same thing (like different expiration times for a futures commodity).\
<p>This program will open a chart window for each symbol in the list. Sierra Chart requires an open window to get the symbol data. With a long symbol list, the number of windows will quickly become difficult to manage. It is recommended that you open a new and separate chartbook for each index you create. In the empty chartbook, first open a base symbol that you wish to compare against (I prefer the SPY ETF for the S&P 500). Add this study as a custom study. Once it runs, it will open the required number of chart windows to create the index. The extra windows can be hidden or minimized (this is the default). This is recommended to reduce the CPU load for drawing graphics that won't be seen. To return to your first window, use the CW menu to find your original symbol. The new index will be on this chart and not any of the others. If a source chart window gets closed, the program will have an internal error for that symbol. To bring the window back, go back into the study settings, exit the window, and everything should be recreated. If the chart's time frame is changed, new data chart windows will be opened for each symbol in the new time frame. This can create an excessive number of windows if you're not paying attention.\
<p>General Sierra Chart Notes. For each chart, Sierra Chart will try to load as many days as in <i>Chart Settings >> Use Number Of Days To Load</i>. Do NOT load an excessive number of days for an intraday chart. It will create a very heavy processing load and slow down Sierra Chart. With the way the index is calculated, I could not use Sierra Chart's internal moving averages to smooth the line out. It is still easy to add one, though. Go into 'Study Settings', select this study and mark it hidden, add a moving average study after this one, change 'Based On' to 'Custom Stock Index Creator', change 'Input Data' to 'Index', and change 'Scale' to 'Independent'. The same steps can be used for my Moving Average Ribbon or Bollinger Band Ribbon. To get rid of the source graph, use the 'Display As Main Price Graph' settings option. Be sure to rename the graph tab to something meaningful. From there, studies like moving averages and Bollinger Bands (even MACD) will use the index as their data sources. SAR, trend, and Fibonacci lines can also be drawn. This offers some interesting index analysis options. Studies that depend on bars and volume will not work since the index is only a line. Note that this program cannot take into account stock splits. This usually will not be a problem since either the data source or Sierra Chart will recalculate the price numbers internally.\
<p>Methodology for creating your own index. First identify the type of index you want: full market, sector, sub-sector, peer, commodity, leading, lagging, and so on. Next choose the symbols you want. This is the hard part. If you sort of know what the graph you want should look like, choose symbols with similar graphs. Choosing symbols with vastly different graphs will just muddy the index and make it useless. Choose enough symbols so that if one spikes or dips that it will not trash the index. I normally try for 10-30 symbols depending on the index type. Note that adding too many symbols doesn't always improve the graph and will just load down Sierra Chart. Finally assign a weight value to each chosen symbol.\
<p>I typically break weights up into 5 easy to remember levels: excellent 2.0, good 1.0, average 0.7, OK 0.5, and tolerable 0.3. You don't have to exactly use my given numbers, but it makes for easy use when learning and sharing. Look at the graph for each chosen symbol and write down a weight according to how good it is. If you want to compare the symbol to an existing chart, load this program with only that symbol in the list. If the graph follows what would be your ideal index path, give it a weight of 2.0 (excellent, but rarely happens). If it is reasonably close, give it 1.0 (good, most of the better charts will be at this level). If it kind of follows what you want with some strays, give it 0.7 (average). If it kind of follows but has more strays than you like, give it 0.5 (OK). Anything else that barely follows gets 0.3 (tolerable). If it doesn't follow at all, dump it for another symbol. Generally, anything below OK shouldn't be added to the index as it will not help the graph line. If you find you have a lot more symbols than you thought you would, take the highest rated symbols and leave out the rest.\
<p>For my first application, I wanted a leading market index. This is the example that is included with the default settings. These symbols were picked in early 2016 when this study was first programmed. They may not be appropriate for the future based on the criteria listed above. The great thing about building your own index is that symbols can later be removed or swapped if one of them goes bad. The proper name for this index is: A leading market index in relation to the SPY (S&P 500) ETF for daily bars (template: INDEX_TYPE in relation to SYMBOL for TIME_FRAME). Naming proper relation and time period is important. An index may not work very well on a different time period.\
<p>I want symbols that generally follow SPY but move before it does in the new direction in a clean way. The leading market indicators most sites tout DO NOT do this and would perform poorly in an index. In my research, it turned out that luxury items and non-necessities dominated my list. This makes sense as when people do not have a lot of extra money to spend, these are the first items that get cut from the budget. When comparing charts to SPY, I gave preference to the following: (1) clear turn down before SPY in 2007. (2) Clean pivot up in March 2009. (3) Slow turn down in 2015 when the market went flat. (4) Failure to rise back up in the late 2015 partial recovery.\
<p>The following lists out the symbols I researched for my index. Anything below 0.3 is a symbol I do not like for this index. I left some in there as comparisons to the good symbols that can be looked up for what not to do. At the time, a few of those seemed like a good idea but later got pruned from the list. Do not be hesitant to chop if you need to.\
<ul><li>General: xiv 1.0, wood 1.0, xlb 0.3, xle 0.7, xlu 0.3, erx 0.7, iyr 0.3, iyt 1.0, eem 0.2, pscm 1.0\
<li>Industrial Metals Related: aa 0.2, slx 0.2, jjc 0.2, cper 0.2\
<li>Shipping: fdx 0.5, ups 0.5, sea 0.3\
<li>Luxury or High Priced: lvmuy 0.1, tif 1.0, sig 0.5, pvh 1.0, vfc 1.0, lux 0.5, kors 1.0, har 0.7, pii 1.0, rl 0.5, hot 0.7, wynn 1.0, giii 1.0, cfi 0.3, mov 0.5\
<li>Vehicles: hog 1.0, cat 0.3, de 0.3\
<li>Apparel: burl 0.5, dds 1.0, jwn 1.0, kss 1.0, mw 0.3, smrt 0.7, tjx 0.2\
<li>Travel: htz 1.0, car 1.0, pcln 0.2, expe 0.2, cpa 1.0, mar 1.0\
<li>Food: bwld 0.3, cake 0.5, cbrl 0.5, cmg 0.5, dnkn 0.5, txrh 0.5 (Tend to recover early (like in 2009) but move too far. This is an interesting index on its own.)\
<li>Bonds: jnk 1.0, lqd 0.5\
</ul>\
<p>From my criteria above, I have too many symbols for a practical list. This is a good thing for diversification. This means I'll take the better ones and leave out the rest:\
<br><i>xiv 1.0, wood 1.0, iyt 1.0, pscm 1.0, tif 1.0, pvh 1.0, vfc 1.0, kors 1.0, pii 1.0, wynn 1.0, giii 1.0, hog 1.0, dds 1.0, jwn 1.0, kss 1.0, htz 1.0, car 1.0, cpa 1.0, mar 1.0, jnk 1.0, xle 0.7, erx 0.7, har 0.7, hot 0.7, smrt 0.7</i>\
<p>When you build a list of leading indicators, do NOT try to use it to trade other leading indicators. Trade following or lagging indicators. In this example, SPY, SPXL, or SPXU could potentially be medium to long term traded from this index.\
<p>Another index option is to put together a list of the better movers for a sector or sub-sector to compare your stock against (a peer index). If the stock starts falling off early compared to the index (with or without market news), that would give a warning signal. Conspiracy theorists (who are usually right about this subject) will often note that (illegal) market insiders will start dumping a stock before there is news of a problem. A divergence from the peer index might show that problem before the news gets out.\
<p>Reading The Index. Periodically choose short, medium, and long term time frames to see how well the index is tracking on your main chart. A long term view will show overall movement. Medium to short term views will realign the index and main chart and possibly show movements that are harder to see if the index line has move too far in relation to the main chart.\
<hr>\
<p>";

/*
*Good+Avg: xiv 1.0, wood 1.0, iyt 1.0, pscm 1.0, tif 1.0, pvh 1.0, vfc 1.0, kors 1.0, pii 1.0, wynn 1.0, giii 1.0, hog 1.0, dds 1.0, jwn 1.0, kss 1.0, htz 1.0, car 1.0, cpa 1.0, mar 1.0, jnk 1.0, xle 0.7, erx 0.7, har 0.7, hot 0.7, smrt 0.7

*Good+Avg+OK: xiv 1.0,wood 1.0,iyt 1.0,pscm 1.0,tif 1.0,pvh 1.0,vfc 1.0,kors 1.0,pii 1.0,wynn 1.0,giii 1.0,hog 1.0,dds 1.0,jwn 1.0,kss 1.0,htz 1.0,car 1.0,cpa 1.0,mar 1.0,jnk 1.0,xle 0.7,erx 0.7,har 0.7,hot 0.7,smrt 0.7,fdx 0.5,ups 0.5,burl 0.5,cake 0.5,cbrl 0.5,cmg 0.5,dnkn 0.5,txrh 0.5,lqd 0.5
*/

		//Output Graph
		Graph_Index.Name = "Index"; //drawn graphs must have a name.
		Graph_Index.PrimaryColor = RGB(255, 255, 255); //white
		Graph_Index.DrawStyle = DRAWSTYLE_LINE;
		Graph_Index.LineStyle = LINESTYLE_SOLID; //if LineWidth>1, lines will appear solid
		Graph_Index.LineWidth = 2;
		Graph_Index.DrawZeros = 0; //unlikely a valid point will hit 0. This will remove the line if nothing is there in the far back history.
		
		Graph_Volume.Name = "VolumeIdx"; //drawn graphs must have a name.
		Graph_Volume.PrimaryColor = RGB(255, 255, 0); //yellow
		Graph_Volume.DrawStyle = DRAWSTYLE_IGNORE; //DRAWSTYLE_BAR
		Graph_Volume.LineStyle = LINESTYLE_SOLID; //if LineWidth>1, lines will appear solid
		Graph_Volume.LineWidth = 2;
		Graph_Volume.DrawZeros = 1; //bar range will be above and below 0.
		//Maybe try to experiment will bar coloring in the future.
		//Graph_Volume.PrimaryColor = RGB(0,255,0); //green
		//Graph_Volume.SecondaryColor = RGB(255,0,0); //red
		//Graph_Volume.SecondaryColorUsed = true;
		//Graph_Volume.AutoColoring = AUTOCOLOR_BASEGRAPH; //not useful, based on price bars and not graph data
		//also: studies.cpp scsf_ColorBarAboveBelow (only for main price OHLC bars?) DRAWSTYLE_COLORBAR
		//Graph_Volume[sc.Index] = Value; Graph_Volume.DataColor[sc.Index] = Graph_Volume.PrimaryColor; (Graph_Volume.SecondaryColor)
		//DRAWSTYLE_BAR, DRAWSTYLE_COLORBAR, DRAWSTYLE_IGNORE, DRAWSTYLE_HIDDEN
		//SC_VOLUME
		
		//User Inputs
		
		In_Enabled.Name = "Are you ready for MANY open windows?";
		In_Enabled.SetYesNo(0);
		In_Enabled.SetDescription("Enables study. A window for each symbol must be open to get data from it. A long symbol list will create a lot of windows. It is recommended to put each index in its own chartbook for organization.");
		
		In_SymbolList.Name = "Symbol List (symbol1 weight1, symbol2 weight2, ...";
		In_SymbolList.SetString("xiv 1.0, wood 1.0, iyt 1.0, pscm 1.0, tif 1.0, pvh 1.0, vfc 1.0, kors 1.0, pii 1.0, wynn 1.0, giii 1.0, hog 1.0, dds 1.0, jwn 1.0, kss 1.0, htz 1.0, car 1.0, cpa 1.0, mar 1.0, jnk 1.0, xle 0.7, erx 0.7, har 0.7, hot 0.7, smrt 0.7");
		In_SymbolList.SetDescription("Enter a list of symbols and weights separated by a comma. The symbol charts will be automatically loaded. For weight, 1.0 is 100% and 0.5 is 50%. Example: <i>sym1 1.0, sym2 1.0, sym3 0.7, sym4 0.3</i>");
		
		In_NormalizeBars.Name = "Number Of Bars For Normalization";
		In_NormalizeBars.SetInt(1000);
		In_NormalizeBars.SetIntLimits(10, 10000);
		In_NormalizeBars.SetDescription("Number of bars back to use to calculate the internal normalization multiplier. This should be far enough back to catch regular movement. The internal multiplier makes regular movement between symbols equal and balanced. The weight settings above determine how much influence on the index a symbol has.");
		
		In_InputData.Name = "Input Data";
		In_InputData.SetInputDataIndex(SC_LAST); //default to bar close.
		In_InputData.SetDescription("Usually \"Last\" for Close or \"HLC Avg\" for Typical Price.");
		
		In_VolumeSelection.Name = "Volume Selection";
		In_VolumeSelection.SetCustomInputIndex(1);
		In_VolumeSelection.SetCustomInputStrings("Ignore;Price*Volume (Weighted);Price*Volume Avg (Weighted);Volume (Weighted);Volume (Straight)");
		In_VolumeSelection.SetDescription("'Ignore' skips the volume calculation. 'Price*Volume (Weighted)' (recommended setting) multiplies Typical Price times Volume times Weight to get a uniform unit of movement across all source data charts and sums it with the other source data charts. This will show total amounts spent per bar. 'Price*Volume Avg' takes 'Price*Volume (Weighted)' and divides it by the total number of active stocks. 'Volume (Weighted)' multiplies the Volume times Weight and sums it with the other source data charts. 'Volume (Straight)' just sums all the Volumes of the source data charts.");
		
		In_IntradayLoadWeekendData.Name = "Intraday: Load Weekend Data?";
		In_IntradayLoadWeekendData.SetYesNo(1);
		In_IntradayLoadWeekendData.SetDescription("Load weekend intraday data if available. This does not load weekend data for historical daily bars.");
		
		In_LimitChartNumber.Name = "Limit Number Of Open Charts";
		In_LimitChartNumber.SetInt(200);
		In_LimitChartNumber.SetDescription("Safety Setting. Opening different time frames will open matching charts for that time frame. The number of charts can quickly get out of hand and eat up all the system resources. Any chart above the given number will cause this program to disable itself and remove the index line. There is also a strange upgrade condition that might cause an infinite chart loop. This setting will stop that once the limit number is reached. Use \"Close Charts\" to close the unused charts. Set to 0 to disable (not recommended). If you do disable, make sure your symbol list and other charts in the chartbook are backed up because you will not be able to get back to them in an infinite loop.");
		
		In_CloseCharts.Name = "Close All Other Charts?";
		In_CloseCharts.SetCustomInputIndex(0);
		In_CloseCharts.SetCustomInputStrings("No;One Shot");
		In_CloseCharts.SetDescription("If set to \"One Shot\", will close all other charts in the chart book except this one. This is used to close charts that are not part of the index and taking up resources. WARNING: This will not save any data from an opened chart that gets closed.");
		
		In_WindowState.Name = "Data Windows State";
		In_WindowState.SetCustomInputIndex(1);
		In_WindowState.SetCustomInputStrings("Ignore;Minimize;Restore;Maximize");
		In_WindowState.SetDescription("For each data source window this program opens, perform the following on the window: Ignore: Let the window be opened with the current Sierra Chart defaults. Minimize: Minimize the window to keep it out of the way and to keep it from taking up graphical processing resources (recommended). Restore: Restore the window to the previous size and position. Maximize: Maximize the window to full screen.");
		
		InFlag_SymbolsFound.Name = "FLAG: Number Of Symbols Found";
		InFlag_SymbolsFound.SetInt(0);
		InFlag_SymbolsFound.SetDescription("This is a Flag reporting the number of symbols found in the symbol list (not necessarily openable symbols). Changing it will not do anything.");
		
		//future need:
		//Close all other charts using the upcoming array option.
		//Take over existing symbols with different time frames so only one chart per symbol is opened.
		
		return;
		}
	
	// Section 2 - Do data processing here
	
	//Last call to function (destructor). This must be at the top to make sure it gets executed.
	if(sc.LastCallToFunction) {
		//release dynamically allocated string.
		if(pStrSymbolList != NULL) {
			sc.FreeMemory(pStrSymbolList);
			sc.SetPersistentPointer(0, NULL);
			}
		//release dynamically allocated object array.
		if(pStockNodeArray != NULL) {
			delete [] pStockNodeArray;
			sc.SetPersistentPointer(1, NULL);
			}
		return;
		}

	//Nothing to do if disabled.
	if(In_Enabled.GetYesNo() == 0) {return;}
	
	//Set local variables.
	IndexEnd = sc.ArraySize - 1;
	
	//If first run. Set persistent variables here.
	if(sc.Index == 0) {
		//Set the index of the first array element to begin drawing at
		sc.DataStartIndex = 0;
		//Reset arrays
		for(index=0; index<sc.ArraySize; index++) {
			SummationArray[index] = 0.0;
			ActiveArrays[index] = 0.0;
			Graph_Index[index] = 0.0;
			Graph_Volume[index] = 0.0;
			}
		//Volume selection.
		pVolumeSelection = In_VolumeSelection.GetIndex();
		//Close all other charts.
		if(In_CloseCharts.GetIndex()) {
			In_CloseCharts.SetCustomInputIndex(0);
			for(i=1; i<=sc.GetHighestChartNumberUsedInChartBook(); i++) {
				if(i != sc.ChartNumber) {sc.CloseChart(i);}
				}
			//Reload the extra charts so the program doesn't crash.
			sc.FlagFullRecalculate = 1;
			return;
			}
		//Limit number of open charts
		pLimitNumberOfOpenCharts = In_LimitChartNumber.GetInt();
		
		//Chart Data Type
		n_ACSIL::s_BarPeriod BarPeriod;
		sc.GetBarPeriodParameters(BarPeriod);
		pChartDataType = BarPeriod.ChartDataType; //INTRADAY_DATA or DAILY_DATA
		if(pChartDataType == INTRADAY_DATA) {pBarPeriod = BarPeriod.IntradayChartBarPeriodParameter1;}
		else {pBarPeriod = BarPeriod.HistoricalChartBarPeriodType;} //also enables BarPeriod.HistoricalChartDaysPerBar
		
		/* old way
		//Chart Data Type
		pChartDataType = sc.ChartDataType; //INTRADAY_DATA or DAILY_DATA
		if(pChartDataType == INTRADAY_DATA) {pBarPeriod = sc.SecondsPerBar;}
		else {pBarPeriod = sc.DailyDataBarPeriod;} //also enables sc.DaysPerBar
		*/

		//Intraday: Load Weekend Data
		pLoadWeekendData = In_IntradayLoadWeekendData.GetYesNo();
		//Input data type
		pInputDataIndex = In_InputData.GetInputDataIndex();
		
		//Create the string item list.
		if(pStrSymbolList == NULL) {
			pStrSymbolList = (char*)sc.AllocateMemory((PDStrSymbolListLength + 1) * sizeof(char)); //create new array
			if(pStrSymbolList != NULL) (sc.SetPersistentPointer(0, pStrSymbolList)); //save pointer
			else {In_Enabled.SetYesNo(0); sc.SetPersistentPointer(0, NULL); return;} //error: disable study
			}
		//Clean the symbol list string before processing. Disable if nothing is there.
		pStockIndexNodeCount = CleanStockIndexItemListStr(In_SymbolList.GetString(), pStrSymbolList, PDStrSymbolListLength);
		InFlag_SymbolsFound.SetInt(pStockIndexNodeCount);
		if(pStockIndexNodeCount < 1) {In_Enabled.SetYesNo(0); return;}
		//Create the s_StockIndexNode array.
		if(pStockNodeArray != NULL) {delete [] pStockNodeArray;} //clear out previous array
		pStockNodeArray = new s_StockIndexNode[pStockIndexNodeCount]; //create new array
		if(pStockNodeArray != NULL) {sc.SetPersistentPointer(1, pStockNodeArray);} //save pointer
		else {In_Enabled.SetYesNo(0); sc.SetPersistentPointer(1, NULL); return;} //error: disable study
		//Load up pStockNodeArray.
		LoadStockIndexArray(pStrSymbolList, pStockNodeArray, pStockIndexNodeCount);
		//Cleaned string goes back to settings
		In_SymbolList.SetString(pStrSymbolList);
		
		//Find or open the chart.
		for(i=0; i<pStockIndexNodeCount; i++) {
			if(pStockNodeArray[i].Symbol[0]=='\0') {continue;} //safety
			//OpenChartParameters.Reset(); //obsolete
			OpenChartParameters.PriorChartNumber = pStockNodeArray[i].ChartNumber; //First parameter that gets checked. Set to 0 if unknown.
			OpenChartParameters.Symbol = pStockNodeArray[i].Symbol;
			if(pChartDataType == INTRADAY_DATA) { //intraday chart
				OpenChartParameters.ChartDataType = INTRADAY_DATA; //INTRADAY_DATA or DAILY_DATA
				OpenChartParameters.IntradayBarPeriodType = IBPT_DAYS_MINS_SECS;
				OpenChartParameters.IntradayBarPeriodLength = pBarPeriod; //time length in seconds
				}
			else { //daily chart
				OpenChartParameters.ChartDataType = DAILY_DATA;
				OpenChartParameters.HistoricalChartBarPeriod = (HistoricalChartBarPeriodEnum) pBarPeriod;
				//Options from enum: HISTORICAL_CHART_PERIOD_DAYS=1, HISTORICAL_CHART_PERIOD_WEEKLY, HISTORICAL_CHART_PERIOD_MONTHLY,	HISTORICAL_CHART_PERIOD_QUARTERLY, HISTORICAL_CHART_PERIOD_YEARLY
				}
			OpenChartParameters.DaysToLoad = 0; //0 is the same as the calling chart
			OpenChartParameters.LoadWeekendData = pLoadWeekendData; //only for intraday charts, daily ignores.
			pStockNodeArray[i].ChartNumber = sc.OpenChartOrGetChartReference(OpenChartParameters);
			//Set new chart window state.
			if(In_WindowState.GetInt() > 0) {sc.SetChartWindowState(pStockNodeArray[i].ChartNumber, In_WindowState.GetInt());}
			//Safety: limit number of open charts and prevent infinite chart loops.
			if( (pLimitNumberOfOpenCharts > 0) && (pStockNodeArray[i].ChartNumber > pLimitNumberOfOpenCharts) ) {
				In_Enabled.SetYesNo(0);
				sc.AddMessageToLog("WARNING: Limit Number Of Open Charts exceeded. Disabling study. Either increase the value in study settings or close unused charts.", 1);
				return;
				}
			} //end for()
	
		pResetIssued = 1;
		} //end if bar==0
	
	//Flag reset. Can't do anything until it finishes.
	if(sc.DownloadingHistoricalData) {pResetIssued = 1; return;}
	if(sc.IsFullRecalculation) {pResetIssued = 1;} //need last bar of full recalc, so don't return here.
	
	//Nothing to do if not at the end. This seems to happen every bar.
	//Since there is more data waiting, returning to fetch it is very fast.
	if(sc.Index != IndexEnd) {return;}

	//Handle a full recalc.
	if(pResetIssued >= 1) {
		pResetIssued = 0;
		for(index=0; index<=IndexEnd; index++) {
			SummationArray[index] = 0.0;
			ActiveArrays[index] = 0.0;
			Graph_Index[index] = 0.0;
			Graph_Volume[index] = 0.0;
			}
		//Find high and low for the multiplier. This can only be done once all the bars have loaded.
		for(i=0; i<pStockIndexNodeCount; i++) {
			//Safety. Skip charts that didn't load.
			if(pStockNodeArray[i].ChartNumber <= 0) {continue;}
		
			//Get the remote BaseData from the given chart.
			sc.GetChartBaseData((-1 * pStockNodeArray[i].ChartNumber), RemoteBaseData);
			//Safety check.
			if(RemoteBaseData[pInputDataIndex].GetArraySize() <= 0) {continue;} //nothing to do
			//Define a reference array to the remote array (like SC_LAST or SC_HLC).
			RemoteBaseArray = RemoteBaseData[pInputDataIndex];
			//Arrays aren't sync'd. Find last bar.
			RemoteIndexEnd = sc.GetNearestMatchForDateTimeIndex(pStockNodeArray[i].ChartNumber, IndexEnd); //Returns -1 if remote chart isn't fully loaded and this chart will get a full recalc.
			//Safety: if not fully loaded, abort and wait until it is.
			if(RemoteIndexEnd < 0) {pResetIssued = 1; return;}
			
			//Find recent high and low for normalization multiplier.
			bars = In_NormalizeBars.GetInt();
			if(bars > RemoteIndexEnd) {bars = RemoteIndexEnd;}
			index = RemoteIndexEnd - bars;
			if(index < 0) {index = 0;}
			high = RemoteBaseArray[index];
			low = RemoteBaseArray[index];
			for(; index <= RemoteIndexEnd; index++) {
				if(high < RemoteBaseArray[index]) {high = RemoteBaseArray[index];}
				if( (low > RemoteBaseArray[index]) && (RemoteBaseArray[index] > 0.0) ) {low = RemoteBaseArray[index];}
				}
			pStockNodeArray[i].PriceOffset = low;
			if(high > low) {pStockNodeArray[i].Multiplier = (float)(100.0 / (high - low));} //divide by zero check. typecast to avoid obnoxious M$VC++ warnings
			else {pStockNodeArray[i].Multiplier = 1.0;} //fallback
			
			//load the multi-chart array
			for(index=IndexEnd,RemoteIndex=0; (RemoteIndex>=0 && index>=0); index--) {
				//WARNING: this is inefficient but mandatory to keep the various charts aligned.
				//While the charts "should" be aligned if each index is pointing to the same bar, missing data could skew that.
				RemoteIndex = sc.GetNearestMatchForDateTimeIndex(pStockNodeArray[i].ChartNumber, index);
				//Nothing to do if zero.
				if(RemoteBaseArray[RemoteIndex] <= 0.0) {continue;}
				//Scale the value to normalize then apply weight.
				//Calc: ArrayOut[i] = ((ArrayIn[i] - PriceOffset) * Multiplier) * Weight;
				SummationArray[index] += ((RemoteBaseArray[RemoteIndex] - pStockNodeArray[i].PriceOffset) * pStockNodeArray[i].Multiplier) * pStockNodeArray[i].Weight;
				//Increment active array index position count.
				ActiveArrays[index] += 1.0;
				//Volume calculations
				if( (pVolumeSelection == 1) || (pVolumeSelection == 2) ) { //Price*Volume and Price*Volume Avg
					Graph_Volume[index] += RemoteBaseData[SC_HLC][RemoteIndex] * RemoteBaseData[SC_VOLUME][RemoteIndex] * pStockNodeArray[i].Weight;
					}
				else if(pVolumeSelection == 3) { //Volume (Weighted)
					Graph_Volume[index] += RemoteBaseData[SC_VOLUME][RemoteIndex] * pStockNodeArray[i].Weight;
					}
				else if(pVolumeSelection == 4) { //Volume (Straight)
					Graph_Volume[index] += RemoteBaseData[SC_VOLUME][RemoteIndex];
					}
				else {Graph_Volume[index] = 0.0;} //saftey catch all
				}
			} //end for(i<pStockIndexNodeCount)
		//divide out the average and graph it.
		for(index=0; index<=IndexEnd; index++) {
			if(ActiveArrays[index] > 0.0) {
				Graph_Index[index] = (SummationArray[index] / ActiveArrays[index]) + PDStockIndexOffset;
				if(pVolumeSelection == 2) {Graph_Volume[index] /= ActiveArrays[index];} //Calculate volume average.
				}
			}
		} //end if reset
	
	
	//Regular calculation.
	SummationArray[sc.Index] = 0.0;
	ActiveArrays[sc.Index] = 0.0;
	Graph_Volume[sc.Index] = 0.0;
	//Recalculate the last bar to avoid movement. With multiple open charts, one might be unready and skipped.
	SummationArray[sc.Index-1] = 0.0;
	ActiveArrays[sc.Index-1] = 0.0;
	Graph_Volume[sc.Index-1] = 0.0;
	for(i=0; i<pStockIndexNodeCount; i++) {
		//Safety. Skip charts that didn't load.
		if(pStockNodeArray[i].ChartNumber <= 0) {continue;}
		//Get the chart.
		sc.GetChartBaseData((-1 * pStockNodeArray[i].ChartNumber), RemoteBaseData);
		RemoteBaseArray = RemoteBaseData[pInputDataIndex]; //alias to make the code more understandable.
		if(RemoteBaseArray.GetArraySize() <= 0) {continue;} //Safety check.
		RemoteIndexEnd = sc.GetNearestMatchForDateTimeIndex(pStockNodeArray[i].ChartNumber, sc.Index);
		//Safety: if not fully loaded, abort and wait until it is.
		if(RemoteIndexEnd < 0) {continue;} //symbol will be added in next chart iteration.
		//Nothing to do if zero.
		if(RemoteBaseArray[RemoteIndexEnd] <= 0.0) {continue;}
		
		//Do the calculation.
		SummationArray[sc.Index] += ((RemoteBaseArray[RemoteIndexEnd] - pStockNodeArray[i].PriceOffset) * pStockNodeArray[i].Multiplier) * pStockNodeArray[i].Weight;
		//Increment active array index position count.
		ActiveArrays[sc.Index] += 1.0;
		//Volume calculations
		if( (pVolumeSelection == 1) || (pVolumeSelection == 2) ) { //Price*Volume and Price*Volume Avg
			Graph_Volume[sc.Index] += RemoteBaseData[SC_HLC][RemoteIndexEnd] * RemoteBaseData[SC_VOLUME][RemoteIndexEnd] * pStockNodeArray[i].Weight;
			}
		else if(pVolumeSelection == 3) { //Volume (Weighted)
			Graph_Volume[sc.Index] += RemoteBaseData[SC_VOLUME][RemoteIndexEnd] * pStockNodeArray[i].Weight;
			}
		else if(pVolumeSelection == 4) { //Volume (Straight)
			Graph_Volume[sc.Index] += RemoteBaseData[SC_VOLUME][RemoteIndexEnd];
			}
		else {Graph_Volume[sc.Index] = 0.0;} //saftey catch all
		//Previous bar. This way is guaranteed to be close to the true previous bar if there are data gaps.
		RemoteIndex = sc.GetNearestMatchForDateTimeIndex(pStockNodeArray[i].ChartNumber, (sc.Index-1));
		//Recalculate the last bar to avoid movement.
		if(RemoteBaseArray[RemoteIndex] > 0.0) {
			SummationArray[sc.Index-1] += ((RemoteBaseArray[RemoteIndex] - pStockNodeArray[i].PriceOffset) * pStockNodeArray[i].Multiplier) * pStockNodeArray[i].Weight;
			ActiveArrays[sc.Index-1] += 1.0;
			}
		//Volume calculations
		if( (pVolumeSelection == 1) || (pVolumeSelection == 2) ) { //Price*Volume and Price*Volume Avg
			Graph_Volume[sc.Index-1] += RemoteBaseData[SC_HLC][RemoteIndex] * RemoteBaseData[SC_VOLUME][RemoteIndex] * pStockNodeArray[i].Weight;
			}
		else if(pVolumeSelection == 3) { //Volume (Weighted)
			Graph_Volume[sc.Index-1] += RemoteBaseData[SC_VOLUME][RemoteIndex] * pStockNodeArray[i].Weight;
			}
		else if(pVolumeSelection == 4) { //Volume (Straight)
			Graph_Volume[sc.Index-1] += RemoteBaseData[SC_VOLUME][RemoteIndex];
			}
		else {Graph_Volume[sc.Index-1] = 0.0;} //saftey catch all
		}
	//Final calculation for the averages.
	if(ActiveArrays[sc.Index] > 0.0) {
		Graph_Index[sc.Index] = (SummationArray[sc.Index] / ActiveArrays[sc.Index]) + PDStockIndexOffset;
		if(pVolumeSelection == 2) {Graph_Volume[sc.Index] /= ActiveArrays[sc.Index];} //Calculate volume average.
		}
	else {
		Graph_Index[sc.Index] = 0.0;
		Graph_Volume[sc.Index] = 0.0;
		}
	//Recalculate the last bar to avoid movement. With multiple open charts, one might be unready and skipped.
	if(ActiveArrays[sc.Index-1] > 0.0) {
		Graph_Index[sc.Index-1] = (SummationArray[sc.Index-1] / ActiveArrays[sc.Index-1]) + PDStockIndexOffset;
		if(pVolumeSelection == 2) {Graph_Volume[sc.Index-1] /= ActiveArrays[sc.Index-1];} //Calculate volume average.
		}
	else {
		Graph_Index[sc.Index-1] = 0.0;
		Graph_Volume[sc.Index-1] = 0.0;
		}
} //end Custom Stock Index Creator

/*
Open a new chartbook. Add the following studies with the following settings:
Custom Stock Index Creator: Short Name: Call Index, Chart Region: 2, Scale: Automatic, Symbol List: enter the call symbols, Volume (Straight). Subgraphs Tab: Index: Ignore. VolumeIdx: Green, Bar, Solid, 2.
Overlay (Single Line): Short Name: Call Index Line, Based On: Call Index, Chart Region 1, Scale: Independent, Chart Number: this one, Input Data: Index, Multiplier: 1, Display Zero Values: Yes. Subgraphs Tab: Green, Line, Solid, 2.
Custom Stock Index Creator: Short Name: Put Index, Chart Region: 1, Scale: Automatic, Symbol List: enter the put symbols, Volume (Straight). Subgraphs Tab: Index: Red, Line, Solid, 2. VolumeIdx: Ignore.
Study Subgraph Multiply: (singular version, not plural) Based On: Put Index, Short Name: Put Volume, Chart Region: 2, Scale Automatic, Input Data: Volume, Multiplier -1, Draw Zeros: Yes. Subgraphs Tab: Red, Bar, Solid, 2.
Study Subgraphs Add: (plural version, not singular) Short Name: Volume Difference, Chart Region 3, Scale: Automatic, Input Study 1: Call Index: Volume, Input Study 2: Put Volume: Result, Offset: 0, Draw Zeros: Yes, Perform Add With Zero Value: Yes. Subgraphs Tab: Purple, Bar, Solid, 2.

Sometimes a graph will disappear. Do Chart >> Recalculate or CTRL-INSERT. Full recalculate doesn't always restore the graph but partial will.

Results:
Main Price Graph should have green and red lines for call and put price index lines.
Second graph should have a stacked bar graph for green/call and red/put. Red lines should be negative and proportionally scaled to the green ones.
Third graph should have the difference between the green and red of the second graph.

To save all this: Analysis >> Studies >> Save Studies As Study Collection.
Enter a name like: Call-Put Index
Hit the "Save All" button.
Open a new chartbook for a different set of option symbols, select Analysis >> Call-Put Index, go into the Custom Stock Index Creator settings and enter new Symbol Lists for the new call/put symbol.
*/
