Merge branch 'candlestick_calcs'
This commit is contained in:
@ -9,19 +9,25 @@
|
|||||||
|
|
||||||
-- The time the trading logic will begin to enter trades.
|
-- The time the trading logic will begin to enter trades.
|
||||||
-- Exiting trades is 24/7.
|
-- Exiting trades is 24/7.
|
||||||
create constant variable int StartTimeHour = 9
|
create constant variable DateTime TradingStartTime = EPLHelpers.parseTime("00:00")
|
||||||
|
|
||||||
-- The time the trading logic will begin to enter trades.
|
-- The time the trading logic will no longer enter trades.
|
||||||
-- Exiting trades is 24/7.
|
-- Exiting trades is 24/7.
|
||||||
create constant variable int EndTimeHour = 17
|
create constant variable DateTime TradingEndTime = EPLHelpers.parseTime("23:59")
|
||||||
|
|
||||||
-- The time frame for OHLC calculation.
|
-- The time frame for OHLC calculation.
|
||||||
-- Example values: '5s', '1m', '1h 30m', '2h', '12h', '1d'.
|
-- Example values: '5s', '1m', '1h 30m', '2h', '12h', '1d'.
|
||||||
create constant variable string OHLCInterval = '10s'
|
create constant variable string OHLCInterval = '10s'
|
||||||
|
|
||||||
|
-- The number of ticks for OHLC TB calculation.
|
||||||
|
create constant variable int OHLCTicks = 100
|
||||||
|
|
||||||
-- Amount to be traded, measured in units.
|
-- Amount to be traded, measured in units.
|
||||||
create constant variable int TradeSize = 10
|
create constant variable int TradeSize = 10
|
||||||
|
|
||||||
|
-- How large is a pip?
|
||||||
|
create constant variable BigDecimal PipSize = new BigDecimal(0.0001)
|
||||||
|
|
||||||
-- How many events to use for simple moving average calculation
|
-- How many events to use for simple moving average calculation
|
||||||
create constant variable int SMASize = 5
|
create constant variable int SMASize = 5
|
||||||
|
|
||||||
@ -35,11 +41,11 @@ create constant variable int RefSize = 5
|
|||||||
|
|
||||||
-- Define the window as length 1, using the structure of the TickEvent
|
-- Define the window as length 1, using the structure of the TickEvent
|
||||||
-- java class to describe what the window contains.
|
-- java class to describe what the window contains.
|
||||||
create window CurrentTickWindow#length(1) as TickEvent
|
create window CurrentTick#length(1) as TickEvent
|
||||||
|
|
||||||
-- Describe how events get added to the window. This runs every time
|
-- Describe how events get added to the window. This runs every time
|
||||||
-- a new TickEvent is posted.
|
-- a new TickEvent is posted.
|
||||||
insert into CurrentTickWindow select * from TickEvent
|
insert into CurrentTick select * from TickEvent
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
@ -47,47 +53,85 @@ insert into CurrentTickWindow select * from TickEvent
|
|||||||
--
|
--
|
||||||
|
|
||||||
-- InTradingHours will be set to true when the current time is between
|
-- InTradingHours will be set to true when the current time is between
|
||||||
-- StartTime and EndTime.
|
-- TradingStartTime and TradingEndTime.
|
||||||
create variable bool InTradingHours = false
|
create variable bool InTradingHours = false
|
||||||
|
|
||||||
-- Update on each tick
|
-- Update on each tick
|
||||||
-- NOTE: see "timer:within" pattern for possible alternate formulation
|
on TickEvent as t
|
||||||
on TickEvent as t set InTradingHours =
|
set InTradingHours = EPLHelpers.inTimeRange(t.time, TradingStartTime, TradingEndTime)
|
||||||
(EPLHelpers.getHour(t.time) >= StartTimeHour and
|
|
||||||
EPLHelpers.getHour(t.time) < EndTimeHour)
|
|
||||||
|
--
|
||||||
|
-- In long position. Set later.
|
||||||
|
--
|
||||||
|
|
||||||
|
create variable bool InLongEntry = false
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- A stream of OHLC values calculated from TickEvents
|
-- A stream of OHLC values calculated from TickEvents
|
||||||
--
|
--
|
||||||
|
|
||||||
-- Create the stream to contain OHLCEvents
|
|
||||||
create variant schema OHLCStream as OHLCEvent
|
|
||||||
|
|
||||||
-- Send every TickEvent to the OHLC plugin. The plugin will post an
|
-- Send every TickEvent to the OHLC plugin. The plugin will post an
|
||||||
-- OHLCEvent to OHLCStream every OHLCInterval amount of time. It uses
|
-- OHLCEvent to OHLCStream every OHLCInterval amount of time. It uses
|
||||||
-- TickEvent.time ("time") as the source of the timestamp, and uses
|
-- TickEvent.time ("time") as the source of the timestamp, and uses
|
||||||
-- TickEvent.midDouble() as the value to use in the OHLC calculation.
|
-- TickEvent.midDouble() as the value to use in the OHLC calculation.
|
||||||
|
|
||||||
|
create variant schema OHLCStream as OHLCEvent
|
||||||
|
|
||||||
insert into OHLCStream
|
insert into OHLCStream
|
||||||
select * from TickEvent#OHLC(OHLCInterval, time, midDouble)
|
select * from TickEvent#OHLC("OHLC", "time", OHLCInterval, time, midDouble)
|
||||||
|
|
||||||
|
-- Send every TickEvent to the OHLC plugin. The plugin will post an
|
||||||
|
-- OHLCEvent using Heikin-Ashi calculations to HKStream every
|
||||||
|
-- OHLCInterval amount of time. It uses TickEvent.midDouble() as the
|
||||||
|
-- value to use in the OHLC calculation.
|
||||||
|
|
||||||
|
create variant schema HKStream as OHLCEvent
|
||||||
|
|
||||||
|
insert into HKStream
|
||||||
|
select * from TickEvent#OHLC("HeikinAshi", "time", OHLCInterval, time, midDouble)
|
||||||
|
|
||||||
|
-- Send every TickEvent to the OHLC plugin. The plugin will post an
|
||||||
|
-- OHLCEvent to TBStream every OHLCTicks ticks. It uses
|
||||||
|
-- TickEvent.time ("time") as the source of the timestamp, and uses
|
||||||
|
-- TickEvent.midDouble() as the value to use in the OHLC calculation.
|
||||||
|
|
||||||
|
create variant schema TBStream as OHLCEvent
|
||||||
|
|
||||||
|
insert into TBStream
|
||||||
|
select * from TickEvent#OHLC("HeikinAshi", "ticks", OHLCTicks, time, midDouble)
|
||||||
|
|
||||||
|
-- Send every TickEvent to the OHLC plugin. The plugin will post an
|
||||||
|
-- OHLCEvent using Heikin-Ashi calculations to TBHKStream every
|
||||||
|
-- OHLCTicks ticks. It uses TickEvent.midDouble() as the value to use
|
||||||
|
-- in the OHLC calculation.
|
||||||
|
|
||||||
|
create variant schema TBHKStream as OHLCEvent
|
||||||
|
|
||||||
|
insert into TBHKStream
|
||||||
|
select * from TickEvent#OHLC("HeikinAshi", "ticks", OHLCTicks, time, midDouble)
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Simple moving average streams
|
-- Simple moving average streams
|
||||||
--
|
--
|
||||||
|
|
||||||
-- SMACloseStream contains OHLCValueEvents. These are like
|
-- SMACloseStream bundles a simple moving average of the close values
|
||||||
-- OHLCEvents, but add an extra field for an arbitrary value. In this
|
-- with the values of the OHLCStream.
|
||||||
-- stream, that extra value will contain the average of OHLC close
|
create schema SMACloseStream as (time DateTime,
|
||||||
-- values.
|
open double,
|
||||||
create schema SMACloseStream as ats.plugin.OHLCValueEvent
|
high double,
|
||||||
|
low double,
|
||||||
|
close double,
|
||||||
|
averageClose double)
|
||||||
|
|
||||||
-- Average the most recent OHLC close values from OHLCStream and
|
-- Average the most recent OHLC close values from OHLCStream and
|
||||||
-- post an event that contains open, high, low, close, and
|
-- post an event that contains open, high, low, close, and
|
||||||
-- SMA(close). The number of OHLC events used in the SMA calc is set
|
-- SMA(close). The number of OHLC events used in the SMA calc is set
|
||||||
-- by the SMASize variable.
|
-- by the SMASize variable.
|
||||||
insert into SMACloseStream
|
insert into SMACloseStream
|
||||||
select new ats.plugin.OHLCValueEvent(time, open, high, low, close, Avg(close))
|
select time, open, high, low, close, Avg(close) as averageClose
|
||||||
from OHLCStream#length(SMASize)
|
from OHLCStream#length(SMASize)
|
||||||
|
|
||||||
|
|
||||||
@ -98,21 +142,19 @@ insert into SMACloseStream
|
|||||||
-- A stream that feeds B1 and B2. Each event contains a double
|
-- A stream that feeds B1 and B2. Each event contains a double
|
||||||
-- precision floating point value "low", and a timestamp called
|
-- precision floating point value "low", and a timestamp called
|
||||||
-- "time".
|
-- "time".
|
||||||
create schema BStream as (low double, time org.joda.time.DateTime)
|
create schema BStream as (low double, time DateTime)
|
||||||
|
|
||||||
-- Listen to the last "RefSize" number of SMACloseStream events. Look
|
-- Listen to the last "RefSize" number of SMACloseStream events. Look
|
||||||
-- for an OHLC bar with a lower average close than its neighbors. Add
|
-- for an OHLC bar with a lower average close than its neighbors. Add
|
||||||
-- that bar's low value and its timestamp to the stream. As described
|
-- that bar's low value and its timestamp to the stream.
|
||||||
-- in SMACloseStream, "value" in the query below represents
|
|
||||||
-- SMA(close).
|
|
||||||
insert into BStream
|
insert into BStream
|
||||||
select prev(1, low) as low, prev(1, time) as time from SMACloseStream#length(RefSize)
|
select prev(1, low) as low, prev(1, time) as time from SMACloseStream#length(RefSize)
|
||||||
where prev(0, value) > prev(1, value)
|
where prev(0, averageClose) > prev(1, averageClose)
|
||||||
and prev(1, value) < prev(2, value)
|
and prev(1, averageClose) < prev(2, averageClose)
|
||||||
|
|
||||||
|
|
||||||
-- Define B1 to contain the same fields as BStream
|
-- Define B1 to contain the same fields as BStream
|
||||||
create schema B1 (low double, time org.joda.time.DateTime)
|
create schema B1 (low double, time DateTime)
|
||||||
|
|
||||||
-- B1 contains the most recent low value and time from BStream.
|
-- B1 contains the most recent low value and time from BStream.
|
||||||
-- This is the last time an average close was lower
|
-- This is the last time an average close was lower
|
||||||
@ -123,33 +165,33 @@ insert into B1 select prev(0, low) as low, prev(0, time) as time
|
|||||||
|
|
||||||
-- B2 contains the *second* most recent occurrence in BStream, but is
|
-- B2 contains the *second* most recent occurrence in BStream, but is
|
||||||
-- otherwise the same as B1.
|
-- otherwise the same as B1.
|
||||||
create schema B2 (low double, time org.joda.time.DateTime)
|
create schema B2 (low double, time DateTime)
|
||||||
|
|
||||||
insert into B2 select prev(1, low) as low, prev(1, time) as time
|
insert into B2 select prev(1, low) as low, prev(1, time) as time
|
||||||
from BStream#length(RefSize)
|
from BStream#length(RefSize)
|
||||||
|
|
||||||
|
|
||||||
-- A stream that feeds P1 and P2.
|
-- A stream that feeds P1 and P2.
|
||||||
create schema PStream as (low double, time org.joda.time.DateTime)
|
create schema PStream as (low double, time DateTime)
|
||||||
|
|
||||||
-- Find an OHLC bar with a higher average close than its neighbors.
|
-- Find an OHLC bar with a higher average close than its neighbors.
|
||||||
-- Add that low value and its timestamp to the stream.
|
-- Add that low value and its timestamp to the stream.
|
||||||
insert into PStream
|
insert into PStream
|
||||||
select prev(1, low) as low, prev(1, time) as time from SMACloseStream#length(RefSize)
|
select prev(1, low) as low, prev(1, time) as time from SMACloseStream#length(RefSize)
|
||||||
where prev(0, value) < prev(1, value)
|
where prev(0, averageClose) < prev(1, averageClose)
|
||||||
and prev(1, value) > prev(2, value)
|
and prev(1, averageClose) > prev(2, averageClose)
|
||||||
|
|
||||||
-- P1 contains the most recent low value and time from PStream.
|
-- P1 contains the most recent low value and time from PStream.
|
||||||
-- This is the last time an average close was higher
|
-- This is the last time an average close was higher
|
||||||
-- than the ones before and after.
|
-- than the ones before and after.
|
||||||
-- Since the time is included in the event, no separate PT1 is needed.
|
-- Since the time is included in the event, no separate PT1 is needed.
|
||||||
create schema P1 (low double, time org.joda.time.DateTime)
|
create schema P1 (low double, time DateTime)
|
||||||
|
|
||||||
insert into P1 select prev(0, low) as low, prev(0, time) as time
|
insert into P1 select prev(0, low) as low, prev(0, time) as time
|
||||||
from PStream#length(RefSize)
|
from PStream#length(RefSize)
|
||||||
|
|
||||||
-- P2 contains the second most recent occurrence in PStream.
|
-- P2 contains the second most recent occurrence in PStream.
|
||||||
create schema P2 (low double, time org.joda.time.DateTime)
|
create schema P2 (low double, time DateTime)
|
||||||
|
|
||||||
insert into P2 select prev(1, low) as low, prev(1, time) as time
|
insert into P2 select prev(1, low) as low, prev(1, time) as time
|
||||||
from PStream#length(RefSize)
|
from PStream#length(RefSize)
|
||||||
@ -168,9 +210,9 @@ insert into MaxHigh3Window
|
|||||||
select max(high) as high from OHLCStream#length(3)
|
select max(high) as high from OHLCStream#length(3)
|
||||||
|
|
||||||
|
|
||||||
-- Long entry events contain the current tick's midpoint value and
|
-- Long entry events contain the current tick's midpoint value,
|
||||||
-- timestamp.
|
-- timestamp, and instrument name.
|
||||||
create schema LongEntryStream as (current BigDecimal, time org.joda.time.DateTime, instrument String)
|
create schema LongEntryStream as (current BigDecimal, time DateTime, instrument String)
|
||||||
|
|
||||||
-- The long entry calc below is translated from this entry in the
|
-- The long entry calc below is translated from this entry in the
|
||||||
-- spreadsheet:
|
-- spreadsheet:
|
||||||
@ -184,7 +226,7 @@ create schema LongEntryStream as (current BigDecimal, time org.joda.time.DateTim
|
|||||||
|
|
||||||
insert into LongEntryStream
|
insert into LongEntryStream
|
||||||
select C.mid as current, C.time as time, C.instrument as instrument
|
select C.mid as current, C.time as time, C.instrument as instrument
|
||||||
from CurrentTickWindow as C,
|
from CurrentTick as C,
|
||||||
MaxHigh3Window as T,
|
MaxHigh3Window as T,
|
||||||
B1#lastevent, B2#lastevent,
|
B1#lastevent, B2#lastevent,
|
||||||
P1#lastevent, P2#lastevent
|
P1#lastevent, P2#lastevent
|
||||||
@ -194,8 +236,10 @@ insert into LongEntryStream
|
|||||||
and EPLHelpers.laterThan(B1.time, P1.time)
|
and EPLHelpers.laterThan(B1.time, P1.time)
|
||||||
and EPLHelpers.laterThan(B2.time, P2.time)
|
and EPLHelpers.laterThan(B2.time, P2.time)
|
||||||
and EPLHelpers.laterThan(P1.time, B2.time)
|
and EPLHelpers.laterThan(P1.time, B2.time)
|
||||||
|
and InTradingHours
|
||||||
|
and not InLongEntry
|
||||||
|
|
||||||
-- Because multiple streams feed LongEntryStream (CurrentTickWindow,
|
-- Because multiple streams feed LongEntryStream (CurrentTick,
|
||||||
-- MaxHigh3Window, B1, B2...), an event on any of those streams causes
|
-- MaxHigh3Window, B1, B2...), an event on any of those streams causes
|
||||||
-- the LongEntryStream logic above to be triggered. This often causes
|
-- the LongEntryStream logic above to be triggered. This often causes
|
||||||
-- multiple LongEntryStream events to be generated for a single tick
|
-- multiple LongEntryStream events to be generated for a single tick
|
||||||
@ -203,14 +247,101 @@ insert into LongEntryStream
|
|||||||
--
|
--
|
||||||
-- LongEntryDistinct filters out duplicate LongEntryStream events,
|
-- LongEntryDistinct filters out duplicate LongEntryStream events,
|
||||||
-- leaving a maximum of one event per tick.
|
-- leaving a maximum of one event per tick.
|
||||||
create schema LongEntryDistinct as (current BigDecimal, time org.joda.time.DateTime,
|
create schema LongEntryDistinct
|
||||||
instrument String, units int)
|
as (current BigDecimal,
|
||||||
|
time DateTime,
|
||||||
|
instrument String,
|
||||||
|
units int)
|
||||||
|
|
||||||
insert into LongEntryDistinct
|
insert into LongEntryDistinct
|
||||||
select le.current as current, le.time as time,
|
select le.current as current, le.time as time,
|
||||||
le.instrument as instrument, TradeSize as units
|
le.instrument as instrument, TradeSize as units
|
||||||
from pattern [every-distinct(le.time) le=LongEntryStream]
|
from pattern [every-distinct(le.time) le=LongEntryStream]
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Long entry derived values
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Register if We're in a long entry when we get the LongEntryDistinct
|
||||||
|
-- event. (InLongEntry is declared near top of file)
|
||||||
|
on LongEntryDistinct as le set InLongEntry = true
|
||||||
|
|
||||||
|
-- LongEntryTime contains the timestamp of the last long entry,
|
||||||
|
-- or null if none has happened yet.
|
||||||
|
create variable DateTime LongEntryTime = null
|
||||||
|
|
||||||
|
on LongEntryDistinct as le set LongEntryTime = le.time
|
||||||
|
|
||||||
|
-- LongEntryStopBarCount contains the number of OHLCStream bars that
|
||||||
|
-- have occurred since the most recent long entry.
|
||||||
|
create variable int LongEntryStopBarCount = 0
|
||||||
|
|
||||||
|
-- Reset LongEntryStopBarCount when we get a new LE.
|
||||||
|
on LongEntryDistinct as le set LongEntryStopBarCount = 0
|
||||||
|
|
||||||
|
-- Increment LongEntryStopBarCount on every bar.
|
||||||
|
on OHLCStream as ohlc set LongEntryStopBarCount = LongEntryStopBarCount + 1
|
||||||
|
|
||||||
|
-- LongEntryPrice contains the price of the last long entry,
|
||||||
|
-- or null if none has happened yet.
|
||||||
|
create variable BigDecimal LongEntryPrice = null
|
||||||
|
|
||||||
|
on LongEntryDistinct as le set LongEntryPrice = le.current
|
||||||
|
|
||||||
|
-- LongEntryPreEntryBar is a stream that keeps bars just prior to the
|
||||||
|
-- last long entry
|
||||||
|
create schema LongEntryPreEntryBar
|
||||||
|
as (time DateTime,
|
||||||
|
open double,
|
||||||
|
high double,
|
||||||
|
low double,
|
||||||
|
close double)
|
||||||
|
|
||||||
|
-- Add bars so long as we're not in a long position
|
||||||
|
insert into LongEntryPreEntryBar
|
||||||
|
select time, open, high, low, close from OHLCStream
|
||||||
|
where InLongEntry is false
|
||||||
|
|
||||||
|
-- Contains the second-most-recent LongEntryPreEntryBar's "low" value
|
||||||
|
create schema LongEntryPreEntryBarPrevLow (low double)
|
||||||
|
|
||||||
|
insert into LongEntryPreEntryBarPrevLow select prev(1, low) as low
|
||||||
|
from LongEntryPreEntryBar#length(2)
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Long exit
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Long exit event definition
|
||||||
|
create schema LongExitStream
|
||||||
|
as (current BigDecimal,
|
||||||
|
time DateTime,
|
||||||
|
instrument String,
|
||||||
|
units int)
|
||||||
|
|
||||||
|
-- The long exit calc below is translated from this entry in the
|
||||||
|
-- spreadsheet:
|
||||||
|
--
|
||||||
|
-- LX = (LESBC <= 60 and C < MIN(PreEntrybar(Low,1), (LongEntryPrice - 10 pips)))
|
||||||
|
-- or
|
||||||
|
-- (LESBC>60 and C<(EntryPrice + 2 pips)
|
||||||
|
|
||||||
|
insert into LongExitStream
|
||||||
|
select C.mid as current,
|
||||||
|
C.time as time,
|
||||||
|
C.instrument as instrument,
|
||||||
|
0 - TradeSize as units -- negative for "sell"
|
||||||
|
from CurrentTick as C,
|
||||||
|
LongEntryPreEntryBarPrevLow#lastevent as L
|
||||||
|
where ((LongEntryStopBarCount <= 60 and C.mid < min(L.low, LongEntryPrice - (10 * PipSize))) or
|
||||||
|
(LongEntryStopBarCount > 60 and C.mid < LongEntryPrice + (2 * PipSize)))
|
||||||
|
and InLongEntry
|
||||||
|
|
||||||
|
-- Register the long position as closed
|
||||||
|
on LongExitStream as lx set InLongEntry = false
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Event logging
|
-- Event logging
|
||||||
@ -224,27 +355,51 @@ create schema LogStream as (stream string, event string)
|
|||||||
-- of these can be either helpful or too noisy. Comment/uncomment as
|
-- of these can be either helpful or too noisy. Comment/uncomment as
|
||||||
-- you see fit.
|
-- you see fit.
|
||||||
|
|
||||||
insert into LogStream select 'TickEvent' as stream, EPLHelpers.str(*) as event from TickEvent
|
-- insert into LogStream select 'TickEvent' as stream, EPLHelpers.str(*) as event from TickEvent
|
||||||
|
|
||||||
insert into LogStream select 'OHLCStream' as stream, EPLHelpers.str(*) as event from OHLCStream
|
-- insert into LogStream select 'OHLCStream' as stream, EPLHelpers.str(*) as event from OHLCStream
|
||||||
|
|
||||||
|
-- insert into LogStream select 'HKStream' as stream, EPLHelpers.str(*) as event from HKStream
|
||||||
|
|
||||||
|
-- insert into LogStream select 'TBStream' as stream, EPLHelpers.str(*) as event from TBStream
|
||||||
|
|
||||||
|
-- insert into LogStream select 'TBHKStream' as stream, EPLHelpers.str(*) as event from TBHKStream
|
||||||
|
|
||||||
|
-- insert into LogStream select 'InTradingHours' as stream, EPLHelpers.str(time, InTradingHours) as event from TickEvent
|
||||||
|
|
||||||
-- insert into LogStream select 'BStream' as stream, EPLHelpers.str(*) as event from BStream
|
-- insert into LogStream select 'BStream' as stream, EPLHelpers.str(*) as event from BStream
|
||||||
|
|
||||||
-- insert into LogStream select 'PStream' as stream, EPLHelpers.str(*) as event from PStream
|
-- insert into LogStream select 'PStream' as stream, EPLHelpers.str(*) as event from PStream
|
||||||
|
|
||||||
insert into LogStream select 'B1' as stream, EPLHelpers.str(*) as event from B1
|
-- insert into LogStream select 'B1' as stream, EPLHelpers.str(*) as event from B1
|
||||||
|
|
||||||
insert into LogStream select 'B2' as stream, EPLHelpers.str(*) as event from B2
|
-- insert into LogStream select 'B2' as stream, EPLHelpers.str(*) as event from B2
|
||||||
|
|
||||||
insert into LogStream select 'P1' as stream, EPLHelpers.str(*) as event from P1
|
-- insert into LogStream select 'P1' as stream, EPLHelpers.str(*) as event from P1
|
||||||
|
|
||||||
insert into LogStream select 'P2' as stream, EPLHelpers.str(*) as event from P2
|
-- insert into LogStream select 'P2' as stream, EPLHelpers.str(*) as event from P2
|
||||||
|
|
||||||
-- insert into LogStream select 'MaxHigh3Window' as stream, EPLHelpers.str(*) as event from MaxHigh3Window
|
-- insert into LogStream select 'MaxHigh3Window' as stream, EPLHelpers.str(*) as event from MaxHigh3Window
|
||||||
|
|
||||||
-- insert into LogStream select 'LongEntryStream' as stream, EPLHelpers.str(*) as event from LongEntryStream
|
-- insert into LogStream select 'LongEntryStream' as stream, EPLHelpers.str(*) as event from LongEntryStream
|
||||||
|
|
||||||
-- insert into LogStream select 'LongEntryDistinct' as stream, EPLHelpers.str(*) as event from LongEntryDistinct
|
insert into LogStream select 'LongEntryDistinct' as stream, EPLHelpers.str(*) as event from LongEntryDistinct
|
||||||
|
|
||||||
|
-- insert into LogStream select 'InLongEntry' as stream, EPLHelpers.str(InLongEntry) as event from OHLCStream
|
||||||
|
|
||||||
|
-- insert into LogStream select 'LongEntryTime' as stream, EPLHelpers.str(LongEntryTime) as event from TickEvent
|
||||||
|
|
||||||
|
-- insert into LogStream select 'LongEntryPrice' as stream, EPLHelpers.str(LongEntryPrice) as event from OHLCStream
|
||||||
|
|
||||||
|
-- insert into LogStream select 'LongEntryStopBarCount' as stream, EPLHelpers.str(LongEntryStopBarCount) as event from OHLCStream where InLongEntry
|
||||||
|
|
||||||
|
-- insert into LogStream select 'LongEntryPreEntryBar' as stream, EPLHelpers.str(*) as event from LongEntryPreEntryBar
|
||||||
|
|
||||||
|
-- insert into LogStream select 'LongEntryPreEntryBarPrevLow' as stream, EPLHelpers.str(*) as event from LongEntryPreEntryBarPrevLow
|
||||||
|
|
||||||
|
-- insert into LogStream select 'LXPrice' as stream, EPLHelpers.str(mid, LongEntryPrice, LongEntryPrice - (10 * PipSize)) as event from TickEvent
|
||||||
|
-- where InLongEntry and mid < LongEntryPrice
|
||||||
|
|
||||||
|
insert into LogStream select 'LongExitStream' as stream, EPLHelpers.str(*) as event from LongExitStream
|
||||||
|
|
||||||
-- TODO (for Seth): look into LogSink http://esper.espertech.com/release-7.1.0/esper-reference/html/dataflow.html#dataflow-reference-logsink
|
-- TODO (for Seth): look into LogSink http://esper.espertech.com/release-7.1.0/esper-reference/html/dataflow.html#dataflow-reference-logsink
|
||||||
|
|||||||
@ -1,27 +1,54 @@
|
|||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeComparator;
|
||||||
|
import org.joda.time.format.DateTimeFormat;
|
||||||
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.joda.time.DateTimeComparator;
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EPLHelpers contains small helper routines used within epl files.
|
||||||
|
*/
|
||||||
public class EPLHelpers {
|
public class EPLHelpers {
|
||||||
final static Logger log = LoggerFactory.getLogger(EPLHelpers.class);
|
final static Logger log = LoggerFactory.getLogger(EPLHelpers.class);
|
||||||
|
private static DateTimeFormatter timeFormatter = DateTimeFormat.forPattern("HH:mm");
|
||||||
|
private static DateTimeComparator timeComparator = DateTimeComparator.getTimeOnlyInstance();
|
||||||
|
|
||||||
|
|
||||||
/** Return the hour of the day for the given date. */
|
/**
|
||||||
public static int getHour(DateTime date) {
|
* A simple toString() wrapper.
|
||||||
return date.getHourOfDay();
|
*/
|
||||||
|
public static String str(Object... objects) {
|
||||||
|
StringJoiner sj = new StringJoiner(", ");
|
||||||
|
for (Object o : objects) {
|
||||||
|
sj.add(o != null ? o.toString() : "null");
|
||||||
|
}
|
||||||
|
return sj.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A simple toString() wrapper for use in epl. */
|
/**
|
||||||
public static String str(Object o) { return o.toString(); }
|
* Return a DateTime object for the time. Should be specified in
|
||||||
|
* 24-hour "hh:mm" format.
|
||||||
|
*/
|
||||||
|
public static DateTime parseTime(String time) {
|
||||||
|
return timeFormatter.parseDateTime(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the time portion of 'now' is between the time
|
||||||
|
* portion of 'start' and 'end'.
|
||||||
|
*/
|
||||||
|
public static boolean inTimeRange(DateTime now, DateTime start, DateTime end) {
|
||||||
|
return laterThan(now, start) && earlierThan(now, end);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two times and return true if the first is earlier than
|
* Compare two times and return true if the first is earlier than
|
||||||
* the second.
|
* the second.
|
||||||
*/
|
*/
|
||||||
public static boolean earlierThan(DateTime a, DateTime b) {
|
public static boolean earlierThan(DateTime a, DateTime b) {
|
||||||
return DateTimeComparator.getInstance().compare(a, b) < 0;
|
return timeComparator.compare(a, b) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,6 +56,6 @@ public class EPLHelpers {
|
|||||||
* the second.
|
* the second.
|
||||||
*/
|
*/
|
||||||
public static boolean laterThan(DateTime a, DateTime b) {
|
public static boolean laterThan(DateTime a, DateTime b) {
|
||||||
return DateTimeComparator.getInstance().compare(a, b) > 0;
|
return timeComparator.compare(a, b) > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,13 +9,13 @@ import com.espertech.esper.client.StatementAwareUpdateListener;
|
|||||||
import com.espertech.esper.client.UpdateListener;
|
import com.espertech.esper.client.UpdateListener;
|
||||||
import com.espertech.esper.client.time.CurrentTimeEvent;
|
import com.espertech.esper.client.time.CurrentTimeEvent;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ats.orders.MarketOrderRequest;
|
import ats.orders.MarketOrderRequest;
|
||||||
import ats.plugin.OHLCEvent;
|
import ats.plugin.OHLCEvent;
|
||||||
import ats.plugin.OHLCPlugInViewFactory;
|
import ats.plugin.OHLCPlugInViewFactory;
|
||||||
import ats.plugin.OHLCValueEvent;
|
|
||||||
|
|
||||||
|
|
||||||
public class EsperProcessor implements TickProcessor {
|
public class EsperProcessor implements TickProcessor {
|
||||||
@ -35,7 +35,7 @@ public class EsperProcessor implements TickProcessor {
|
|||||||
// register event types defined in java classes
|
// register event types defined in java classes
|
||||||
config.addEventType(TickEvent.class);
|
config.addEventType(TickEvent.class);
|
||||||
config.addEventType(OHLCEvent.class);
|
config.addEventType(OHLCEvent.class);
|
||||||
config.addEventType(OHLCValueEvent.class);
|
config.addEventType(DateTime.class);
|
||||||
|
|
||||||
// add OHLC plugin
|
// add OHLC plugin
|
||||||
config.addPlugInView("ATS", "OHLC", OHLCPlugInViewFactory.class.getName());
|
config.addPlugInView("ATS", "OHLC", OHLCPlugInViewFactory.class.getName());
|
||||||
@ -61,6 +61,20 @@ public class EsperProcessor implements TickProcessor {
|
|||||||
newData[0].get("current"),
|
newData[0].get("current"),
|
||||||
newData[0].get("time"));
|
newData[0].get("time"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// respond to long exit events
|
||||||
|
addStatement("select * from LongExitStream",
|
||||||
|
(newData, oldData) -> {
|
||||||
|
String instrument = (String)newData[0].get("instrument");
|
||||||
|
Integer units = (Integer)newData[0].get("units");
|
||||||
|
trader.placeOrder(new MarketOrderRequest(instrument, units));
|
||||||
|
|
||||||
|
log.debug("Long exit triggered: {} of {} at price {} at time {}",
|
||||||
|
units,
|
||||||
|
instrument,
|
||||||
|
newData[0].get("current"),
|
||||||
|
newData[0].get("time"));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
25
src/main/java/ats/plugin/CandlestickCalc.java
Normal file
25
src/main/java/ats/plugin/CandlestickCalc.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package ats.plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CandlestickCalc embodies a method of calculating candlestick
|
||||||
|
* values. For example, OHLC or Heikin-Ashi.
|
||||||
|
*/
|
||||||
|
interface CandlestickCalc {
|
||||||
|
public enum Type { OHLC, HeikinAshi };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the calculation for the beginning of an interval.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept a current tick value to apply to the current
|
||||||
|
* calculations.
|
||||||
|
*/
|
||||||
|
void applyValue(double value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the values calculated since the last reset point.
|
||||||
|
*/
|
||||||
|
OHLCValues getValues();
|
||||||
|
}
|
||||||
17
src/main/java/ats/plugin/CandlestickWindow.java
Normal file
17
src/main/java/ats/plugin/CandlestickWindow.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package ats.plugin;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CandlestickWindow runs a CandlestickCalc across a number of
|
||||||
|
* ticks and sends
|
||||||
|
*/
|
||||||
|
interface CandlestickWindow {
|
||||||
|
public enum Type { time, ticks };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a tick with the given time and value.
|
||||||
|
*/
|
||||||
|
public void update(DateTime timestamp, double value);
|
||||||
|
|
||||||
|
}
|
||||||
102
src/main/java/ats/plugin/HeikinAshiCandlestickCalc.java
Normal file
102
src/main/java/ats/plugin/HeikinAshiCandlestickCalc.java
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package ats.plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HeikinAshiCandlestickCalc calculates values using the Heikin-Ashi
|
||||||
|
* technique.
|
||||||
|
*
|
||||||
|
* @see <a href="https://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:heikin_ashi">Heikin-Ashi description</a>
|
||||||
|
*/
|
||||||
|
class HeikinAshiCandlestickCalc implements CandlestickCalc {
|
||||||
|
private Double lastOpen;
|
||||||
|
private Double lastClose;
|
||||||
|
private Double currentOpen;
|
||||||
|
private Double currentClose;
|
||||||
|
private Double currentHigh;
|
||||||
|
private Double currentLow;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the calculation for the beginning of an interval.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
lastOpen = currentOpen;
|
||||||
|
lastClose = currentClose;
|
||||||
|
|
||||||
|
currentOpen = null;
|
||||||
|
currentClose = null;
|
||||||
|
currentHigh = null;
|
||||||
|
currentLow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept a current tick value to apply to the current
|
||||||
|
* calculations.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void applyValue(double value) {
|
||||||
|
if (currentOpen == null) {
|
||||||
|
currentOpen = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClose = value;
|
||||||
|
|
||||||
|
if (currentLow == null) {
|
||||||
|
currentLow = value;
|
||||||
|
} else if (currentLow.compareTo(value) > 0) {
|
||||||
|
currentLow = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentHigh == null) {
|
||||||
|
currentHigh = value;
|
||||||
|
} else if (currentHigh.compareTo(value) < 0) {
|
||||||
|
currentHigh = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HA-Open = (HA-Open(-1) + HA-Close(-1)) / 2
|
||||||
|
*/
|
||||||
|
private double calcOpen() {
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
sum += lastOpen != null ? lastOpen : currentOpen;
|
||||||
|
sum += lastClose != null ? lastClose : currentClose;
|
||||||
|
|
||||||
|
return sum / 2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HA-Close = (Open(0) + High(0) + Low(0) + Close(0)) / 4
|
||||||
|
*/
|
||||||
|
private double calcClose() {
|
||||||
|
return (currentOpen + currentHigh + currentLow + currentClose) / 4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HA-High = Maximum of the High(0), HA-Open(0) or HA-Close(0)
|
||||||
|
*/
|
||||||
|
private double calcHigh(double haOpen, double haClose) {
|
||||||
|
return Math.max(currentHigh, Math.max(haOpen, haClose));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HA-Low = Minimum of the Low(0), HA-Open(0) or HA-Close(0)
|
||||||
|
*/
|
||||||
|
private double calcLow(double haOpen, double haClose) {
|
||||||
|
return Math.min(currentLow, Math.min(haOpen, haClose));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return our calculated values up to now.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OHLCValues getValues() {
|
||||||
|
double o = calcOpen();
|
||||||
|
double c = calcClose();
|
||||||
|
double h = calcHigh(o, c);
|
||||||
|
double l = calcLow(o, c);
|
||||||
|
|
||||||
|
return new OHLCValues(o, h, l, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/main/java/ats/plugin/OHLCCandlestickCalc.java
Normal file
56
src/main/java/ats/plugin/OHLCCandlestickCalc.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package ats.plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OHLCCandlestickCalc calculates values for OHLC.
|
||||||
|
*/
|
||||||
|
class OHLCCandlestickCalc implements CandlestickCalc {
|
||||||
|
private Double open;
|
||||||
|
private Double close;
|
||||||
|
private Double high;
|
||||||
|
private Double low;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the calculation for the beginning of an interval.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
open = null;
|
||||||
|
close = null;
|
||||||
|
high = null;
|
||||||
|
low = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept a current tick value to apply to the current
|
||||||
|
* calculations.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return our calculated values up to now.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OHLCValues getValues() {
|
||||||
|
return new OHLCValues(open, high, low, close);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,10 @@ public class OHLCEvent {
|
|||||||
private double close;
|
private double close;
|
||||||
|
|
||||||
|
|
||||||
|
public OHLCEvent(DateTime time, OHLCValues values) {
|
||||||
|
this(time, values.open, values.high, values.low, values.close);
|
||||||
|
}
|
||||||
|
|
||||||
public OHLCEvent(DateTime time,
|
public OHLCEvent(DateTime time,
|
||||||
double open, double high,
|
double open, double high,
|
||||||
double low, double close)
|
double low, double close)
|
||||||
|
|||||||
@ -1,92 +1,94 @@
|
|||||||
package ats.plugin;
|
package ats.plugin;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
import com.espertech.esper.client.EventBean;
|
import com.espertech.esper.client.EventBean;
|
||||||
import com.espertech.esper.client.EventType;
|
import com.espertech.esper.client.EventType;
|
||||||
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
|
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
|
||||||
import com.espertech.esper.core.service.EPStatementHandleCallback;
|
import com.espertech.esper.epl.expression.core.ExprEvaluator;
|
||||||
import com.espertech.esper.core.service.EngineLevelExtensionServicesContext;
|
|
||||||
import com.espertech.esper.epl.expression.core.ExprNode;
|
import com.espertech.esper.epl.expression.core.ExprNode;
|
||||||
import com.espertech.esper.event.EventAdapterService;
|
import com.espertech.esper.event.EventAdapterService;
|
||||||
import com.espertech.esper.schedule.ScheduleHandleCallback;
|
|
||||||
import com.espertech.esper.view.ViewSupport;
|
import com.espertech.esper.view.ViewSupport;
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.GregorianCalendar;
|
import org.joda.time.DateTime;
|
||||||
import java.util.Iterator;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.
|
* OHLCPlugInView computes OHLC bars for a given time interval.
|
||||||
*/
|
*/
|
||||||
public class OHLCPlugInView extends ViewSupport {
|
public class OHLCPlugInView extends ViewSupport {
|
||||||
private static final Logger log = LoggerFactory.getLogger(OHLCPlugInView.class);
|
private static final Logger log = LoggerFactory.getLogger(OHLCPlugInView.class);
|
||||||
private static final int LATE_EVENT_SLACK_SECONDS = 5;
|
private final AgentInstanceViewFactoryChainContext context;
|
||||||
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 timestampExpression;
|
||||||
private final ExprNode valueExpression;
|
private final ExprNode valueExpression;
|
||||||
|
private CandlestickWindow window;
|
||||||
private DateTime windowStartTime;
|
private EventAdapterService service;
|
||||||
private DateTime windowEndTime;
|
|
||||||
private Double open;
|
|
||||||
private Double close;
|
|
||||||
private Double high;
|
|
||||||
private Double low;
|
|
||||||
private EventBean[] lastData;
|
private EventBean[] lastData;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new plugin.
|
||||||
|
*/
|
||||||
public OHLCPlugInView(AgentInstanceViewFactoryChainContext context,
|
public OHLCPlugInView(AgentInstanceViewFactoryChainContext context,
|
||||||
|
ExprNode calcTypeExpression,
|
||||||
|
ExprNode timeTypeExpression,
|
||||||
ExprNode intervalExpression,
|
ExprNode intervalExpression,
|
||||||
ExprNode timestampExpression,
|
ExprNode timestampExpression,
|
||||||
ExprNode valueExpression)
|
ExprNode valueExpression)
|
||||||
{
|
{
|
||||||
agentContext = context;
|
this.context = context;
|
||||||
scheduleSlot = context.getStatementContext().getScheduleBucket().allocateSlot();
|
|
||||||
|
|
||||||
interval = parseInterval(intervalExpression);
|
|
||||||
// log.info("Interval is {}", interval);
|
|
||||||
|
|
||||||
this.timestampExpression = timestampExpression;
|
this.timestampExpression = timestampExpression;
|
||||||
this.valueExpression = valueExpression;
|
this.valueExpression = valueExpression;
|
||||||
|
service = context.getStatementContext().getEventAdapterService();
|
||||||
|
|
||||||
|
CandlestickCalc calc = chooseCalc(calcTypeExpression);
|
||||||
|
window = chooseWindow(context, timeTypeExpression, intervalExpression, calc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the time period specified by the given expression value.
|
* Return the proper CandlestickCalc according to the value of the
|
||||||
|
* calc type parameter.
|
||||||
*/
|
*/
|
||||||
private Duration parseInterval(ExprNode interval) {
|
private CandlestickCalc chooseCalc(ExprNode calcTypeExpression) {
|
||||||
ExprEvaluator evaluator = interval.getForge().getExprEvaluator();
|
String calcType = exprNodeValue(calcTypeExpression);
|
||||||
String intervalStr = (String)evaluator.evaluate(null, true, agentContext);
|
|
||||||
return parseInterval(intervalStr);
|
if (CandlestickCalc.Type.valueOf(calcType) == CandlestickCalc.Type.HeikinAshi) {
|
||||||
|
log.info("Using Heikin-Ashi calc type");
|
||||||
|
return new HeikinAshiCandlestickCalc();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Using OHLC calc type");
|
||||||
|
return new OHLCCandlestickCalc();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the time period specified by the given string.
|
* Return the proper window according to the value of the
|
||||||
|
* calc type parameter.
|
||||||
|
* @param intervalExpression
|
||||||
*/
|
*/
|
||||||
public static Duration parseInterval(String interval) {
|
private CandlestickWindow chooseWindow(AgentInstanceViewFactoryChainContext context,
|
||||||
interval = interval.replaceAll("\\s+","");
|
ExprNode timeTypeExpression,
|
||||||
return periodFormatter.parsePeriod(interval).toStandardDuration();
|
ExprNode intervalExpression,
|
||||||
|
CandlestickCalc calc)
|
||||||
|
{
|
||||||
|
String timeType = exprNodeValue(timeTypeExpression);
|
||||||
|
|
||||||
|
if (CandlestickWindow.Type.valueOf(timeType) == CandlestickWindow.Type.time) {
|
||||||
|
log.info("Using time based window type");
|
||||||
|
return new TimeCandlestickWindow(this, calc, context, intervalExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Using ticks based window type");
|
||||||
|
return new TicksCandlestickWindow(this, calc, context, intervalExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the string value of a node.
|
||||||
|
*/
|
||||||
|
private String exprNodeValue(ExprNode node) {
|
||||||
|
ExprEvaluator evaluator = node.getForge().getExprEvaluator();
|
||||||
|
return (String)evaluator.evaluate(null, true, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,15 +96,7 @@ public class OHLCPlugInView extends ViewSupport {
|
|||||||
*/
|
*/
|
||||||
private DateTime getTimestamp(EventBean event) {
|
private DateTime getTimestamp(EventBean event) {
|
||||||
ExprEvaluator evaluator = timestampExpression.getForge().getExprEvaluator();
|
ExprEvaluator evaluator = timestampExpression.getForge().getExprEvaluator();
|
||||||
return (DateTime)evaluator.evaluate(new EventBean[] {event}, true, agentContext);
|
return (DateTime)evaluator.evaluate(new EventBean[] {event}, true, context);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,7 +104,7 @@ public class OHLCPlugInView extends ViewSupport {
|
|||||||
*/
|
*/
|
||||||
private double getValue(EventBean event) {
|
private double getValue(EventBean event) {
|
||||||
ExprEvaluator evaluator = valueExpression.getForge().getExprEvaluator();
|
ExprEvaluator evaluator = valueExpression.getForge().getExprEvaluator();
|
||||||
return (double)evaluator.evaluate(new EventBean[] {event}, true, agentContext);
|
return (double)evaluator.evaluate(new EventBean[] {event}, true, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,150 +117,17 @@ public class OHLCPlugInView extends ViewSupport {
|
|||||||
for (EventBean event : newData) {
|
for (EventBean event : newData) {
|
||||||
DateTime timestamp = getTimestamp(event);
|
DateTime timestamp = getTimestamp(event);
|
||||||
double value = getValue(event);
|
double value = getValue(event);
|
||||||
|
window.update(timestamp, value);
|
||||||
ensureWindow(timestamp);
|
|
||||||
applyValue(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make sure our window times are set up and current.
|
* Send an event to all plugin listeners.
|
||||||
*/
|
*/
|
||||||
private void ensureWindow(DateTime timestamp) {
|
public void postEvent(OHLCEvent event) {
|
||||||
if (timestamp == null) return;
|
EventBean[] newData = new EventBean[] {service.adapterForBean(event)};
|
||||||
|
updateChildren(newData, lastData);
|
||||||
if (windowStartTime == null) {
|
lastData = newData;
|
||||||
// create open window
|
|
||||||
windowStartTime = makeWindowStartTime(timestamp, interval);
|
|
||||||
windowEndTime = makeWindowEndTime(windowStartTime, interval);
|
|
||||||
scheduleCallback(windowEndTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inWindow(timestamp)) {
|
|
||||||
// past current window.
|
|
||||||
// post and create a new one.
|
|
||||||
postData();
|
|
||||||
|
|
||||||
windowStartTime = makeWindowStartTime(timestamp, interval);
|
|
||||||
windowEndTime = makeWindowEndTime(windowStartTime, interval);
|
|
||||||
scheduleCallback(windowEndTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// calc how far into the current window we are
|
|
||||||
long intoPeriod = intoToday.getMillis() % interval.getMillis();
|
|
||||||
|
|
||||||
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(DateTime endTime) {
|
|
||||||
SchedulingService sched = agentContext.getStatementContext().getSchedulingService();
|
|
||||||
if (handle != null) {
|
|
||||||
// remove old schedule
|
|
||||||
// log.info("Removing old callback");
|
|
||||||
sched.remove(handle, scheduleSlot);
|
|
||||||
handle = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime currentTime = toDateTime(sched.getTime());
|
|
||||||
DateTime targetTime = endTime.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
|
|
||||||
// log.info("Callback running");
|
|
||||||
OHLCPlugInView.this.postData();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handle = new EPStatementHandleCallback(agentContext.getEpStatementAgentInstanceHandle(), callback);
|
|
||||||
sched.add(callbackTime, handle, scheduleSlot);
|
|
||||||
// log.info("Scheduled callback for {}", callbackTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime lastStartTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update listeners with our new value.
|
|
||||||
*/
|
|
||||||
private void postData() {
|
|
||||||
if (open == null) {
|
|
||||||
// log.info("No data to post");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastStartTime != null && lastStartTime.compareTo(windowStartTime) == 0) {
|
|
||||||
log.warn("DUP START TIME");
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.info("posting {} with {} events in {}", windowStartTime, eventCount, Thread.currentThread());
|
|
||||||
|
|
||||||
OHLCEvent value = new OHLCEvent(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;
|
|
||||||
|
|
||||||
lastStartTime = windowStartTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -279,11 +140,10 @@ public class OHLCPlugInView extends ViewSupport {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public EventType getEventType() {
|
public EventType getEventType() {
|
||||||
return getEventType(agentContext.getStatementContext().getEventAdapterService());
|
return getEventType(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static EventType getEventType(EventAdapterService service)
|
protected static EventType getEventType(EventAdapterService service) {
|
||||||
{
|
|
||||||
return service.addBeanType(OHLCEvent.class.getName(),
|
return service.addBeanType(OHLCEvent.class.getName(),
|
||||||
OHLCEvent.class,
|
OHLCEvent.class,
|
||||||
false, false, false);
|
false, false, false);
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
package ats.plugin;
|
package ats.plugin;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.espertech.esper.client.EventType;
|
import com.espertech.esper.client.EventType;
|
||||||
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
|
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
|
||||||
import com.espertech.esper.core.service.StatementContext;
|
import com.espertech.esper.core.service.StatementContext;
|
||||||
@ -10,9 +13,7 @@ import com.espertech.esper.view.ViewFactory;
|
|||||||
import com.espertech.esper.view.ViewFactoryContext;
|
import com.espertech.esper.view.ViewFactoryContext;
|
||||||
import com.espertech.esper.view.ViewFactorySupport;
|
import com.espertech.esper.view.ViewFactorySupport;
|
||||||
import com.espertech.esper.view.ViewParameterException;
|
import com.espertech.esper.view.ViewParameterException;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,6 +22,8 @@ import org.joda.time.DateTime;
|
|||||||
public class OHLCPlugInViewFactory extends ViewFactorySupport {
|
public class OHLCPlugInViewFactory extends ViewFactorySupport {
|
||||||
private EventAdapterService eventAdapterService;
|
private EventAdapterService eventAdapterService;
|
||||||
private List<ExprNode> params;
|
private List<ExprNode> params;
|
||||||
|
private ExprNode calcType;
|
||||||
|
private ExprNode timeType;
|
||||||
private ExprNode interval;
|
private ExprNode interval;
|
||||||
private ExprNode timestamp;
|
private ExprNode timestamp;
|
||||||
private ExprNode value;
|
private ExprNode value;
|
||||||
@ -35,8 +38,8 @@ public class OHLCPlugInViewFactory extends ViewFactorySupport {
|
|||||||
{
|
{
|
||||||
eventAdapterService = context.getEventAdapterService();
|
eventAdapterService = context.getEventAdapterService();
|
||||||
|
|
||||||
if (params.size() != 3) {
|
if (params.size() != 5) {
|
||||||
throw new ViewParameterException("OHLC view takes three parameters: time interval, timestamp expression, and mid price expression.");
|
throw new ViewParameterException("OHLC view takes five parameters: calulation type (\"OHLC\" or \"Heikin-Ashi\"), batching type (\"time\" or \"ticks\"), time interval or tick count, timestamp expression, and mid price expression.");
|
||||||
}
|
}
|
||||||
this.params = params;
|
this.params = params;
|
||||||
}
|
}
|
||||||
@ -57,20 +60,36 @@ public class OHLCPlugInViewFactory extends ViewFactorySupport {
|
|||||||
statementContext,
|
statementContext,
|
||||||
params, true);
|
params, true);
|
||||||
|
|
||||||
interval = validatedNodes[0];
|
calcType = validatedNodes[0];
|
||||||
timestamp = validatedNodes[1];
|
timeType = validatedNodes[1];
|
||||||
value = validatedNodes[2];
|
interval = validatedNodes[2];
|
||||||
|
timestamp = validatedNodes[3];
|
||||||
|
value = validatedNodes[4];
|
||||||
|
|
||||||
|
Class calcTypeClass = calcType.getForge().getEvaluationType();
|
||||||
|
if (calcTypeClass != String.class)
|
||||||
|
{
|
||||||
|
throw new ViewParameterException("OHLC view needs a String-typed calc type value for parameter 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Class timeTypeClass = timeType.getForge().getEvaluationType();
|
||||||
|
if (timeTypeClass != String.class)
|
||||||
|
{
|
||||||
|
throw new ViewParameterException("OHLC view needs a String-typed time type value for parameter 2");
|
||||||
|
}
|
||||||
|
|
||||||
Class intervalClass = interval.getForge().getEvaluationType();
|
Class intervalClass = interval.getForge().getEvaluationType();
|
||||||
if ((intervalClass != String.class))
|
if ((intervalClass != String.class) &&
|
||||||
|
(intervalClass != Integer.class) &&
|
||||||
|
(intervalClass != int.class))
|
||||||
{
|
{
|
||||||
throw new ViewParameterException("OHLC view needs String-typed interval value for parameter 1.");
|
throw new ViewParameterException("OHLC view needs String- or integer-typed interval value for parameter 3 - got " + intervalClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
Class timestampClass = timestamp.getForge().getEvaluationType();
|
Class timestampClass = timestamp.getForge().getEvaluationType();
|
||||||
if ((timestampClass != DateTime.class))
|
if ((timestampClass != DateTime.class))
|
||||||
{
|
{
|
||||||
throw new ViewParameterException("OHLC view needs DateTime typed timestamp values for parameter 2");
|
throw new ViewParameterException("OHLC view needs DateTime typed timestamp values for parameter 4");
|
||||||
}
|
}
|
||||||
|
|
||||||
Class valueClass = value.getForge().getEvaluationType();
|
Class valueClass = value.getForge().getEvaluationType();
|
||||||
@ -78,7 +97,7 @@ public class OHLCPlugInViewFactory extends ViewFactorySupport {
|
|||||||
(valueClass != Double.class) &&
|
(valueClass != Double.class) &&
|
||||||
(valueClass != BigDecimal.class))
|
(valueClass != BigDecimal.class))
|
||||||
{
|
{
|
||||||
throw new ViewParameterException("OHLC view needs double or BigDecimal values for parameter 3");
|
throw new ViewParameterException("OHLC view needs double or BigDecimal values for parameter 5");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +105,8 @@ public class OHLCPlugInViewFactory extends ViewFactorySupport {
|
|||||||
* Create a new view using already-passed context and params.
|
* Create a new view using already-passed context and params.
|
||||||
*/
|
*/
|
||||||
public View makeView(AgentInstanceViewFactoryChainContext agentContext) {
|
public View makeView(AgentInstanceViewFactoryChainContext agentContext) {
|
||||||
return new OHLCPlugInView(agentContext, interval, timestamp, value);
|
return new OHLCPlugInView(agentContext, calcType, timeType,
|
||||||
|
interval, timestamp, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
package ats.plugin;
|
|
||||||
|
|
||||||
import org.joda.time.DateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OHLCValueEvent stores one bar of OHLC info.
|
|
||||||
*/
|
|
||||||
public class OHLCValueEvent extends OHLCEvent {
|
|
||||||
private double value;
|
|
||||||
|
|
||||||
|
|
||||||
public OHLCValueEvent() {
|
|
||||||
this(null, 0, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OHLCValueEvent(DateTime time,
|
|
||||||
double open, double high,
|
|
||||||
double low, double close,
|
|
||||||
double value)
|
|
||||||
{
|
|
||||||
super(time, open, high, low, close);
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static OHLCValueEvent make(DateTime time,
|
|
||||||
double open, double high,
|
|
||||||
double low, double close,
|
|
||||||
double value)
|
|
||||||
{
|
|
||||||
return new OHLCValueEvent(time, open, high, low, close, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getValue() { return value; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a human readable representation of this event.
|
|
||||||
*/
|
|
||||||
public String toString() {
|
|
||||||
return String.format("OHLCValueEvent[%s, open=%.3f, high=%.3f, low=%.3f, close=%.3f, value=%.3f]",
|
|
||||||
getTime(), getOpen(), getHigh(), getLow(), getClose(), value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
src/main/java/ats/plugin/OHLCValues.java
Normal file
22
src/main/java/ats/plugin/OHLCValues.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package ats.plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OHLCValues is a struct for storing open, high, low, and close
|
||||||
|
* values.
|
||||||
|
*/
|
||||||
|
public class OHLCValues {
|
||||||
|
public double open;
|
||||||
|
public double high;
|
||||||
|
public double low;
|
||||||
|
public double close;
|
||||||
|
|
||||||
|
|
||||||
|
public OHLCValues(double open, double high,
|
||||||
|
double low, double close)
|
||||||
|
{
|
||||||
|
this.open = open;
|
||||||
|
this.high = high;
|
||||||
|
this.low = low;
|
||||||
|
this.close = close;
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/main/java/ats/plugin/TicksCandlestickWindow.java
Normal file
85
src/main/java/ats/plugin/TicksCandlestickWindow.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package ats.plugin;
|
||||||
|
|
||||||
|
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
|
||||||
|
import com.espertech.esper.epl.expression.core.ExprEvaluator;
|
||||||
|
import com.espertech.esper.epl.expression.core.ExprNode;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TicksCandlestickWindow runs a CandlestickCalc across a set number
|
||||||
|
* of ticks for each window.
|
||||||
|
*/
|
||||||
|
class TicksCandlestickWindow implements CandlestickWindow {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(TicksCandlestickWindow.class);
|
||||||
|
private final AgentInstanceViewFactoryChainContext context;
|
||||||
|
/** How many ticks in each window? */
|
||||||
|
private final long windowTicks;
|
||||||
|
private OHLCPlugInView plugin;
|
||||||
|
private CandlestickCalc calc;
|
||||||
|
/** How many ticks into the window we are. */
|
||||||
|
private long currentTick = 0;
|
||||||
|
/** The time the window started. */
|
||||||
|
private DateTime windowStartTime;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new window that calculates across a set number of
|
||||||
|
* ticks.
|
||||||
|
*/
|
||||||
|
public TicksCandlestickWindow(OHLCPlugInView plugin,
|
||||||
|
CandlestickCalc calc,
|
||||||
|
AgentInstanceViewFactoryChainContext context,
|
||||||
|
ExprNode intervalExpression)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.calc = calc;
|
||||||
|
this.context = context;
|
||||||
|
windowTicks = parseTickCount(intervalExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the time period specified by the given expression value.
|
||||||
|
*/
|
||||||
|
private long parseTickCount(ExprNode intervalExpression) {
|
||||||
|
ExprEvaluator evaluator = intervalExpression.getForge().getExprEvaluator();
|
||||||
|
Object o = evaluator.evaluate(null, true, context);
|
||||||
|
return new Long((Integer)o).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a tick with the given time and value.
|
||||||
|
*/
|
||||||
|
public void update(DateTime timestamp, double value) {
|
||||||
|
ensureWindow(timestamp);
|
||||||
|
calc.applyValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure our window times are set up and current.
|
||||||
|
* @param timestamp
|
||||||
|
*/
|
||||||
|
public void ensureWindow(DateTime timestamp) {
|
||||||
|
if (windowStartTime == null)
|
||||||
|
windowStartTime = timestamp;
|
||||||
|
|
||||||
|
currentTick++;
|
||||||
|
|
||||||
|
if (currentTick < windowTicks) return;
|
||||||
|
|
||||||
|
OHLCValues values = calc.getValues();
|
||||||
|
if (values == null) {
|
||||||
|
log.info("No data to post");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.postEvent(new OHLCEvent(windowStartTime, values));
|
||||||
|
|
||||||
|
// reset for next window
|
||||||
|
calc.reset();
|
||||||
|
currentTick = 0;
|
||||||
|
windowStartTime = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
198
src/main/java/ats/plugin/TimeCandlestickWindow.java
Normal file
198
src/main/java/ats/plugin/TimeCandlestickWindow.java
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
package ats.plugin;
|
||||||
|
|
||||||
|
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.ExprEvaluator;
|
||||||
|
import com.espertech.esper.epl.expression.core.ExprNode;
|
||||||
|
import com.espertech.esper.schedule.ScheduleHandleCallback;
|
||||||
|
import com.espertech.esper.schedule.SchedulingService;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
import org.joda.time.format.PeriodFormatter;
|
||||||
|
import org.joda.time.format.PeriodFormatterBuilder;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TimeCandlestickWindow runs a CandlestickCalc across ticks for a set
|
||||||
|
* duration each window.
|
||||||
|
*/
|
||||||
|
class TimeCandlestickWindow implements CandlestickWindow {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(TimeCandlestickWindow.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 context;
|
||||||
|
private final long scheduleSlot;
|
||||||
|
private EPStatementHandleCallback handle;
|
||||||
|
private final Duration interval;
|
||||||
|
private OHLCPlugInView plugin;
|
||||||
|
private CandlestickCalc calc;
|
||||||
|
private DateTime windowStartTime;
|
||||||
|
private DateTime windowEndTime;
|
||||||
|
private DateTime lastStartTime;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a window over the given duration.
|
||||||
|
*/
|
||||||
|
public TimeCandlestickWindow(OHLCPlugInView plugin,
|
||||||
|
CandlestickCalc calc,
|
||||||
|
AgentInstanceViewFactoryChainContext context,
|
||||||
|
ExprNode intervalExpression)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.calc = calc;
|
||||||
|
this.context = context;
|
||||||
|
scheduleSlot = context.getStatementContext().getScheduleBucket().allocateSlot();
|
||||||
|
interval = parseInterval(intervalExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a tick with the given time and value.
|
||||||
|
*/
|
||||||
|
public void update(DateTime timestamp, double value) {
|
||||||
|
ensureWindow(timestamp);
|
||||||
|
calc.applyValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, context);
|
||||||
|
return parseInterval(intervalStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the time period specified by the given string.
|
||||||
|
*/
|
||||||
|
public static Duration parseInterval(String interval) {
|
||||||
|
interval = interval.replaceAll("\\s+","");
|
||||||
|
return periodFormatter.parsePeriod(interval).toStandardDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure our window times are set up and current.
|
||||||
|
*/
|
||||||
|
public void ensureWindow(DateTime timestamp) {
|
||||||
|
if (timestamp == null) return;
|
||||||
|
|
||||||
|
if (windowStartTime == null) {
|
||||||
|
// create open window
|
||||||
|
windowStartTime = makeWindowStartTime(timestamp, interval);
|
||||||
|
windowEndTime = makeWindowEndTime(windowStartTime, interval);
|
||||||
|
scheduleCallback(windowEndTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inWindow(timestamp)) {
|
||||||
|
// past current window.
|
||||||
|
// post and create a new one.
|
||||||
|
postData();
|
||||||
|
|
||||||
|
windowStartTime = makeWindowStartTime(timestamp, interval);
|
||||||
|
windowEndTime = makeWindowEndTime(windowStartTime, interval);
|
||||||
|
scheduleCallback(windowEndTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// calc how far into the current window we are
|
||||||
|
long intoPeriod = intoToday.getMillis() % interval.getMillis();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up a callback to post an event when our time window expires.
|
||||||
|
*/
|
||||||
|
private void scheduleCallback(DateTime endTime) {
|
||||||
|
SchedulingService sched = context.getStatementContext().getSchedulingService();
|
||||||
|
if (handle != null) {
|
||||||
|
// remove old schedule
|
||||||
|
// log.info("Removing old callback");
|
||||||
|
sched.remove(handle, scheduleSlot);
|
||||||
|
handle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime currentTime = toDateTime(sched.getTime());
|
||||||
|
DateTime targetTime = endTime.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
|
||||||
|
// log.info("Callback running");
|
||||||
|
TimeCandlestickWindow.this.postData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handle = new EPStatementHandleCallback(context.getEpStatementAgentInstanceHandle(), callback);
|
||||||
|
sched.add(callbackTime, handle, scheduleSlot);
|
||||||
|
// log.info("Scheduled callback for {}", callbackTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update listeners with our new value.
|
||||||
|
*/
|
||||||
|
private void postData() {
|
||||||
|
if (lastStartTime != null && lastStartTime.compareTo(windowStartTime) == 0) {
|
||||||
|
log.warn("DUP START TIME");
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.info("posting {} with {} events in {}", windowStartTime, eventCount, Thread.currentThread());
|
||||||
|
|
||||||
|
OHLCValues values = calc.getValues();
|
||||||
|
if (values == null) {
|
||||||
|
// log.info("No data to post");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.postEvent(new OHLCEvent(windowStartTime, values));
|
||||||
|
|
||||||
|
calc.reset();
|
||||||
|
lastStartTime = windowStartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user