/*
* Copyright (C) 2012 - present by Yann Le Tallec.
* Please see distribution for license.
*/
package com.assylias.jbloomberg;
import com.assylias.bigblue.utils.TypedObject;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.google.common.collect.TreeBasedTable;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that represents the result returned by a Bloomberg IntradayTickData request.
* Note: the LocalDateTime objects are based on the UTC timezone. For other timezones the calling code needs to apply the relevant timezone conversions.<br>
* This implementation uses guava's Tables which are a good fit for the type of structure returned by historical/static
* data requests (guava's Tables can be thought of as Excel spreadsheets with rows and columns).
* <br>
* To continue the analogy with Excel, the data is stored in a sheet which contains one row per date and one column per
* field. However, because several ticks can happen at the same time, each cell can contain one or more data.
* <br>
* Convenience methods are provided to access one specific rows / columns. Those methods return immutable copies of the
* underlying rows / columns.
* <br>
* Finally, the object returned from the cell's getters (i.e. a combination of a date / field) are either boxed
* primitives or Strings. So if a query is supposed to return a double for example, it is normally safe to assume
* that the returned Object is in fact a Double and that the Object can be cast to a double.
* <br>
* This class is thread safe by being synchronized. That would not scale very well under high contention but that is an
* unlikely use case.
*/
public class IntradayTickData extends AbstractRequestResult {
private final static Logger logger = LoggerFactory.getLogger(IntradayTickData.class);
/**
* a Table of date / field / value, which contains one row per date, one column per field.
* The values are either a single value or a list of values if there was more than one tick for that datetime.
*/
private final Table<OffsetDateTime, IntradayTickField, TypedObject> data = TreeBasedTable.create();
/**
* IntradayBar only return one security's data - this is the security
*/
private final String security;
public IntradayTickData(String security) {
this.security = security;
}
@Override
public synchronized boolean isEmpty() {
return data.isEmpty();
}
@Override
public synchronized String toString() {
StringBuilder sb = new StringBuilder("[DATA]");
if (isEmpty()) {
sb.append("{}");
} else {
sb.append("{").append(data).append("}");
}
if (!getSecurityErrors().isEmpty()) {
sb.append("[SECURITY_ERRORS]").append(getSecurityErrors());
}
if (!getFieldErrors().isEmpty()) {
sb.append("[FIELD_EXCEPTIONS]").append(getFieldErrors());
}
return sb.toString();
}
/**
* Adds a value to the HistoricalData structure for that security / field / date combination.
*/
@Override
synchronized void add(OffsetDateTime date, String field, Object value) {
try {
IntradayTickField f = IntradayTickField.of(field);
TypedObject previousValue = data.get(date, f);
TypedObject newValue = TypedObject.of(value);
if (previousValue == null) { //new value, just add it
data.put(date, f, newValue);
} else if (previousValue.isList()) { //already several values in a list - add the new value to the list
previousValue.asList().add(newValue);
} else { //already one value: create a list of values
List<TypedObject> list = new ArrayList<> ();
list.add(previousValue);
list.add(newValue);
data.put(date, f, TypedObject.of(list));
}
} catch (IllegalArgumentException e) {
logger.debug("{} - {}", e.getMessage(), value);
}
}
/**
*
* @return the security for which the intraday data has been retrieved
*/
public String getSecurity() {
return security;
}
/**
*
* @param field the field for which the data is needed
* @return a multimap that can contain one or more values per date.
*/
public Multimap<OffsetDateTime, TypedObject> forField(IntradayTickField field) {
Map<OffsetDateTime, TypedObject> fieldData = data.column(field);
LinkedListMultimap<OffsetDateTime, TypedObject> multimap = LinkedListMultimap.create(fieldData.size());
for (Map.Entry<OffsetDateTime, TypedObject> e : fieldData.entrySet()) {
TypedObject v = e.getValue();
if (v.isList()) {
for (TypedObject value : v.asList()) {
multimap.put(e.getKey(), value);
}
} else {
multimap.put(e.getKey(), v);
}
}
return multimap;
}
}