/******************************************************************************* * Copyright (c) 2013 Luigi Sgro. All rights reserved. This * program and the accompanying materials are made available under the terms of * the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Luigi Sgro - initial API and implementation ******************************************************************************/ package com.quantcomponents.ui.algo; import java.lang.reflect.InvocationTargetException; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabItem; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.part.ViewPart; import com.quantcomponents.algo.ITradeStatsPoint; import com.quantcomponents.algo.TradeStatsProcessor; import com.quantcomponents.core.model.IMutableSeries; import com.quantcomponents.core.model.ISeries; import com.quantcomponents.core.model.ISeriesListener; import com.quantcomponents.core.model.ISeriesPoint; import com.quantcomponents.core.series.LinkedListSeries; import com.quantcomponents.core.series.SimplePoint; import com.quantcomponents.core.utils.LangUtils; public class TradingStatsView extends ViewPart implements ISelectionListener, ISeriesListener<Date, Double> { private static class ExecutionInfo { public ExecutionInfo(TradeStatsProcessor processor, IMutableSeries<Date, Double, ISeriesPoint<Date, Double>> outputSeries) { this.processor = processor; this.outputSeries = outputSeries; } TradeStatsProcessor processor; IMutableSeries<Date, Double, ISeriesPoint<Date, Double>> outputSeries; } private class TradeSeriesContentProvider implements IStructuredContentProvider, ISeriesListener<Date, Double> { ISeries<Date, Double, ISeriesPoint<Date, Double>> data; final Deque<ITradeStatsPoint> trades = new LinkedList<ITradeStatsPoint>(); @Override public void dispose() { if (data != null) { data.removeSeriesListener(this); } } @SuppressWarnings("unchecked") @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { if (data != null) { data.removeSeriesListener(this); } if (newInput != null) { data = (ISeries<Date, Double, ISeriesPoint<Date,Double>>) newInput; fillTradeSeries(); data.addSeriesListener(this); } } @Override public void onItemUpdated(ISeriesPoint<Date, Double> existingItem, ISeriesPoint<Date, Double> updatedItem) { if (updatedItem instanceof ITradeStatsPoint) { synchronized (this) { trades.removeLast(); trades.addLast((ITradeStatsPoint) updatedItem); } } } @Override public void onItemAdded(ISeriesPoint<Date, Double> newItem) { if (newItem instanceof ITradeStatsPoint) { synchronized (this) { trades.addLast((ITradeStatsPoint) newItem); } } } @Override public synchronized Object[] getElements(Object inputElement) { return trades.toArray(new Object[trades.size()]); } private synchronized void fillTradeSeries() { trades.clear(); for (ISeriesPoint<Date, Double> point : data) { if (point instanceof ITradeStatsPoint) { trades.add((ITradeStatsPoint) point); } } } } public static final String VIEW_ID = "com.quantcomponents.ui.algo.tradingStats"; private static final int SORT_ASCENDING = 1; private final Map<TradingAgentExecutionWrapper, ExecutionInfo> executionMap = new HashMap<TradingAgentExecutionWrapper, ExecutionInfo>(); private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); private final DecimalFormat df = new DecimalFormat("0.00"); private volatile Composite parent; private volatile TabFolder rootTabs; private volatile TabItem tabList; private volatile TabItem tabStats; private volatile TableViewer tradesTableViewer; private volatile Text bestTradeTimeDisplay; private volatile Text bestTradePnlDisplay; private volatile Text worstTradeTimeDisplay; private volatile Text worstTradePnlDisplay; private volatile Text maxDrawdownEndTimeDisplay; private volatile Text maxDrawdownValueDisplay; private volatile Text maxRunupEndTimeDisplay; private volatile Text maxRunupValueDisplay; private volatile Text lowestEquityTimeDisplay; private volatile Text lowestEquityValueDisplay; private volatile Text highestEquityTimeDisplay; private volatile Text highestEquityValueDisplay; private volatile ExecutionInfo currentExecutionInfo; @Override public void createPartControl(Composite parent) { this.parent = parent; rootTabs = new TabFolder(parent, SWT.NULL); tabList = new TabItem(rootTabs, SWT.NULL); tabList.setText("List"); tabStats = new TabItem(rootTabs, SWT.NULL); tabStats.setText("Statistics"); Composite tradesContainer = new Composite(rootTabs, SWT.NULL); tabList.setControl(tradesContainer); // ----------------------- TRADE TABLE ----------------------- // Composite listContainer = new Composite(rootTabs, SWT.NULL); tabList.setControl(listContainer); listContainer.setLayout(new FillLayout()); tradesTableViewer = new TableViewer(listContainer, SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION | SWT.BORDER); TableViewerColumn viewColDateTime = new TableViewerColumn(tradesTableViewer, SWT.NONE); viewColDateTime.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { ITradeStatsPoint t = (ITradeStatsPoint) element; return sdf.format(t.getIndex()); } }); TableColumn columnDateTime = viewColDateTime.getColumn(); columnDateTime.setText("Time"); columnDateTime.setWidth(140); columnDateTime.setResizable(true); TableViewerColumn viewColSide = new TableViewerColumn(tradesTableViewer, SWT.NONE); viewColSide.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { ITradeStatsPoint t = (ITradeStatsPoint) element; return t.getTrade().getOrder() != null ? t.getTrade().getOrder().getSide().name() : ""; } }); TableColumn columnSide = viewColSide.getColumn(); columnSide.setText("Side"); columnSide.setWidth(40); columnSide.setResizable(true); TableViewerColumn viewColSize = new TableViewerColumn(tradesTableViewer, SWT.NONE); viewColSize.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { ITradeStatsPoint t = (ITradeStatsPoint) element; return t.getTrade().getOrder() != null ? Integer.toString(t.getTrade().getOrder().getAmount()) : ""; } }); TableColumn columnSize = viewColSize.getColumn(); columnSize.setText("Size"); columnSize.setWidth(40); columnSize.setResizable(true); TableViewerColumn viewColExPrice = new TableViewerColumn(tradesTableViewer, SWT.RIGHT); viewColExPrice.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { ITradeStatsPoint t = (ITradeStatsPoint) element; return df.format(t.getTrade().getExecutionPrice()); } }); TableColumn columnExPrice = viewColExPrice.getColumn(); columnExPrice.setText("Ex.Price"); columnExPrice.setWidth(80); columnExPrice.setResizable(true); TableViewerColumn viewColAvgPrice = new TableViewerColumn(tradesTableViewer, SWT.RIGHT); viewColAvgPrice.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { ITradeStatsPoint t = (ITradeStatsPoint) element; return df.format(t.getTrade().getAveragePrice()); } }); TableColumn columnAvgPrice = viewColAvgPrice.getColumn(); columnAvgPrice.setText("Avg.Price"); columnAvgPrice.setWidth(80); columnAvgPrice.setResizable(true); TableViewerColumn viewColTradePnl = new TableViewerColumn(tradesTableViewer, SWT.RIGHT); viewColTradePnl.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { ITradeStatsPoint t = (ITradeStatsPoint) element; return df.format(t.getTradePnl()); } }); TableColumn columnTradePnl = viewColTradePnl.getColumn(); columnTradePnl.setText("Trade P&&L"); columnTradePnl.setWidth(80); columnTradePnl.setResizable(true); TableViewerColumn viewColMaxFavExc = new TableViewerColumn(tradesTableViewer, SWT.RIGHT); viewColMaxFavExc.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { ITradeStatsPoint t = (ITradeStatsPoint) element; return df.format(t.getMaxFavorableExcursion()); } }); TableColumn columnMaxFavExc = viewColMaxFavExc.getColumn(); columnMaxFavExc.setText("Fav.Chg."); columnMaxFavExc.setWidth(80); columnMaxFavExc.setResizable(true); TableViewerColumn viewColMaxAdvExc = new TableViewerColumn(tradesTableViewer, SWT.RIGHT); viewColMaxAdvExc.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { ITradeStatsPoint t = (ITradeStatsPoint) element; return df.format(t.getMaxAdverseExcursion()); } }); TableColumn columnMaxAdvExc = viewColMaxAdvExc.getColumn(); columnMaxAdvExc.setText("Adv.Chg."); columnMaxAdvExc.setWidth(80); columnMaxAdvExc.setResizable(true); Table table = tradesTableViewer.getTable(); table.setHeaderVisible(true); table.setLinesVisible(true); table.setSortColumn(columnDateTime); table.setSortDirection(SORT_ASCENDING); tradesTableViewer.setContentProvider(new TradeSeriesContentProvider()); // ----------------------- TRADE STATS ----------------------- // Composite statsContainer = new Composite(rootTabs, SWT.NULL); tabStats.setControl(statsContainer); statsContainer.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); GridLayout rootLayout = new GridLayout(); rootLayout.verticalSpacing = 10; rootLayout.numColumns = 3; statsContainer.setLayout(rootLayout); Label bestTradeLabel = new Label(statsContainer, SWT.NULL); bestTradeLabel.setText("Best trade"); bestTradeTimeDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER); GridData bestTradeTimeDisplayGridData = new GridData(); bestTradeTimeDisplayGridData.widthHint = 250; bestTradeTimeDisplay.setLayoutData(bestTradeTimeDisplayGridData); bestTradePnlDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER | SWT.RIGHT); GridData bestTradePnlDisplayGridData = new GridData(); bestTradePnlDisplayGridData.widthHint = 100; bestTradePnlDisplay.setLayoutData(bestTradePnlDisplayGridData); Label worstTradeLabel = new Label(statsContainer, SWT.NULL); worstTradeLabel.setText("Worst trade"); worstTradeTimeDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER); GridData worstTradeTimeDisplayGridData = new GridData(); worstTradeTimeDisplayGridData.widthHint = 250; worstTradeTimeDisplay.setLayoutData(worstTradeTimeDisplayGridData); worstTradeTimeDisplay.setSize(400, 10); worstTradePnlDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER | SWT.RIGHT); GridData worstTradePnlDisplayGridData = new GridData(); worstTradePnlDisplayGridData.widthHint = 100; worstTradePnlDisplay.setLayoutData(worstTradePnlDisplayGridData); Label maxDrawdownLabel = new Label(statsContainer, SWT.NULL); maxDrawdownLabel.setText("Max drawdown"); maxDrawdownEndTimeDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER); GridData maxDrawdownEndTimeDisplayGridData = new GridData(); maxDrawdownEndTimeDisplayGridData.widthHint = 250; maxDrawdownEndTimeDisplay.setLayoutData(maxDrawdownEndTimeDisplayGridData); maxDrawdownEndTimeDisplay.setSize(400, 10); maxDrawdownValueDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER | SWT.RIGHT); GridData maxDrawdownValueDisplayGridData = new GridData(); maxDrawdownValueDisplayGridData.widthHint = 100; maxDrawdownValueDisplay.setLayoutData(maxDrawdownValueDisplayGridData); Label maxRunupLabel = new Label(statsContainer, SWT.NULL); maxRunupLabel.setText("Max runup"); maxRunupEndTimeDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER); GridData maxRunupEndTimeDisplayGridData = new GridData(); maxRunupEndTimeDisplayGridData.widthHint = 250; maxRunupEndTimeDisplay.setLayoutData(maxRunupEndTimeDisplayGridData); maxRunupEndTimeDisplay.setSize(400, 10); maxRunupValueDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER | SWT.RIGHT); GridData maxRunupValueDisplayGridData = new GridData(); maxRunupValueDisplayGridData.widthHint = 100; maxRunupValueDisplay.setLayoutData(maxRunupValueDisplayGridData); Label lowestEquityPointLabel = new Label(statsContainer, SWT.NULL); lowestEquityPointLabel.setText("Lowest equity"); lowestEquityTimeDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER); GridData lowestEquityTimeDisplayGridData = new GridData(); lowestEquityTimeDisplayGridData.widthHint = 250; lowestEquityTimeDisplay.setLayoutData(lowestEquityTimeDisplayGridData); lowestEquityValueDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER | SWT.RIGHT); GridData lowestEquityValueDisplayGridData = new GridData(); lowestEquityValueDisplayGridData.widthHint = 100; lowestEquityValueDisplay.setLayoutData(lowestEquityValueDisplayGridData); Label highestEquityPointLabel = new Label(statsContainer, SWT.NULL); highestEquityPointLabel.setText("Highest equity"); highestEquityTimeDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER); GridData highestEquityTimeDisplayGridData = new GridData(); highestEquityTimeDisplayGridData.widthHint = 250; highestEquityTimeDisplay.setLayoutData(highestEquityTimeDisplayGridData); highestEquityValueDisplay = new Text(statsContainer, SWT.READ_ONLY | SWT.BORDER | SWT.RIGHT); GridData highestEquityValueDisplayGridData = new GridData(); highestEquityValueDisplayGridData.widthHint = 100; highestEquityValueDisplay.setLayoutData(highestEquityValueDisplayGridData); getSite().getPage().addSelectionListener(this); selectionChanged(null, getSite().getPage().getSelection()); } @Override public void setFocus() { } @Override public void selectionChanged(IWorkbenchPart part, ISelection selection) { if (selection instanceof IStructuredSelection) { IStructuredSelection structured = (IStructuredSelection) selection; Object object = structured.getFirstElement(); if (object instanceof TradingAgentExecutionWrapper) { final TradingAgentExecutionWrapper executionHandle = (TradingAgentExecutionWrapper) object; try { getViewSite().getWorkbenchWindow().run(true, false, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { ExecutionInfo executionInfo = executionMap.get(executionHandle); if (executionInfo == null) { TradeStatsProcessor processor = new TradeStatsProcessor(); String processorOutputSeriesID = "output-" + processor.toString(); ISeries<Date, Double, ISeriesPoint<Date, Double>> executionOutputSeries = executionHandle.getManager().getExecutionOutput(executionHandle.getHandle()); executionInfo = new ExecutionInfo(processor, new LinkedListSeries<Date, Double, ISeriesPoint<Date, Double>>(processorOutputSeriesID, false)); executionInfo.processor.wire(Collections.singletonMap(TradeStatsProcessor.INPUT_SERIES_NAME, executionOutputSeries) ,executionInfo.outputSeries); executionMap.put(executionHandle, executionInfo); } if (currentExecutionInfo != null) { currentExecutionInfo.outputSeries.removeSeriesListener(TradingStatsView.this); } currentExecutionInfo = executionInfo; currentExecutionInfo.outputSeries.addSeriesListener(TradingStatsView.this); TradingStatsView.this.parent.getDisplay().asyncExec(new Runnable() { @Override public void run() { tradesTableViewer.setInput(currentExecutionInfo.outputSeries); }}); updateStats(); } catch (Exception e1) { e1.printStackTrace(); } }}); } catch (Exception e) { MessageDialog.openError(parent.getShell(), "Trading stats update failed", "Error while changing selection: " + LangUtils.exceptionMessage(e)); } } } } @Override public void dispose() { for (ExecutionInfo executionInfo : executionMap.values()) { executionInfo.processor.unwire(); } } private void updateStats() { final TradeStatsProcessor tradeStatsProcessor = currentExecutionInfo == null ? null : currentExecutionInfo.processor; if (tradeStatsProcessor != null) { parent.getDisplay().asyncExec(new Runnable() { @Override public void run() { ITradeStatsPoint bestTrade = tradeStatsProcessor.getBestTrade(); if (bestTrade != null) { bestTradeTimeDisplay.setText(sdf.format(bestTrade.getIndex())); bestTradePnlDisplay.setText(df.format(bestTrade.getTradePnl())); } else { bestTradeTimeDisplay.setText(""); bestTradePnlDisplay.setText(""); } ITradeStatsPoint worstTrade = tradeStatsProcessor.getWorstTrade(); if (worstTrade != null) { worstTradeTimeDisplay.setText(sdf.format(worstTrade.getIndex())); worstTradePnlDisplay.setText(df.format(worstTrade.getTradePnl())); } else { worstTradeTimeDisplay.setText(""); worstTradePnlDisplay.setText(""); } SimplePoint maxDDEnd = tradeStatsProcessor.getEndOfMaxDrawdown(); if (maxDDEnd != null) { maxDrawdownEndTimeDisplay.setText(sdf.format(maxDDEnd.getIndex())); maxDrawdownValueDisplay.setText(df.format(maxDDEnd.getValue() - tradeStatsProcessor.getStartOfMaxDrawdown().getValue())); } else { maxDrawdownEndTimeDisplay.setText(""); maxDrawdownValueDisplay.setText(""); } SimplePoint maxRUEnd = tradeStatsProcessor.getEndOfMaxRunup(); if (maxRUEnd != null) { maxRunupEndTimeDisplay.setText(sdf.format(maxRUEnd.getIndex())); maxRunupValueDisplay.setText(df.format(maxRUEnd.getValue() - tradeStatsProcessor.getStartOfMaxRunup().getValue())); } else { maxRunupEndTimeDisplay.setText(""); maxRunupValueDisplay.setText(""); } SimplePoint lowestEP = tradeStatsProcessor.getLowestEquityPoint(); if (lowestEP != null) { lowestEquityTimeDisplay.setText(sdf.format(lowestEP.getIndex())); lowestEquityValueDisplay.setText(df.format(lowestEP.getValue())); } else { lowestEquityTimeDisplay.setText(""); lowestEquityValueDisplay.setText(""); } SimplePoint highestEP = tradeStatsProcessor.getHighestEquityPoint(); if (highestEP != null) { highestEquityTimeDisplay.setText(sdf.format(highestEP.getIndex())); highestEquityValueDisplay.setText(df.format(highestEP.getValue())); } else { highestEquityTimeDisplay.setText(""); highestEquityValueDisplay.setText(""); } tradesTableViewer.refresh(); }}); } } @Override public void onItemUpdated(ISeriesPoint<Date, Double> existingItem, ISeriesPoint<Date, Double> updatedItem) { updateStats(); } @Override public void onItemAdded(ISeriesPoint<Date, Double> newItem) { updateStats(); } }