net.tradelib.core.Strategy.java Source code

Java tutorial

Introduction

Here is the source code for net.tradelib.core.Strategy.java

Source

// Copyright 2015 Ivan Popivanov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package net.tradelib.core;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

public abstract class Strategy implements IBrokerListener {

    protected String name;
    protected long dbId = -1;
    protected String dbUrl;
    protected IBroker broker;
    protected BarHierarchy barData = new BarHierarchy();
    protected List<Execution> executions = new ArrayList<Execution>();

    private LocalDateTime tradingStart = LocalDateTime.of(1990, 1, 1, 0, 0);
    private LocalDateTime tradingStop = LocalDateTime.of(2100, 1, 1, 0, 0);

    // protected Portfolio portfolio = new Portfolio();
    protected Account account = new Account();

    protected Connection connection = null;

    protected LocalDate lastDay = LocalDate.MIN;

    protected LocalDateTime lastTimestamp = LocalDateTime.MIN;

    protected boolean checkBars = true;
    protected boolean maintainAccount = true;
    // True if old positions should be deleted from the database
    protected boolean cleanupPositions = true;

    public void setCleanupPositions(boolean b) {
        this.cleanupPositions = b;
    }

    public void setMaintainAccount(boolean b) {
        this.maintainAccount = b;
    }

    public void setCheckBars(boolean b) {
        this.checkBars = b;
    }

    public LocalDateTime getLastTimestamp() {
        return lastTimestamp;
    }

    protected void connectIfNecessary() throws SQLException {
        if (connection == null) {
            connection = DriverManager.getConnection(dbUrl);
            connection.setAutoCommit(false);
        }
    }

    public void initialize(Context context) throws Exception {
        // Cache some context
        setBroker(context.broker);
        setDbUrl(context.dbUrl);
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setBroker(IBroker broker) throws Exception {
        this.broker = broker;
        this.broker.addBrokerListener(this);
    }

    public IBroker getBroker() {
        return broker;
    }

    public void setDbUrl(String dbUrl) {
        this.dbUrl = dbUrl;
    }

    public String getDbUrl() {
        return dbUrl;
    }

    protected void subscribe(String symbol) throws Exception {
        barData.addSymbol(symbol);
        getBroker().subscribe(symbol);
    }

    public void cleanupDb() throws SQLException {
        if (dbUrl == null || name == null)
            return;

        connectIfNecessary();

        // Load the strategy unique id from the "strategies" table
        getDbId();

        String query = "DELETE FROM executions WHERE strategy_id=?";
        PreparedStatement stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        stmt.executeUpdate();
        stmt.close();

        query = "DELETE FROM trades WHERE strategy_id=?";
        stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        stmt.executeUpdate();
        stmt.close();

        query = "DELETE FROM pnls WHERE strategy_id=?";
        stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        stmt.executeUpdate();
        stmt.close();

        query = "DELETE FROM trade_summaries WHERE strategy_id=?";
        stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        stmt.executeUpdate();
        stmt.close();

        if (cleanupPositions) {
            query = "DELETE FROM strategy_positions WHERE strategy_id=?";
            stmt = connection.prepareStatement(query);
            stmt.setLong(1, dbId);
            stmt.executeUpdate();
            stmt.close();
        }

        query = "DELETE FROM end_equity WHERE strategy_id=?";
        stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        stmt.executeUpdate();
        stmt.close();

        connection.commit();
    }

    public void getDbId() throws SQLException {
        if (dbId >= 0)
            return;

        connectIfNecessary();

        // Get the strategy id
        String query = "SELECT id FROM strategies where name=?";
        PreparedStatement stmt = connection.prepareStatement(query);
        stmt.setString(1, name);
        ResultSet rs = stmt.executeQuery();
        if (!rs.next()) {
            // The name doesn't exist, insert it
            rs.close();
            stmt.close();
            stmt = connection.prepareStatement("INSERT INTO strategies(name) values (?)");
            stmt.setString(1, name);
            // Ignore errors, some other process may insert the strategy.
            try {
                stmt.executeUpdate();
            } catch (Exception e) {
            }
            stmt.close();
            // Repeat the query
            stmt = connection.prepareStatement(query);
            stmt.setString(1, name);
            rs = stmt.executeQuery();
            rs.next();
        }

        dbId = rs.getLong(1);
        rs.close();
        stmt.close();
    }

    public void writeExecutions() throws SQLException {

        connectIfNecessary();

        getDbId();

        String query = "INSERT INTO executions(symbol,strategy_id,ts,price,quantity,signal_name) VALUES (?,?,?,?,?,?)";
        PreparedStatement stmt = connection.prepareStatement(query);
        for (Execution execution : executions) {
            stmt.setString(1, execution.getSymbol());
            stmt.setLong(2, dbId);
            stmt.setTimestamp(3, Timestamp.valueOf(execution.getDateTime()));
            stmt.setDouble(4, execution.getPrice());
            stmt.setLong(5, execution.getQuantity());
            stmt.setString(6, execution.getSignal());
            // stmt.executeUpdate();
            stmt.addBatch();
        }
        stmt.executeBatch();
        connection.commit();
    }

    public void writeExecutionsAndTrades() throws Exception {
        writeExecutions();
        writeTrades();
    }

    public void writeTrades() throws Exception {
        for (String symbol : account.getPortfolioSymbols()) {
            writeTrades(broker.getInstrument(symbol));
        }
    }

    public void writeTrades(Instrument instrument) throws Exception {
        BarHistory history = barData.getHistory(instrument.getSymbol(), Duration.ofDays(1));
        Series pnl = account.getPnlSeries(instrument);
        if (pnl.size() == 0)
            return;

        connectIfNecessary();

        getDbId();

        long start = System.nanoTime();

        // Write the PnL
        String query = " REPLACE INTO pnls(strategy_id,symbol,ts,pnl) VALUES (?,?,?,?) ";
        PreparedStatement stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        stmt.setString(2, instrument.getSymbol());
        for (int ii = 0; ii < pnl.size(); ++ii) {
            stmt.setTimestamp(3, Timestamp.valueOf(pnl.getTimestamp(ii)));
            stmt.setDouble(4, pnl.get(ii));
            stmt.addBatch();
        }
        stmt.executeBatch();
        connection.commit();
        stmt.close();

        long elapsedTime = System.nanoTime() - start;
        // System.out.println("pnls insert took " + String.format("%.4f secs",(double)elapsedTime/1e9));

        TradingResults tr = account.getPortfolioTradingResults(instrument);
        // TradingResults trold = portfolio.getTradingResults(instrument);
        // Write the trade statistics
        if (tr.stats.size() > 0) {

            query = " INSERT INTO trades(strategy_id,symbol,start,end,initial_position, "
                    + "       max_position,num_transactions,pnl,pct_pnl,tick_pnl,fees) "
                    + " VALUES(?,?,?,?,?,?,?,?,?,?,?)";
            stmt = connection.prepareStatement(query);
            stmt.setLong(1, dbId);
            stmt.setString(2, instrument.getSymbol());
            start = System.nanoTime();
            int ii = 0;
            for (Trade tradeStats : tr.stats) {
                stmt.setTimestamp(3, Timestamp.valueOf(tradeStats.start));
                stmt.setTimestamp(4, Timestamp.valueOf(tradeStats.end));
                stmt.setLong(5, tradeStats.initialPosition);
                stmt.setLong(6, tradeStats.maxPosition);
                stmt.setLong(7, tradeStats.numTransactions);
                // System.out.println(tradeStats.pnl);
                stmt.setDouble(8, tradeStats.pnl);
                stmt.setDouble(9, tradeStats.pctPnl);
                stmt.setDouble(10, tradeStats.tickPnl);
                stmt.setDouble(11, tradeStats.fees);
                //            Trade oldTradeStats = trold.stats.get(ii);
                //            if(tradeStats.pnl != oldTradeStats.pnl ||
                //               tradeStats.numTransactions != oldTradeStats.numTransactions ||
                //               tradeStats.initialPosition != oldTradeStats.initialPosition) {
                //               
                //               throw new Exception("Alternative trade statitics don't match!");
                //            }
                stmt.executeUpdate();

                ++ii;
            }
            connection.commit();
            stmt.close();

            elapsedTime = System.nanoTime() - start;
            // System.out.println("trades insert took " + String.format("%.4f secs",(double)elapsedTime/1e9));
        }

        writeTradeSummary(instrument, "All", tr.all);
        writeTradeSummary(instrument, "Long", tr.longs);
        writeTradeSummary(instrument, "Short", tr.shorts);

        connection.commit();
    }

    protected void writeTradeSummary(String symbol, String type, TradeSummary tradeSummary) throws SQLException {

        connectIfNecessary();

        if (tradeSummary.numTrades > 0) {
            String query = " INSERT INTO trade_summaries (strategy_id,symbol,type,num_trades,gross_profits, "
                    + "      gross_losses,profit_factor,average_daily_pnl,daily_pnl_stddev,sharpe_ratio, "
                    + "      average_trade_pnl,trade_pnl_stddev,pct_positive,pct_negative,max_win,max_loss, "
                    + "      average_win,average_loss,average_win_loss,equity_min,equity_max,max_drawdown, "
                    + "      max_drawdown_pct) " + " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
            PreparedStatement stmt = connection.prepareStatement(query);
            stmt.setLong(1, dbId);
            stmt.setString(2, symbol);
            stmt.setString(3, type);
            stmt.setLong(4, tradeSummary.numTrades);
            setDoubleParam(stmt, 5, tradeSummary.grossProfits);
            setDoubleParam(stmt, 6, tradeSummary.grossLosses);
            setDoubleParam(stmt, 7, tradeSummary.profitFactor);
            setDoubleParam(stmt, 8, tradeSummary.averageDailyPnl);
            setDoubleParam(stmt, 9, tradeSummary.dailyPnlStdDev);
            setDoubleParam(stmt, 10, tradeSummary.sharpeRatio);
            setDoubleParam(stmt, 11, tradeSummary.averageTradePnl);
            setDoubleParam(stmt, 12, tradeSummary.tradePnlStdDev);
            setDoubleParam(stmt, 13, tradeSummary.pctPositive);
            setDoubleParam(stmt, 14, tradeSummary.pctNegative);
            setDoubleParam(stmt, 15, tradeSummary.maxWin);
            setDoubleParam(stmt, 16, tradeSummary.maxLoss);
            setDoubleParam(stmt, 17, tradeSummary.averageWin);
            setDoubleParam(stmt, 18, tradeSummary.averageLoss);
            setDoubleParam(stmt, 19, tradeSummary.averageWinLoss);
            setDoubleParam(stmt, 20, tradeSummary.equityMin);
            setDoubleParam(stmt, 21, tradeSummary.equityMax);
            setDoubleParam(stmt, 22, tradeSummary.maxDD);
            setDoubleParam(stmt, 23, tradeSummary.maxDDPct);
            stmt.executeUpdate();
            connection.commit();
        }
    }

    public void writeEquity() throws SQLException {
        connectIfNecessary();

        // Accumulate using the last value for each day (the end equity)
        Series eq = getAccount().getEquity().toDaily((Double x, Double y) -> y);

        String query = " REPLACE INTO end_equity(strategy_id,ts,equity) VALUES (?,?,?) ";
        PreparedStatement stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        for (int ii = 0; ii < eq.size(); ++ii) {
            stmt.setTimestamp(2, Timestamp.valueOf(eq.getTimestamp(ii)));
            stmt.setDouble(3, eq.get(ii));
            stmt.addBatch();
        }
        stmt.executeBatch();
        connection.commit();
        stmt.close();
    }

    private void setDoubleParam(PreparedStatement stmt, int index, double value) throws SQLException {
        if (Double.isFinite(value)) {
            stmt.setDouble(index, value);
        } else {
            stmt.setNull(index, Types.DOUBLE);
        }
    }

    protected void writeTradeSummary(Instrument instrument, String type, TradeSummary tradeSummary)
            throws SQLException {
        writeTradeSummary(instrument.getSymbol(), type, tradeSummary);
    }

    protected void onNewDay(LocalDate previousDay, LocalDate day) throws Exception {
    }

    protected void onBarOpen(BarHistory history, Bar bar) throws Exception {
    }

    protected void onBarClose(BarHistory history, Bar bar) throws Exception {
    }

    protected void onBarClosed(BarHistory history, Bar bar) throws Exception {
    }

    protected void onOrderNotification(OrderNotification on) throws Exception {
    }

    public void barOpenHandler(Bar bar) throws Exception {
        checkBar(bar);

        LocalDate newDay = bar.getDateTime().toLocalDate();

        if (newDay.isAfter(lastDay)) {
            // Update the account equity once per day
            if (!lastDay.equals(LocalDate.MIN) && maintainAccount) {
                getAccount().updateEndEquity(lastDay.atTime(23, 59, 59));

                // Signal a new day has started
                onNewDay(lastDay, newDay);
            }

            lastDay = newDay;
        }

        BarHistory history = barData.getHistory(bar);
        // null means the strategy is not interested in this symbol
        if (history != null) {
            onBarOpen(history, bar);
        }
    }

    public void barCloseHandler(Bar bar) throws Exception {
        checkBar(bar);

        BarHistory history = barData.getHistory(bar);
        // null means the strategy is not interested in this symbol
        if (history != null) {
            history.add(bar);
            onBarClose(history, bar);
        }
    }

    public void barClosedHandler(Bar bar) throws Exception {
        checkBar(bar);

        if (bar.getDateTime().isAfter(getTradingStart()) && maintainAccount) {
            Instrument instrument = broker.getInstrument(bar.getSymbol());
            getAccount().mark(instrument, bar);
        }

        BarHistory history = barData.getHistory(bar);
        // null means the strategy is not interested in this symbol
        if (history != null) {
            onBarClosed(history, bar);
        }

        if (bar.getDateTime().isAfter(lastTimestamp)) {
            lastTimestamp = bar.getDateTime();
        }
    }

    protected void checkBar(Bar bar) throws Exception {
        if (checkBars && (bar.getOpen() <= 0 || bar.getHigh() <= 0 || bar.getLow() <= 0 || bar.getClose() <= 0)) {
            throw new RuntimeException(
                    String.format("Negative data for %s [%2$tY-%2$tm-%2$td]: %3$f %4$f %5$f %6$f", bar.getSymbol(),
                            bar.getDateTime(), bar.getOpen(), bar.getHigh(), bar.getLow(), bar.getClose()));
        }
    }

    public void orderExecutedHandler(OrderNotification on) throws Exception {
        executions.add(on.execution);
        // portfolio.addTransaction(on.execution);
        getAccount().addTransaction(on.execution);
        onOrderNotification(on);
    }

    /**
     * @brief Get basic statistics to evaluate performance.
     * 
     * Computations are based off the equity curve.
     * 
     * For a quick strategy evaluation, I currently use the approach from
     * "Building Reliable Trading Systems", by Keith Fitschen.
     *  
     *    * PnL
     *    * PnL as percentage
     *    * End Equity
     *    * Cash Max Drawdown
     *    * Percentage Max Drawdown
     *    
     * @return A time series with the afford-mentioned columns
     * 
     * @throws SQLException 
     */
    public Series getAnnualStats() throws Exception {
        Series result = new Series(5);

        connectIfNecessary();

        String query = "SELECT ts,equity FROM end_equity WHERE strategy_id=" + Long.toString(dbId)
                + " ORDER BY ts ASC";
        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery(query);

        double equity = Double.NaN;
        double startEquity = Double.NaN;
        double maxEquity = Double.NaN;
        double minEquity = Double.NaN;
        double maxDD = Double.NaN;
        double maxDDPct = 0.0;

        LocalDateTime last = null;

        if (rs.next()) {
            last = rs.getTimestamp(1).toLocalDateTime();
            equity = rs.getDouble(2);
            maxEquity = equity;
            minEquity = equity;
            startEquity = equity;
            maxDD = 0.0;
        }

        while (rs.next()) {
            // Kick off the statistics at the first different equity
            if (result.size() == 0 && rs.getDouble(2) == equity) {
                last = rs.getTimestamp(1).toLocalDateTime();
                continue;
            }

            LocalDateTime ldt = rs.getTimestamp(1).toLocalDateTime();
            if (ldt.getYear() == last.getYear()) {
                // Same year, update the counters
                equity = rs.getDouble(2);
                maxEquity = Math.max(maxEquity, equity);
                minEquity = Math.min(minEquity, equity);
                maxDD = Math.min(maxDD, equity - maxEquity);
                maxDDPct = Math.min(maxDDPct, equity / maxEquity - 1.0);
            } else {
                // Starting a new year. Summarize statistics and reset the counters.
                double pnl = equity - startEquity;
                double pnlPct = equity / startEquity - 1.0;
                result.append(last, pnl, pnlPct, equity, maxDD, maxDDPct);

                startEquity = equity;
                equity = rs.getDouble(2);

                maxEquity = equity;
                minEquity = equity;

                maxDD = 0.0;
                maxDDPct = 0.0;

                last = ldt;
            }
        }

        // Add the last year
        if (!Double.isNaN(equity)) {
            double pnl = equity - startEquity;
            double pnlPct = equity / startEquity - 1.0;
            result.append(last, pnl, pnlPct, equity, maxDD, maxDDPct);
        }

        connection.commit();

        return result;
    }

    /**
     * @brief Get basic statistics used to evaluate performance.
     * 
     * For a quick strategy evaluation, I currently use the approach from
     * "Building Reliable Trading Systems", by Keith Fitschen. 
     *    * PnL
     *    * Drawdown
     *    
     * @return A time series with two columns - PnL and MaxDrawdown
     * @throws SQLException 
     */
    public Series getAnnualStatsOld() throws SQLException {
        Series annualStats = new Series(2);

        connectIfNecessary();

        getDbId();

        String query = "SELECT ts,pnl FROM pnls WHERE strategy_id=" + Long.toString(dbId)
                + " AND symbol = 'TOTAL' ORDER BY ts ASC";
        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery(query);

        double equity = 0.0;
        double maxEquity = Double.MIN_VALUE;
        double minEquity = Double.MAX_VALUE;
        double maxDrawdown = Double.MAX_VALUE;

        LocalDateTime last = LocalDateTime.of(0, 1, 1, 0, 0);

        while (rs.next()) {
            LocalDateTime ldt = rs.getTimestamp(1).toLocalDateTime();
            double pnl = rs.getDouble(2);

            // Kick off the statistics at the first positive PnL
            if (annualStats.size() == 0 && pnl == 0.0)
                continue;

            if (ldt.getYear() == last.getYear()) {
                // Same year, update the counters
                equity += pnl;
                maxEquity = Math.max(maxEquity, equity);
                minEquity = Math.min(minEquity, equity);
                maxDrawdown = Math.min(maxDrawdown, equity - maxEquity);
            } else {
                // Starting a new year. Summarize statistics and reset the counters.
                if (maxDrawdown != Double.MAX_VALUE) {
                    annualStats.append(last, equity, maxDrawdown);
                }
                equity = pnl;
                maxEquity = equity;
                minEquity = equity;
                maxDrawdown = equity;
                last = ldt;
            }
        }

        // Add the last year
        if (maxDrawdown != Double.MAX_VALUE) {
            annualStats.append(last, equity, maxDrawdown);
        }

        connection.commit();

        return annualStats;
    }

    public Series getPnl() throws Exception {
        return getPnl("TOTAL");
    }

    public Series getPnl(String symbol) throws Exception {
        Series pnl = new Series(2);

        connectIfNecessary();

        getDbId();

        String query = "SELECT ts,pnl FROM pnls WHERE strategy_id=" + Long.toString(dbId) + " AND symbol = '"
                + symbol + "' ORDER BY ts ASC";
        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery(query);

        double equity = 0.0;
        double maxEquity = Double.MIN_VALUE;
        double minEquity = Double.MAX_VALUE;
        double maxDrawdown = Double.MAX_VALUE;

        LocalDateTime last = LocalDateTime.of(0, 1, 1, 0, 0);

        while (rs.next()) {
            pnl.append(rs.getTimestamp(1).toLocalDateTime(), rs.getDouble(2));
        }

        connection.commit();

        return pnl;
    }

    public Series getEquity() {
        return getAccount().getEquity();
    }

    public TimeSeries<BigDecimal> getAnnualPnl(String symbols) throws SQLException {
        TimeSeries<BigDecimal> pnl = new TimeSeries<BigDecimal>(1);

        connectIfNecessary();

        getDbId();

        // Build the query
        String query = "SELECT ts,pnl FROM pnls WHERE strategy_id=" + Long.toString(dbId) + " AND ";
        String inList = "";
        if (!symbols.isEmpty()) {
            String[] strs = symbols.split("\\s*,\\s*");
            for (String str : strs) {
                if (!inList.isEmpty())
                    inList = inList + ",\"" + str + "\"";
                else
                    inList = "\"" + str + "\"";
            }
            query += " symbol in (" + inList + ") ";
        } else {
            // Exclude the totals
            query += " symbol <> 'TOTAL' ";
        }

        query += " ORDER BY ts";

        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery(query);
        while (rs.next()) {
            if (pnl.size() != 0) {
                LocalDateTime ldt = rs.getTimestamp(1).toLocalDateTime();
                BigDecimal pp = rs.getBigDecimal(2);
                if (ldt.getYear() != pnl.getTimestamp(pnl.size() - 1).getYear()) {
                    // Entered a new year
                    pnl.add(LocalDateTime.of(ldt.getYear(), 1, 1, 0, 0), pp);
                } else if (pp.compareTo(BigDecimal.ZERO) != 0) {
                    // Same year
                    int id = pnl.size() - 1;
                    BigDecimal prevPnl = pnl.get(id);
                    pnl.set(id, prevPnl.add(pp));
                }
            } else {
                BigDecimal pp = rs.getBigDecimal(2);

                // Start adding PnL after the first non-zero value
                if (pp.compareTo(BigDecimal.ZERO) == 0)
                    continue;

                LocalDateTime ldt = rs.getTimestamp(1).toLocalDateTime();
                pnl.add(LocalDateTime.of(ldt.getYear(), 1, 1, 0, 0), pp);
            }
        }

        connection.commit();

        return pnl;
    }

    class TradeTotalsBuilder {
        private long numTrades;

        private double grossProfits;
        private double grossLosses;

        private double mean;
        private double variance;

        private AverageAndVariance dailyPnlStats;
        private AverageAndVariance pnlStats;

        private long nonZero;
        private long positive;
        private long negative;

        private double maxWin;
        private double maxLoss;

        private Average averageWinTrade;
        private Average averageLossTrade;

        private TimeSeries<Double> pnl;
        private int pnlId;

        private double previousEquity;
        private double minEquity;
        private double maxEquity;
        private double maxDrawdown;

        TradeTotalsBuilder() {
            numTrades = 0;

            grossProfits = 0.0;
            grossLosses = 0.0;

            nonZero = 0;
            positive = 0;
            negative = 0;

            maxWin = 0.0;
            maxLoss = 0.0;

            averageWinTrade = new Average();
            averageLossTrade = new Average();

            minEquity = 0.0;
            maxEquity = 0.0;
            previousEquity = 0.0;
            maxDrawdown = 0.0;

            pnl = new TimeSeries<Double>();

            dailyPnlStats = new AverageAndVariance();
            pnlStats = new AverageAndVariance();
        }

        void add(long position, double pnl) {
            ++numTrades;
            if (pnl < 0.0) {
                ++nonZero;
                ++negative;
                averageLossTrade.add(pnl);
                grossLosses += pnl;
            } else if (pnl > 0.0) {
                ++nonZero;
                ++positive;
                averageWinTrade.add(pnl);
                grossProfits += pnl;
            }

            pnlStats.add(pnl);

            maxWin = Math.max(maxWin, pnl);
            maxLoss = Math.min(maxLoss, pnl);
        }

        TradeSummary summarize() {
            TradeSummary summary = new TradeSummary();

            summary.numTrades = numTrades;

            if (numTrades > 0) {
                summary.grossLosses = grossLosses;
                summary.grossProfits = grossProfits;
                summary.profitFactor = grossLosses != 0.0 ? Math.abs(grossProfits / grossLosses)
                        : Math.abs(grossProfits);

                summary.averageTradePnl = pnlStats.getAverage();
                summary.tradePnlStdDev = pnlStats.getStdDev();
                summary.pctNegative = numTrades > 0 ? (double) negative / numTrades * 100.0 : 0.0;
                summary.pctPositive = numTrades > 0 ? (double) positive / numTrades * 100.0 : 0.0;

                summary.maxLoss = maxLoss;
                summary.maxWin = maxWin;
                summary.averageLoss = averageLossTrade.get();
                summary.averageWin = averageWinTrade.get();
                summary.averageWinLoss = summary.averageLoss != 0.0
                        ? summary.averageWin / Math.abs(summary.averageLoss)
                        : summary.averageWin;
            }

            return summary;
        }
    };

    private class PnlPair {
        public boolean seenNonZeroPnl;
        public double pnl;

        public PnlPair(double d) {
            pnl = d;
            seenNonZeroPnl = pnl != 0.0;
        }

        public boolean seenNonZero() {
            return seenNonZeroPnl;
        }

        public double pnl() {
            return pnl;
        }

        public void add(double pnl) {
            this.pnl += pnl;
            seenNonZeroPnl |= this.pnl != 0.0;
        }
    }

    /**
     * @throws SQLException 
     * @brief Computes total statistics for all trades for this strategy
     * in the database.
     *
     * Goes through the trades and the pnls for all instruments and
     * computes "TradeSummary". The new trade summary and the pnl are
     * inserted into the corresponding tables using the string "name"
     * as the symbol for the instrument.
     * 
     * We use "TOTAL" for "name" (unlikely to have a real symbol TOTAL),
     * but it's good to have things flexible.
     *
     * @param[in] the id to use for the entries in the various tables
     */
    protected void totalTradeStats(String name) throws SQLException {

        connectIfNecessary();

        getDbId();

        String query = "DELETE FROM pnls WHERE strategy_id=" + Long.toString(dbId) + " AND symbol = \"" + name
                + "\"";
        Statement stmt = connection.createStatement();
        stmt.executeUpdate(query);
        connection.commit();

        stmt.close();

        query = "DELETE FROM trade_summaries WHERE strategy_id=" + Long.toString(dbId) + " AND symbol = \"" + name
                + "\"";
        stmt = connection.createStatement();
        stmt.executeUpdate(query);
        connection.commit();

        stmt.close();

        // Go through each individual trade
        TradeTotalsBuilder shortsBuilder = new TradeTotalsBuilder();
        TradeTotalsBuilder longsBuilder = new TradeTotalsBuilder();
        TradeTotalsBuilder allBuilder = new TradeTotalsBuilder();

        query = "SELECT initial_position,pnl FROM trades WHERE strategy_id=" + Long.toString(dbId);
        stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery(query);
        while (rs.next()) {
            long position = rs.getLong(1);
            double pnl = rs.getDouble(2);
            if (position < 0) {
                shortsBuilder.add(position, pnl);
                allBuilder.add(position, pnl);
            } else {
                longsBuilder.add(position, pnl);
                allBuilder.add(position, pnl);
            }
        }
        stmt.close();

        // The pair tells us:
        //    1. Weather we have seen non-zero PnL for that timestamp
        //    2. The total PnL
        TreeMap<LocalDateTime, PnlPair> pnlMap = new TreeMap<LocalDateTime, PnlPair>();
        query = "SELECT ts,pnl FROM pnls WHERE strategy_id=?";
        PreparedStatement pstmt = connection.prepareStatement(query);
        pstmt.setLong(1, dbId);
        rs = pstmt.executeQuery();
        while (rs.next()) {
            LocalDateTime ldt = rs.getTimestamp(1).toLocalDateTime();
            double pnl = rs.getDouble(2);
            PnlPair pp = pnlMap.get(ldt);
            if (pp != null) {
                pp.add(pnl);
            } else {
                pnlMap.put(ldt, new PnlPair(pnl));
            }
        }
        pstmt.close();

        // Write the total PnL and collect the basic per-bar and equity statistics
        query = "INSERT INTO pnls (strategy_id,symbol,ts,pnl) values (?,?,?,?)";
        pstmt = connection.prepareStatement(query);
        pstmt.setLong(1, dbId);
        pstmt.setString(2, name);

        AverageAndVariance barStats = new AverageAndVariance();

        double equity = 0.0;
        double maxEquity = Double.MIN_VALUE;
        double minEquity = Double.MAX_VALUE;
        double maxDD = Double.MAX_VALUE;
        double maxDDPct = Double.MAX_VALUE;

        for (Map.Entry<LocalDateTime, PnlPair> entry : pnlMap.entrySet()) {
            PnlPair pp = entry.getValue();
            double pnl = pp.pnl();

            // Write the PnL
            pstmt.setTimestamp(3, Timestamp.valueOf(entry.getKey()));
            pstmt.setDouble(4, pnl);
            // pstmt.executeUpdate();
            pstmt.addBatch();

            // Collect statistics
            if (pp.seenNonZero())
                barStats.add(pnl);

            equity += pnl;
            maxEquity = Math.max(maxEquity, equity);
            minEquity = Math.min(minEquity, equity);
            maxDD = Math.min(maxDD, equity - maxEquity);
            maxDDPct = Math.min(maxDDPct, equity / maxEquity - 1);
            if (Double.isNaN(maxDDPct) || !Double.isFinite(maxDDPct)) {
                Logger.getLogger("").warning(String.format("Fixing a bad maximum drawdown [%f]", maxDDPct));
                maxDDPct = Double.MAX_VALUE;
            }
        }

        pstmt.executeBatch();
        connection.commit();
        pstmt.close();

        // Write out the total as a trade summary
        TradeSummary summary = allBuilder.summarize();
        summary.equityMin = minEquity;
        summary.equityMax = maxEquity;
        summary.maxDD = maxDD;
        summary.maxDDPct = maxDDPct * 100;

        summary.averageDailyPnl = barStats.getAverage();
        summary.dailyPnlStdDev = barStats.getStdDev();
        summary.sharpeRatio = Functions.sharpeRatio(summary.averageDailyPnl, summary.dailyPnlStdDev, 252);

        writeTradeSummary(name, "All", summary);

        // For the shorts and longs totals we don't have equityMin, equityMax, etc
        writeTradeSummary(name, "Long", longsBuilder.summarize());
        writeTradeSummary(name, "Short", shortsBuilder.summarize());

        connection.commit();
    }

    public void totalTradeStats() throws Exception {
        totalTradeStats("TOTAL");
    }

    // public Portfolio getPortfolio() { return portfolio; }
    public Account getAccount() {
        return account;
    }

    protected class Status {
        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        public long getStrategyId() {
            return strategyId;
        }

        public void setStrategyId(long strategyId) {
            this.strategyId = strategyId;
        }

        public String getSymbol() {
            return symbol;
        }

        public void setSymbol(String symbol) {
            this.symbol = symbol;
        }

        public String getTradingSymbol() {
            return tradingSymbol;
        }

        public void setTradingSymbol(String symbol) {
            this.tradingSymbol = symbol;
        }

        public LocalDateTime getDateTime() {
            return ts;
        }

        public void setDateTime(LocalDateTime ts) {
            this.ts = ts;
        }

        public double getPosition() {
            return position;
        }

        public void setPosition(double position) {
            this.position = position;
        }

        public double getPnl() {
            return pnl;
        }

        public void setPnl(double pnl) {
            this.pnl = pnl;
        }

        public double getLastClose() {
            return lastClose;
        }

        public void setLastClose(double lastClose) {
            this.lastClose = lastClose;
        }

        public double getLastTradingClose() {
            return lastTradingClose;
        }

        public void setLastTradingClose(double lastClose) {
            this.lastTradingClose = lastClose;
        }

        public LocalDateTime getLastDateTime() {
            return lastDateTime;
        }

        public void setLastDateTime(LocalDateTime ldt) {
            this.lastDateTime = ldt;
        }

        public double getEntryPrice() {
            return entryPrice;
        }

        public void setEntryPrice(double entryPrice) {
            this.entryPrice = entryPrice;
        }

        public double getEntryRisk() {
            return entryRisk;
        }

        public void setEntryRisk(double entryRisk) {
            this.entryRisk = entryRisk;
        }

        public double getProfitTarget() {
            return profitTarget;
        }

        public void setProfitTarget(double profitTarget) {
            this.profitTarget = profitTarget;
        }

        public LocalDateTime getEntryDateTime() {
            return since;
        }

        public void setEntryDateTime(LocalDateTime ldt) {
            this.since = ldt;
        }

        public double getStopLoss() {
            return stopLoss;
        }

        public void setStopLoss(double stopLoss) {
            this.stopLoss = stopLoss;
        }

        public void addOrder(Order order) {
            orders.add(order);
        }

        public Status(String symbol) {
            setSymbol(symbol);
            orders = new ArrayList<Order>();
            numericProperties = new HashMap<String, Double>();
        }

        public Status(int strategyId, String symbol) {
            this(symbol);
            setStrategyId(strategyId);
        }

        public void addProperty(String name, double value) {
            numericProperties.put(name, value);
        }

        public void persist(Connection con) throws Exception {

            // Build the JSON status
            JsonObject jo = new JsonObject();
            assert getPosition() != Double.NaN : "Position must not be NaN!";
            jo.addProperty("position", getPosition());
            if (!Double.isNaN(getPnl())) {
                jo.addProperty("pnl", getPnl());
            }
            jo.addProperty("last_close", getLastTradingClose());
            if (!Double.isNaN(getEntryPrice())) {
                jo.addProperty("entry_price", getEntryPrice());
            }
            if (!Double.isNaN(getEntryRisk())) {
                jo.addProperty("entry_risk", getEntryRisk());
            }
            if (!Double.isNaN(getProfitTarget())) {
                jo.addProperty("profit_target", getProfitTarget());
            }
            if (!Double.isNaN(getStopLoss())) {
                jo.addProperty("stop_loss", getStopLoss());
            }
            if (tradingSymbol != null && !tradingSymbol.isEmpty()) {
                jo.addProperty("trading_symbol", getTradingSymbol());
            }
            numericProperties.forEach((k, v) -> jo.addProperty(k, v));
            JsonArray ordersArray = new JsonArray();
            for (Order oo : orders) {
                ordersArray.add(oo.toJsonString());
            }
            jo.add("orders", ordersArray);
            Gson gson = new GsonBuilder().setPrettyPrinting()
                    .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();

            String query = " REPLACE INTO strategy_positions "
                    + " (strategy_id,symbol,ts,position,last_close,last_ts,details) " + " VALUES(?,?,?,?,?,?,?) ";
            String jsonString = gson.toJson(jo);
            PreparedStatement stmt = con.prepareStatement(query);
            stmt.setLong(1, getStrategyId());
            stmt.setString(2, getSymbol());
            if (getDateTime().equals(LocalDateTime.MIN)) {
                stmt.setNull(3, Types.TIMESTAMP);
            } else {
                stmt.setTimestamp(3, Timestamp.valueOf(getDateTime()));
            }
            stmt.setDouble(4, getPosition());
            stmt.setDouble(5, getLastClose());
            stmt.setTimestamp(6, Timestamp.valueOf(getLastDateTime()));
            stmt.setString(7, jsonString);

            stmt.executeUpdate();

            con.commit();
        }

        private long id;
        private long strategyId;
        private String symbol;
        private String tradingSymbol;
        private LocalDateTime ts = null;
        private double position = Double.NaN;
        private LocalDateTime since = null;
        private double pnl = Double.NaN;
        private double lastClose = Double.NaN;
        private double lastTradingClose = Double.NaN;
        private LocalDateTime lastDateTime = null;
        private double entryPrice = Double.NaN;
        private double entryRisk = Double.NaN;
        private double profitTarget = Double.NaN;
        private double stopLoss = Double.NaN;

        private List<Order> orders = null;
        private HashMap<String, Double> numericProperties = null;
    }

    public void persistStatus(Strategy.Status status) throws Exception {
        connectIfNecessary();
        status.persist(connection);
    }

    public TradeSummary getSummary(String symbol, String type) throws SQLException {
        TradeSummary summary = new TradeSummary();

        connectIfNecessary();

        String query = " SELECT num_trades, gross_profits, gross_losses, profit_factor, "
                + "      average_daily_pnl, daily_pnl_stddev, sharpe_ratio, "
                + "      average_trade_pnl, trade_pnl_stddev, pct_positive, "
                + "      pct_negative, max_win, max_loss, average_win, average_loss, "
                + "      average_win_loss, equity_min, equity_max, max_drawdown, " + "      max_drawdown_pct "
                + " FROM trade_summaries " + " WHERE strategy_id = ? AND symbol = ? AND type = ?";
        PreparedStatement stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        stmt.setString(2, symbol);
        stmt.setString(3, type);

        ResultSet rs = stmt.executeQuery();
        if (!rs.next())
            return null;

        summary.numTrades = rs.getLong(1);
        summary.grossProfits = rs.getDouble(2);
        summary.grossLosses = rs.getDouble(3);
        summary.profitFactor = rs.getDouble(4);
        summary.averageDailyPnl = rs.getDouble(5);
        summary.dailyPnlStdDev = rs.getDouble(6);
        summary.sharpeRatio = rs.getDouble(7);
        summary.averageTradePnl = rs.getDouble(8);
        summary.tradePnlStdDev = rs.getDouble(9);
        summary.pctPositive = rs.getDouble(10);
        summary.pctNegative = rs.getDouble(11);
        summary.maxWin = rs.getDouble(12);
        summary.maxLoss = rs.getDouble(13);
        summary.averageWin = rs.getDouble(14);
        summary.averageLoss = rs.getDouble(15);
        summary.averageWinLoss = rs.getDouble(16);
        summary.equityMin = rs.getDouble(17);
        summary.equityMax = rs.getDouble(18);
        summary.maxDD = rs.getDouble(19);
        summary.maxDDPct = rs.getDouble(20);

        connection.commit();
        return summary;
    }

    public JsonObject writeStrategyReport() throws Exception {
        // Annual statistics
        Series annualStats = getAnnualStats();

        JsonObject result = new JsonObject();

        if (annualStats.size() > 0) {

            Average avgPnl = new Average();
            Average avgPnlPct = new Average();
            Average avgDD = new Average();
            Average avgDDPct = new Average();
            JsonArray asa = new JsonArray();

            for (int ii = 0; ii < annualStats.size(); ++ii) {
                JsonObject ajo = new JsonObject();
                ajo.addProperty("year", annualStats.getTimestamp(ii).getYear());
                ajo.addProperty("pnl", Math.round(annualStats.get(ii, 0)));
                ajo.addProperty("pnl_pct", annualStats.get(ii, 1) * 100.0);
                ajo.addProperty("end_equity", Math.round(annualStats.get(ii, 2)));
                ajo.addProperty("maxdd", Math.round(annualStats.get(ii, 3)));
                ajo.addProperty("maxdd_pct", annualStats.get(ii, 4) * 100.0);
                asa.add(ajo);

                avgPnl.add(annualStats.get(ii, 0));
                avgPnlPct.add(annualStats.get(ii, 1));
                avgDD.add(annualStats.get(ii, 3));
                avgDDPct.add(annualStats.get(ii, 4));
            }

            result.add("annual_stats", asa);

            result.addProperty("pnl", Math.round(avgPnl.get()));
            result.addProperty("pnl_pct", avgPnlPct.get() * 100.0);
            result.addProperty("avgdd", Math.round(avgDD.get()));
            result.addProperty("avgdd_pct", avgDDPct.get() * 100.0);
            result.addProperty("gain_to_pain", avgPnl.get() / Math.abs(avgDD.get()));
        }

        // Global statistics
        LocalDateTime maxDateTime = LocalDateTime.MIN;
        double maxEndEq = Double.MIN_VALUE;
        double maxDD = Double.MAX_VALUE;
        double maxDDPct = Double.MAX_VALUE;

        Series equity = getEquity();
        for (int ii = 0; ii < equity.size(); ++ii) {
            if (equity.get(ii) > maxEndEq) {
                maxEndEq = equity.get(ii);
                maxDateTime = equity.getTimestamp(ii);
            }
            maxDD = Math.min(maxDD, equity.get(ii) - maxEndEq);
            maxDDPct = Math.min(maxDDPct, equity.get(ii) / maxEndEq - 1);
        }

        double lastDD = equity.get(equity.size() - 1) - maxEndEq;
        double lastDDPct = (lastDD / maxEndEq) * 100;

        JsonObject jo = new JsonObject();
        jo.addProperty("cash", lastDD);
        jo.addProperty("pct", lastDDPct);

        result.add("total_maxdd", jo);

        jo = new JsonObject();
        jo.addProperty("date", maxDateTime.format(DateTimeFormatter.ISO_DATE));
        jo.addProperty("equity", Math.round(maxEndEq));

        result.add("total_peak", jo);

        if (equity.size() > 2) {
            int ii = equity.size() - 1;
            int jj = ii - 1;

            for (; jj >= 0 && equity.getTimestamp(jj).getYear() == equity.getTimestamp(ii).getYear(); --jj) {
            }

            if (jj >= 0 && equity.getTimestamp(jj).getYear() != equity.getTimestamp(ii).getYear()) {
                ++jj;
                maxDateTime = equity.getTimestamp(jj);
                maxEndEq = equity.get(jj);
                for (++jj; jj < equity.size(); ++jj) {
                    if (equity.get(jj) > maxEndEq) {
                        maxEndEq = equity.get(jj);
                        maxDateTime = equity.getTimestamp(jj);
                    }
                }

                lastDD = equity.get(equity.size() - 1) - maxEndEq;
                lastDDPct = (lastDD / maxEndEq) * 100;

                jo = new JsonObject();
                jo.addProperty("cash", lastDD);
                jo.addProperty("pct", lastDDPct);

                result.add("latest_maxdd", jo);

                jo = new JsonObject();
                jo.addProperty("date", maxDateTime.format(DateTimeFormatter.ISO_DATE));
                jo.addProperty("equity", Math.round(maxEndEq));

                result.add("latest_peak", jo);
            }
        }

        TradeSummary summary = getSummary("TOTAL", "All");
        if (summary != null) {
            result.addProperty("avg_trade_pnl", Math.round(summary.averageTradePnl));
            result.addProperty("maxdd", Math.round(maxDD));
            result.addProperty("maxdd_pct", maxDDPct * 100);
            result.addProperty("num_trades", summary.numTrades);
        } else {
            result.addProperty("avg_trade_pnl", 0);
            result.addProperty("maxdd", 0);
            result.addProperty("maxdd_pct", 0);
            result.addProperty("num_trades", 0);
        }

        Gson gson = new GsonBuilder().setPrettyPrinting()
                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();

        connectIfNecessary();

        String query = " REPLACE INTO strategy_report (strategy_id,last_date,report) " + " VALUES(?,?,?) ";
        PreparedStatement stmt = connection.prepareStatement(query);
        stmt.setLong(1, dbId);
        stmt.setTimestamp(2, Timestamp.valueOf(getLastTimestamp()));
        String jsonString = gson.toJson(result);
        stmt.setString(3, jsonString);
        stmt.executeUpdate();

        connection.commit();

        return result;
    }

    public Order enterLong(String symbol, long quantity, String signal) throws Exception {
        Order order = Order.enterLong(symbol, quantity, signal);
        broker.submitOrder(order);
        return order;
    }

    public Order enterLong(String symbol, long quantity) throws Exception {
        Order order = Order.enterLong(symbol, quantity);
        broker.submitOrder(order);
        return order;
    }

    public Order enterLongLimit(String symbol, double limitPrice, long quantity) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterLongLimit(symbol, quantity, limitPrice);
        broker.submitOrder(order);
        return order;
    }

    public Order enterLongLimit(String symbol, double limitPrice, long quantity, String signal) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterLongLimit(symbol, quantity, limitPrice, signal);
        broker.submitOrder(order);
        return order;
    }

    public Order enterLongLimit(String symbol, double limitPrice, long quantity, String signal, int barsValidFor)
            throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterLongLimit(symbol, quantity, limitPrice, signal);
        order.setExpiration(barsValidFor);
        broker.submitOrder(order);
        return order;
    }

    public Order enterLongStop(String symbol, double stopPrice, long quantity) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterLongStop(symbol, quantity, stopPrice);
        broker.submitOrder(order);
        return order;
    }

    public Order enterLongStop(String symbol, double stopPrice, long quantity, String signal) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterLongStop(symbol, quantity, stopPrice, signal);
        broker.submitOrder(order);
        return order;
    }

    public Order enterLongStop(String symbol, double stopPrice, long quantity, String signal, int barsValidFor)
            throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterLongStop(symbol, quantity, stopPrice, signal);
        order.setExpiration(barsValidFor);
        broker.submitOrder(order);
        return order;
    }

    public Order enterShortLimit(String symbol, double limitPrice, long quantity) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterShortLimit(symbol, quantity, limitPrice);
        broker.submitOrder(order);

        return order;
    }

    public Order enterShortLimit(String symbol, double limitPrice, long quantity, String signal) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterShortLimit(symbol, quantity, limitPrice, signal);
        broker.submitOrder(order);

        return order;
    }

    public Order enterShortLimit(String symbol, double limitPrice, long quantity, String signal, int barsValidFor)
            throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterShortLimit(symbol, quantity, limitPrice, signal);
        order.setExpiration(barsValidFor);
        broker.submitOrder(order);

        return order;
    }

    public Order enterShortStop(String symbol, double stopPrice, long quantity) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterShortStop(symbol, quantity, stopPrice);
        broker.submitOrder(order);

        return order;
    }

    public Order enterShortStop(String symbol, double stopPrice, long quantity, String signal) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterShortStop(symbol, quantity, stopPrice, signal);
        broker.submitOrder(order);

        return order;
    }

    public Order enterShortStop(String symbol, double stopPrice, long quantity, String signal, int barsValidFor)
            throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterShortStop(symbol, quantity, stopPrice, signal);
        order.setExpiration(barsValidFor);
        broker.submitOrder(order);

        return order;
    }

    public Order enterShort(String symbol, long quantity, String signal) throws Exception {
        Order order = Order.enterShort(symbol, quantity, signal);
        broker.submitOrder(order);
        return order;
    }

    public Order enterShort(String symbol, long quantity) throws Exception {
        Order order = Order.enterShort(symbol, quantity);
        broker.submitOrder(order);
        return order;
    }

    public Order exitShort(String symbol) throws Exception {
        Order order = Order.exitShort(symbol, Order.POSITION_QUANTITY);
        broker.submitOrder(order);
        return order;
    }

    public Order exitShort(String symbol, String signal) throws Exception {
        Order order = Order.exitShort(symbol, Order.POSITION_QUANTITY, signal);
        broker.submitOrder(order);
        return order;
    }

    public Order exitShort(String symbol, long quantity) throws Exception {
        Order order = Order.exitShort(symbol, quantity);
        broker.submitOrder(order);
        return order;
    }

    public Order exitShort(String symbol, long quantity, String signal) throws Exception {
        Order order = Order.exitShort(symbol, quantity, signal);
        broker.submitOrder(order);
        return order;
    }

    public Order exitLong(String symbol) throws Exception {
        Order order = Order.exitLong(symbol, Order.POSITION_QUANTITY);
        broker.submitOrder(order);
        return order;
    }

    public Order exitLong(String symbol, String signal) throws Exception {
        Order order = Order.exitLong(symbol, Order.POSITION_QUANTITY, signal);
        broker.submitOrder(order);
        return order;
    }

    public Order exitLong(String symbol, long quantity) throws Exception {
        Order order = Order.exitLong(symbol, quantity);
        broker.submitOrder(order);
        return order;
    }

    public Order exitLong(String symbol, long quantity, String signal) throws Exception {
        Order order = Order.exitLong(symbol, quantity, signal);
        broker.submitOrder(order);
        return order;
    }

    public Order exitLongStop(String symbol, double stopPrice, long quantity) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.exitLongStop(symbol, quantity, stopPrice);
        broker.submitOrder(order);

        return order;
    }

    public Order exitLongStop(String symbol, double stopPrice, long quantity, String signal) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.exitLongStop(symbol, quantity, stopPrice, signal);
        broker.submitOrder(order);

        return order;
    }

    public Order exitLongStop(String symbol, double stopPrice, long quantity, String signal, int barsValidFor)
            throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.exitLongStop(symbol, quantity, stopPrice, signal);
        order.setExpiration(barsValidFor);
        broker.submitOrder(order);

        return order;
    }

    public Order exitShortStop(String symbol, double stopPrice, long quantity) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.exitShortStop(symbol, quantity, stopPrice);
        broker.submitOrder(order);

        return order;
    }

    public Order exitShortStop(String symbol, double stopPrice, long quantity, String signal) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.exitShortStop(symbol, quantity, stopPrice, signal);
        broker.submitOrder(order);

        return order;
    }

    public Order exitShortStop(String symbol, double stopPrice, long quantity, String signal, int barsValidFor)
            throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.exitShortStop(symbol, quantity, stopPrice, signal);
        order.setExpiration(barsValidFor);
        broker.submitOrder(order);

        return order;
    }

    public Order enterLongStopLimit(String symbol, double stopPrice, double limitPrice, long quantity,
            String signal, int barsValidFor) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterLongStopLimit(symbol, quantity, stopPrice, limitPrice, signal);
        order.setExpiration(barsValidFor);
        broker.submitOrder(order);

        return order;
    }

    public Order enterShortStopLimit(String symbol, double stopPrice, double limitPrice, long quantity,
            String signal, int barsValidFor) throws Exception {
        assert quantity > 0;
        assert broker != null;

        Order order = Order.enterShortStopLimit(symbol, quantity, stopPrice, limitPrice, signal);
        order.setExpiration(barsValidFor);
        broker.submitOrder(order);

        return order;
    }

    public void start() throws Exception {
        getBroker().start();
    }

    public void finalize() throws Exception {

    }

    public void setTradingStart(LocalDateTime ldt) {
        tradingStart = ldt;
    }

    public LocalDateTime getTradingStart() {
        return tradingStart;
    }

    public void setTradingStop(LocalDateTime ldt) {
        tradingStop = ldt;
    }

    public LocalDateTime getTradingStop() {
        return tradingStop;
    }

    public void setInitialEquity(LocalDateTime ldt, double initialEquity) {
        getAccount().setInitialEquity(ldt, initialEquity);
    }

    public void setInitialEquity(LocalDate ld, double initialEquity) {
        getAccount().setInitialEquity(ld.atStartOfDay(), initialEquity);
    }

    public void updateEndEquity() {
        getAccount().updateEndEquity();
    }
}