Package com.quantcomponents.algo.ta

Source Code of com.quantcomponents.algo.ta.AverageCrossingTradingAgent

/*******************************************************************************
* 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.algo.ta;

import java.util.Date;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.quantcomponents.algo.IOrderReceiver;
import com.quantcomponents.algo.IPosition;
import com.quantcomponents.algo.ITradingAgent;
import com.quantcomponents.algo.OrderBean;
import com.quantcomponents.core.calendar.CalendarTradingSchedule;
import com.quantcomponents.core.calendar.ITradingCalendar;
import com.quantcomponents.core.calendar.ITradingSchedule;
import com.quantcomponents.core.model.IContract;
import com.quantcomponents.core.model.ISeries;
import com.quantcomponents.core.model.ISeriesAugmentable;
import com.quantcomponents.core.model.ISeriesListener;
import com.quantcomponents.core.model.ISeriesPoint;
import com.quantcomponents.core.model.OrderSide;
import com.quantcomponents.core.model.OrderType;
import com.quantcomponents.marketdata.IOHLCPoint;
import com.quantcomponents.marketdata.IOHLCTimeSeries;
import com.quantcomponents.marketdata.TimeSeriesTail;

/**
* A trivial example of a trend following trading strategy.
* It starts folling the trend based on the crossing of a short moving average above or below a long moving average of the prices.
* When the short moving average crosses above the long one, it takes a long position, and vice-versa.
* Number of periods for short and long moving averages, trading calendar to be used, position size (short or long) are configurable.
* Also among the configuration parameters there is: <emph>ignoreLastPeriod</emph>: this parameter should be set when trading in real
* time, and not set when backtesting. In fact when trading in real time, only the completed periods must enter into the algorithm.
* When backtesting, all the periods must be taken into account, since otherwise the last of the series will not enter the test.
* <br>
* <b>NOTE</b>This is only an example to show how to write trading algorithms with this framework: it has been proved consistenly
* loss-making!
*
*/
public class AverageCrossingTradingAgent implements ITradingAgent, ISeriesListener<Date, Double> {
  private static final Logger logger = Logger.getLogger(AverageCrossingTradingAgent.class.getName());
  public static final String TRADING_CALENDAR_NAME = "tradingCalendarName";
  public static final String SHORT_AVERAGE_PERIODS = "shortAveragePeriods";
  public static final String LONG_AVERAGE_PERIODS = "longAveragePeriods";
  public static final String POSITION_SIZE = "positionSize";
  public static final String IGNORE_LAST_PERIOD = "ignoreLastPeriod";
  // configuration values
  private final int positionSize;
  private final boolean ignoreLastPeriod;
  // tail operators
  private final TimeSeriesTail<IOHLCPoint> shortAveragingTail;
  private final TimeSeriesTail<IOHLCPoint> longAveragingTail;
  // input/output
  private volatile IOrderReceiver orderReceiver;
  private volatile IOHLCTimeSeries stockTimeSeries;
  private volatile ISeriesAugmentable<Date, Double, ISeriesPoint<Date, Double>> outputSeries;
  // status
  private volatile RunningStatus runningStatus = RunningStatus.NEW;
  private volatile int position; 
 
  public AverageCrossingTradingAgent(ITradingCalendar tradingCalendar, int shortAveragePeriods, int longAveragePeriods, int positionSize,
      boolean ignoreLastPeriod) {
    this.positionSize = positionSize;
    this.ignoreLastPeriod = ignoreLastPeriod;
    if (ignoreLastPeriod) {
      shortAveragePeriods++;
      longAveragePeriods++;
    }
    ITradingSchedule tradingSchedule = new CalendarTradingSchedule(tradingCalendar);
    shortAveragingTail = new TimeSeriesTail<IOHLCPoint>(tradingSchedule, shortAveragePeriods);
    longAveragingTail = new TimeSeriesTail<IOHLCPoint>(tradingSchedule, longAveragePeriods);
  }

  @Override
  public void wire(Map<String, ? extends ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>>> inputSeries, ISeriesAugmentable<Date, Double, ISeriesPoint<Date, Double>> outputSeries) {
    this.outputSeries = outputSeries;
    ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>> series = inputSeries.get(AverageCrossingTradingAgentFactory.INPUT_SERIES_NAMES[0]);
    if (!(series instanceof IOHLCTimeSeries)) {
      throw new IllegalArgumentException("Only '" + IOHLCTimeSeries.class.getName() + "' instances can be passed as input series");
    }
    stockTimeSeries = (IOHLCTimeSeries) series;
  }

  @Override
  public void unwire() {
    kill();
  }
 
  @Override
  public void setOrderReceiver(IOrderReceiver orderReceiver) {
    this.orderReceiver = orderReceiver;
  }

  @Override
  public void pause() {
    synchronized (runningStatus) {
      if (runningStatus == RunningStatus.RUNNING) {
        runningStatus = RunningStatus.PAUSED;
      }
    }
  }

  @Override
  public void resume() {
    synchronized (runningStatus) {
      if (runningStatus == RunningStatus.PAUSED) {
        runningStatus = RunningStatus.RUNNING;
      }
    }
  }

  @Override
  public RunningStatus getRunningStatus() {
    return runningStatus;
  }

  @Override
  public void inputComplete() {
    kill();
  }

  @Override
  public void run() {
    synchronized (runningStatus) {
      if (runningStatus != RunningStatus.NEW) {
        throw new IllegalStateException("Could not run from running status: " + runningStatus.name());
      }
      runningStatus = RunningStatus.RUNNING;
    }
    stockTimeSeries.addSeriesListener(this);
    try {
      while (!Thread.interrupted() && runningStatus != RunningStatus.TERMINATED) {
        updatePosition();
        synchronized(this) {
          wait();
        }
      }
    } catch (InterruptedException e) {
      logger.log(Level.WARNING, "Interrupted. Exit", e);
    } finally {
      synchronized (runningStatus) {
        runningStatus = RunningStatus.TERMINATED;
      }
    }
    stockTimeSeries.removeSeriesListener(this);
  }

  @Override
  public synchronized void kill() {
    synchronized (runningStatus) {
      runningStatus = RunningStatus.TERMINATED;
    }
    notify();
  }

  @Override
  public void onItemUpdated(ISeriesPoint<Date, Double> existingItem, ISeriesPoint<Date, Double> updatedItem) {
    if (runningStatus == RunningStatus.RUNNING) {
      updatePosition();
    }
  }

  @Override
  public void onItemAdded(ISeriesPoint<Date, Double> newItem) {
    if (runningStatus == RunningStatus.RUNNING) {
      if (outputSeries != null) {
        outputSeries.insertFromTail(newItem);
      }
      updatePosition();
    }
  }

  @Override
  public void onOrderSubmitted(String orderId, boolean active) { }

  @Override
  public void onOrderFilled(String orderId, int filled, boolean full, double averagePrice) { }

  @Override
  public void onOrderCancelled(String orderId) { }

  @Override
  public void onOrderStatus(String orderId, String status) { }

  @Override
  public void onPositionUpdate(IContract contract, IPosition position) { }

  private double calculateMovingAverage(ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>> timeSeries, boolean ignoreLastPeriod) {
    double result;
    int actualNumberOfPeriods = timeSeries.size();
    if (ignoreLastPeriod) {
      actualNumberOfPeriods--;
    }
    double totalSum = 0.0;
    int currentPeriod = 0;
    for (ISeriesPoint<Date, Double> point : timeSeries) {
      if (currentPeriod >= actualNumberOfPeriods) {
        break;
      }
      IOHLCPoint bar = (IOHLCPoint) point;
      totalSum += bar.getClose();
      currentPeriod++;
    }
    result = totalSum / currentPeriod;
    return result; // it must never be called with 0 periods
  }
 
  private void updatePosition() {
    if (stockTimeSeries != null && orderReceiver != null) {
      if (stockTimeSeries.size() < 2) {
      if (stockTimeSeries.size() < 1 || ignoreLastPeriod) {
        return;
      }
    }
    ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>> shortTail = (ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>>) shortAveragingTail.transform(stockTimeSeries);
    ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>> longTail = (ISeries<Date, Double, ? extends ISeriesPoint<Date, Double>>) longAveragingTail.transform(stockTimeSeries);
    double shortAverageResult = calculateMovingAverage(shortTail, ignoreLastPeriod);
    double longAverageResult = calculateMovingAverage(longTail, ignoreLastPeriod);
    int deltaPosition = 0;
    if (shortAverageResult < longAverageResult) {
      deltaPosition = -positionSize - position;
    } else if (shortAverageResult > longAverageResult) {
      deltaPosition = positionSize - position;
    }
    if (deltaPosition != 0) {
      try {
        OrderBean order = new OrderBean(stockTimeSeries.getContract(), deltaPosition > 0 ? OrderSide.BUY : OrderSide.SELL, OrderType.MARKET, Math.abs(deltaPosition), 0.0, 0.0);
          String orderId = orderReceiver.sendOrder(order);
          order.setId(orderId);
          position += deltaPosition;
        } catch (Exception e) {
          logger.log(Level.SEVERE, "Error while adding delta position: " + deltaPosition, e);
        }
      }
    }
  }
}
TOP

Related Classes of com.quantcomponents.algo.ta.AverageCrossingTradingAgent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.