Class EventPattern.Builder

  • Enclosing class:
    EventPattern

    public static final class EventPattern.Builder
    extends Object
    Builder is the only way to create an EventFeed instance. Event patterns are built in two stages: parameters and pattern. Since the pattern references the parameters, parameters must be defined first. This can be done by either providing a parameters map to the builder or defining parameters in the builder.

    Defining Event Parameters

    Building Parameters

    The following annotated code example demonstrates how event parameters are defined using a an EventPattern.Builder:

     // Pattern name "Trade Blip" is the MatchEvent subject.
     // This is an ordered pattern meaning events must arrive in the specified order to match the pattern.
     final EventPattern.Builder builder = EventPattern.builder("Trade Blip", EventPattern.PatternType.ORDERED);
     // First parameter is an order report for the ACME equity symbol.
     final MessageKey orderKey = new EMessageKey(OrderReport.class, "ACME");
     // Second parameter is a trade report for the ACME equity symbol.
     final MessageKey tradeKey = new EMessageKey(EquityTrade.class, "ACME");
    
     builder.beginParameterMap()                // Must specify this before defining parameters
            .beginParameter("ord")              // Parameter name is used in the pattern below.
            .messageKey(orderKey)               // 1. Specify a ESubscribeFeed (required).
            .scope(EFeed.FeedScope.REMOTE_ONLY) // 2. Specify the subscribe feed scope (required).
                                                // 3. Subscription condition is optional and not defined.
            .endParameter()                     // Parameter "ord" defined.
            .beginParameter("trd")
            .messageKey(tradeKey)
            .scope(EFeed.FeedScope.REMOTE_ONLY)
            .endParameter()                     // Parameter "trd" defined.
            .endParameterMap()                  // All parameters defined.
            // Ordered pattern defined starting here.

    Defining Parameter Maps

    If multiple event parameters are based on the same parameters, then defining a parameter map once for all patterns is desirable. The map definition is Map<String, EventPattern.FeedInfo> where the String key is the parameter name used in the preceding example and the FeedInfo defines the ESubscribeFeed. The parameter map definition is similar to the previous EventPattern.Builder example:

     // All parameters use the new word feed.
     final EMessageKey orderKey = new EMessageKey(OrderReport.class, "ACME");
     final EMessageKey tradeKey = new EMessageKey(EquityTrade.class, "ACME");
     final Map<String, EventPattern.FeedInfo> params = new HashMap<>();
    
     // Defining "ord" parameter.
     params.put("ord",                                                // Parameter name is used in pattern definition.
                new EventPattern.FeedInfo(orderKey,                   // 1. Define ESubscribeFeed message key (required).
                                          EFeed.FeedScope.REMOTE_ONLY // 2. Define subscribe feed scope (required).
                                                                      // 3. Subscription condition is optional and not provided here.
                                         ));
    
     // Defining "trd" parameter.
     params.put("trd",
                new EventPattern.FeedInfo(tradeKey,
                                          EFeed.FeedScope.REMOTE_ONLY));
    
     // Create builder using the parameter map.
     final EventPattern.Builder builder = EventPattern.builder("Trade Blip", EventPattern.PatternType.ORDERED, params);

    Define Event Patterns

    With the event parameters defined, it is now time to define the event pattern. Patterns come in two types: ordered and unordered.

    Ordered Pattern

    Events must arrive in the pattern defined order to generate a match. Order patterns are defined by two components: single and multiple.

    Single Component

    A single component is associated with a single parameter and has two optional properties: match count and match condition.

    A match count specifies how the minimum and maximum times an event must contiguously appear in the pattern. matchCount(2, 4) means that the event must appear at least twice in succession and at most four times. If the event occurs only once and is followed by a different event or more than 4 times in succession, then the pattern match fails.

    A match condition is a MatchCondition where the two arguments are the latest event and a read-only list of the previously matched events. This method returns true if the current event matches the pattern condition, allowing the matching process to continue or complete. When false is returned, then this current match fails.

    The match condition takes three arguments: the latest, unmatched event, the capturing groups map, and the user-defined data map.

    CapturingGroups

    Like Pattern, one or more named capturing groups map be defined using beginGroup(String) and endGroup(String). Any matching events which occur between beginGroup and endGroup are stored in a List associated with the group name. Capturing group lists are retrieved from the group map using the group name as the key.

    All event patterns have one capturing group: EventPattern.ALL_EVENTS. The "all events" group contains all matched events.

    User Cache

    eBus provides pattern client with a map to store user-defined data. This map is carried through the matching process, allowing MatchConditions to access information calculated in earlier matches. If a pattern is successfully matched, then this forwarded in MatchEvent.userCache in case this user-defined information is needed for post-match processing.

    The user-defined map is typed Map<Object, Object> so as to allow the user full flexibility with respect what Objects may be used as keys and values.

    Multi-Component

    A multi-component acts like a Java regular expression character class with a quantifier [abc]{n, m}. Multi-components are composed of multiple single components. Each single component may have an optional match condition but not a match count. That is because the match count is applied to the multi-component as a whole. The following is a multi-component example picks up where the previous examples left off. The pattern parameters are defined and the pattern builder instantiated. Now it is time to define the event pattern itself.

     builder.beginGroup("g0")      // opening the "g0" named capturing group
            .beginMultiComponent() // Begin the multi-component definition.
            .matchCount(1, 2)      // The subcomponents may appear once or twice
            // MatchCondition parameters:
            // e: latest event
            // g: named capturing groups map where key is the group name.
            // u: user-defined data cache.
            .addSubordinate("ord", // Either the first event is an order with quantity > 1,000 OR ...
                            (e, g, u) -> {
                                final OrderReportMessage ord = (OrderReportMessage) e;
                                final boolean retcode = (ord.tradedQuantity > 1_000);
    
                                // If the order event meets the condition, then store the trade.
                                if (retcode) {
                                    // Average price calculation requires:
                                    // 1. Sumation of trade prices.
                                    // 2. Number of trades.
                                    u.put(sum, BigDecimal.valueOf(ord.tradedPrice));
                                    u.put(count, BigDecimal.ONE);
                                }
    
                                return (retcode);
                            })
            .addSubordinate("trd", // ... a trade at the currently lowest price for the trading session.
                            (e, g, u) -> {
                                final EquityTradeMessage trd = (EquityTradeMessage) e;
                                final boolean retcode = (trd.tradeType == PriceType.LOW);
    
                                if (retcode) {
                                    u.put(sum, (trd.trade).price);
                                    u.put(count, BigDecimal.ONE);
                                }
    
                                return (retcode);
                            })
            .endMultiComponent() // Multi-component definition ended.
            .endGroup("g0")
            .beginGroup("g1")                 // Collect all the middle trades.
            .beginSingleComponent("trd")
            .matchCount(4, Integer.MAX_VALUE) // Look for at least four trades with increasing prices.
            .matchCondition(                  // Get the all matched events list (so far) and calculate the average trade price.
                 (e, g, u) -> {
                     final EquityTradeMessage trd = (EquityTradeMessage) e;
                     final BigDecimal trdPx = (trd.trade).price;
                     final BigDecimal pxSum = (BigDecimal) u.get(sum);
                     final BigDecimal numTrades = (BigDecimal) u.get(count);
                     final BigDecimal avgPx = pxSum.divide(numTrades);
                     final boolean retcode = (trdPx.compareTo(avgPx) > 0);
    
                     // If this trade met the condition, then add its price to the sum.
                     if (retcode) {
                         u.put(sum, pxSum.add(trdPx));
                         u.put(count, numTrades.add(BigDecimal.ONE));
                     }
    
                     return (retcode);
                 })
            .endSingleComponent()
            .endGroup("g1")
            .beginSingleComponent("trd") // Look for a final trade with a quantity less than 80% of the previous trade.
            .matchCondition(
                 (e, g, u) -> {
                     final EquityTrade trade = (EquityTrade) e;
                     final List<ENotificationMessage> trades = g.get(EventPattern.ALL_EVENTS);
                     final EquityTrade prevTrade = (EquityTrade) (trades.get(trades.size() - 1));
    
                     return ((trade.trade).size < (int) (0.8 * (prevTrade.trade).size));
                 })
            .endSingleComponent(); // End of pattern definition.

    Unordered Patterns

    An unordered pattern matches events based on match count only, allowing events to arrive in no particular order. Once the desired number of events are collected, then a MatchEvent is generated and passed to the subscriber.

    The following looks for at least 5 consecutive orders and trades demonstrating a price rise and there must be at least one order and one trade. This example uses the same parameter map as used in the ordered pattern example.

     builder.beginSingleComponent("ord")
            .matchCount(4)
            .endSingleComponent()
            .beginSingleComponent("trd")
            .matchCount(4)
            .endSingleComponent()
     

    The above pattern is not enough to guarantee the pattern requirement for at least one order and one trade. This is done using a pattern condition (which see).

    Pattern Attributes

    There are three attributes which apply to the entire pattern: until(condition), isExclusive(flag) and patternCondition(Predicate).

    Pattern Until Condition

    Builder.until(condition) says that the pattern match is valid as long as condition returns true. If the condition returns false, then the current match is abandoned. The most common use is by comparing the first and latest message timestamps and continuing the match as long as the time difference is within a time limit. The default until condition always returns true.

     builder.until(// Keep matching as long as the first and latest events are within one hour.
                   // t: all collected matching events so far.
                   // e: latest event.
                   (t, e) ->
                   {
                       final long duration = (e.timestamp - (t.get(0)).timestamp);
    
                       return (duration <= ONE_HOUR_LIMIT);
                   })

    Pattern IsExclusive Flag

    Builder.isExclusive(flag) specifies whether events appearing in one successful within a pattern may be used for another match within the same pattern. If flag is true, then events may not be shared between matches. This means that once a match occurs, then all in-progress matches are thrown away and matching starts from scratch.

    Please note that exclusivity applies only within a pattern and note between patterns. The same event may be used to satisfy two different patterns.

     builder.isExclusive(true);

    Pattern Condition

    A pattern condition is applied to the generated MatchEvent. Only if the condition returns true is the match event posted to the subscriber and if the pattern is exclusive are the other in-progress matches discarded.

    The pattern condition is optional. If not defined, the default pattern condition always returns true.

    Continuing with the unordered pattern example, the following pattern condition guarantees at least one order and one trade.

     builder.patternCondition(
                     p ->
                     {
                         final List<ENotificationMessage> events = p.group(EventPattern.ALL_EVENTS);
                         final boolean hasOrder = containsClass(OrderReport.class, events); // containsClass returns true if the list contains at least one instance of the class.
                         final boolean hasTrade = containsClass(EquityTrade.class, events);
    
                         return (hasOrder && hasTrade);
                     });
     

    Pattern Instantiation

    Once the event parameters, event pattern, and pattern attributes are defined, an EventPattern instance is created using build().

    final EventPattern pattern = builder.build();
    See Also:
    EventPattern.builder(String, EventPattern.PatternType), EventPattern.builder(String, EventPattern.PatternType, Map)