How To Synchronize Linked Charts and chart panes to selected Foreign Ticker

The Foreign() documentation includes a very nice Relative Performance Indicator that shows multiple tickers on the same chart.

How can I mouse click on one of the Relative Performance plots, and automatically (via AFL) switch any other chart panes (and any other symbol linked charts) to the selected ticker name?

//Copied from http://www.amibroker.com/guide/afl/setforeign.html 
SetChartOptions(blankbars = 10);
_N( TickerList = ParamStr("Tickers", "^DJI,MSFT,GE") ); 
NumBars = 20; 
fvb = Status("firstvisiblebar"); 
Plot( 100 * ( C - C[ fvb ] ) / C[ fvb ], Name(), colorBlue ); 
for( i = 0; ( symbol = StrExtract( TickerList, i ) ) != ""; i++ ) 
{ 
  fc = Foreign( symbol, "C" ); 

  if( ! IsNull( fc[ 0 ] ) ) 
  { 
     Plot( 100 * ( fc - fc[ fvb ] )/ fc[ fvb ], 
                    symbol, 
          colorLightOrange + ( (2*i) % 15 ),
          styleLine ); 
  } 
} 
PlotGrid( 0, colorYellow ); 
_N( Title = "{{NAME}} - Relative Performance [%]: {{VALUES}}" );

//vvvvvvvvvvvvvvvvvv NEED HELP HERE vvvvvvvvvvvvvvvvvvvvvvv
//Detect Click on Plot and Switch Ticker
ThisTicker = Name(); 
_TRACE("AFL - Ticker  " + ThisTicker);
/*

if (Status("ActionEX") == actionIndicator )
{
	//Get TouchedTicker
	//Switch Ticker to the one touched
	//Force Refresh
}
*/

rpi

@tomas I do see the warnings about using OLE. I am wondering if this code snippet can safely be incorporated into previous code to select and change the active ticker (symbol) from AFL.

SelectedTicker = ParamList("Show Ticker", "^DJI,MSFT,GE,CIM,IBM,STX",1);
if (Status("ActionEX") == actionIndicator)
	{
	AB = CreateObject( "Broker.Application" );
	abDoc = AB.ActiveDocument;
	abDoc.Name = SelectedTicker;
	}

Hello @SwingTradeMonkey

You can try my code. It might benefit from further enchancements, but it seems to work quite well. It's really simple - just a few lines of additional code. No fancy stuff. Just click on one of those foreign tickers' lines (anywhere) holding CTRL key pressed:

Title = "{{NAME}} - Relative Performance [%]: {{VALUES}}";
SetChartOptions( blankbars = 10 );
TickerList = "^DJI,MSFT,GE,CIM,IBM,STX";
ChartNumber = 2; // This chart's symbol will be changed 

gcy  = Nz( GetCursorYPosition( 0 ) );
CtrlPressed =  GetAsyncKeyState( 17 ) < 0 ;
MouseButtonPressed = GetCursorMouseButtons();
FirstSelection = 1;

fvb = Status( "firstvisiblebar" );
Plot( 100 * ( C - C[ fvb ] ) / C[ fvb ], Name(), colorBlue );
PlotGrid( 0, colorYellow );

for( i = 0; ( Symbol = StrExtract( TickerList, i ) ) != ""; i++ )
{
    fc = Foreign( Symbol, "C" );

    if( ! IsNull( fc[ 0 ] ) )
    {

        Ticker = 100 * ( fc - fc[ fvb ] ) / fc[ fvb ];
        SelectedValueTicker = SelectedValue( Ticker );
        Plot( Ticker, Symbol, colorLightOrange + ( ( 2 * i ) % 15 ), styleLine );

        if( MouseButtonPressed AND CtrlPressed AND ( gcy < ( SelectedValueTicker + 0.7 ) AND gcy > ( SelectedValueTicker - 0.7 ) ) AND FirstSelection )
        {
            Say( "" + Symbol );
  
            Ab = CreateObject( "Broker.Application" );
			Chart = AB.Documents(ChartNumber);
			Chart.Name = Symbol;			
			FirstSelection = 0;
        }
    }
}

ChangingTickers

IMPORTANT NOTE AND WARNING: A quote from: https://www.amibroker.com/guide/objects.html#AnalysisDocs

OLE automation interface is provided to control AmiBroker from the OUTSIDE process (such as windows scripting host). While it is possible to access Broker.Application and underlying objects from AFL formulas you should be very careful NOT to touch any user interface objects (Documents, Document, Windows, Window , Analysis object) from AFL formula because doing so, you will be likely "Sawing Off the Branch You're Sitting On". Especially things like switching chart tabs from currently running chart formula are totally forbidden. Changing user interface objects via OLE from AFL that is currently running within those user interface parts is recipe for disaster. You have been warned.

As I am aware about the above warning I tried another approach. I wanted to run from the AFL external java script via ShellExecute() and change selected chart's symbol from the outside of AB. I know it would be safer, but I didn't succeed in importing the ticker name from AB to java script. For this reason (and because my java script knowledge is limited) I would like to ask anybody who knows how to do it, what is the best way of dynamic importing some variables from AB to java script and vice versa. I know that:

  • I can save some values in a text file and then read it from jscript - but that in some cases might be too slow for dynamic exchange of information.

  • I can use windows clipboard via ClipboardSet(), but I wasn't able to retrieve clipboard's content from the jscript.

  • I can use CreateObject() or CreateStaticObject() - but that is not recommended

  • It can be done using DLL Plugin, but for now that is out of my reach.

I would really appreciate some advice from @Tomasz and/or other experienced users.

Regards

5 Likes

@Milosz Thank you!!! with a minor modification this appears to work on the chart. I recognize the cautionary statements and look forward to @Tomasz comments.
Your code also presents an elegant way to capture the Control-Mouse-Click and to Identify the selected plot!

Here are my changes:

	   //Chart = AB.Documents(ChartNumber);
	   //Chart.Name = Symbol;			
 	     Chart = AB.ActiveDocument;
	     Chart.Name = Symbol;

@Milosz, re JScript, here is an example to test how to pass arguments.

(For the other users: :stop_sign: skip all of this if you are not already comfortable with scripting).

(This code, does not do anything useful, It only prints some values using the WScript.Echo function; in Windows, this will open a small popup window with a message. Parsing the parameters and using them in additional code is to be done as needed).

Copy this JScript code in a file that you will save in the AmiBroker's scripts subfolder, giving to it a .js file extension, calling it "argstest.js"

var objArgs = WScript.Arguments;
var params;   // To verify passed params 

if (objArgs.Count() == 0) {
	WScript.Echo("No parameters found");
}
else
{
	params = "";
	for (i=0, n=objArgs.length; i<n; i++) {
		params += '\nParam '+i+'='+objArgs(i);
	}
	WScript.Echo(params);
}

Invoke the above jscript file using the following .afl code: do it directly from the AmiBroker formulas code editor, running it ONCE with the debugger start button.

// Sample code to test passing up to 4 args to a .js script via ShellExecute 

currSymbol = "\"" + Name() + "\""; // Include anything that could have spaces in quotes
source = 4; // 0 = Yahoo, 4 = Google, etc. see AQ doc
dateFrom = "2009-08-07";
dateTo = "2013-12-11";
currDir = fGetCwd();
filename = currDir + "\\scripts\\argstest.js";
for (i = 0; i < 5; i++) {
	switch(i) {
		case 0: args = ""; break;
		case 1: args = currSymbol; break;
		case 2: args = currSymbol + " " + NumToStr(source, 1); break;
		case 3: args = currSymbol + " " + NumToStr(source, 1) + " \"" + dateFrom + "\""; break;
		case 4: args = currSymbol + " " + NumToStr(source, 1) + " \"" + dateFrom + "\" \"" + dateTo + "\""; break;
	}		
	_TRACE("Passing : [" + args + "]");
	ShellExecute(filename, args , "");
};	

If everything is ok, you should get five small popup windows with the passed parameters as messages.

3 Likes

Yes, in this case, my intention was to change some other chart's symbol (or multiple other charts' symbols if those charts are linked by "Symbol Link") not the one which contains the code.

Of course the code can be further improved or changed if needed. I just wanted to propose the very simple idea of "capturing" the plots (without using GuiButtons() etc.) and if possible get some feedback about the best ways of exchanging information between AB and outside java scripts to be able to do it properly.

@beppe Thanks for the reply :+1: I will study your examples.

Regards

@beppe Thank you for showing the example of using ShellExecute() to pass arguments to jscript file. I did't know it is possible and haven't seen similar examples. Below is my code rewritten to use this approach. This solution consists of two codes. Java script file - in this case RelativePerformance.js which is located in Amibroker's Scripts folder:

// Jscript code
AB = new ActiveXObject( "Broker.Application" );
var objArgs = WScript.Arguments;

if (objArgs.Count() == 0) WScript.Echo("No parameters found");

else { Chart = AB.Documents(2);  Chart.Name = objArgs(0); }

And the AFL code:

Title = "{{NAME}} - Relative Performance [%]: {{VALUES}}";
SetChartOptions( blankbars = 10 );
TickerList = "WIG20,MWIG40,SWIG80,NCINDEX";
ChartNumber = 2; // This chart's symbol will be changed 

gcy  = Nz( GetCursorYPosition( 0 ) );
CtrlPressed =  GetAsyncKeyState( 17 ) < 0 ;
MouseButtonPressed = GetCursorMouseButtons();
FirstSelection = 1;

fvb = Status( "firstvisiblebar" );
Plot( 100 * ( C - C[ fvb ] ) / C[ fvb ], Name(), colorBlue );

for( i = 0; ( Symbol = StrExtract( TickerList, i ) ) != ""; i++ )
{
    fc = Foreign( Symbol, "C" );

    if( ! IsNull( fc[ 0 ] ) )
    {

        Ticker = 100 * ( fc - fc[ fvb ] ) / fc[ fvb ];
        SelectedValueTicker = SelectedValue( Ticker );
        PlotStyle = StyleLine;

        if( MouseButtonPressed AND CtrlPressed AND ( gcy < ( SelectedValueTicker + 0.7 ) AND gcy > ( SelectedValueTicker - 0.7 ) ) AND FirstSelection )
        {
            
			currDir = fGetCwd();
			filename = currDir + "\\scripts\\RelativePerformance.js";
			args = "\"" + Symbol + "\""; // Include anything that could have spaces in quotes
			ShellExecute( filename, args , "" );
			
			Say( "" + Symbol );
			FirstSelection = 0;
			PlotStyle = StyleLine|styleThick;

        }
        
        Plot( Ticker, Symbol, colorLightOrange + ( ( 2 * i ) % 15 ), PlotStyle );
    }
}

PlotGrid( 0, colorYellow );

ChangingTickersJscript

Both approaches (from this and from my first post) seem to work equally well. I will be further exploring pros and cons of different methods of exchanging data between AFL and outside scripts (in both directions) - because it is very useful for me.

Regards.

10 Likes

... and just a slight modification to pass to jscript both Symbol name and the number of the chart to be synchronised. In this case, this number is defined only once - in the AFL code :

Jscript:

// Jscript code
AB = new ActiveXObject( "Broker.Application" );
var objArgs = WScript.Arguments;

if (objArgs.Count() == 0) WScript.Echo("No parameters found");

else { Chart = AB.Documents(objArgs(1));  Chart.Name = objArgs(0); }

In the AFL code I have just replaced this line:

args = "\"" + Symbol + "\""; // Include anything that could have spaces in the name

with this one:

args = "\"" + Symbol + "\"" + " " + ChartNumber; // Include anything that could have spaces in the name
4 Likes

You can call OLE from AFL but you have to make sure you are not creating endless loops. The simplest way to achieve that is to run any OLE from ParamTrigger().
ParamTrigger ensures ONE-SHOT execution and protects from loop caused by
AFL execute->OLE call causing chart refresh->chart refresh->AFL execute (loop)

// recommended way of calling OLE from within chart AFL
if( ParamTrigger("Trigger action", "Now" ) )
{
  AB = CreateObject("Broker.Application");
  ....
}
4 Likes

I'm working on a version that uses the GUI buttons, and they too seem a perfect fit for ONE-SHOT triggering.

I'm still working on it, mainly to cleanup some code to arrange rows of GUI buttons in different layouts. Hope to be able to post it when done.
If @Milosz agrees, I will also include in the same code, his very nice "mouse" handling! (With proper credit).

Here is a screenshot:
image

4 Likes

Tomasz, thank you for the reply!

I must say this information and official permission to use OLE - under the above condition, comes as a relief and nice surprise to me. Being able to use COM objects and their methods and properties directly from the AFL code, is very useful in some cases. I was reading so many warnings why user should not use OLE from within AFL, that I was dreading to do so ... I understand the reason for that - you have written, that many users tend to abuse it in many different ways. I was looking for another ways and @beppe came up with interesting alternative which allowed me to rewrite the code without using OLE.

In my first code, I call OLE only under very strict conditions and for this reason I think it meets the above "safety" criteria.

Let me quote one of your replies from another thread referring this topic and explaining why in some cases OLE calls are ignored when used from within AFL code. It might be interesting for some users wondering why their OLE codes might sometimes not work as expected :

You should NEVER EVER use OLE calls that trigger re-execution of running AFL from the same AFL formula. It can be compared with cutting the tree branch you are sitting on. That is crazy idea to start from.

OLE is supposed to be called from the OUTSIDE only. I.e. from the outside .js or .vbs file that you run using Windows Explorer for example!

And if you change the ticker from the outside all you need to call AB.RefreshAll() from your OLE script once (not more than once per second). That is all what is really needed.

If you try to call it from the inside (people always try things that are forbidden) AmiBroker will PROTECT itself from infinite loops that such actions would cause by ignoring certain calls (like looping RefreshAll() that would cause another RefreshAll() and another and another).

Apart from the link which is probably well known to anybody interested in using OLE: https://www.amibroker.com/guide/objects.html, I came across a less known, old article from AB Newsletters tips: https://www.amibroker.com/newsletter/01-2000.html with more information and examples of using OLE.

https://www.amibroker.com/guide/afl/createobject.html
https://www.amibroker.com/guide/afl/createstaticobject.html

Regards.

1 Like

@beppe feel free to use any part of my code that you wish :slight_smile: The part that allows to identify selected plot using mouse clicks, is really simple and might be further improved.

I was wondering, if your and @SwingTradeMonkey approach - to plot relative performance charts using different (and changing) Symbols, is the best idea? I proposed using different chart window for plotting selected symbol not to influence the base chart. In my opinion, in case of foreign tickers or composites, such charts should be rather plotted using some reference (most liquid) symbol (for example Index). If you plot relative performance charts using different arrays (with possible data holes), the chart will be drawn improperly and will be changing from symbol to symbol. I'd rather choose the best (reference) symbol for plotting relative performance chart and locked it (using Symbol lock) to be sure I have always comparable results. In my examples relative performance chart is plotted using only one constant symbol and the selected symbol is plotted in other chart window - using its own array.

Regards

All that I previously wrote is true. OLE is old technology. It locks GUI thread and calling thread and has potential of deadlocks/loops if used improperly (as explained earlier). It should be avoided if possible (if alternatives exists). But...it is useful for one time tasks like for example this: http://www.amibroker.com/kb/2006/09/01/how-to-change-property-for-multiple-symbols-at-once/

1 Like

@Milosz you are right, but this seemed the original goal.
Your idea to use a different chart probably need to be further emphasized.

This solution, in the end, is probably a bit fancy way to change the active ticker (in one of the open charts).

But this idea helped us to finally understand if/when it is OK to invoke OLE from the AFL, to explore JScript args, and in my case to work a little with GUY buttons (something I never used until now).

1 Like

@beppe I agree with you. This topic was an opportunity for me to learn some new stuff - especially your method of passing arguments to jscript via shellExecute() - I appreciate that, and test different ways of changing symbols on selected chart(s). This will be very useful for me because I replace some of my Explorations with chart based solutions and using GuiButtons() (which now allow keyboard navigation and hover detection) and this solution allows me to easily and effectively navigate and synchronize charts with the displayed content in various ways. One can argue, that instead of this method, PlotForeign() or SetForeign() can be used, but it requires modifying existing charts and in case of complex ones, it might be problematic. Besides foreign symbol plotted using not its own array (with data holes) will be displayed incorrectly.

As I said, I will be still exploring different methods of exchanging data between AFL and outside scripts (and vice-versa) to be able to choose the most suitable for each occasion :slight_smile:

Best.

@beppe, @milosz, @Tomasz I want to thank you guys for the input, feedback, code and results. The initial question was answered and improved. I too learned more about the new Gui functionality and much more!

Although the the RPI (relative performance indicator) has shortcomings (when data holes exist) identified above, I feel it gives rapid visualization into the potential dogs and rising stars. Plus the interactive ability to type in a new ticker (e.g. spy) gives us the ability to instantly compare performance to the index (or benchmark) of our choice!

Hello @beppe,

Thank you for sharing this!

You simplified a rather complicated aspect... Would :heart: that, if I could!

2 Likes

Milosz, I tried to complete your code but I don't know. I swear I have read but I do not understand. The tool you propose is very good and helps a lot. Can you complete your code so you can paste it?
regards