package org.marketcetera.photon.views; import java.math.BigDecimal; import java.util.EnumSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.eclipse.core.databinding.observable.Observables; import org.eclipse.core.databinding.observable.Realm; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.list.WritableList; import org.eclipse.core.databinding.observable.value.ComputedValue; import org.eclipse.core.databinding.observable.value.IValueChangeListener; import org.eclipse.core.databinding.observable.value.ValueChangeEvent; import org.marketcetera.algo.BrokerAlgo; import org.marketcetera.algo.BrokerAlgoSpec; import org.marketcetera.algo.BrokerAlgoTag; import org.marketcetera.algo.BrokerAlgoTagSpec; import org.marketcetera.core.ClassVersion; import org.marketcetera.photon.BrokerManager; import org.marketcetera.photon.BrokerManager.Broker; import org.marketcetera.photon.commons.databinding.ITypedObservableValue; import org.marketcetera.photon.commons.databinding.TypedObservableValueDecorator; import org.marketcetera.photon.ui.databinding.NewOrReplaceOrderObservable; import org.marketcetera.trade.*; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.ObjectArrays; /* $License$ */ /** * The abstract superclass for model objects that represent order tickets. * * @author gmiller * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @since 0.6.0 */ @ClassVersion("$Id: OrderTicketModel.java 16752 2013-11-14 02:54:13Z colin $") public abstract class OrderTicketModel { protected static final Object BLANK = new NullSentinel(""); //$NON-NLS-1$ private final NewOrReplaceOrderObservable mOrderObservable = new NewOrReplaceOrderObservable(); private final ITypedObservableValue<BrokerID> mBrokerId; private final ITypedObservableValue<Side> mSide; private final ITypedObservableValue<BigDecimal> mQuantity; private final ITypedObservableValue<BigDecimal> mDisplayQuantity; private final ITypedObservableValue<BigDecimal> mPrice; private final ITypedObservableValue<TimeInForce> mTimeInForce; private final ITypedObservableValue<String> mAccount; private final ITypedObservableValue<OrderType> mOrderType; private final ITypedObservableValue<Boolean> mIsLimitOrder; private final ITypedObservableValue<BrokerAlgo> mBrokerAlgo; private final WritableList mCustomFieldsList = new WritableList(); private final WritableList mAlgoTagsList = new WritableList(); private Broker selectedBroker; private BrokerAlgo selectedAlgo; private final IObservableList validAlgos = new WritableList(new SyncRealm(), Lists.newArrayList(BLANK), BrokerAlgo.class); private final IObservableList unmodifiableValidAlgos = Observables.unmodifiableObservableList(validAlgos); /** * Constructor. */ public OrderTicketModel() { mSide = mOrderObservable.observeSide(); mQuantity = mOrderObservable.observeQuantity(); mDisplayQuantity = mOrderObservable.observeDisplayQuantity(); mOrderType = mOrderObservable.observeOrderType(); mPrice = mOrderObservable.observePrice(); mTimeInForce = mOrderObservable.observeTimeInForce(); mAccount = mOrderObservable.observeAccount(); mBrokerId = mOrderObservable.observeBrokerId(); mBrokerAlgo = mOrderObservable.observeBrokerAlgo(); mIsLimitOrder = TypedObservableValueDecorator.decorate( new ComputedValue(Boolean.class) { @Override protected Object calculate() { return mOrderType.getValue() == OrderType.Limit; } }, true, Boolean.class); mIsLimitOrder.addValueChangeListener(new IValueChangeListener() { @Override public void handleValueChange(ValueChangeEvent event) { if (!mIsLimitOrder.getTypedValue()) { mPrice.setValue(null); } } }); // this listener watches the selection of the broker ID combo and updates the list of available broker algos mBrokerId.addValueChangeListener(new IValueChangeListener() { @Override public void handleValueChange(ValueChangeEvent inEvent) { if(getSelectedBroker() != null) { Set<BrokerAlgoSpec> algos = getSelectedBroker().getAlgos(); validAlgos.clear(); // add a blank value at the top that allows you to unselect an algo validAlgos.add(BLANK); if(algos != null) { for(BrokerAlgoSpec algo : algos) { Set<BrokerAlgoTag> algoTags = new TreeSet<BrokerAlgoTag>(); for(BrokerAlgoTagSpec tagSpec : algo.getAlgoTagSpecs()) { algoTags.add(new BrokerAlgoTag(tagSpec)); } validAlgos.add(new BrokerAlgo(algo, algoTags)); } } } } }); // this listener watches the selection of the algo and populates the algo tag table mBrokerAlgo.addValueChangeListener(new IValueChangeListener() { @Override public void handleValueChange(ValueChangeEvent inEvent) { updateAlgoTags(); } }); } /** * Populate algo table with appropriate label - value pairs */ public void updateAlgoTags() { // find selected algo and populate the algo table for(int i = 0; i < mAlgoTagsList.size(); i ++){ ((BrokerAlgoTag)mAlgoTagsList.get(i)).removePropertyChangeListener(null); } mAlgoTagsList.clear(); if(selectedAlgo != null && selectedAlgo.getAlgoTags() != null) { for(BrokerAlgoTag tag : selectedAlgo.getAlgoTags()) { mAlgoTagsList.add(tag); } } } /** * Get the selectedBroker value. * * @return a <code>Broker</code> value */ public Broker getSelectedBroker() { return selectedBroker; } /** * Sets the selectedBroker value. * * @param inSelectedBroker a <code>Broker</code> value */ public void setSelectedBroker(Broker inSelectedBroker) { selectedBroker = inSelectedBroker; } /** * Get the selectedAlgo value. * * @return a <code>BrokerAlgo</code> value */ public BrokerAlgo getSelectedAlgo() { return selectedAlgo; } /** * Sets the selectedAlgo value. * * @param inSelectedAlgo a <code>BrokerAlgo</code> value */ public void setSelectedAlgo(BrokerAlgo inSelectedAlgo) { selectedAlgo = inSelectedAlgo; } /** * Returns an observable that tracks the current order. * * @return the order observable */ public final NewOrReplaceOrderObservable getOrderObservable() { return mOrderObservable; } /** * Returns the broker of the ticket being edited. * * @return the broker observable */ public final ITypedObservableValue<BrokerID> getBrokerId() { return mBrokerId; } /** * Get the brokerAlgo value. * * @return an <code>ITypedObservableValue<BrokerAlgo></code> value */ public ITypedObservableValue<BrokerAlgo> getBrokerAlgo() { return mBrokerAlgo; } /** * Returns an observable that tracks the side of the current order. * * @return the side observable */ public final ITypedObservableValue<Side> getSide() { return mSide; } /** * Returns an observable that tracks the quantity of the current order. * * @return the quantity observable */ public final ITypedObservableValue<BigDecimal> getQuantity() { return mQuantity; } /** * Returns an observable that tracks the display quantity of the current order. * * @return the quantity observable */ public final ITypedObservableValue<BigDecimal> getDisplayQuantity() { return mDisplayQuantity; } /** * Returns an observable that tracks the symbol of the current order. * * @return the symbol observable */ public abstract ITypedObservableValue<String> getSymbol(); /** * Returns an observable that tracks the order type of the current order. * * @return the order type observable */ public final ITypedObservableValue<OrderType> getOrderType() { return mOrderType; } /** * Returns an observable that tracks whether the order is a limit order. * * @return the limit order type observable */ public final ITypedObservableValue<Boolean> isLimitOrder() { return mIsLimitOrder; } /** * Returns an observable that tracks the price of the current order. * * @return the price observable */ public final ITypedObservableValue<BigDecimal> getPrice() { return mPrice; } /** * Returns an observable that tracks the time in force of the current order. * * @return the time in force observable */ public final ITypedObservableValue<TimeInForce> getTimeInForce() { return mTimeInForce; } /** * Returns an observable that tracks the account of the current order. * * @return the account observable */ public final ITypedObservableValue<String> getAccount() { return mAccount; } /** * Clear the existing order message and replace it with a new empty one. */ public final void clearOrderMessage() { mOrderObservable.setValue(createNewOrder()); } /** * Creates a new empty order. * * @return the new order */ protected OrderSingle createNewOrder() { NewOrReplaceOrder currentOrder = getOrderObservable().getTypedValue(); OrderSingle order = Factory.getInstance().createOrderSingle(); order.setTimeInForce(org.marketcetera.trade.TimeInForce.Day); if (currentOrder != null) { // save broker selection order.setBrokerID(currentOrder.getBrokerID()); } return order; } /** * The list that should store a collection of {@link CustomField} objects. * These custom fields are presented to the user, and each can be activated * for inclusion into all messages generated by this order ticket. * * Modify this list directly to add and remove items. * * @return the list of custom fields */ public final WritableList getCustomFieldsList() { return mCustomFieldsList; } /** * * * * @return */ public final WritableList getAlgoTagsList() { return mAlgoTagsList; } /** * This method is responsible for "completing" the order message prior to * sending it. */ public void completeMessage() { addCustomFields(); } /** * Loops through the list of custom fields and adds the enabled fields to * the message. */ private void addCustomFields() { NewOrReplaceOrder order = mOrderObservable.getTypedValue(); Map<String, String> map = Maps.newHashMap(); for (Object customFieldObject : mCustomFieldsList) { CustomField customField = (CustomField) customFieldObject; if (customField.isEnabled()) { String key = customField.getKeyString(); String value = customField.getValueString(); map.put(key, value); } } if (!map.isEmpty()) { order.setCustomFields(map); } } /** * Get the valid values for the side. * * @return the valid sides */ public Object[] getValidSideValues() { return EnumSet.complementOf( EnumSet.of(Side.Unknown, Side.SellShortExempt)).toArray(); } /** * Get the valid values for the order type. * * @return the valid order types */ public Object[] getValidOrderTypeValues() { return EnumSet.complementOf(EnumSet.of(OrderType.Unknown)).toArray(); } /** * Get the valid values for the broker. * * @return the valid brokers */ public IObservableList getValidBrokers() { return BrokerManager.getCurrent().getAvailableBrokers(); } public IObservableList getValidAlgos() { return unmodifiableValidAlgos; } /** * Get the valid values for the time in force. * * @return the valid time in force values */ public Object[] getValidTimeInForceValues() { return ObjectArrays.concat(BLANK, EnumSet.complementOf( EnumSet.of(TimeInForce.Unknown)).toArray()); } /** * An object that can be used in place of null. It has a {@link #toString()} * value for display purposes, but it corresponds to a null model value. */ @ClassVersion("$Id: OrderTicketModel.java 16752 2013-11-14 02:54:13Z colin $") static class NullSentinel { private final String mString; /** * Constructor. * * @param string * the value for {@link #toString()} */ public NullSentinel(String string) { mString = string; } @Override public String toString() { return mString; } } @ClassVersion("$Id: OrderTicketModel.java 16752 2013-11-14 02:54:13Z colin $") private final class SyncRealm extends Realm { @Override public boolean isCurrent() { return true; } @Override protected void syncExec(Runnable runnable) { synchronized(OrderTicketModel.this) { super.syncExec(runnable); } } } }