/*
* Copyright (c) 2004-2011 Marco Maccaferri and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Marco Maccaferri - initial API and implementation
*/
package org.eclipsetrader.core.feed;
import java.beans.PropertyChangeSupport;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipsetrader.core.instruments.ISecurity;
import org.eclipsetrader.core.repositories.IPropertyConstants;
import org.eclipsetrader.core.repositories.IStore;
import org.eclipsetrader.core.repositories.IStoreObject;
import org.eclipsetrader.core.repositories.IStoreProperties;
import org.eclipsetrader.core.repositories.StoreProperties;
public class History implements IHistory, IStoreObject {
private ISecurity security;
private IOHLC[] bars = new IOHLC[0];
private ISplit[] splits = new ISplit[0];
private TimeSpan timeSpan;
private IOHLC highest;
private IOHLC lowest;
private IStore store;
private IStoreProperties storeProperties;
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
private class Key {
private Date first;
private Date last;
private TimeSpan timeSpan;
public Key(Date first, Date last, TimeSpan timeSpan) {
this.first = first;
this.last = last;
this.timeSpan = timeSpan;
}
public Date getFirst() {
return first;
}
public Date getLast() {
return last;
}
public boolean isInRange(Date date) {
if (first != null && date.before(first)) {
return false;
}
if (last != null && date.after(last)) {
return false;
}
return true;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int hash = 13 * timeSpan.hashCode();
if (first != null) {
hash += 7 * first.hashCode();
}
if (last != null) {
hash += 11 * last.hashCode();
}
return hash;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Key)) {
return false;
}
return hashCode() == obj.hashCode();
}
}
private Map<Key, WeakReference<HistoryDay>> historyMap = new HashMap<Key, WeakReference<HistoryDay>>();
protected History() {
}
public History(ISecurity security, IOHLC[] bars) {
this(security, bars, null, TimeSpan.days(1));
}
public History(ISecurity security, IOHLC[] bars, TimeSpan timeSpan) {
this.timeSpan = timeSpan;
setSecurity(security);
setOHLC(bars);
}
public History(ISecurity security, IOHLC[] bars, ISplit[] splits, TimeSpan timeSpan) {
this.timeSpan = timeSpan;
setSecurity(security);
setOHLC(bars);
setSplits(splits);
}
public History(IStore store, IStoreProperties storeProperties) {
setStore(store);
setStoreProperties(storeProperties);
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getSecurity()
*/
@Override
public ISecurity getSecurity() {
return security;
}
protected void setSecurity(ISecurity security) {
this.security = security;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getFirst()
*/
@Override
public IOHLC getFirst() {
return bars != null && bars.length != 0 ? bars[0] : null;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getLast()
*/
@Override
public IOHLC getLast() {
return bars != null && bars.length != 0 ? bars[bars.length - 1] : null;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getHighest()
*/
@Override
public IOHLC getHighest() {
return highest;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getLowest()
*/
@Override
public IOHLC getLowest() {
return lowest;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getOHLC()
*/
@Override
public IOHLC[] getOHLC() {
return bars;
}
public void setOHLC(IOHLC[] bars) {
if (Arrays.equals(this.bars, bars)) {
return;
}
IOHLC[] oldBars = this.bars;
List<IOHLC> l = new ArrayList<IOHLC>(Arrays.asList(bars));
Collections.sort(l, new Comparator<IOHLC>() {
@Override
public int compare(IOHLC o1, IOHLC o2) {
return o1.getDate().compareTo(o2.getDate());
}
});
this.bars = l.toArray(new IOHLC[l.size()]);
updateRange();
updateSubsets();
propertyChangeSupport.firePropertyChange(IPropertyConstants.BARS, oldBars, this.bars);
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getSubset(java.util.Date, java.util.Date)
*/
@Override
public IHistory getSubset(Date first, Date last) {
Key key = new Key(first, last, timeSpan);
WeakReference<HistoryDay> reference = historyMap.get(key);
HistoryDay history = reference != null ? reference.get() : null;
if (history != null) {
return history;
}
IOHLC[] subset = getOHLCSubset(first, last);
history = new HistoryDay(security, timeSpan, subset);
historyMap.put(key, new WeakReference<HistoryDay>(history));
return history;
}
private IOHLC[] getOHLCSubset(Date first, Date last) {
List<IOHLC> l = new ArrayList<IOHLC>();
for (IOHLC b : bars) {
if ((first == null || !b.getDate().before(first)) && (last == null || !b.getDate().after(last))) {
l.add(b);
}
}
return l.toArray(new IOHLC[l.size()]);
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getSubset(java.util.Date, java.util.Date, org.eclipsetrader.core.feed.TimeSpan)
*/
@Override
public IHistory getSubset(Date first, Date last, TimeSpan timeSpan) {
if (this.timeSpan != null && this.timeSpan.equals(timeSpan)) {
return getSubset(first, last);
}
Key key = new Key(first, last, timeSpan);
WeakReference<HistoryDay> reference = historyMap.get(key);
HistoryDay history = reference != null ? reference.get() : null;
if (history != null) {
return history;
}
Calendar c = Calendar.getInstance();
if (first != null) {
c.setTime(first);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
first = c.getTime();
}
if (last != null) {
c.setTime(last);
c.set(Calendar.HOUR_OF_DAY, 23);
c.set(Calendar.MINUTE, 59);
c.set(Calendar.SECOND, 59);
c.set(Calendar.MILLISECOND, 999);
last = c.getTime();
}
List<IStore> storeList = new ArrayList<IStore>();
List<IStoreProperties> propertyList = new ArrayList<IStoreProperties>();
if (store != null) {
IStore[] childStores = store.fetchChilds(null);
if (childStores != null) {
for (IStore childStore : childStores) {
IStoreProperties properties = childStore.fetchProperties(null);
Date barsDate = (Date) properties.getProperty(IPropertyConstants.BARS_DATE);
if (first != null && barsDate.before(first) || last != null && barsDate.after(last)) {
continue;
}
storeList.add(childStore);
propertyList.add(properties);
}
}
}
IStoreProperties[] properties = propertyList.toArray(new IStoreProperties[propertyList.size()]);
history = new HistoryDay(security, timeSpan, storeList.toArray(new IStore[storeList.size()]), properties) {
@Override
protected IStoreObject[] updateStoreObjects() {
IStoreObject[] storeObject = super.updateStoreObjects();
Set<Entry<Key, WeakReference<HistoryDay>>> set = historyMap.entrySet();
Entry<Key, WeakReference<HistoryDay>>[] entry = set.toArray(new Entry[set.size()]);
Set<Key> updatedElements = new HashSet<Key>();
TimeSpan skipTimeSpan = TimeSpan.days(1);
for (int ii = 0; ii < storeObject.length; ii++) {
Date barsDate = (Date) storeObject[ii].getStoreProperties().getProperty(IPropertyConstants.BARS_DATE);
for (int i = 0; i < entry.length; i++) {
HistoryDay element = entry[i].getValue().get();
Key key = entry[i].getKey();
if (element != null && element != this && !element.getTimeSpan().equals(skipTimeSpan)) {
if (!entry[i].getKey().isInRange(barsDate)) {
continue;
}
updatedElements.add(key);
}
}
}
for (Key key : updatedElements) {
HistoryDay element = historyMap.get(key).get();
if (element == null) {
continue;
}
Map<Date, IStore> storeList = new HashMap<Date, IStore>();
Map<Date, IStoreProperties> propertyList = new HashMap<Date, IStoreProperties>();
IStore[] childStores = store != null ? store.fetchChilds(null) : null;
if (childStores != null) {
for (IStore childStore : childStores) {
IStoreProperties properties = childStore.fetchProperties(null);
Date barsDate = (Date) properties.getProperty(IPropertyConstants.BARS_DATE);
if (!key.isInRange(barsDate)) {
continue;
}
storeList.put(barsDate, childStore);
propertyList.put(barsDate, properties);
}
}
for (int i = 0; i < storeObject.length; i++) {
Date barsDate = (Date) storeObject[i].getStoreProperties().getProperty(IPropertyConstants.BARS_DATE);
if (!key.isInRange(barsDate)) {
continue;
}
storeList.put(barsDate, storeObject[i].getStore());
propertyList.put(barsDate, storeObject[i].getStoreProperties());
}
Collection<IStore> s = storeList.values();
Collection<IStoreProperties> p = propertyList.values();
element.setStoreProperties(s.toArray(new IStore[s.size()]), p.toArray(new IStoreProperties[p.size()]));
}
return storeObject;
}
};
historyMap.put(key, new WeakReference<HistoryDay>(history));
return history;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getDay(java.util.Date)
*/
@Override
public IHistory[] getDay(Date date) {
Calendar c = Calendar.getInstance();
c.setTime(date);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
date = c.getTime();
IStore dayStore = null;
IStoreProperties dayProperties = null;
if (store != null) {
IStore[] childStores = store.fetchChilds(null);
if (childStores != null) {
for (int i = 0; i < childStores.length; i++) {
IStoreProperties childProperties = childStores[i].fetchProperties(null);
Date barsDate = (Date) childProperties.getProperty(IPropertyConstants.BARS_DATE);
if (date.equals(barsDate)) {
dayStore = childStores[i];
dayProperties = childProperties;
break;
}
}
}
}
if (dayStore == null || dayProperties == null) {
return new IHistory[0];
}
List<IHistory> l = new ArrayList<IHistory>();
String[] propertyNames = dayProperties.getPropertyNames();
for (int i = 0; i < propertyNames.length; i++) {
Object value = dayProperties.getProperty(propertyNames[i]);
if (!(value instanceof IOHLC[])) {
continue;
}
TimeSpan timeSpan = TimeSpan.fromString(propertyNames[i]);
if (timeSpan != null) {
Key key = new Key(date, date, timeSpan);
WeakReference<HistoryDay> reference = historyMap.get(key);
HistoryDay history = reference != null ? reference.get() : null;
if (history == null) {
IStore[] storeList = new IStore[] {
dayStore,
};
IStoreProperties[] propertiesList = new IStoreProperties[] {
dayProperties
};
history = createHistoryDay(storeList, propertiesList, timeSpan);
historyMap.put(key, new WeakReference<HistoryDay>(history));
}
l.add(history);
}
}
return l.toArray(new IHistory[l.size()]);
}
@SuppressWarnings("unchecked")
private HistoryDay createHistoryDay(IStore[] storeList, IStoreProperties[] propertiesList, TimeSpan timeSpan) {
HistoryDay history = new HistoryDay(security, timeSpan, storeList, propertiesList) {
@Override
protected IStoreObject[] updateStoreObjects() {
IStoreObject[] storeObject = super.updateStoreObjects();
Set<Entry<Key, WeakReference<HistoryDay>>> set = historyMap.entrySet();
Entry<Key, WeakReference<HistoryDay>>[] entry = set.toArray(new Entry[set.size()]);
Set<Key> updatedElements = new HashSet<Key>();
for (int ii = 0; ii < storeObject.length; ii++) {
TimeSpan timeSpan = (TimeSpan) storeObject[ii].getStoreProperties().getProperty(IPropertyConstants.TIME_SPAN);
Date barsDate = (Date) storeObject[ii].getStoreProperties().getProperty(IPropertyConstants.BARS_DATE);
for (int i = 0; i < entry.length; i++) {
HistoryDay element = entry[i].getValue().get();
Key key = entry[i].getKey();
if (element != null && element != this && element.getTimeSpan().equals(timeSpan)) {
if (!entry[i].getKey().isInRange(barsDate)) {
continue;
}
updatedElements.add(key);
}
}
}
for (Key key : updatedElements) {
HistoryDay element = historyMap.get(key).get();
if (element == null) {
continue;
}
Map<Date, IStore> storeList = new HashMap<Date, IStore>();
Map<Date, IStoreProperties> propertyList = new HashMap<Date, IStoreProperties>();
IStore[] childStores = store != null ? store.fetchChilds(null) : null;
if (childStores != null) {
for (IStore childStore : childStores) {
IStoreProperties properties = childStore.fetchProperties(null);
Date barsDate = (Date) properties.getProperty(IPropertyConstants.BARS_DATE);
if (!key.isInRange(barsDate)) {
continue;
}
storeList.put(barsDate, childStore);
propertyList.put(barsDate, properties);
}
}
for (int i = 0; i < storeObject.length; i++) {
Date barsDate = (Date) storeObject[i].getStoreProperties().getProperty(IPropertyConstants.BARS_DATE);
if (!key.isInRange(barsDate)) {
continue;
}
storeList.put(barsDate, storeObject[i].getStore());
propertyList.put(barsDate, storeObject[i].getStoreProperties());
}
Collection<IStore> s = storeList.values();
Collection<IStoreProperties> p = propertyList.values();
element.setStoreProperties(s.toArray(new IStore[s.size()]), p.toArray(new IStoreProperties[p.size()]));
}
return storeObject;
}
};
return history;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getTimeSpan()
*/
@Override
public TimeSpan getTimeSpan() {
return timeSpan;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getSplits()
*/
@Override
public ISplit[] getSplits() {
return splits;
}
public void setSplits(ISplit[] splits) {
if (Arrays.equals(this.splits, splits)) {
return;
}
ISplit[] oldValue = this.splits;
this.splits = splits;
propertyChangeSupport.firePropertyChange(IPropertyConstants.SPLITS, oldValue, this.splits);
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.feed.IHistory#getAdjustedOHLC()
*/
@Override
public IOHLC[] getAdjustedOHLC() {
IDividend[] dividends = (IDividend[]) security.getAdapter(IDividend[].class);
if ((dividends == null || dividends.length == 0) && (splits == null || splits.length == 0)) {
return bars;
}
IOHLC[] l = new IOHLC[bars.length];
for (int i = 0; i < l.length; i++) {
double splitFactor = 1.0;
if (splits != null) {
for (int s = 0; s < splits.length; s++) {
if (bars[i].getDate().before(splits[s].getDate())) {
splitFactor *= splits[s].getNewQuantity() / splits[s].getOldQuantity();
}
}
}
double cumulatedDividends = 0.0;
if (dividends != null) {
for (int d = 0; d < dividends.length; d++) {
if (bars[i].getDate().before(dividends[d].getExDate())) {
cumulatedDividends += dividends[d].getValue();
}
}
}
l[i] = new OHLC(bars[i].getDate(), bars[i].getOpen() / splitFactor - cumulatedDividends, bars[i].getHigh() / splitFactor - cumulatedDividends, bars[i].getLow() / splitFactor - cumulatedDividends, bars[i].getClose() / splitFactor - cumulatedDividends, (long) (bars[i].getVolume() * splitFactor));
}
return l;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
@Override
@SuppressWarnings({
"unchecked", "rawtypes"
})
public Object getAdapter(Class adapter) {
if (adapter.isAssignableFrom(getClass())) {
return this;
}
if (adapter.isAssignableFrom(PropertyChangeSupport.class)) {
return propertyChangeSupport;
}
return null;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.repositories.IStoreObject#getStore()
*/
@Override
public IStore getStore() {
return store;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.repositories.IStoreObject#setStore(org.eclipsetrader.core.repositories.IStore)
*/
@Override
public void setStore(IStore store) {
this.store = store;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.repositories.IStoreObject#getStoreProperties()
*/
@Override
public IStoreProperties getStoreProperties() {
if (storeProperties == null) {
storeProperties = new StoreProperties();
}
storeProperties.setProperty(IPropertyConstants.OBJECT_TYPE, IHistory.class.getName());
storeProperties.setProperty(IPropertyConstants.SECURITY, security);
storeProperties.setProperty(IPropertyConstants.BARS, bars);
storeProperties.setProperty(IPropertyConstants.TIME_SPAN, timeSpan);
storeProperties.setProperty(IPropertyConstants.SPLITS, splits);
return storeProperties;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.repositories.IStoreObject#setStoreProperties(org.eclipsetrader.core.repositories.IStoreProperties)
*/
@Override
public void setStoreProperties(IStoreProperties storeProperties) {
this.storeProperties = storeProperties;
this.security = (ISecurity) storeProperties.getProperty(IPropertyConstants.SECURITY);
IOHLC[] bars = (IOHLC[]) storeProperties.getProperty(IPropertyConstants.BARS);
List<IOHLC> l1 = bars != null ? Arrays.asList(bars) : new ArrayList<IOHLC>();
Collections.sort(l1, new Comparator<IOHLC>() {
@Override
public int compare(IOHLC o1, IOHLC o2) {
return o1.getDate().compareTo(o2.getDate());
}
});
this.bars = l1.toArray(new IOHLC[l1.size()]);
this.timeSpan = (TimeSpan) storeProperties.getProperty(IPropertyConstants.TIME_SPAN);
ISplit[] splits = (ISplit[]) storeProperties.getProperty(IPropertyConstants.SPLITS);
List<ISplit> l2 = splits != null ? Arrays.asList(splits) : new ArrayList<ISplit>();
Collections.sort(l2, new Comparator<ISplit>() {
@Override
public int compare(ISplit o1, ISplit o2) {
return o1.getDate().compareTo(o2.getDate());
}
});
this.splits = l2.toArray(new ISplit[l2.size()]);
updateRange();
}
protected void updateRange() {
highest = null;
lowest = null;
for (IOHLC b : bars) {
if (highest == null || b.getHigh() > highest.getHigh()) {
highest = b;
}
if (lowest == null || b.getLow() < lowest.getLow()) {
lowest = b;
}
}
}
protected void updateSubsets() {
for (Key key : historyMap.keySet()) {
WeakReference<HistoryDay> reference = historyMap.get(key);
HistoryDay history = reference.get();
if (history == null) {
continue;
}
if (history.getTimeSpan().equals(timeSpan)) {
IOHLC[] subset = getOHLCSubset(key.getFirst(), key.getLast());
history.setOHLC(subset);
}
}
}
}