add OHLC plugin

This commit is contained in:
2018-05-26 23:19:12 -07:00
parent 476f3708f4
commit 26b199a4f9
9 changed files with 734 additions and 31 deletions

View File

@ -0,0 +1,275 @@
package ats.plugin;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.core.service.EPStatementHandleCallback;
import com.espertech.esper.core.service.EngineLevelExtensionServicesContext;
import com.espertech.esper.epl.expression.core.ExprNode;
import com.espertech.esper.event.EventAdapterService;
import com.espertech.esper.schedule.ScheduleHandleCallback;
import com.espertech.esper.view.ViewSupport;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.joda.time.Period;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;
import com.espertech.esper.epl.expression.core.ExprEvaluator;
import com.espertech.esper.schedule.SchedulingService;
import org.joda.time.Duration;
import org.joda.time.format.ISODateTimeFormat;
import java.util.TimeZone;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;
import org.joda.time.PeriodType;
/**
* OHLCPlugInView computes OHLC bars for a given time interval.
*/
public class OHLCPlugInView extends ViewSupport {
private static final Logger log = LoggerFactory.getLogger(OHLCPlugInView.class);
private static final int LATE_EVENT_SLACK_SECONDS = 5;
private static final PeriodFormatter periodFormatter = new PeriodFormatterBuilder()
.appendDays().appendSuffix("d ")
.appendHours().appendSuffix("h ")
.appendMinutes().appendSuffix("m ")
.appendSeconds().appendSuffix("s")
.toFormatter();
private final AgentInstanceViewFactoryChainContext agentContext;
private final long scheduleSlot;
private EPStatementHandleCallback handle;
private final Duration interval;
private final ExprNode timestampExpression;
private final ExprNode valueExpression;
private DateTime windowStartTime;
private DateTime windowEndTime;
private Double open;
private Double close;
private Double high;
private Double low;
private EventBean[] lastData;
public OHLCPlugInView(AgentInstanceViewFactoryChainContext context,
ExprNode intervalExpression,
ExprNode timestampExpression,
ExprNode valueExpression)
{
agentContext = context;
scheduleSlot = context.getStatementContext().getScheduleBucket().allocateSlot();
interval = parseInterval(intervalExpression);
log.info("Interval is {}", interval);
this.timestampExpression = timestampExpression;
this.valueExpression = valueExpression;
}
/**
* Return the time period specified by the given expression value.
*/
private Duration parseInterval(ExprNode interval) {
ExprEvaluator evaluator = interval.getForge().getExprEvaluator();
String intervalStr = (String)evaluator.evaluate(null, true, agentContext);
return periodFormatter.parsePeriod(intervalStr).toStandardDuration();
}
/**
* Return the timestamp for the event.
*/
private DateTime getTimestamp(EventBean event) {
ExprEvaluator evaluator = timestampExpression.getForge().getExprEvaluator();
//Long l = (Long)evaluator.evaluate(new EventBean[] {event}, true, agentContext);
return (DateTime)evaluator.evaluate(new EventBean[] {event}, true, agentContext);
//return toDateTime(l);
}
/**
* Convert a bare long value to a proper DateTime entity. Assumes
* UTC time zone.
*/
public static DateTime toDateTime(long l) {
return new DateTime(l, DateTimeZone.UTC);
}
/**
* Return the value for the event.
*/
private double getValue(EventBean event) {
ExprEvaluator evaluator = valueExpression.getForge().getExprEvaluator();
return (double)evaluator.evaluate(new EventBean[] {event}, true, agentContext);
}
/**
* Notify that data has been added or removed from the Viewable parent.
*/
@Override
public void update(EventBean[] newData, EventBean[] oldData) {
if (newData == null) return;
for (EventBean event : newData) {
DateTime timestamp = getTimestamp(event);
double value = getValue(event);
ensureWindow(timestamp);
applyValue(value);
}
}
/**
* Make sure our window times are set up and current.
*/
private void ensureWindow(DateTime timestamp) {
if (timestamp == null) return;
if (windowStartTime == null) {
// create open window
windowStartTime = makeWindowStartTime(timestamp, interval);
windowEndTime = makeWindowEndTime(windowStartTime, interval);
}
if (!inWindow(timestamp)) {
// past current window.
// post and create a new one.
postData();
scheduleCallback();
windowStartTime = makeWindowStartTime(timestamp, interval);
windowEndTime = makeWindowEndTime(windowStartTime, interval);
}
}
public static DateTime makeWindowStartTime(DateTime timestamp,
Duration interval)
{
DateTime today = timestamp.withTimeAtStartOfDay();
// log.info("Timestamp is {}", timestamp);
// log.info("Day start is {}", today);
Duration intoToday = new Duration(today, timestamp);
// then modulo timestamp to find how far into the current window we'd be in
long intoPeriod = intoToday.getMillis() % interval.getMillis();
// then subtract modulo from current time to get window start
return timestamp.minus(intoPeriod);
}
private static DateTime makeWindowEndTime(DateTime startTime, Duration interval) {
if (startTime == null || interval == null) return null;
return startTime.plus(interval.getMillis());
}
/**
* Return true if the timestamp is within the current time window.
*/
private boolean inWindow(DateTime timestamp) {
if (timestamp == null) return false;
return timestamp.compareTo(windowStartTime) >= 0 &&
timestamp.compareTo(windowEndTime) < 0;
}
private void applyValue(double value) {
if (open == null) {
open = value;
}
close = value;
if (low == null) {
low = value;
} else if (low.compareTo(value) > 0) {
low = value;
}
if (high == null) {
high = value;
} else if (high.compareTo(value) < 0) {
high = value;
}
}
/**
* Set up a callback to post an event when our time window expires.
*/
private void scheduleCallback() {
SchedulingService sched = agentContext.getStatementContext().getSchedulingService();
if (handle != null) {
// remove old schedule
sched.remove(handle, scheduleSlot);
handle = null;
}
DateTime currentTime = toDateTime(sched.getTime());
DateTime targetTime = windowEndTime.plusSeconds(LATE_EVENT_SLACK_SECONDS);
long callbackTime = targetTime.getMillis() - currentTime.getMillis();
ScheduleHandleCallback callback = new ScheduleHandleCallback() {
public void scheduledTrigger(EngineLevelExtensionServicesContext esc) {
handle = null; // clear out schedule handle
OHLCPlugInView.this.postData();
}
};
handle = new EPStatementHandleCallback(agentContext.getEpStatementAgentInstanceHandle(), callback);
sched.add(callbackTime, handle, scheduleSlot);
}
/**
* Update listeners with our new value.
*/
private void postData() {
if (open == null) return;
OHLCValue value = new OHLCValue(windowStartTime, open, high, low, close);
// send
EventAdapterService service = agentContext.getStatementContext().getEventAdapterService();
EventBean[] newBeans = new EventBean[] {service.adapterForBean(value)};
updateChildren(newBeans, lastData);
// reset for next post
lastData = newBeans;
open = null;
close = null;
high = null;
low = null;
}
//
// for the EventCollection interface
//
/**
* Provides metadata information about the type of object the
* event collection contains.
*/
@Override
public EventType getEventType() {
return getEventType(agentContext.getStatementContext().getEventAdapterService());
}
protected static EventType getEventType(EventAdapterService service)
{
return service.addBeanType(OHLCValue.class.getName(),
OHLCValue.class,
false, false, false);
}
/**
* (Don't) allow iteration through all elements in this event collection.
*/
@Override
public Iterator<EventBean> iterator() {
throw new UnsupportedOperationException("Can't iterate.");
}
}

View File

@ -0,0 +1,106 @@
package ats.plugin;
import com.espertech.esper.client.EventType;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.epl.expression.core.ExprNode;
import com.espertech.esper.event.EventAdapterService;
import com.espertech.esper.view.View;
import com.espertech.esper.view.ViewFactory;
import com.espertech.esper.view.ViewFactoryContext;
import com.espertech.esper.view.ViewFactorySupport;
import com.espertech.esper.view.ViewParameterException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.joda.time.DateTime;
/**
* OHLCPlugInViewFactory manages creation of OHLC plugin views.
*/
public class OHLCPlugInViewFactory extends ViewFactorySupport {
private EventAdapterService eventAdapterService;
private List<ExprNode> params;
private ExprNode interval;
private ExprNode timestamp;
private ExprNode value;
/**
* Pass in EPL query view params.
*/
public void setViewParameters(ViewFactoryContext context,
List<ExprNode> params)
throws ViewParameterException
{
eventAdapterService = context.getEventAdapterService();
if (params.size() != 3) {
throw new ViewParameterException("OHLC view takes three parameters: time interval, timestamp expression, and mid price expression.");
}
this.params = params;
}
/**
* Attach the factory to a parent event type such that the
* factory can validate attach requirements and determine an event
* type for resulting views.
*/
public void attach(EventType parentEventType,
StatementContext statementContext,
ViewFactory optionalParentFactory,
List<ViewFactory> parentViewFactories)
throws ViewParameterException
{
ExprNode[] validatedNodes = ViewFactorySupport.validate(getViewName(),
parentEventType,
statementContext,
params, true);
interval = validatedNodes[0];
timestamp = validatedNodes[1];
value = validatedNodes[2];
Class intervalClass = interval.getForge().getEvaluationType();
if ((intervalClass != String.class))
{
throw new ViewParameterException("OHLC view needs String-typed interval value for parameter 1.");
}
Class timestampClass = timestamp.getForge().getEvaluationType();
if ((timestampClass != DateTime.class))
{
throw new ViewParameterException("OHLC view needs DateTime typed timestamp values for parameter 2");
}
Class valueClass = value.getForge().getEvaluationType();
if ((valueClass != double.class) &&
(valueClass != Double.class) &&
(valueClass != BigDecimal.class))
{
throw new ViewParameterException("OHLC view needs double or BigDecimal values for parameter 3");
}
}
/**
* Create a new view using already-passed context and params.
*/
public View makeView(AgentInstanceViewFactoryChainContext agentContext) {
return new OHLCPlugInView(agentContext, interval, timestamp, value);
}
/**
* Returns the event type that the view that is created by the
* view factory would create for events posted by the view.
*/
public EventType getEventType() {
return OHLCPlugInView.getEventType(eventAdapterService);
}
/**
* Return the pretty name of our view type.
*/
public String getViewName() {
return OHLCPlugInView.class.getSimpleName();
}
}

View File

@ -0,0 +1,47 @@
/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package ats.plugin;
import java.util.Date;
public class OHLCTick {
private String ticker;
private double price;
private long timestamp;
public OHLCTick(String ticker, double price, long timestamp) {
this.ticker = ticker;
this.price = price;
this.timestamp = timestamp;
}
public String getTicker() {
return ticker;
}
public double getPrice() {
return price;
}
public long getTimestamp() {
return timestamp;
}
public String toString() {
return "ticker " + ticker +
" price " + price +
" timestamp " + printTime(timestamp);
}
private String printTime(long timestamp) {
return new Date(timestamp).toString();
}
}

View File

@ -0,0 +1,50 @@
/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package ats.plugin;
import com.espertech.esper.client.EPServiceProvider;
import com.espertech.esper.client.EPStatement;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.StatementAwareUpdateListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
public class OHLCUpdateListener implements StatementAwareUpdateListener {
private final static Logger log = LoggerFactory.getLogger(OHLCUpdateListener.class);
public void update(EventBean[] newData, EventBean[] oldData, EPStatement epStatement, EPServiceProvider epServiceProvider) {
for (int i = 0; i < newData.length; i++) {
//if (log.isInfoEnabled()) {
log.info("Statement " + String.format("%s", epStatement.getName()) + " produced: " + getProperties(newData[i]));
//}
}
}
private String getProperties(EventBean theEvent) {
StringBuilder buf = new StringBuilder();
for (String name : theEvent.getEventType().getPropertyNames()) {
Object value = theEvent.get(name);
buf.append(name);
buf.append("=");
if (name.contains("timestamp")) {
buf.append(new Date((Long) value));
} else {
buf.append(value);
}
buf.append(" ");
}
return buf.toString();
}
}

View File

@ -0,0 +1,54 @@
package ats.plugin;
import org.joda.time.DateTime;
/**
* OHLCValue stores one bar of OHLC info.
*/
public class OHLCValue {
private DateTime time;
private double open;
private double high;
private double low;
private double close;
public OHLCValue(DateTime time,
double open, double high,
double low, double close)
{
this.time = time;
this.open = open;
this.high = high;
this.low = low;
this.close = close;
}
public DateTime getTime() {
return time;
}
public Double getOpen() {
return open;
}
public Double getHigh() {
return high;
}
public Double getLow() {
return low;
}
public Double getClose() {
return close;
}
/**
* Return a human readable representation of this event.
*/
public String toString() {
return String.format("OHLCValue[%s,open=%s,high=%s,low=%s,close=%s]",
time, open, high, low, close);
}
}