/*
* Author: Balch
* Created: 9/6/14 10:05 AM
*
* This file is part of MockTrade.
*
* MockTrade is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MockTrade is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MockTrade. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2014
*/
package com.balch.mocktrade.order;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.text.format.DateUtils;
import com.balch.android.app.framework.types.Money;
import com.balch.mocktrade.finance.FinanceModel;
import com.balch.mocktrade.finance.Quote;
import com.balch.mocktrade.investment.Investment;
import com.balch.mocktrade.receivers.OrderReceiver;
import com.balch.mocktrade.settings.Settings;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.Date;
/**
* This is package-private on purpose!!! It is intended to contain
* shared functionality between OrderModel implementations
*/
class OrderManager {
private static final String TAG = OrderManager.class.getSimpleName();
public interface OrderManagerListener {
OrderResult executeOrder(Order order, Quote quote, Money price) throws SQLException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException;
Investment getInvestmentBySymbol(String symbol, Long accountId);
boolean updateOrder(Order order) throws IllegalAccessException;
}
private final FinanceModel mFinanceModel;
private final Context mContext;
private final Settings mSettings;
private final OrderManagerListener mOrderManagerListener;
public OrderManager(Context context, FinanceModel financeModel, Settings settings,
OrderManagerListener listener) {
this.mSettings = settings;
this.mContext = context.getApplicationContext();
this.mFinanceModel = financeModel;
this.mOrderManagerListener = listener;
}
public void scheduleOrderServiceAlarm(boolean isMarketOpen){
AlarmManager alarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
Intent intent = OrderReceiver.getIntent(mContext);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Date startTime = isMarketOpen ?
new Date(System.currentTimeMillis() + mSettings.getPollOrderInterval()*1000) :
this.mFinanceModel.nextMarketOpen();
alarmManager.set(AlarmManager.RTC_WAKEUP,
startTime.getTime(),
pendingIntent);
}
public OrderResult attemptExecuteOrder(Order order, Quote quote) throws InvocationTargetException, SQLException, InstantiationException, IllegalAccessException, NoSuchMethodException {
if (order == null) {
throw new IllegalArgumentException("Order not found");
}
if (quote == null) {
throw new IllegalArgumentException("Quote not found");
}
OrderResult result;
switch (order.getStrategy()) {
case MARKET:
result = executeMarketOrder(order, quote);
break;
case MANUAL:
result = this.mOrderManagerListener.executeOrder(order, quote, order.getLimitPrice());
break;
case LIMIT:
result = executeLimitOrder(order, quote);
break;
case STOP_LOSS:
result = executeStopLossOrder(order, quote);
break;
case TRAILING_STOP_AMOUNT_CHANGE:
case TRAILING_STOP_PERCENT_CHANGE:
result = executeTrailingStopLossOrder(order, quote);
break;
default:
throw new UnsupportedOperationException();
}
return result;
}
boolean isQuoteValid(Quote quote) {
Date tradeDate = quote.getLastTradeTime();
return (mFinanceModel.isMarketOpen() && isToday(tradeDate));
}
private boolean isToday(Date date) {
return DateUtils.isToday(date.getTime());
}
private OrderResult executeLimitOrder(Order order, Quote quote) throws InvocationTargetException, SQLException, InstantiationException, IllegalAccessException, NoSuchMethodException {
OrderResult orderResult = new OrderResult(false, null, null, null, 0);
if (this.isQuoteValid(quote)) {
int compareQuoteToLimit = quote.getPrice().compareTo(order.getLimitPrice());
if ( ((order.getAction() == Order.OrderAction.BUY) && (compareQuoteToLimit <= 0)) ||
((order.getAction() == Order.OrderAction.SELL) && (compareQuoteToLimit >= 0))) {
orderResult = this.mOrderManagerListener.executeOrder(order, quote, quote.getPrice());
}
}
return orderResult;
}
private OrderResult executeTrailingStopLossOrder(Order order, Quote quote) throws InvocationTargetException, SQLException, InstantiationException, IllegalAccessException, NoSuchMethodException {
if (order.getAction() == Order.OrderAction.BUY) {
throw new UnsupportedOperationException("Cannot have a Stop Loss order if the action is BUY");
}
boolean highestPriceChanged = false;
if (order.getHighestPrice().getDollars() == 0.0) {
Investment investment = mOrderManagerListener.getInvestmentBySymbol(order.getSymbol(), order.getAccount().getId());
if (investment == null) {
throw new IllegalArgumentException("Can't sell and investment you don't own");
}
order.setHighestPrice(investment.getPrice());
highestPriceChanged = true;
}
OrderResult orderResult = new OrderResult(false, null, null, null, 0);
if (this.isQuoteValid(quote)) {
if (quote.getPrice().compareTo(order.getHighestPrice()) > 0) {
order.setHighestPrice(quote.getPrice());
highestPriceChanged = true;
} else {
Money delta = Money.subtract(order.getHighestPrice(), quote.getPrice());
boolean executeOrder;
if (order.getStrategy() == Order.OrderStrategy.TRAILING_STOP_AMOUNT_CHANGE) {
executeOrder = (delta.compareTo(order.getStopPrice()) >= 0);
} else if (order.getStrategy() == Order.OrderStrategy.TRAILING_STOP_PERCENT_CHANGE) {
double percent = delta.getDollars() * 100f / order.getHighestPrice().getDollars();
executeOrder = percent >= order.getStopPercent();
} else {
throw new IllegalArgumentException("Invalid Order Strategy: " + order.getStrategy());
}
if (executeOrder) {
orderResult = this.mOrderManagerListener.executeOrder(order, quote, quote.getPrice());
}
}
}
if (highestPriceChanged) {
if (!mOrderManagerListener.updateOrder(order)) {
throw new IllegalArgumentException("Error updating order");
}
}
return orderResult;
}
private OrderResult executeStopLossOrder(Order order, Quote quote) throws InvocationTargetException, SQLException, InstantiationException, IllegalAccessException, NoSuchMethodException {
if (order.getAction() == Order.OrderAction.BUY) {
throw new UnsupportedOperationException("Cannot have a Stop Loss order if the action is BUY");
}
OrderResult orderResult = new OrderResult(false, null, null, null, 0);
if (this.isQuoteValid(quote)) {
int compareQuoteToLimit = quote.getPrice().compareTo(order.getLimitPrice());
if (compareQuoteToLimit <= 0) {
orderResult = this.mOrderManagerListener.executeOrder(order, quote, quote.getPrice());
}
}
return orderResult;
}
private OrderResult executeMarketOrder(Order order, Quote quote) throws InvocationTargetException, SQLException, InstantiationException, IllegalAccessException, NoSuchMethodException {
OrderResult orderResult = new OrderResult(false, null, null, null, 0);
if (this.isQuoteValid(quote)) {
orderResult = this.mOrderManagerListener.executeOrder(order, quote, quote.getPrice());
}
return orderResult;
}
}