Articles

Free Download More Info
Here are some articles about developing for Personal Stock Streamer.

You can rss.gif subscribe to this feed with an RSS reader.
  • Subscribe to this RSS Feed
  • Developing a Trailing Stop Alert Extension
    10/11/2005 10:54AM

    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.

    In general there are a number of techniques for setting trailing stops, but for this example we will use the simplest stop loss strategy:

    • At the moment the trailing stop is set, the high water mark is calculated as the higher of the purchase price and the most recent trading price.
    • When the ticker is updated, compare the current stock price to the high water mark.  If the stock price is higher, then adjust the high water mark upward.  If the stock price is lower by the set percentage (default 20%), raise an alert.

    Implementing more complicated trailing stop techniques is left as an exercise for the reader.  Of course we appreciate all contributions.  :-)

     

    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.  This will prevent unnecessary recalculations.
    • _TrailingStopMark attribute is the current high water mark.
    • _TrailingStopPct attribute is the threshold percentage.
    • _TrailingStopAlertAction attribute is the name of the alert action to run when the ticker hits the threshold.
    • _TrailingStopMemo attribute is a string that shows the stop mark and percentage in one string.  This is used as the visual feedback for display in the Trailing Stop column.

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

    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.  During initialization we have to perform several tasks:

    • Create an instance of the handler class, and register it as the target for the application events we want to respond to.
    • Add two menu items to the Tools menu that allows users to set and clear trailing stops for a ticker.
    • Create a custom column for visual feedback so that users can see which tickers have a trailing stop set.

    The initialization code is fairly straightforward so there is no reason to cover it in this article.  Look at the source code for details.

    The handler class is a little more complicated.  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).  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.  This means that the actual setting of the trailing stop can not be done in the menu handler.  The only real tricky part 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 "post" and the target as "x".  This little trick is necessary because of how our particular HTML browser control works.

    Set selectedTickers = Application.ActiveDocument.Selection.Tickers

    If (selectedTickers.Count > 0) Then
        Set wndManager = Application.GetObject("WindowManager")
        Set wndBrowser = wndManager.CreateBrowserWindow(300, 140, me)

        ...

        form = "<body bgcolor=white><form name=form1 method=post target=" + Chr(34) + "x" + Chr(34) + ">"

        ...

        form = form + "</form></body>"

        wndBrowser.SetHTML(form)
        wndBrowser.Title = "Set Trailing Stop"

    Note that when we create the browser window above, we set "me" as the handler object.  This means that 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.

    public Function OnFormSubmitted ( form )
        If (form.Value("submit") = "OK") Then
            SetMarks form.Value("pct"), form.Value("act")
        End If

        ... 

    The SetMarks function does most of the work of setting the trailing stops.  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.  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.

    The work of checking the high water mark is done in the OnTickerUpdated method of the handler class.  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.  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.

    dStopMark = 0
    dStopPct = 0

    On Error Resume Next
    dStopMark = CDbl(ticker.GetProperty("_TrailingStopMark"))
    dStopPct = CDbl(ticker.GetProperty("_TrailingStopPct"))
    On Error Goto 0

    At this point, if the dStopMark and dStopPct variables are non-zero, then a trailing stop has been set for the ticker.  Now what we need to do is get the current price of the ticker and compare it to the high water mark.  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.  We generate the alert only if the threshold has been reached.

    dPrice = ticker.GetProperty("Price")

    If (dPrice > dStopMark) Then
        dStopMark = dPrice
        
        ticker.SetProperty "_TrailingStopMark", dStopMark
        ...
     Else
        dDiffPct = ((dStopMark - dPrice) / dStopMark) * 100
       
        If (dDiffPct >= dStopPct) Then
            RunAlertAction ticker.GetProperty("_TrailingStopAlertAction"), ticker
        End If
    End If

    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.

    Set manager = Application.GetObject("AlertManager")

    If Not manager Is Nothing Then
        For Each action in manager.Actions
            If action.GetProperty("Name") = name Then
                action.Run2 ticker, "Trailing Stop"
                Exit For
            End If
        Next
    End If

    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.

    The full source of the Trailing Stop extension is available here.

  • Recreating the Capital Gains Report
    08/01/2005 1:52PM

    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.

    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 Streamer 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 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.  This also eliminated the need to duplicate the transaction processing logic that is already inside Personal Stock Streamer.  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 Streamer 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.

  • How to Create Scriptable Custom Columns
    07/27/2005 3:28PM

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

     

  • How to Create a Custom Chart Indicator
    05/02/2005 9:25AM
    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 Streamer 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 a Custom Report Extension
    04/11/2005 10:19AM
    Introduction

    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.

    Assuming you have read the Introduction to Personal Stock Streamer 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 Streamer 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 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:

    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.

  • 1 2 >>