<?xml version="1.0"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
	<title>PSS Development Articles</title><link>http://beta.personalstockstreamer.com:80/developers/articles</link><description>Articles about PSS scripting and development    RSS is generated under /icms_xml/PSS_DEV.xml</description><language>en-us</language><pubDate>Thu, 31 Mar 2005 21:30:32 EST</pubDate><lastBuildDate>Sat, 26 Nov 2005 14:05:21 EST</lastBuildDate><docs>http://blogs.law.harvard.edu/tech/rss</docs><generator>formVista::iCMS BLOG</generator><item><title>How to Create Scriptable Custom Columns</title><author>support.nospamplease@nospam.personalstockstreamer.com</author><dc:creator>DTLink Support (Anatoly)</dc:creator><link>http://beta.personalstockstreamer.com:80/developers/articles?article_id=47</link><comments>http://beta.personalstockstreamer.com:80/developers/articles?article_id=47#comments</comments><description>      &lt;p&gt;One of the new features added in Personal Stock Streamer 7.1 is the ability to    have custom columns in the Active Securities view, including scriptable    columns.&amp;nbsp; In this article I will discuss how to create and manage    scriptable columns. To learn about custom columns in general, read the section in the Personal Stock Streamer User's Guide as well as &lt;a href=&quot;/support/articles.html?COMP=clog_list&amp;amp;cmd=detail&amp;amp;cs_clog_entries_ref=45&quot;&gt;this article&lt;/a&gt;.&lt;br /&gt;&lt;/p&gt;  &lt;p&gt;The new script object that controls the custom columns is the ViewManager    object.&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;Set ViewManager = Application.GetObject(&amp;quot;ViewManager&amp;quot;)&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;At this point you can create your custom columns.&amp;nbsp; Expression columns are    very straightforward.&amp;nbsp; For example, if you wanted to create a column that    always showed double the current price:&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;ViewManager.CreateExpressionColumn &amp;quot;DoublePrice&amp;quot;, &amp;quot;Price *     2&amp;quot;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;You could have just as easily added this column through the Custom Columns    interface in the program preferences.&lt;/p&gt;  &lt;p&gt;The way scriptable columns work are a little more complicated, because    scriptable columns are actually memo columns that get their value from a custom    ticker attribute.&amp;nbsp; (To learn&amp;nbsp;more about custom attributes, see    the&amp;nbsp;scripting object model documentation for the Ticker object.)&amp;nbsp;This    means that the value of scriptable columns is filled in indirectly, and you    must do the work yourself to fill in the custom attribute and update the    view.&amp;nbsp; The reason it was done this way is to make the custom column    processing more efficient by avoiding having to recalculate the column value    every time the ticker was updated, leaving your script in full control over the    timing of the updates.&lt;/p&gt;  &lt;p&gt;So to start, let's create a custom column that is associated with a custom    attribute.&amp;nbsp; This example is taken from the Trailing Stop extension.&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;ViewManager.CreateMemoColumn &amp;quot;Trailing Stop&amp;quot;,     &amp;quot;_TrailingStopComment&amp;quot;, True&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;   This code creates a memo column called &amp;quot;Trailing Stop&amp;quot; that gets its value from    the _TrailingStopComment attribute, and is read-only in the Active Securities    view&amp;nbsp;so the user can not edit it.&amp;nbsp; How do we fill in the    _TrailingStopComment variable?&amp;nbsp; The answer to this is inside the Trailing    Stop extension: the _TrailingStopComment attribute is set when the trailing    stop is set on the ticker, or when the high water mark is updated.&amp;nbsp; The    code is as follows:&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;tmp = FormatNumber(CDbl(dStopMark), 2) + &amp;quot;, &amp;quot; +     FormatNumber(CDbl(dStopPct), 2) + &amp;quot;%&amp;quot;&lt;br /&gt;    ticker.SetProperty &amp;quot;_TrailingStopComment&amp;quot;, tmp &lt;/font&gt;  &lt;/p&gt;  &lt;p&gt;The last thing that needs to be done is that the user interface needs to be    updated in order to show the new value for this column:&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;Set ViewManager = Application.GetObject(&amp;quot;ViewManager&amp;quot;)&lt;br /&gt;    ViewManager.UpdateObjectInView(ticker)&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;This design ensures that the user interface is only updated when necessary,    preventing unnecessary running of script code.&lt;/p&gt;  &lt;p&gt;With this basic code it is possible to create columns whose value is calculated    through script, which allows you to go through your portfolio and do things    like calculations based on historical data or relative to another ticker in the    portfolio.&amp;nbsp;&amp;nbsp;If you are interested in creating your own scriptable    columns, I recommend reading our Personal Stock Streamer object model    documentation and samples found on the&amp;nbsp;Developers section of this    site.&amp;nbsp; Also, the full source code for the Trailing Stop extension is    available &lt;a href=&quot;/formvista/frontend/icms.php/rd/121&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;&amp;nbsp;&lt;/p&gt;      </description><pubDate>Wed, 27 Jul 2005 15:28:59 EST</pubDate><guid>http://beta.personalstockstreamer.com:80/developers/articles?article_id=47</guid></item><item><title>Developing a Trailing Stop Alert Extension</title><author>support.nospamplease@nospam.personalstockstreamer.com</author><dc:creator>DTLink Support (Anatoly)</dc:creator><link>http://beta.personalstockstreamer.com:80/developers/articles?article_id=53</link><comments>http://beta.personalstockstreamer.com:80/developers/articles?article_id=53#comments</comments><description>&lt;p&gt;Over the years we have been asked for a way to set trailing stop alerts on   your stocks. It is a feature we always wanted to provide, but the software was   not ready for it. Now with Personal Stock Streamer 7 extensions, the system can   be written much easier and in a much more powerful and flexible way than would   have been possible before.&lt;/p&gt;&lt;p&gt;In general there are a number of techniques for setting trailing stops, but   for this example we will use the simplest stop loss strategy:  &lt;/p&gt;&lt;ul&gt;&lt;li&gt;At the moment the trailing stop is set, the high water mark&amp;nbsp;is calculated as   the higher of the purchase price and the most recent trading price.   &lt;/li&gt;&lt;li&gt;When the ticker is updated, compare the current stock price to the high   water mark.&amp;nbsp; If the stock price is higher, then adjust the high water mark   upward.&amp;nbsp; If the stock price is lower by&amp;nbsp;the set percentage (default 20%), raise   an alert.&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;Implementing more complicated trailing stop techniques is left as an exercise   for the reader.&amp;nbsp;&amp;nbsp;Of course we appreciate all contributions.&amp;nbsp; :-)&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;        In order to make the extension fairly efficient, we will use the custom   attribute support in the scripting object model to keep track of several values   that we need.&amp;nbsp; This will prevent unnecessary recalculations.  &lt;ul&gt;&lt;li&gt;_TrailingStopMark attribute is the current high water mark.   &lt;/li&gt;&lt;li&gt;_TrailingStopPct attribute is the threshold percentage.   &lt;/li&gt;&lt;li&gt;_TrailingStopAlertAction attribute is the name of the alert action to run   when the ticker hits the threshold.   &lt;/li&gt;&lt;li&gt;_TrailingStopMemo attribute is a string that shows the stop mark and   percentage in one string.&amp;nbsp; This is used as the visual feedback for display in   the Trailing Stop column.&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;It is important to note that we will use the new custom column support in   Personal Stock Streamer 7.1 to create the Trailing Stop column.&amp;nbsp; This column is   created by the extension but is not enabled in the view by default, so if you   want to see whether a trailing stop is set for a ticker you will need to   manually enable this column.&lt;/p&gt;  &lt;p&gt;As with previous extensions, this extension will repeat the familiar pattern   of having a handler class with several functions to handler the various events   from the application, followed by initialization code that runs when the   extension is loaded.&amp;nbsp; During initialization we have to perform several   tasks:&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;Create an instance of the handler class, and register it as the target for   the application events we want to respond to.   &lt;/li&gt;&lt;li&gt;Add two menu items to the Tools menu that allows users to set and clear   trailing stops for a ticker.   &lt;/li&gt;&lt;li&gt;Create a custom column for visual feedback so that users can see which   tickers have a trailing stop set.&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;The initialization code is fairly straightforward so there is no reason to   cover it in this article.&amp;nbsp; Look at the source code for details.&lt;/p&gt;  &lt;p&gt;The handler class is a little more complicated.&amp;nbsp;&amp;nbsp;When the user selects to set   a trailing stop, the handler must ask the user for some additional parameters   (such as the stop percentage and alert action).&amp;nbsp; The only way to do this within   the script object model is to display an HTML form in a small window and handle   the form submit event in our handler to read those parameters back.&amp;nbsp; This means   that the actual setting of the trailing stop can not be done in the menu   handler.&amp;nbsp; The only real tricky part&amp;nbsp;in this section is that in order to get the   form to post correctly back to our class, we must set the form method as &amp;quot;post&amp;quot;   and the target as &amp;quot;x&amp;quot;.&amp;nbsp; This little trick is necessary because of how our   particular HTML browser control works.&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;courier new&quot;&gt;Set selectedTickers =   Application.ActiveDocument.Selection.Tickers &lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;courier new&quot;&gt;If (selectedTickers.Count &amp;gt; 0) Then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Set   wndManager = Application.GetObject(&amp;quot;WindowManager&amp;quot;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Set wndBrowser =   wndManager.CreateBrowserWindow(300, 140, me)&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;courier new&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; form = &amp;quot;&amp;lt;body bgcolor=white&amp;gt;&amp;lt;form name=form1   method=post target=&amp;quot; + Chr(34) + &amp;quot;x&amp;quot; + Chr(34) + &amp;quot;&amp;gt;&amp;quot;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;   ...&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; form = form + &amp;quot;&amp;lt;/form&amp;gt;&amp;lt;/body&amp;gt;&amp;quot;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;   wndBrowser.SetHTML(form)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;wndBrowser.Title = &amp;quot;Set Trailing   Stop&amp;quot;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Note that when we create the browser window above, we set &amp;quot;me&amp;quot; as the handler   object.&amp;nbsp; This means that&amp;nbsp;when the user clicks the OK button to submit the form   we must handle the response in the OnFormSubmitted method, which is where we can   pull the parameters and set the trailing stops for the selected tickers.&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;public Function OnFormSubmitted ( form )&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; If   (form.Value(&amp;quot;submit&amp;quot;) = &amp;quot;OK&amp;quot;) Then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SetMarks form.Value(&amp;quot;pct&amp;quot;),   form.Value(&amp;quot;act&amp;quot;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End If&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ...&amp;nbsp;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;The SetMarks function does most of the&amp;nbsp;work of setting the trailing stops.&amp;nbsp;   It iterates through the selected tickers, gets the default stop mark for each   one (which is automatically calculated from the last purchase price or last   trading price) and sets the properties.&amp;nbsp; Since the high water mark is calculated   automatically, a future enhancement to this extension would be to allow the user   to override this value in case a different high water mark is desired.&lt;/p&gt;  &lt;p&gt;The work of checking the high water mark is done in the OnTickerUpdated   method of the handler class.&amp;nbsp; Since this method is called for every ticker that   is updated, the first thing we must do is check whether a trailing stop is set   for the ticker.&amp;nbsp; Since the GetProperty call will fail if the custom attributes   have not been set for this ticker, we must make sure to set the error handling   appropriately so that our script will not fail.&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;dStopMark = 0&lt;br /&gt;dStopPct = 0&lt;br /&gt;&lt;br /&gt;On Error Resume   Next&lt;br /&gt;dStopMark = CDbl(ticker.GetProperty(&amp;quot;_TrailingStopMark&amp;quot;))&lt;br /&gt;dStopPct =   CDbl(ticker.GetProperty(&amp;quot;_TrailingStopPct&amp;quot;))&lt;br /&gt;On Error Goto 0&lt;/font&gt; &lt;/p&gt;  &lt;p&gt;At this point, if the dStopMark and dStopPct variables are non-zero, then a   trailing stop has been set for the ticker.&amp;nbsp; Now what we need to do is get the   current price of the ticker and compare it to the high water mark.&amp;nbsp; If it is   higher, we move the high water mark, and if it is lower then we calculate the   percentage difference and compare it to the threshold value.&amp;nbsp; We generate the   alert only if the threshold has been reached.&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;dPrice = ticker.GetProperty(&amp;quot;Price&amp;quot;)&lt;br /&gt;&lt;br /&gt;If   (dPrice &amp;gt; dStopMark) Then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; dStopMark = dPrice &lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;   ticker.SetProperty &amp;quot;_TrailingStopMark&amp;quot;, dStopMark&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br /&gt;&amp;nbsp;Else&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;   dDiffPct = ((dStopMark - dPrice) / dStopMark) * 100 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; If (dDiffPct   &amp;gt;= dStopPct) Then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RunAlertAction   ticker.GetProperty(&amp;quot;_TrailingStopAlertAction&amp;quot;), ticker&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End If&lt;br /&gt;End   If&lt;/font&gt; &lt;/p&gt;  &lt;p&gt;The RunAlertAction method does the work of actually firing the alert, which   involves looking up the desired alert action and running it through the alert   manager.&lt;/p&gt;  &lt;p&gt;&lt;font face=&quot;Courier New&quot;&gt;Set manager =   Application.GetObject(&amp;quot;AlertManager&amp;quot;)&lt;br /&gt;&lt;br /&gt;If Not manager Is Nothing   Then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; For Each action in manager.Actions&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; If   action.GetProperty(&amp;quot;Name&amp;quot;) = name Then&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; action.Run2 ticker,   &amp;quot;Trailing Stop&amp;quot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;Exit For&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;End If&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;Next&lt;br /&gt;End   If&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Note that we use the new Run2 method of the alert action object, which allows   us to specify the attribute to which the alert is related and will cause the   little yellow alert bell to be displayed in the Trailing Stop column as visual   feedback that the trailing stop alert has fired.&lt;/p&gt;  &lt;p&gt;The full source of the Trailing Stop extension is available &lt;a href=&quot;/formvista/frontend/icms.php/rd/121&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;  </description><pubDate>Tue, 11 Oct 2005 10:54:42 EST</pubDate><guid>http://beta.personalstockstreamer.com:80/developers/articles?article_id=53</guid></item><item><title>Recreating the Capital Gains Report</title><author>support.nospamplease@nospam.personalstockstreamer.com</author><dc:creator>DTLink Support (Anatoly)</dc:creator><link>http://beta.personalstockstreamer.com:80/developers/articles?article_id=48</link><comments>http://beta.personalstockstreamer.com:80/developers/articles?article_id=48#comments</comments><description>    &lt;p&gt;Although Personal Stock Streamer already has a built-in capital gains report, I    took it upon myself to rewrite it in VBScript not only to prove that it can be    done, but to extend it and make it easily customizable.&lt;/p&gt;  &lt;p&gt;Normally, calculating capital gains for a given ticker would require applying    each transaction checking whether it generated a change in position, and    whether that position actually generated capital gains.&amp;nbsp; For example, a    simple buy transaction may not generate capital gains, but a sale transaction    may generate a gain or a loss if there is an open long position.&amp;nbsp;This type    of processing is already done internally within Personal Stock Streamer to    calculate gains and losses, and to duplicate this in script would require a lot    of work.&amp;nbsp; Getting this functionality for free is one advantage of having the capital    gains report embedded in the main application.&lt;/p&gt;  &lt;p&gt;So for this extension I had to cheat a little bit: I modified the script plug-in in Personal Stock Streamer 7.1 to expose the functionality described above, so that it would be    trivially simple (and much faster) to rewrite the capital gains report in script.&amp;nbsp; This also eliminated the need to duplicate the transaction processing logic that is    already inside Personal Stock Streamer.&amp;nbsp; What&amp;nbsp;I did is add an    optional argument to the Ticker::ApplyTransactionToCurrentHoldings() and    Ticker::ApplyTransactionsToCurrentHoldingsUntil() methods that allows you to pass    a reference to a callback object.&amp;nbsp; This callback object would define a    handful of methods that would be called at the appropriate time by the main    application, and all the report had to do was gather and summarize the    data.&amp;nbsp; In the case of this report, the callback object is the same object    that the report is running in, so I just pass &amp;quot;me&amp;quot; as the argument.&lt;/p&gt;  &lt;p&gt;Before I go into describing these callback methods, I should say something about    the design of the handler class for this report.&amp;nbsp; In order to separate out    the long-term gains, short-term gains, and other income and expenses,    I&amp;nbsp;created three arrays that get populated with summary data by the    callback methods I am about to describe.&amp;nbsp; This neatly separates the    processing and output code, which allows easy modification of the    formatting&amp;nbsp;code to get any style of report you need.&lt;/p&gt;  &lt;p&gt;The report handler defines four callback methods.&amp;nbsp; The first two,    OnCBAddHoldings and OnCBSubtractHoldings,&amp;nbsp;are called by Personal Stock    Streamer whenever a transaction affects the current holdings for a    ticker.&amp;nbsp; Because not all transactions that affect holdings are related to    the capital gains report, there is a quick check at the beginning of each    method to make sure that only certain transactions get saved in the summary    arrays.&amp;nbsp; The second two callback methods, OnCBAddIncome and    OnCBSubtractIncome, are called for dividends, income, and expense transactions    that do not affect the holdings.&amp;nbsp; This allows us to put together a nice    summary of those transactions as well.&amp;nbsp; There is absolutely nothing    complicated about these callback methods because all they do is store the    summary data for later.&lt;/p&gt;  &lt;p&gt;After the processing is finished, the output method is called once for each of    the three sections of the report, which sorts the array by ticker and generates    the HTML output that is displayed in the Reports view.&amp;nbsp; One possible    enhancement that could be made in a future version of this report is to have    sub-totals for each ticker to make it easier to see the gains for individual    investments.&lt;/p&gt;  &lt;p&gt;The full source code for the Capital Gains report is available &lt;a href=&quot;/formvista/frontend/icms.php/rd/123&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;    </description><pubDate>Mon, 01 Aug 2005 13:52:24 EST</pubDate><guid>http://beta.personalstockstreamer.com:80/developers/articles?article_id=48</guid></item><item><title>How to Create a Custom Chart Indicator</title><author>support.nospamplease@nospam.personalstockstreamer.com</author><dc:creator>DTLink Support (Anatoly)</dc:creator><link>http://beta.personalstockstreamer.com:80/developers/articles?article_id=22</link><comments>http://beta.personalstockstreamer.com:80/developers/articles?article_id=22#comments</comments><description>This article is a brief description of the   implementation of a price relative indicator.&amp;nbsp; The price relative indicator&amp;nbsp; compares the   price of one security to another, and is often used to compare the   performance of a particular stock to a market index such as the S&amp;amp;P 500.&amp;nbsp; The indicator is drawn on a separate set of axes from the   main chart, and the calculation is &lt;code&gt;Price[Ticker]/Price[S&amp;amp;P 500]&lt;/code&gt;.  &lt;p&gt;As with any typical chart extension, we will define a   handler class to create the chart objects and to recalculate the indicator as   necessary.&amp;nbsp; In particular, an indicator class only requires two functions:   &lt;strong&gt;Create&lt;/strong&gt; to create the objects that make up the chart and hook   them up to the chart manager, and &lt;strong&gt;Recalc&lt;/strong&gt; to actually calculate the   indicator.&amp;nbsp; However there are a couple tricky parts to this particular   indicator:&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt; Because this indicator is relative to the price of a second ticker symbol, we first need     to make sure the second ticker actually exists in our portfolio, because     Personal Stock Streamer will only retrieve historical data for a ticker that is part of     a portfolio.&amp;nbsp; So if the ticker is not present in the current portfolio     we need to create an invisible ticker and add it to the portfolio.&amp;nbsp;     Also, if an invisible ticker is created we need to make sure to delete it when     the chart is closed. &lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;    &lt;li&gt;We need to make sure that the secondary ticker has     enough historical data to do our calculations, or we need to request that it     be retrieved from the data source.&amp;nbsp; One thing to note here is that     normally we would have to set up an OnHistoryUpdated event handler so that our     object is notified when the historical data arrives, but for chart indicators     we do not need to do that because the chart window already catches that event     and automatically calls our &lt;strong&gt;Recalc&lt;/strong&gt; function. &lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;I'm not going to go through the chart setup because it is relatively straightforward;&amp;nbsp;the setup   code is well-commented, so you can read it to see   how the various components of the chart are created and hooked up.&lt;/p&gt;  &lt;p&gt;As for the tricky parts, here is the first part of the   Recalc function:&lt;/p&gt;&lt;pre&gt;Set AltTicker = Doc.FindTicker(Nothing,&lt;br /&gt; ChartObject.GetParam(0), 0) ' check if the ticker exists in&lt;br /&gt; the document If AltTicker&lt;br /&gt; Is Nothing Then '&lt;br /&gt;&lt;br /&gt; if ticker was not found, create&lt;br /&gt;  temporary ticker Set AltTicker = Doc.CreateTicker Set Portfolio = Doc.CurrentPortfolio If Not AltTicker Is Nothing Then&lt;br /&gt;&lt;br /&gt;  AltTicker.SetProp &amp;quot;Symbol&amp;quot;, ChartObject.GetParam(0)&lt;br /&gt;  AltTicker.SetProp &amp;quot;Visible&amp;quot;, &amp;quot;0&amp;quot;&lt;br /&gt;&lt;br /&gt;  Portfolio.Insert -1, AltTicker&lt;br /&gt;&lt;br /&gt;  ' set the label on this chart&lt;br /&gt;  MyDataSet.Label = MyDataSet.ID + &amp;quot; (&amp;quot; + ChartObject.GetParam(0) + &amp;quot;) &amp;quot;&lt;br /&gt;&lt;br /&gt;  ' make sure this ticker gets deleted when the chart is closed&lt;br /&gt;  ChartObject.DeferDeleteTicker(AltTicker)&lt;br /&gt; End If&lt;br /&gt;End If&lt;/pre&gt;  &lt;p&gt;What we're doing in the code above is finding the   relative ticker (such as the S&amp;amp;P 500) in the document, which is passed   as the first parameter in the chart object.&amp;nbsp; (Programmers start counting at&amp;nbsp;0, so   the first parameter is actually at the 0th location.)&amp;nbsp; This part of the code   handles the case where the ticker does not exist in the docuemnt, so it creates   the ticker object, sets the &lt;code&gt;Symbol&lt;/code&gt; and &lt;code&gt;Visible&lt;/code&gt; properties, and inserts it into the current   portfolio.&amp;nbsp; Because the ticker is invisible it doesn't really matter where   you insert it as long as you clean up after yourself, which is what the call to   &lt;code&gt;DeferDeleteTicker()&lt;/code&gt; does.&lt;/p&gt;  &lt;p&gt;The next part of the code should be pretty   straightforward. It gets the data set from the relative ticker or requests it   from the application if necessary:&lt;/p&gt;&lt;pre&gt;If Not AltTicker Is Nothing Then&lt;br /&gt; Set AltCloseDataSet = ChartObject.GetDataSetFromTicker(AltTicker, &amp;quot;Close&amp;quot;)&lt;br /&gt;&lt;br /&gt; ...&lt;br /&gt; &lt;br /&gt; ' check for a valid data set&lt;br /&gt; If AltCloseDataSet Is Nothing Then&lt;br /&gt;  &lt;br /&gt;  ' if there was no data set, set a dummy value so it will at least display the chart&lt;br /&gt;  MyDataSet.Data(MyDataSet.Size - 1) = 0&lt;br /&gt;  MyDataSet.Label = MyDataSet.ID + &amp;quot; (&amp;quot; + ChartObject.GetParam(0) + &amp;quot;) &amp;quot;&lt;br /&gt;&lt;br /&gt;  ' request historical data from the application&lt;br /&gt;  ChartObject.RequestHistoricalDataForTicker(AltTicker)&lt;/pre&gt;  &lt;p&gt;Once we have the data for the relative ticker, the next   part of the code actually calculates the relative indicator: &lt;/p&gt;&lt;pre&gt; Else&lt;br /&gt;  ' loop through all of the data and calculate the Price Relative indicator&lt;br /&gt;  For x = 0 To CloseDataSet.Size - 1&lt;br /&gt;   If (CloseDataSet.IsValidData(x) And CloseDataSet.Data(x) &amp;gt; 0) Then&lt;br /&gt;    LastValidCloseData = x&lt;br /&gt;&lt;br /&gt;    If (AltCloseDataSet.IsValidData(x) And AltCloseDataSet.Data(x) &amp;gt; 0) Then&lt;br /&gt;     LastValidAltCloseData = x&lt;br /&gt;&lt;br /&gt;     MyDataSet.Data(x) = CloseDataSet.Data(x) / AltCloseDataSet.Data(x)&lt;br /&gt;    End If&lt;br /&gt;   End If&lt;br /&gt;  Next&lt;br /&gt;&lt;br /&gt;  ...&lt;br /&gt;  &lt;br /&gt;  ' check data at the end of the range&lt;br /&gt;  If LastValidAltCloseData &amp;lt; LastValidCloseData - 2 Then&lt;br /&gt;   ChartObject.RequestHistoricalDataForTicker(AltTicker)&lt;br /&gt;  End If&lt;br /&gt; End If&lt;br /&gt;End If&lt;/pre&gt;  &lt;p&gt;Note how we're keeping track of the last valid data for   the relative ticker in the &lt;code&gt;LastValidAltCloseData&lt;/code&gt; variable. What this allows us   to do is to check if perhaps there is some data at the end of our range that is   missing for the relative ticker and tries to request it. This handles the case   where data for that ticker may not be updated for whatever reason.&lt;/p&gt;  &lt;p&gt;Other than registering our indicator with the system,   that's pretty much it.&amp;nbsp; As we do more of these types of extensions, you   will notice that there really isn't that much work involved, and&amp;nbsp;a lot can   be accomplished with just a few dozen lines of code.&lt;/p&gt;  &lt;p&gt;The full code for this extension is &lt;a href=&quot;/formvista/frontend/icms.php?rd=105&quot;&gt;here&lt;/a&gt;, and the complete packaged extension is available for download   through the Extension manager under the Tools menu.&lt;/p&gt;  &lt;p&gt;&amp;nbsp;&lt;/p&gt;</description><pubDate>Mon, 02 May 2005 09:25:30 EST</pubDate><guid>http://beta.personalstockstreamer.com:80/developers/articles?article_id=22</guid></item><item><title>How to Create a Custom Report Extension</title><author>support.nospamplease@nospam.personalstockstreamer.com</author><dc:creator>DTLink Support (Anatoly)</dc:creator><link>http://beta.personalstockstreamer.com:80/developers/articles?article_id=19</link><comments>http://beta.personalstockstreamer.com:80/developers/articles?article_id=19#comments</comments><description>    &lt;strong&gt;Introduction&lt;/strong&gt;  &lt;p&gt;    &lt;/p&gt;  &lt;p&gt;The reports you see in Personal Stock Streamer are simple HTML  documents that are generated and formatted by the reporting engine. The  built-in reports (capital gains, etc.) are written in C++ and compiled  into Personal Stock Streamer, but with the new Personal Stock Streamer  7.0 software it is possible to create custom reports completely in  script. For this example, we will create a YTD performance report that  shows the difference between the current price of a stock and the price  at the beginning of the year.  &lt;/p&gt;    &lt;p&gt;Assuming you have read the Introduction to Personal Stock Streamer  Extensions article, we can jump right into defining the report class.  &lt;/p&gt;              &lt;strong&gt;The Report Handler&lt;/strong&gt;    &lt;p&gt;  The report handler class has just one required callback method, GenerateReport(), that will be called by the report generator engine when you select your report from the reports list.    &lt;/p&gt;&lt;pre&gt;class PriceReportHandler&lt;br /&gt;&lt;br /&gt; ' create the report&lt;br /&gt; public Function GenerateReport ( Name, Handler, Folder )&lt;br /&gt;&lt;br /&gt; end Function&lt;/pre&gt;    &lt;p&gt;  Because multiple reports can have the same handler class, the GenerateReport() method is passed the Name of the requested report so that your code can differentiate between multiple reports.  The second argument, the Handler object, is what you will use to get the report parameters and to generate the report.  The Folder object is the current folder or portfolio over which the report should be generated.    &lt;/p&gt;&lt;p&gt;  Since reports are just simple HTML documents, we can start by sending the header to the reporting engine.    &lt;/p&gt;&lt;pre&gt;' generate report  &lt;br /&gt;Handler.WriteReport &amp;quot;&amp;lt;body bgcolor=White&amp;gt;&amp;quot;&lt;br /&gt;Handler.WriteReport &amp;quot;&amp;lt;b&amp;gt;Price Performance Report for &amp;quot; + Handler.FilterPeriod + &amp;quot;&amp;lt;/b&amp;gt;&amp;quot;&lt;br /&gt;Handler.WriteReport &amp;quot;&amp;lt;table border=0 cellpadding=0 cellspacing=10&amp;gt;&amp;quot;&lt;br /&gt;Handler.WriteReport &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;&amp;lt;b&amp;gt;Symbol&amp;lt;/b&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;b&amp;gt;Starting Price&amp;lt;/b&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;b&amp;gt;Ending Price&amp;lt;/b&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;b&amp;gt;Gain&amp;lt;/b&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;b&amp;gt;% Gain&amp;lt;/b&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot;&lt;/pre&gt;    &lt;p&gt;  As you can see, this report uses the Handler.FilterPeriod property to show the user what the selected filter period is.  The filter period will be the same as what is selected in the Reports tab.    &lt;/p&gt;&lt;p&gt;  Now we are ready to write the report:    &lt;/p&gt;&lt;pre&gt;RecurseFolder Handler, Folder&lt;br /&gt;&lt;br /&gt;Handler.WriteReport &amp;quot;&amp;lt;/table&amp;gt;&amp;lt;/body&amp;gt;&amp;quot;&lt;/pre&gt;    &lt;p&gt;  Unfortunately we don't get anything for free here.  Because portfolios and folders may themselves have nested folders, we must define a RecurseFolder method to go through all of them and generate the report.  The last line then writes the end of the table and finishes the report.    &lt;/p&gt;&lt;p&gt;  So now we have to write the RecurseFolder method, which is the most complicated method in this class.    &lt;/p&gt;&lt;pre&gt;public Function RecurseFolder ( Handler, Folder )&lt;br /&gt; ' process the tickers in the folder&lt;br /&gt; Set Tickers = Folder.Tickers&lt;br /&gt;&lt;br /&gt; For i = 1 To Tickers.Count&lt;/pre&gt;    &lt;p&gt;  We start by getting the tickers from the folder and iterating over the collection using the For statement.  For each ticker we must retrieve the historical data for the requested date range (ie. the current year) and calculate the difference between the price at the end of the range vs. the price at the beginning of the range.    &lt;/p&gt;&lt;pre&gt;' get the history data for this year&lt;br /&gt;Tickers.Item(i).SetProp &amp;quot;HistoryStart&amp;quot;, Handler.FilterDateStart&lt;br /&gt;Tickers.Item(i).SetProp &amp;quot;HistoryEnd&amp;quot;, Handler.FilterDateEnd&lt;br /&gt;&lt;br /&gt;Set History = Tickers.Item(i).HistoryData&lt;/pre&gt;    &lt;p&gt;  We are again using properties of the Handler object to get the start and end of the date range requested by the user.  Then we retrieve the historical data for that range.    &lt;/p&gt;&lt;pre&gt;If Not History Is Nothing And History.Count &amp;gt; 0 Then&lt;/pre&gt;    &lt;p&gt;  Here is a little complication.  What if the historical data for that range is not available or has not been retrieved yet?  We will handle this shortly, but first we handle the case where we do have the data.    &lt;/p&gt;&lt;pre&gt;BeginValue = History.Item(0).GetProp(&amp;quot;Close&amp;quot;)&lt;br /&gt;EndValue = History.Item(History.Count - 1).GetProp(&amp;quot;Close&amp;quot;)&lt;br /&gt;&lt;br /&gt;Diff = EndValue - BeginValue&lt;br /&gt;DiffPct = ((EndValue - BeginValue) / BeginValue) * 100&lt;/pre&gt;    &lt;p&gt;  This case is easy.  We get the data at the beginning and end of the range and calculate the difference.  Then we can write the report:    &lt;/p&gt;&lt;pre&gt;Handler.WriteReport &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;quot; + Tickers.Item(i).GetProp(&amp;quot;Symbol&amp;quot;) + &amp;quot;&amp;lt;/td&amp;gt;&amp;quot;&lt;br /&gt;Handler.WriteReport &amp;quot;&amp;lt;td align=right&amp;gt;&amp;quot; + FormatNumber(EndValue,2) + &amp;quot;&amp;lt;/td&amp;gt;&amp;lt;td align=right&amp;gt;&amp;quot; + FormatNumber(BeginValue,2) + &amp;quot;&amp;lt;/td&amp;gt;&amp;quot;&lt;br /&gt;Handler.WriteReport &amp;quot;&amp;lt;td align=right&amp;gt;&amp;quot; + FormatNumber(Diff,2) + &amp;quot;&amp;lt;/td&amp;gt;&amp;lt;td align=right&amp;gt;&amp;quot; + FormatNumber(DiffPct,2) + &amp;quot;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;quot;&lt;/pre&gt;    &lt;p&gt;  This produces a formatted table filled in with all the right values.  There is a case that is not handled by this code, and that is what if only partial data is available for the date range?  Because Personal Stock Streamer expires the historical data cache each day, this case would not occur and therefore does not need to be handled explicitly.    &lt;/p&gt;&lt;p&gt;  Now we can handle the case where no historical data is available for the range:    &lt;/p&gt;&lt;pre&gt;Else&lt;br /&gt; Handler.WriteReport &amp;quot;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;quot; + Tickers.Item(i).GetProp(&amp;quot;Symbol&amp;quot;) + &amp;quot;&amp;lt;/td&amp;gt;&amp;quot;&lt;br /&gt; Handler.WriteReport &amp;quot;&amp;lt;td align=right&amp;gt;Waiting for Data&amp;lt;/td&amp;gt;&amp;quot;&lt;br /&gt; &lt;br /&gt; Tickers.Item(i).DownloadHistoryData Handler.FilterDateStart, Handler.FilterDateEnd&lt;br /&gt;End If&lt;/pre&gt;    &lt;p&gt;  Basically what we are doing here is putting up a &amp;quot;Waiting for Data&amp;quot; sign and requesting historical data for the report range through the Ticker object.  This request will happen asynchronously, so somehow we will need to handle it.  I will explain this part shortly, but first we need to finish up this method:    &lt;/p&gt;&lt;pre&gt; Next&lt;br /&gt; &lt;br /&gt; ' recursively loop through the subfolders&lt;br /&gt; Set Folders = Folder.Folders&lt;br /&gt; &lt;br /&gt; For i = 1 To Folders.Count&lt;br /&gt;  RecurseFolder Handler, Folders.Item(i)&lt;br /&gt; Next&lt;br /&gt; &lt;br /&gt;end Function&lt;/pre&gt;    &lt;p&gt;  This code finishes out the For loop, then recursively calls itself for any nested folders.    &lt;/p&gt;&lt;p&gt;  Now we get to a little complication.  Although the report handler class only has one required method, we need to handle the case where the historical data request was sent to Personal Stock Streamer but the data actually arrives at a later time.  Fortunately Personal Stock Streamer provides a way to handle this case by registering interest with the event manager.  If I may jump ahead a little bit to show part of the initialization code for this extension:    &lt;/p&gt;&lt;pre&gt;Set EventManager = Application.GetObject(&amp;quot;EventManager&amp;quot;)&lt;br /&gt;&lt;br /&gt;If Not EventManager Is Nothing Then&lt;br /&gt; EventManager.RegisterHandlerMethod ReportHandler, &amp;quot;OnHistoryUpdated&amp;quot;&lt;br /&gt;End If&lt;/pre&gt;    &lt;p&gt;  This is where we hook up our report handler class to the OnHistoryUpdated event that tells us when historical data has arrived for a ticker.  Since we register for this event we must now define an OnHistoryUpdated method in the report handler class:    &lt;/p&gt;&lt;pre&gt;public Function OnHistoryUpdated ( Ticker )&lt;br /&gt; If (Application.GetCurrentView() = &amp;quot;Reports&amp;quot;) Then&lt;br /&gt;  Set EventManager = Application.GetObject(&amp;quot;EventManager&amp;quot;)&lt;br /&gt;  EventManager.RegisterHandlerMethod me, &amp;quot;OnAppTimer&amp;quot;&lt;br /&gt; End If&lt;br /&gt;End Function&lt;/pre&gt;    &lt;p&gt;  Why aren't we updating the report here?  Because OnHistoryUpdated will be called once for each ticker, if you are requesting historical data for multiple tickers at once you could be needlessly updating the report once for each ticker.  Instead what we are doing here is registering for yet another event to notify us when the 1-second application timer goes off.  This allows us to delay recalculating the report by just a little bit until all of the historical data comes in.    &lt;/p&gt;&lt;p&gt;  So now we can define the OnAppTimer event handler.    &lt;/p&gt;&lt;pre&gt;public Function OnAppTimer&lt;br /&gt; EventManager.UnregisterHandlerMethod me, &amp;quot;OnAppTimer&amp;quot;&lt;br /&gt;&lt;br /&gt; Set ReportManager = Application.GetObject(&amp;quot;ReportManager&amp;quot;)&lt;br /&gt; ReportManager.UpdateCurrentReport&lt;br /&gt;End Function&lt;/pre&gt;    &lt;p&gt;  Notice that the first thing we do is to unregister the handler, which makes sure that the handler is not called more than once.  Then the last thing is to force an update of the current report, which is done through the report manager object.  Note that you can't call the GenerateReport method directly because in the OnAppTimer handler do you not have all of the information and extra objects needed for GenerateReport to work.    &lt;/p&gt;&lt;p&gt;  So that is the end of the report handler class, and we should not forget to end the class definition.    &lt;/p&gt;&lt;pre&gt;end Class&lt;/pre&gt;    &lt;p&gt;  Because I already covered part of the initialization with registering for the OnHistoryUpdated event, here is the second part, actually registering the report:    &lt;/p&gt;&lt;pre&gt;Set ReportHandler = new PriceReportHandler&lt;br /&gt;Set ReportManager = Application.GetObject(&amp;quot;ReportManager&amp;quot;)&lt;br /&gt;&lt;br /&gt;If Not ReportManager Is Nothing Then&lt;br /&gt; ReportManager.Register &amp;quot;Price Performance&amp;quot;, ReportHandler&lt;br /&gt;End If&lt;/pre&gt;    &lt;p&gt;  This is probably starting to look very familiar by now.  We simply create an instance of the report handler and register it with the report manager.    &lt;/p&gt;&lt;p&gt;  That is the end of the custom report extension; the full extension code is available &lt;a href=&quot;/formvista/frontend/icms.php?rd=104&quot;&gt;here&lt;/a&gt;.    &lt;/p&gt;&lt;p&gt;  So where do you go from here?  The sky is the limit.  With easy access to your porfolio data and extensive historical data you can now create as many custom reports as you need to help you with your investment decisions.  &lt;/p&gt;  </description><pubDate>Mon, 11 Apr 2005 10:19:12 EST</pubDate><guid>http://beta.personalstockstreamer.com:80/developers/articles?article_id=19</guid></item><item><title>Introduction to Personal Stock Streamer Extensions</title><author>support.nospamplease@nospam.personalstockstreamer.com</author><dc:creator>DTLink Support (Anatoly)</dc:creator><link>http://beta.personalstockstreamer.com:80/developers/articles?article_id=14</link><comments>http://beta.personalstockstreamer.com:80/developers/articles?article_id=14#comments</comments><description>    &lt;p&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Introduction&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For  a long time we have received requests for features and extensions that  we could not easily provide with our limited development resources. As  a micro-corp we must focus on providing the most value possible for the  largest number of customers, so many requests stayed on our to-do lists  for quite a long time. Previous versions of Personal Stock Streamer  included a way to extend the software with plug-ins, but writing  plug-ins required advanced programming knowledge and was therefore out  of the reach of most of our customers.&lt;br /&gt;&lt;br /&gt;With the Personal Stock  Streamer 7.0 release, we have now included a way for our customers to  easily extend the software on their own through scripting. Because  writing extensions is now so much easier than before, we at DTLink  Software are also considering offering custom extension writing  services for our customers who are interested in specific functionality.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;What is Possible&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Personal  Stock Streamer extensions can be written with any of a number of  scripting languages, including VBScript, JScript, PerlScript, and  Python. In all cases the object model will be the same. The same object  model can also be accessed from other OLE-compatible applications such  as Excel, which allows for extensive custom integration, but that is a  topic for another article.&lt;br /&gt;&lt;br /&gt;The object model includes access to  all of your portfolio data, including multiple portfolios, tickers,  transations, alerts, and historical data. It includes the ability to  add custom menus commands to the application and receive events when  those menu commands are selected. It includes the ability to define  custom technical indicators for the charts, the ability to create  custom reports, the ability to generate alerts, and more. Detailed  object model documentation is available on the Personal Stock Streamer  web site.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Scripts vs. Extensions&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Personal  Stock Streamer can run simple standalone scripts, and specially  formatted scripts can be installed as extensions. When scripts are  installed as extensions, they are automatically loaded when Personal  Stock Streamer starts. This allows them to do things such as installing  event handlers that would otherwise not be possible in regular scripts.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;How Extensions Work&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In  order for your script extensions to be loaded when Personal Stock  Streamer starts, two things must happen. First, they must be wrapped in  a simple XML document; second, they must be installed through the  extension installer interface under the Tools menu.&lt;br /&gt;&lt;br /&gt;The XML wrapper looks like this:&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;lt;pss_extension name=&amp;quot;Hello World Sample&amp;quot; version=&amp;quot;1.0&amp;quot;&amp;gt;Sample Hello World Extension&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;lt;author email=&amp;quot;support@dtlink.com&amp;quot; name=&amp;quot;DTLink Software&amp;quot; url=&amp;quot;http://www.dtlink.com&amp;quot; /&amp;gt;&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;lt;script language=&amp;quot;VBScript&amp;quot;&amp;gt;&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;lt;![CDATA[&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace; font-weight: bold;&quot;&gt;*** your script code goes here ***&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;]]&amp;gt;&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;lt;/pss_extension&amp;gt;&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;lt;signature&amp;gt;&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;optional digital signature&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;lt;/signature&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The  XML wrapper briefly describes the extension in the installer interface  and identifies you as the author. The extension code becomes part of a  CDATA node in order to prevent the XML parser from interpreting tags  incorrectly. The only restriction is that your script must not contain  the &amp;quot;]]&amp;gt;&amp;quot; string that ends the CDATA nodes.&lt;br /&gt;&lt;br /&gt;Extensions can  optionally digitally signed in order to verify their authenticity for  users. Personal Stock Streamer will test the extension signature before  it is installed and will warn the user if there is no digital signature  or if it does not match the extension. Oh the other hand, signed  extensions will present with a nice dialog containing information about  you and the extension. In either case the user will decide whether to  allow the extension to be installed.&lt;br /&gt;&lt;br /&gt;Once the extensions are  installed, any code that is at the global scope of the script will be  run when the extension is loaded at program startup. This part of the  code will normally consist of initialization code that registers the  necessary objects and event handlers with the host application.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Creating a Basic Extension&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Before  creating a basic extension I would recommend reviewing the Personal  Stock Streamer object model documentation on the web site. Becoming  familiar with this object model will make following the rest of this  article much easire.&lt;br /&gt;&lt;br /&gt;For this example we will create a basic  extension that adds a menu item to the Tools menu and displays a  message box when the menu is pressed. This requires us to define a  class to be the menu event handler and register it with the application.&lt;br /&gt;&lt;br /&gt;So  let's start with a basic class to handle a menu event. If you have read  the object model documentation you know that the menu handler is called  OnMenuItemSelected(), so we start with this:&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;class HelloWorldHandlerClass&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ' menu event handler&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public Function OnMenuItemSelected ( id )&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; If (id = menuId) Then&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; MsgBox &amp;quot;Hello, World&amp;quot;, vbOKOnly&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; ' set return value to indicate that menu selection was processed&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; OnMenuItemSelected = True&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; End If&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; end Function&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Dim menuId&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;end Class&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br /&gt;Now  we have a class that we can use as a menu handler. Because there may be  other extensions registered, we must first check whether the menu id  matches our menu.&lt;br /&gt;&lt;br /&gt;The next step is to register our handler class with the host application:&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;' get our event objects&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;Set EventManager = Application.GetObject(&amp;quot;EventManager&amp;quot;)&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;Set MenuManager = Application.GetObject(&amp;quot;MenuManager&amp;quot;)&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;Set Handler = new TestHandlerClass&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;' create the menu item&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;If Not MenuManager Is Nothing Then&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ' find the tools menu&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Set MainMenu = MenuManager.MainMenu&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Set ToolMenu = MainMenu.GetSubMenu(&lt;/span&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;MainMenu.Find(&amp;quot;Tools&amp;quot;)&lt;/span&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;)&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;' insert our menu item&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; If Not ToolMenu Is Nothing Then&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; Handler.menuId = ToolMenu.InsertItem(ToolMenu.ItemCount, &amp;quot;Hello World&amp;quot;)&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; End If&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;End If&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;' register the menu handler&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;If Not EventManager Is Nothing Then&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; EventManager.RegisterHandlerMethod Handler, &amp;quot;OnMenuItemSelected&amp;quot;&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;span style=&quot;font-family: courier new,courier,monospace;&quot;&gt;End If&lt;/span&gt;&lt;br style=&quot;font-family: courier new,courier,monospace;&quot; /&gt;&lt;br /&gt;This  section of code does a few things that are necessary for this extension  to work. First, it gets references to the application objects and  creates an instance of our menu handler object. Second, it finds the  Tools menu and inserts the menu item. Note how the code saves the menu  id of the menu item we created in the handler class. All of this should  be pretty straighforward if you're familiar with Visual Basic or  VBScript.&lt;br /&gt;&lt;br /&gt;Lastly, we just need to create the XML wrapper for  this extension like I showed above so that the extension can be  installed properly. The completed extension is available &lt;a href=&quot;../formvista/frontend/icms.php?rd=96&quot;&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-weight: bold;&quot;&gt;Finishing Up&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Testing  your extension is a matter of installing it under Personal Stock  Streamer using the Extensions manager under the Tools menu. Extension  management is based on version number, so once an extension is  installed it can easily be updated in-place as long as don't move the  xml file.&lt;br /&gt;&lt;br /&gt;  &lt;/p&gt;    </description><pubDate>Thu, 31 Mar 2005 23:31:09 EST</pubDate><guid>http://beta.personalstockstreamer.com:80/developers/articles?article_id=14</guid></item>
	</channel>
</rss>
