• Subscribe to this RSS Feed
  • Introduction to Personal Stock Monitor Extensions
    01/10/2010 3:00PM

    Introduction

    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 Monitor 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.

    With the Personal Stock Monitor 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.

    What is Possible

    Personal Stock Monitor 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.

    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 Monitor web site.

    Scripts vs. Extensions

    Personal Stock Monitor 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 Monitor starts. This allows them to do things such as installing event handlers that would otherwise not be possible in regular scripts.

    How Extensions Work

    In order for your script extensions to be loaded when Personal Stock Monitor 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.

    The XML wrapper looks like this:

    <pss_extension name="Hello World Sample" version="1.0">Sample Hello World Extension
    <author email="support@dtlink.com" name="DTLink Software" url="http://www.dtlink.com" />
    <script language="VBScript">
    <![CDATA[
    *** your script code goes here ***
    ]]>
    </script>
    </pss_extension>
    <signature>
    optional digital signature
    </signature>

    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 "]]>" string that ends the CDATA nodes.

    Extensions can optionally digitally signed in order to verify their authenticity for users. Personal Stock Monitor 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.

    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.

    Creating a Basic Extension

    Before creating a basic extension I would recommend reviewing the Personal Stock Monitor object model documentation on the web site. Becoming familiar with this object model will make following the rest of this article much easire.

    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.

    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:

    class HelloWorldHandlerClass

        ' menu event handler
        public Function OnMenuItemSelected ( id )

            If (id = menuId) Then
                MsgBox "Hello, World", vbOKOnly

                ' set return value to indicate that menu selection was processed
                OnMenuItemSelected = True
            End If

        end Function

        Dim menuId

    end Class

    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.

    The next step is to register our handler class with the host application:

    ' get our event objects
    Set EventManager = Application.GetObject("EventManager")
    Set MenuManager = Application.GetObject("MenuManager")
    Set Handler = new TestHandlerClass

    ' create the menu item
    If Not MenuManager Is Nothing Then

        ' find the tools menu
        Set MainMenu = MenuManager.MainMenu
        Set ToolMenu = MainMenu.GetSubMenu(MainMenu.Find("Tools"))

    ' insert our menu item
        If Not ToolMenu Is Nothing Then
            Handler.menuId = ToolMenu.InsertItem(ToolMenu.ItemCount, "Hello World")
        End If
    End If

    ' register the menu handler
    If Not EventManager Is Nothing Then
        EventManager.RegisterHandlerMethod Handler, "OnMenuItemSelected"
    End If

    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.

    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 here.

    Finishing Up

    Testing your extension is a matter of installing it under Personal Stock Monitor 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.

  • How to Create a Custom Report Extension
    01/10/2010 3:00PM
    Introduction

    The reports you see in Personal Stock Monitor 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 Monitor, but with the new Personal Stock Monitor 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.

    Assuming you have read the Introduction to Personal Stock Monitor Extensions article, we can jump right into defining the report class.

    The Report Handler

    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.

    class PriceReportHandler
    
    	' create the report
    	public Function GenerateReport ( Name, Handler, Folder )
    
    	end Function

    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.

    Since reports are just simple HTML documents, we can start by sending the header to the reporting engine.

    ' generate report		
    Handler.WriteReport "<body bgcolor=White>"
    Handler.WriteReport "<b>Price Performance Report for " + Handler.FilterPeriod + "</b>"
    Handler.WriteReport "<table border=0 cellpadding=0 cellspacing=10>"
    Handler.WriteReport "<tr><th><b>Symbol</b></th><th><b>Starting Price</b></th><th><b>Ending Price</b></th><th><b>Gain</b></th><th><b>% Gain</b></th></tr>"

    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.

    Now we are ready to write the report:

    RecurseFolder Handler, Folder
    
    Handler.WriteReport "</table></body>"

    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.

    So now we have to write the RecurseFolder method, which is the most complicated method in this class.

    public Function RecurseFolder ( Handler, Folder )
    	' process the tickers in the folder
    	Set Tickers = Folder.Tickers
    
    	For i = 1 To Tickers.Count

    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.

    ' get the history data for this year
    Tickers.Item(i).SetProp "HistoryStart", Handler.FilterDateStart
    Tickers.Item(i).SetProp "HistoryEnd", Handler.FilterDateEnd
    
    Set History = Tickers.Item(i).HistoryData

    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.

    If Not History Is Nothing And History.Count > 0 Then

    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.

    BeginValue = History.Item(0).GetProp("Close")
    EndValue = History.Item(History.Count - 1).GetProp("Close")
    
    Diff = EndValue - BeginValue
    DiffPct = ((EndValue - BeginValue) / BeginValue) * 100

    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:

    Handler.WriteReport "<tr><td>" + Tickers.Item(i).GetProp("Symbol") + "</td>"
    Handler.WriteReport "<td align=right>" + FormatNumber(EndValue,2) + "</td><td align=right>" + FormatNumber(BeginValue,2) + "</td>"
    Handler.WriteReport "<td align=right>" + FormatNumber(Diff,2) + "</td><td align=right>" + FormatNumber(DiffPct,2) + "</td></tr>"

    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 Monitor expires the historical data cache each day, this case would not occur and therefore does not need to be handled explicitly.

    Now we can handle the case where no historical data is available for the range:

    Else
    	Handler.WriteReport "<tr><td>" + Tickers.Item(i).GetProp("Symbol") + "</td>"
    	Handler.WriteReport "<td align=right>Waiting for Data</td>"
    	
    	Tickers.Item(i).DownloadHistoryData Handler.FilterDateStart, Handler.FilterDateEnd
    End If

    Basically what we are doing here is putting up a "Waiting for Data" 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:

    	Next
    	
    	' recursively loop through the subfolders
    	Set Folders = Folder.Folders
    	
    	For i = 1 To Folders.Count
    		RecurseFolder Handler, Folders.Item(i)
    	Next
    	
    end Function

    This code finishes out the For loop, then recursively calls itself for any nested folders.

    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 Monitor but the data actually arrives at a later time. Fortunately Personal Stock Monitor 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:

    Set EventManager = Application.GetObject("EventManager")
    
    If Not EventManager Is Nothing Then
    	EventManager.RegisterHandlerMethod ReportHandler, "OnHistoryUpdated"
    End If

    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:

    public Function OnHistoryUpdated ( Ticker )
    	If (Application.GetCurrentView() = "Reports") Then
    		Set EventManager = Application.GetObject("EventManager")
    		EventManager.RegisterHandlerMethod me, "OnAppTimer"
    	End If
    End Function

    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.

    So now we can define the OnAppTimer event handler.

    public Function OnAppTimer
    	EventManager.UnregisterHandlerMethod me, "OnAppTimer"
    
    	Set ReportManager = Application.GetObject("ReportManager")
    	ReportManager.UpdateCurrentReport
    End Function

    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.

    So that is the end of the report handler class, and we should not forget to end the class definition.

    end Class

    Because I already covered part of the initialization with registering for the OnHistoryUpdated event, here is the second part, actually registering the report:

    Set ReportHandler = new PriceReportHandler
    Set ReportManager = Application.GetObject("ReportManager")
    
    If Not ReportManager Is Nothing Then
    	ReportManager.Register "Price Performance", ReportHandler
    End If

    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.

    That is the end of the custom report extension; the full extension code is available here.

    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.

  • How to Create a Custom Chart Indicator
    01/10/2010 3:00PM
    This article is a brief description of the implementation of a price relative indicator.  The price relative indicator  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&P 500.  The indicator is drawn on a separate set of axes from the main chart, and the calculation is Price[Ticker]/Price[S&P 500].

    As with any typical chart extension, we will define a handler class to create the chart objects and to recalculate the indicator as necessary.  In particular, an indicator class only requires two functions: Create to create the objects that make up the chart and hook them up to the chart manager, and Recalc to actually calculate the indicator.  However there are a couple tricky parts to this particular indicator:

    • 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 Monitor will only retrieve historical data for a ticker that is part of a portfolio.  So if the ticker is not present in the current portfolio we need to create an invisible ticker and add it to the portfolio.  Also, if an invisible ticker is created we need to make sure to delete it when the chart is closed.
    • 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.  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 Recalc function.

    I'm not going to go through the chart setup because it is relatively straightforward; 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.

    As for the tricky parts, here is the first part of the Recalc function:

    Set AltTicker = Doc.FindTicker(Nothing,
    	ChartObject.GetParam(0), 0) ' check if the ticker exists in
    	the document If AltTicker
    	Is Nothing Then '
    
    	if ticker was not found, create
    		temporary ticker Set AltTicker = Doc.CreateTicker Set Portfolio = Doc.CurrentPortfolio If Not AltTicker Is Nothing Then
    
    		AltTicker.SetProp "Symbol", ChartObject.GetParam(0)
    		AltTicker.SetProp "Visible", "0"
    
    		Portfolio.Insert -1, AltTicker
    
    		' set the label on this chart
    		MyDataSet.Label = MyDataSet.ID + " (" + ChartObject.GetParam(0) + ") "
    
    		' make sure this ticker gets deleted when the chart is closed
    		ChartObject.DeferDeleteTicker(AltTicker)
    	End If
    End If

    What we're doing in the code above is finding the relative ticker (such as the S&P 500) in the document, which is passed as the first parameter in the chart object.  (Programmers start counting at 0, so the first parameter is actually at the 0th location.)  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 Symbol and Visible properties, and inserts it into the current portfolio.  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 DeferDeleteTicker() does.

    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:

    If Not AltTicker Is Nothing Then
    	Set AltCloseDataSet = ChartObject.GetDataSetFromTicker(AltTicker, "Close")
    
    	...
    	
    	' check for a valid data set
    	If AltCloseDataSet Is Nothing Then
    		
    		' if there was no data set, set a dummy value so it will at least display the chart
    		MyDataSet.Data(MyDataSet.Size - 1) = 0
    		MyDataSet.Label = MyDataSet.ID + " (" + ChartObject.GetParam(0) + ") "
    
    		' request historical data from the application
    		ChartObject.RequestHistoricalDataForTicker(AltTicker)

    Once we have the data for the relative ticker, the next part of the code actually calculates the relative indicator:

    	Else
    		' loop through all of the data and calculate the Price Relative indicator
    		For x = 0 To CloseDataSet.Size - 1
    			If (CloseDataSet.IsValidData(x) And CloseDataSet.Data(x) > 0) Then
    				LastValidCloseData = x
    
    				If (AltCloseDataSet.IsValidData(x) And AltCloseDataSet.Data(x) > 0) Then
    					LastValidAltCloseData = x
    
    					MyDataSet.Data(x) = CloseDataSet.Data(x) / AltCloseDataSet.Data(x)
    				End If
    			End If
    		Next
    
    		...
    		
    		' check data at the end of the range
    		If LastValidAltCloseData < LastValidCloseData - 2 Then
    			ChartObject.RequestHistoricalDataForTicker(AltTicker)
    		End If
    	End If
    End If

    Note how we're keeping track of the last valid data for the relative ticker in the LastValidAltCloseData 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.

    Other than registering our indicator with the system, that's pretty much it.  As we do more of these types of extensions, you will notice that there really isn't that much work involved, and a lot can be accomplished with just a few dozen lines of code.

    The full code for this extension is here, and the complete packaged extension is available for download through the Extension manager under the Tools menu.

  • How to Create Scriptable Custom Columns
    01/10/2010 3:00PM

    One of the new features added in Personal Stock Monitor is the ability to have custom columns in the Active Securities view, including scriptable columns.  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 Monitor User's Guide as well as this article.

    The new script object that controls the custom columns is the ViewManager object.

    Set ViewManager = Application.GetObject("ViewManager")

    At this point you can create your custom columns.  Expression columns are very straightforward.  For example, if you wanted to create a column that always showed double the current price:

    ViewManager.CreateExpressionColumn "DoublePrice", "Price * 2"

    You could have just as easily added this column through the Custom Columns interface in the program preferences.

    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.  (To learn more about custom attributes, see the scripting object model documentation for the Ticker object.) 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.  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.

    So to start, let's create a custom column that is associated with a custom attribute.  This example is taken from the Trailing Stop extension.

    ViewManager.CreateMemoColumn "Trailing Stop", "_TrailingStopComment", True

    This code creates a memo column called "Trailing Stop" that gets its value from the _TrailingStopComment attribute, and is read-only in the Active Securities view so the user can not edit it.  How do we fill in the _TrailingStopComment variable?  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.  The code is as follows:

    tmp = FormatNumber(CDbl(dStopMark), 2) + ", " + FormatNumber(CDbl(dStopPct), 2) + "%"
    ticker.SetProperty "_TrailingStopComment", tmp

    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:

    Set ViewManager = Application.GetObject("ViewManager")
    ViewManager.UpdateObjectInView(ticker)

    This design ensures that the user interface is only updated when necessary, preventing unnecessary running of script code.

    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.  If you are interested in creating your own scriptable columns, I recommend reading our Personal Stock Monitor object model documentation and samples found on the Developers section of this site.  Also, the full source code for the Trailing Stop extension is available here.

  • Recreating the Capital Gains Report
    01/10/2010 3:00PM

    Although Personal Stock Monitor 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.

    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.  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. This type of processing is already done internally within Personal Stock Monitor to calculate gains and losses, and to duplicate this in script would require a lot of work.  Getting this functionality for free is one advantage of having the capital gains report embedded in the main application.

    So for this extension I had to cheat a little bit: I modified the script plug-in in Personal Stock Monitor to expose the functionality described above, so that it would be trivially simple (and much faster) to rewrite the capital gains report in script.  This also eliminated the need to duplicate the transaction processing logic that is already inside Personal Stock Monitor.  What 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.  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.  In the case of this report, the callback object is the same object that the report is running in, so I just pass "me" as the argument.

    Before I go into describing these callback methods, I should say something about the design of the handler class for this report.  In order to separate out the long-term gains, short-term gains, and other income and expenses, I created three arrays that get populated with summary data by the callback methods I am about to describe.  This neatly separates the processing and output code, which allows easy modification of the formatting code to get any style of report you need.

    The report handler defines four callback methods.  The first two, OnCBAddHoldings and OnCBSubtractHoldings, are called by Personal Stock Monitor whenever a transaction affects the current holdings for a ticker.  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.  The second two callback methods, OnCBAddIncome and OnCBSubtractIncome, are called for dividends, income, and expense transactions that do not affect the holdings.  This allows us to put together a nice summary of those transactions as well.  There is absolutely nothing complicated about these callback methods because all they do is store the summary data for later.

    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.  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.

    The full source code for the Capital Gains report is available here.

  • 1 2 >>