Code covered by the BSD License  

Highlights from
Realtime trading with Matlab presentation files

image thumbnail
from Realtime trading with Matlab presentation files by Yair Altman
Realtime trading demo & presentation, presented at NYC Computational Finance Conference 23 May 2013

tradingDemo
function tradingDemo

    % Display the GUI window
    GUI.cycle = 0;
    GUI.lastPortfolio = [];
    GUI.selectedRow = 1;
    GUI.trading = false;
    initGUI();

    % TODO - indicate trades on graphs
    % TODO - graph line/color/style to differentiate historical data / streaming quotes
    % TODO - execution callback (update log, alert via email)
    % TODO - Table headers colors

    % Display the initial GUI window
    function initGUI()

        % Create a new figure
        GUI.hFig = findall(0, '-depth',1, 'type','figure', 'Name','Trading demo');
        if isempty(GUI.hFig)
            GUI.hFig = figure('Name','Trading demo', 'NumberTitle','off', 'Visible','off', 'Color','w', 'Position',[100,100,750,600], 'Toolbar','none', 'MenuBar','none');
        else
            clf(GUI.hFig);
            hc=findall(gcf); delete(hc(2:end));  % bypass javacomponent-clf bug on R2012b-R2013a
        end
        drawnow; pause(0.05);
        oldWarn = warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame');
        javaFrame = get(handle(GUI.hFig),'JavaFrame');
        warning(oldWarn);
        folder = fileparts(mfilename('fullpath'));
        newIcon = javax.swing.ImageIcon([folder '/dollar.png']);
        javaFrame.setFigureIcon(newIcon);
        set(GUI.hFig,'Visible','on');
        drawnow; pause(0.05);

        % Prepare time label
        GUI.hTimeLabel = uicontrol('Style','text', 'Units','norm', 'Position',[0,.8,.2,.17], 'FontSize',20, 'Background','w', 'String','');

        % Display the main title
        GUI.hTitle = uicontrol('Style','text', 'Units','norm', 'Position',[.2,.8,.6,.17], 'FontSize',20, 'Background','w', 'Foreground','b', 'String',['Demo trading' 10 'application']);

        % Display the logo (animated GIF)
        displayLogo(GUI.hFig, [folder '/dollar_animated_64x64.gif']);

        % Prepare the resizable panels
        oldWarn = warning('off','MATLAB:hg:PossibleDeprecatedJavaSetHGProperty');
        hMainPanel = uipanel('Units','norm', 'Position',[0,0,1,.78], 'BorderType','none', 'Background','w');
        [hLeftPanel, hRightPanel, hDivider] = uisplitpane(hMainPanel, 'DividerColor',.95*[1,1,1]); %#ok<NASGU>
        [hLeftBottomPanel,  hLeftTopPanel,  hLeftDivider]  = uisplitpane(hLeftPanel,  'Orientation','Vertical'); %#ok<NASGU>
        [hRightBottomPanel, hRightTopPanel, hRightDivider] = uisplitpane(hRightPanel, 'Orientation','Vertical'); %#ok<NASGU>
        warning(oldWarn);

        % Prepare the log editbox
        GUI.hLogPanel = uicontrol('style','edit', 'max',5, 'Parent',hLeftBottomPanel, 'Units','norm', 'Position',[0,0.2,1,0.8], 'Background','w');
        jScroll = findjobj(GUI.hLogPanel);
        try jScroll = jScroll(1); catch; end
        try
            jScroll.setVerticalScrollBarPolicy(jScroll.java.VERTICAL_SCROLLBAR_AS_NEEDED);
            %jScroll.setBorder([]);
            jScroll = jScroll.getViewport;
        catch
            % may possibly already be the viewport
        end
        GUI.jLogPanel = handle(jScroll.getView,'CallbackProperties');
        GUI.jLogPanel.setEditable(false);
        set(GUI.jLogPanel,'HyperlinkUpdateCallback',@linkCallbackFcn);
        logMessage(GUI.jLogPanel, 'Initializing GUI...');

        % Add the action buttons: <Clear log>, <Close positions>, <Pause/Resume trading>
        initButton(hLeftBottomPanel, [.00,.01,.3,.18], 'Clear log',       @(varargin) GUI.jLogPanel.setText(''));
        initButton(hLeftBottomPanel, [.35,.01,.3,.18], 'Close positions', @closePositions);
        initButton(hLeftBottomPanel, [.70,.01,.3,.18], 'Start trading',   @toggleTrading);

        % Prepare the axes
        GUI.axTop    = axes('Parent',hRightTopPanel,    'XColor',[.4,.4,.4], 'YColor',[.4,.4,.4]);
        GUI.axBottom = axes('Parent',hRightBottomPanel, 'XColor',[.4,.4,.4], 'YColor',[.4,.4,.4]);
        GUI.hTopLine    = line('Visible','off', 'Parent',GUI.axTop);
        GUI.hBottomLine = line('Visible','off', 'Parent',GUI.axBottom);
        title(GUI.axBottom, 'P & L');

        % Link all subplots
        %linkaxes([GUI.axTop, GUI.axBottom],'x')
        %linkprop([GUI.axTop, GUI.axBottom],{'XLim','XTickLabel','Xtick'});
        %axes(GUI.axTop);
        %dynamicDateTicks([GUI.axTop, GUI.axBottom]);
        datetick(GUI.axTop,    'x', 'HH:MM:SS')
        datetick(GUI.axBottom, 'x', 'HH:MM:SS')

        % Prepare the data table
        initDataTable(hLeftTopPanel);

        % Check for the existence of IB-Matlab
        if isempty(which('IBMatlab'))
            logMessage(GUI.jLogPanel, 'IB-Matlab application not found - ', 'error')
            logMessage(GUI.jLogPanel, 'contact Yair Altman:</font> <font color="blue"><a href="mailto:altmany@gmail.com?subject=IB-Matlab+for+demo&body=Hi Yair,">altmany@gmail.com</a>', 'error')
        end

        % Stop all existing timers
        try stop(timerfindall);    catch; end
        try delete(timerfindall);  catch; end

        % Start the periodic GUI update timer
        logMessage(GUI.jLogPanel, 'Starting update timer...');
        GUI.inDataCallback = false;
        start(timer('Tag','periodicUpdate', 'Period',0.5, 'ExecutionMode','FixedDelay', 'StartDelay',0.0, 'ErrorFcn','', 'TimerFcn',@periodicUpdateFcn));

        % Update the historical data for all portfolio securities (1st cycle only)
        getHistoricalData();

    end  % initGUI

    % Initialize a specific GUI button
    function initButton(hParent, position, label, callback)
        name = genvarname(label);
        label = strrep(label, ' ', char(10));
        hAxes = axes('Parent',hParent, 'Units','norm', 'Position',position, 'Color',[.3,.3,1], ...
                     'Box','off', 'Xtick',[], 'YTick',[], 'YColor','w', 'XColor','w');
        hText = text(0.5, 0.5, label, 'Parent',hAxes, 'tag',['tx' name], 'Color','k', ...
                     'Horizontal','center', 'FontName','Helvetica', 'FontSize',12, 'Fontweight','bold');
        set([hAxes,hText], 'ButtonDownFcn',{callback,hAxes,hText});
        GUI.(['ax' name]) = hAxes;
        GUI.(['tx' name]) = hText;

        % Create rounded button corners
        N=200; a=2*pi*(0:N)/N;  % number of vertix points
        sa=sin(a);
        ca=cos(a);  ca=0.2*ca+0.7*sign(ca);
        vert = [ca,1.2,1.2,-1.2,-1.2,1.2,1.2; sa,0,-1.2,-1.2,1.2,1.2,0]';
        vert = 0.5 + 0.5*vert;
        p = patch('Parent',hAxes, 'Vertices',vert, 'Faces',1:size(vert,1), 'FaceColor','w', 'EdgeColor','none');
        try set(p, 'LineSmooth','on'); catch, end  % HG1 only
        set(hAxes,'XLim',[0,1],'YLim',[0,1]);
        set(GUI.hFig,'Renderer','painters');  % patch changes to OpenGL which hides the axData labels
    end  % initButton

    % Prepare the data table
    function initDataTable(hParent)
        % Display an empty data table
        GUI.tableData = {'Symbol','Local','Exchange','Type','Pos','Cost','Latest','Value'};
        GUI.hTable = uitable('Data',GUI.tableData, 'ColumnName','', 'RowName','', ...
                             'Parent',hParent, ... 'Enable','inactive', 
                             ... 'ColumnEditable',false(1,numCols), ...
                             'CellSelectionCallback',@tbCellSelection_Callback, ...
                             ... 'ColumnWidth',num2cell(80*ones(1,10)), ...  % this needs to be done below since it gets reset by the new table model
                             'FontSize',8, 'FontName','Helvetica', ...
                             'Units','norm', 'Pos',[0,0,1,1]);
        drawnow;
        pause(0.05);

        % Add special Java-based formatting to the data table
        formatDataTable();
    end  % initDataTable

    % Add special Java-based formatting to the data table
    function formatDataTable()
        % Some basic stuff
        jScroll = findjobj(GUI.hTable);
        try jScroll = jScroll(1); jScroll = jScroll.getViewport;  catch, end  % may possibly already be the viewport
        try jScroll = jScroll.getComponent(0).getViewport;  catch, end  % HG2
        jTable = jScroll.getView;
        %jTable.setEditable(false)  % 'Enable'='inactive' above is better
        jTable.setColumnResizable(true);

        %jTable.setModel(com.jidesoft.grid.DefaultSpanTableModel(data,headers))  % TODO

        % Cell selection callback - entire row
        jTable.setNonContiguousCellSelection(false);
        jTable.setRowSelectionAllowed(true);
        jTable.setColumnSelectionAllowed(false);
        set(jTable.getSelectionModel,'ValueChangedCallback',@tbCellSelection_Callback);

        % Note: we don't use uitable's CellSelectionCallback because it requires Enable=on which allows editing
        %set(handle(jTable,'CallbackProperties'), 'MouseClickedCallback',@tbCellSelection_Callback);  % no good - doesn't work...

        % Disable editing
        ce = javax.swing.DefaultCellEditor(javax.swing.JTextField);
        ce.setClickCountToStart(intmax);  % =never...
        for colIdx = 0 : jTable.getColumnCount-1
            jTable.getColumnModel.getColumn(colIdx).setCellEditor(ce);
        end

        % Set the table to span the entire available width
        jTable.setAutoResizeMode(jTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);

        % Some formatting magic...
        jTable.getTableHeader.setVisible(false);
        jTable.getTableHeader.getParent.setBackground(java.awt.Color.white);
        jTable.getParent.getParent.setBackground(java.awt.Color.white);
        jTable.getParent.setBackground(java.awt.Color.white);
        jTable.setBackground(java.awt.Color.white);
        jTable.getParent.getParent.setBorder([]);

        % Some colors...   % TODO
        %{
        % first the header column
        javaaddpath(fileparts(mfilename('fullpath')));
        cr = ColoredFieldCellRenderer(java.awt.Color(.76,.66,.87)); % .48,.06,.90
        cr.setDisabled(true);  % to bg-color the entire column
        cr.setCellBgColor(0,0,java.awt.Color.white);
        cr.setSmartAlign(false);  % always center
        cr.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);

        % now the header row
        for colIdx = 0 : jTable.getColumnCount-1
            jTable.getColumnModel.getColumn(colIdx).setCellRenderer(cr);
        end
        %}

        % All done - repaint
        jTable.repaint
        GUI.jTable = jTable;
        return;  % debug point
    end  % initDataTable

    % Update the data table with updated portfolio data
    function updateDataTable(portfolioData)
        if 1 %GUI.jTable.getRowCount ~= length(portfolio)+1
            %logMessage(GUI.jLogPanel, 'Updating portfolio data table...');
            data = get(GUI.hTable,'Data');
            data(2:end,:) = [];  % delete data rows (keep header row)
            for idx = 1 : length(portfolioData)
                rowData = portfolioData(idx);
                cost = rowData.averageCost;
                if ~isempty(rowData.contract.m_multiplier)
                    cost = cost / str2double(rowData.contract.m_multiplier);
                end
                data(idx+1,:) = {rowData.symbol, rowData.localSymbol, rowData.exchange, ...
                                 rowData.secType, rowData.position, ...
                                 round(1e4*cost)/1e4, ...
                                 round(1e4*rowData.marketPrice)/1e4, ...
                                 round(rowData.marketValue)};
            end
            GUI.tableData = data;
            set(GUI.hTable,'Data',data);
            GUI.jTable.changeSelection(GUI.selectedRow, 0, false, false);
            drawnow; pause(0.02);
            formatDataTable();
        end
    end  % updateDataTable

    % Refresh data table & issue trade order
    function refreshData()
        try
            % Issue a new trade order
            if GUI.trading && ~isempty(GUI.lastPortfolio)
                numContracts = length(GUI.lastPortfolio);
                contractIdx = randi(numContracts);
                quantity = randi(3) - 2;  % -1, 0 or +1
                tradeContract(contractIdx, quantity);
            end

            % Get the latest portfolio data
            %disp(datestr(now,'=> HH:MM:SS.FFF'))
            GUI.inDataCallback = true;
            portfolioData = IBMatlab('action','portfolio', 'MsgDisplayLevel',1, 'CallbackMessage',@apiMessage, 'AccountName','DU32943');

            % Update the data table
            if ~isequal(GUI.lastPortfolio, portfolioData)
                %GUI.lastPortfolio, portfolioData
                %disp(datestr(now,'<= HH:MM:SS.FFF'))
                GUI.lastPortfolio = portfolioData;

                % Update the uitable
                updateDataTable(portfolioData);
            end
            %disp(datestr(now,'<< HH:MM:SS.FFF'))
            drawnow;
        catch
            % Maybe IB-Matlab is not available...
            err = lasterror;   %#ok<LERR,NASGU>
        end
    end  % refreshData

    % Periodically update the GUI
    function periodicUpdateFcn(hTimer,eventData) %#ok<INUSD>
        % Update the timestamp label
        try
            newLabel = strrep(datestr(now, 'mmm dd-HH:MM:SS'),'-',char(10));
            set(GUI.hTimeLabel, 'String',newLabel);
        catch
            stop(hTimer);
            delete(hTimer);
        end

        % Update the log box's scrollbars (gets reset by Matlab...)
        jScroll = GUI.jLogPanel.getParent.getParent;
        jScroll.setVerticalScrollBarPolicy(jScroll.VERTICAL_SCROLLBAR_AS_NEEDED);

        % Refresh the data table every 5 secs
        %disp([datestr(now) ' - ' num2str(GUI.cycle)])
        if ~GUI.inDataCallback && mod(GUI.cycle,10)==0
            refreshData();
        end
        GUI.inDataCallback = false;
        GUI.cycle = GUI.cycle + 1;

        % Update the axes
        updateAxes();
    end  % periodicUpdateFcn

    % Update the data axes
    function updateAxes()
        try
            if ~ishandle(GUI.axTop),  return;  end  % sanity check

            % Get the latest selected row's data
            rowData = GUI.tableData(GUI.selectedRow+1,:);
            fieldname = genvarname(rowData{2});  % localSymbol
            data = GUI.marketData.(fieldname);

            % Update the top axes
            title(GUI.axTop, [rowData{2} ' @ ' rowData{3}]);
            firstIdx = max(1, data.idx - 899);  % Last 15 mins only
            xdata = data.dateNum(firstIdx:data.idx) - floor(data.dateNum(firstIdx));
            ydata = data.price(firstIdx:data.idx);
            set(GUI.hTopLine, 'XData',xdata, 'YData',ydata, 'Visible','on');
            xdataLim = [xdata(1),xdata(end)];
            set(GUI.axTop, 'XLim',xdataLim);

            % Updating the X labels takes time and is unnecessary, so do it only once every few secs
            if mod(GUI.cycle,10)==1
                datetick(GUI.axTop,'x', 'HH:MM', 'keeplimits');
            end
        catch
            % never mind...
            err = lasterror;   %#ok<LERR,NASGU>
            %GUI
        end

        try
            % Update the bottom axes
            currency = GUI.lastPortfolio(GUI.selectedRow).currency;
            title(GUI.axBottom, ['P&L [' currency ']']);
            pnl = rowData{8} * (ydata-rowData{6});
            set(GUI.hBottomLine, 'XData',xdata, 'YData',pnl, 'Visible','on');
            set(GUI.axBottom, 'XLim',xdataLim);

            % Updating the X labels takes time and is unnecessary, so do it only once every few secs
            if mod(GUI.cycle,10)==1
                datetick(GUI.axBottom,'x', 'HH:MM', 'keeplimits');
            end
        catch
            % never mind...
            err = lasterror;   %#ok<LERR,NASGU>
        end
        drawnow;
    end  % updateAxes

    % Select a different table row
    function tbCellSelection_Callback(hTable, eventData) %#ok<INUSL>
        try
            selectedRow = eventData.Indices(1) - 1;
        catch
            selectedRow = eventData.getSource.getMinSelectionIndex;
        end
        if selectedRow <= 0  % =header row (unselectable)
            GUI.jTable.changeSelection(GUI.selectedRow, 0, false, false);
        else
            GUI.selectedRow = selectedRow;
        end
    end  % tbCellSelection_Callback

    % Get the historical data for all portfolio securities
    function getHistoricalData()
        try
            N = 10000;  % max space for data
            GUI.marketData = [];
            GUI.marketData.IDs = containers.Map('KeyType','double','ValueType','char');
            %dbstop tradingDemo 220
            for idx = 1 : length(GUI.lastPortfolio)
                % Get the latest historical data for this security
                rowData = GUI.lastPortfolio(idx);
                fieldname = genvarname(rowData.localSymbol);
                logMessage(GUI.jLogPanel, ['Retrieving historical data for ' rowData.localSymbol ' @ ' rowData.exchange '...'], 'info');
                contractParams = {'symbol',rowData.symbol, 'localSymbol',rowData.localSymbol, ...
                                  'secType',rowData.secType, 'currency',rowData.currency, ...
                                  'expiry',rowData.expiry, 'exchange',strrep(rowData.exchange,'NASDAQ','SMART')};
                %if ~strcmpi(rowData.secType,'cash'),  continue;  end
                data = IBMatlab('action','history', contractParams{:}, 'WhatToShow','MidPoint', ...
                                'barSize','1 secs', 'durationValue',900, 'DurationUnits','S');

                % Pre-allocate space for the streaming data
                [dateNums, sortedIdx] = sort(data.dateNum);
                dataStruct = struct('security',rowData, 'dateNum',dateNums, 'price',data.close(sortedIdx), 'idx',length(data.close));
                dataStruct.dateNum(end+1:N) = NaN;
                dataStruct.price  (end+1:N) = NaN;
                GUI.marketData.(fieldname) = dataStruct;

                % Start collecting streaming quotes data
                reqId = IBMatlab('action','query', contractParams{:}, 'QuotesNumber',inf, 'ReconnectEvery',inf, 'CallbackTickPrice',@tickPriceCallback);
                if isstruct(reqId)
                    dummy = IBMatlab('action','query', contractParams{:}, 'QuotesNumber',0,   'CallbackTickPrice',@tickPriceCallback);  %#ok<NASGU> % close streaming
                    reqId = IBMatlab('action','query', contractParams{:}, 'QuotesNumber',inf, 'CallbackTickPrice',@tickPriceCallback);              % restart streaming
                end  % might happen from a previous streaming session
                GUI.marketData.IDs(reqId) = fieldname;
                GUI.marketData.(fieldname).reqId = reqId;
                
                % Pause a bit before continuing to the next portfolio security
                pause(2);
                continue;  % debug breakpoint
            end
        catch
            err = lasterror;   %#ok<LERR,NASGU>
        end
    end  % getHistoricalData

    % TickPrice event callback (for streaming quotes)
    function tickPriceCallback(ibConnectionObject, eventData)
        global gGUI  % used for debugging only, not used by the application
        try
            % If GUI has existed, stop collecting data
            reqId = eventData.tickerId;
            if ~ishandle(GUI.hFig)
                ibConnectionObject.cancelMktData(reqId);
            else
                % Append the streaming quotes to the contract data
                fieldType = eventData.field;
                if fieldType == com.ib.client.TickType.BID  % ASK=1, BID==2, LAST==4, CLOSE==9
                    fieldname = GUI.marketData.IDs(reqId);
                    newIdx = GUI.marketData.(fieldname).idx + 1;
                    GUI.marketData.(fieldname).price(newIdx) = eventData.price;
                    GUI.marketData.(fieldname).dateNum(newIdx) = now;
                    GUI.marketData.(fieldname).idx = newIdx;
                    fprintf('%s %s (#%d) = %g\n', datestr(now,'HH:MM:SS.FFF'), fieldname, newIdx, eventData.price);
                    gGUI = GUI;  % used for debugging only, not used by the application
                end
            end
        catch
            % ignore...
        end
    end  % tickPriceCallback

    % Handle API log messages
    function apiMessage(ibConnection,eventData,varargin) %#ok<INUSL>
        try
            msg = strrep(char(eventData.message), ':', ': ');
            if ~strcmpi(eventData.type,'msg2') || (eventData.data2 < 2000 && eventData.data2 ~= 300)
                logMessage(GUI.jLogPanel, msg, 'error');
            elseif ~ismember(eventData.data2,[2100,2107])  % API has been unsubscribed from client data; Market data farm connection is OK
                %msg = [eventData.message ' (' num2str(eventData.data2) ')'];
                logMessage(GUI.jLogPanel, msg, 'info');
            end
        catch
            % never mind...
        end
    end  % apiMessage

    % Trade a specific contract
    function tradeContract(contractIdx, quantity)
        rowData = GUI.lastPortfolio(contractIdx);
        contractParams = {'symbol',rowData.symbol, 'localSymbol',rowData.localSymbol, ...
                          'secType',rowData.secType, 'currency',rowData.currency, ...
                          'expiry',rowData.expiry, 'exchange',strrep(rowData.exchange,'NASDAQ','SMART')};
        if quantity == 0
            msg = 'Decided not to trade in this cycle';
        elseif quantity > 0
            orderId = IBMatlab('action','buy', contractParams{:}, 'quantity',quantity, 'type','mkt');
            msg = sprintf('Buying %d %s (%s) on %s => order ID=%d', quantity, rowData.localSymbol, rowData.secType, rowData.exchange, orderId);
        else  % quantity < 0
            orderId = IBMatlab('action','sell', contractParams{:}, 'quantity',-quantity, 'type','mkt');
            msg = sprintf('Selling %d %s (%s) on %s => order ID=%d', -quantity, rowData.localSymbol, rowData.secType, rowData.exchange, orderId);
        end
        logMessage(GUI.jLogPanel, msg, 'warning');
    end  % tradeContract

    % Button callback - close all open positions
    function closePositions(varargin)
        for contractIdx = 1 : length(GUI.lastPortfolio)
            quantity = -GUI.lastPortfolio(contractIdx).position;
            if quantity ~= 0
                tradeContract(contractIdx, quantity);
            end
        end
    end  % closePositions

    % Button callback - pause/resume trading
    function toggleTrading(hObject,eventData,hAxes,hText) %#ok<INUSL>
        GUI.trading = ~GUI.trading;
        if GUI.trading
            set(hText,'String',['Pause' 10 'trading']);
            set(hAxes,'Color','r');
        else
            set(hText,'String',['Resume' 10 'trading']);
            set(hAxes,'Color','g');
        end
    end  % toggleTrading

end % tradingDemo

% Display the logo (animated GIF)
function displayLogo(hParent, logoFilename)
    htmlStr = ['<html><img src="file:/' logoFilename '">'];
    je = javax.swing.JEditorPane('text/html', htmlStr);
    je.setEditable(false);
    [hje, container] =  javacomponent(je,[],hParent); %#ok<ASGLU>
    set(container, 'Units','norm', 'Pos',[0.8,0.8,0.2,0.18])
end  % displayLogo

% Log message utility
function logMessage(jEditbox,text,severity)
    % Ensure we have an HTML-ready editbox
    HTMLclassname = 'javax.swing.text.html.HTMLEditorKit';
    if ~isa(jEditbox.getEditorKit,HTMLclassname)
        jEditbox.setContentType('text/html');
    end

    % Parse the severity and prepare the HTML message segment
    if nargin<3,  severity='info';  end
    switch lower(severity(1))
        case 'i',  icon = 'greenarrowicon.gif'; color='gray';
        case 'w',  icon = 'demoicon.gif';       color='black';
        otherwise, icon = 'warning.gif';        color='red';
    end
    icon = fullfile(matlabroot,'toolbox/matlab/icons',icon);
    iconTxt =['<img src="file:///',icon,'" height=16 width=16>'];
    msgTxt = ['&nbsp;<font color=',color,'>',datestr(now,'HH:MM:SS'),'&nbsp;',text,'</font>'];
    newText = [iconTxt,msgTxt];
    endPosition = jEditbox.getDocument.getLength;
    if endPosition>0, newText=['<br/>' newText];  end

    % Place the HTML message segment at the bottom of the editbox
    currentHTML = char(jEditbox.getText);
    jEditbox.setText(strrep(currentHTML,'</body>',newText));
    endPosition = jEditbox.getDocument.getLength;
    jEditbox.setCaretPosition(endPosition); % end of content
end  % logMessage

% Hyperlink callback function
function linkCallbackFcn(src,eventData) %#ok<INUSL>
    url = eventData.getURL;      % a java.net.URL object
    switch char(eventData.getEventType)
        case char(eventData.getEventType.ACTIVATED)
            %jEditbox.setPage(url);
            web([char(url.getProtocol) ':' char(url.getFile)]);
    end
end

Contact us