/*
* Copyright (c) 2004-2011 Marco Maccaferri and others.
* 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:
* Marco Maccaferri - initial API and implementation
*/
package org.eclipsetrader.core.ats.engines;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import org.eclipsetrader.core.ats.IScriptStrategy;
import org.eclipsetrader.core.ats.ITradingSystem;
import org.eclipsetrader.core.ats.ITradingSystemContext;
import org.eclipsetrader.core.ats.ITradingSystemInstrument;
import org.eclipsetrader.core.charts.IDataSeries;
import org.eclipsetrader.core.feed.IBar;
import org.eclipsetrader.core.feed.IBarOpen;
import org.eclipsetrader.core.feed.IPricingEnvironment;
import org.eclipsetrader.core.feed.IPricingListener;
import org.eclipsetrader.core.feed.IQuote;
import org.eclipsetrader.core.feed.ITrade;
import org.eclipsetrader.core.feed.PricingDelta;
import org.eclipsetrader.core.feed.PricingEvent;
import org.eclipsetrader.core.instruments.ISecurity;
import org.eclipsetrader.core.trading.IAccount;
import org.eclipsetrader.core.trading.IBroker;
import org.eclipsetrader.core.trading.IPosition;
import org.eclipsetrader.core.trading.IPositionListener;
import org.eclipsetrader.core.trading.PositionEvent;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.ScriptableObject;
public class JavaScriptEngine extends Observable {
public static final String PROPERTY_INSTRUMENTS = "instruments"; //$NON-NLS-1$
public static final String PROPERTY_POSITIONS = "positions"; //$NON-NLS-1$
public static final String PROPERTY_MARKET = "market"; //$NON-NLS-1$
public static final String PROPERTY_BARSIZE = "barSize"; //$NON-NLS-1$
private final ITradingSystem tradingSystem;
private final ITradingSystemContext context;
private final IScriptStrategy strategy;
private final IPricingEnvironment pricingEnvironment;
private final IAccount account;
private final IBroker broker;
private ScriptableObject scope;
private Map<String, ISecurity> instrumentsMap = new HashMap<String, ISecurity>();
private Map<Object, IPosition> positionsMap = new HashMap<Object, IPosition>();
private Map<ISecurity, JavaScriptEngineInstrument> contextsMap = new HashMap<ISecurity, JavaScriptEngineInstrument>();
public IPricingListener pricingListener = new IPricingListener() {
@Override
public void pricingUpdate(PricingEvent event) {
doPricingUpdate(event);
}
};
private IPositionListener positionListener = new IPositionListener() {
@Override
public void positionOpened(PositionEvent e) {
JavaScriptEngineInstrument context = contextsMap.get(e.position.getSecurity());
if (context == null) {
return;
}
context.onPositionOpen(e.position);
updatePositionsMap();
setChanged();
notifyObservers(new EngineEvent(e.position.getSecurity(), e.position));
}
@Override
public void positionClosed(PositionEvent e) {
JavaScriptEngineInstrument context = contextsMap.get(e.position.getSecurity());
if (context == null) {
return;
}
context.onPositionClosed(e.position);
updatePositionsMap();
setChanged();
notifyObservers(new EngineEvent(e.position.getSecurity(), e.position));
}
@Override
public void positionChanged(PositionEvent e) {
JavaScriptEngineInstrument context = contextsMap.get(e.position.getSecurity());
if (context == null) {
return;
}
context.onPositionChange(e.position);
updatePositionsMap();
setChanged();
notifyObservers(new EngineEvent(e.position.getSecurity(), e.position));
}
};
public JavaScriptEngine(ITradingSystem tradingSystem, ITradingSystemContext context) {
this.tradingSystem = tradingSystem;
this.context = context;
this.strategy = (IScriptStrategy) tradingSystem.getStrategy();
this.pricingEnvironment = context.getPricingEnvironment();
this.account = context.getAccount();
this.broker = context.getBroker();
}
public void start() throws Exception {
Context cx = Context.enter();
try {
cx.setWrapFactory(new EnhancedWrapFactory());
scope = new ImporterTopLevel(cx);
scope.putConst("Buy", scope, BaseOrderFunction.Buy);
scope.putConst("Sell", scope, BaseOrderFunction.Sell);
scope.putConst("Limit", scope, BaseOrderFunction.Limit);
scope.putConst("Market", scope, BaseOrderFunction.Market);
scope.putConst("Above", scope, IDataSeries.ABOVE);
scope.putConst("Below", scope, IDataSeries.BELOW);
scope.putConst("None", scope, IDataSeries.NONE);
ScriptableObject.putProperty(scope, BaseOrderFunction.PROPERTY_ACCOUNT, Context.javaToJS(account, scope));
ScriptableObject.putProperty(scope, BaseOrderFunction.PROPERTY_BROKER, Context.javaToJS(broker, scope));
ScriptableObject.putProperty(scope, PROPERTY_INSTRUMENTS, instrumentsMap);
ScriptableObject.putProperty(scope, PROPERTY_POSITIONS, positionsMap);
ScriptableObject.putProperty(scope, PROPERTY_BARSIZE, tradingSystem.getStrategy().getBarsTimeSpan());
ScriptableObject.putProperty(scope, "out", Context.javaToJS(System.out, scope));
updateInstrumentsMap();
updatePositionsMap();
int backfillSize = context.getInitialBackfillSize();
for (ITradingSystemInstrument instrument : tradingSystem.getInstruments()) {
ISecurity security = instrument.getInstrument();
JavaScriptEngineInstrument engineInstrument = new JavaScriptEngineInstrument(scope, security, strategy);
if (backfillSize != 0) {
engineInstrument.backfill(backfillSize);
}
engineInstrument.setPosition(positionsMap.get(security));
engineInstrument.onStrategyStart();
contextsMap.put(instrument.getInstrument(), engineInstrument);
}
account.addPositionListener(positionListener);
pricingEnvironment.addPricingListener(pricingListener);
} finally {
Context.exit();
}
}
public void stop() {
contextsMap.clear();
account.removePositionListener(positionListener);
pricingEnvironment.removePricingListener(pricingListener);
scope = null;
}
public void dispose() {
deleteObservers();
}
void doPricingUpdate(PricingEvent event) {
JavaScriptEngineInstrument instrument = contextsMap.get(event.getSecurity());
if (instrument == null) {
return;
}
for (PricingDelta delta : event.getDelta()) {
Object value = delta.getNewValue();
if (value instanceof IQuote) {
instrument.onQuote((IQuote) value);
setChanged();
notifyObservers(new EngineEvent(event.getSecurity(), value));
}
else if (value instanceof ITrade) {
instrument.onTrade((ITrade) value);
setChanged();
notifyObservers(new EngineEvent(event.getSecurity(), value));
}
else if (value instanceof IBarOpen) {
instrument.onBarOpen((IBarOpen) value);
setChanged();
notifyObservers(new EngineEvent(event.getSecurity(), value));
}
else if (value instanceof IBar) {
instrument.onBar((IBar) value);
setChanged();
notifyObservers(new EngineEvent(event.getSecurity(), instrument.getBars()));
}
}
}
private void updateInstrumentsMap() {
instrumentsMap.clear();
for (ISecurity security : strategy.getInstruments()) {
if (security.getIdentifier() != null) {
instrumentsMap.put(security.getIdentifier().getSymbol(), security);
}
}
}
void updatePositionsMap() {
positionsMap.clear();
for (IPosition position : account.getPositions()) {
ISecurity security = position.getSecurity();
if (security.getIdentifier() != null) {
positionsMap.put(security.getIdentifier().getSymbol(), position);
positionsMap.put(security, position);
}
}
}
public Object get(String name) {
return scope.get(name, scope);
}
public ScriptableObject getScope() {
return scope;
}
public JavaScriptEngineInstrument getContextFor(ISecurity security) {
return contextsMap.get(security);
}
}