package com.sp.services;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import java.lang.Math;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.sp.app.AppConstants.ChartInterval;
import com.sp.app.JPAUtility;
import com.sp.app.SPCommon;
import com.sp.app.SPConfig;
import com.sp.entities.ChartdataDaily;
import com.sp.entities.ChartdataHour;
import com.sp.entities.ChartdataMinute;
import com.sp.entities.ChartdataSecond;
import com.sp.entities.ChartdataTicker;
import com.sp.models.ChartdataData;
import com.sp.models.ChartdataLegacy;
import com.sp.models.ChartdataObject;
import com.sp.repositories.ChartdataDailyRepository;
import com.sp.repositories.ChartdataGenericRepository;
import com.sp.repositories.ChartdataHourRepository;
import com.sp.repositories.ChartdataMinuteRepository;
import com.sp.repositories.ChartdataSecondRepository;
import com.sp.repositories.ChartdataTickerRepository;
import com.sp.repositories.IChartData;
import com.alibaba.fastjson.JSON;
/**
* Product service implement.
*/
@Service
@Component
public class ChartdataServiceImpl implements ChartdataService {
private ChartdataSecondRepository cdsr;
private ChartdataMinuteRepository cdmr;
private ChartdataHourRepository cdhr;
private ChartdataDailyRepository cddr;
private ChartdataTickerRepository cdtr;
private ChartdataGenericRepository cdgr;
@Autowired
public void setChartdataSecondRepository(ChartdataSecondRepository chartdataSecondRepository) {
this.cdsr = chartdataSecondRepository;
}
@Autowired
public void setChartdataMinuteRepository(ChartdataMinuteRepository chartdataMinuteRepository) {
this.cdmr = chartdataMinuteRepository;
}
@Autowired
public void setChartdataHourlyRepository(ChartdataHourRepository chartdataHourRepository) {
this.cdhr = chartdataHourRepository;
}
@Autowired
public void setChartdataDailyRepository(ChartdataDailyRepository chartdataDailyRepository) {
this.cddr = chartdataDailyRepository;
}
@Autowired
public void setChartdataTickerRepository(ChartdataTickerRepository chartdataTickerRepository) {
this.cdtr = chartdataTickerRepository;
}
@Autowired
public void setChartdataTickerRepository(ChartdataGenericRepository chartdataGenericRepository) {
this.cdgr = chartdataGenericRepository;
}
@Scheduled(fixedRate = 5000)
@CacheEvict(value = "getChartDataCached", allEntries = true)
public void cacheEvictionEvent() throws Exception {
//System.out.println("Cache Evicted!");
}
@Override
public void doEvictCache() {
System.out.println("Evicting all cache!");
net.sf.ehcache.CacheManager.getInstance().clearAll();
}
@Override
public Iterable<ChartdataSecond> getChartDataSecond(Date from, Date to) {
return cdsr.getListChartDataSecond(from, to);
}
@Override
public Iterable<ChartdataMinute> getChartDataMinute(Date from, Date to) {
return cdmr.getListChartDataMinute(from, to);
}
@Override
public Iterable<ChartdataHour> getChartDataHour(Date from, Date to) {
return cdhr.getListChartDataHour(from, to);
}
@Override
public Iterable<ChartdataDaily> getChartDataDaily(Date from, Date to) {
return cddr.getListChartDataDaily(from, to);
}
@Override
public void setChartDataSecond(String json_data) {
List<ChartdataSecond> lcds = JSON.parseArray(json_data, ChartdataSecond.class);
for (ChartdataSecond cds : lcds) {
cdsr.replaceChartDataWithRecId(cds.getRecId(),
cds.getMktDatetime().getTime() / 1000,
cds.getProdCode(), cds.getOpen(), cds.getHigh(), cds.getLow(), cds.getClose(), cds.getPrevClose(),
cds.getQty(), cds.getTurnover(), cds.getInstmntCode());
}
}
@Override
public void setChartDataMinute(String json_data) {
List<ChartdataMinute> lcds = JSON.parseArray(json_data, ChartdataMinute.class);
for (ChartdataMinute cds : lcds) {
cdmr.replaceChartData(cds.getMktDatetime().getTime() / 1000,
cds.getProdCode(), cds.getOpen(), cds.getHigh(), cds.getLow(), cds.getClose(), cds.getPrevClose(),
cds.getQty(), cds.getTurnover(), cds.getInstmntCode());
}
}
@Override
public void setChartDataHour(String json_data) {
List<ChartdataHour> lcds = JSON.parseArray(json_data, ChartdataHour.class);
for (ChartdataHour cds : lcds) {
cdhr.replaceChartData(cds.getMktDatetime().getTime() / 1000,
cds.getProdCode(), cds.getOpen(), cds.getHigh(), cds.getLow(), cds.getClose(), cds.getPrevClose(),
cds.getQty(), cds.getTurnover(), cds.getInstmntCode());
}
}
@Override
public void setChartDataDaily(String json_data) {
List<ChartdataDaily> lcds = JSON.parseArray(json_data, ChartdataDaily.class);
for (ChartdataDaily cds : lcds) {
cddr.replaceChartData(cds.getMktDatetime().getTime() / 1000,
cds.getProdCode(), cds.getOpen(), cds.getHigh(), cds.getLow(), cds.getClose(), cds.getPrevClose(),
cds.getQty(), cds.getTurnover(), cds.getInstmntCode());
}
}
@Override
public void putChartDataLegacy(String json_data, ChartInterval intv) {
long rec_id_cnt = 0;
IChartData pcd = null;
switch (intv) {
case MINUTE: pcd = cdmr; break;
case HOUR: pcd = cdhr; break;
case DAILY: pcd = cddr; break;
default: pcd = null; break;
}
List<ChartdataLegacy> lcdl = JSON.parseArray(json_data, ChartdataLegacy.class);
for (ChartdataLegacy cdl : lcdl) {
double open = cdl.open.doubleValue() / Math.pow(10, cdl.dec_in_price);
double high = cdl.high.doubleValue() / Math.pow(10, cdl.dec_in_price);
double low = cdl.low.doubleValue() / Math.pow(10, cdl.dec_in_price);
double close = cdl.close.doubleValue() / Math.pow(10, cdl.dec_in_price);
double prev_close = cdl.prev_close.doubleValue() / Math.pow(10, cdl.dec_in_price);
if (intv == ChartInterval.SECOND) {
String rec_id_s = String.format("%d%d", cdl.log_time, rec_id_cnt++);
BigInteger rec_id = new BigInteger(rec_id_s);
//String str = String.format("%s, %d, %d, %d, %d, %s", rec_id_s, open, high, low, close, cdl.prod_code);
//System.out.println(str);
cdsr.replaceChartDataWithRecId(rec_id, cdl.log_time,
cdl.prod_code, open, high, low, close, prev_close,
cdl.qty, cdl.turnover, cdl.instmnt_code);
} else {
pcd.replaceChartData(cdl.log_time,
cdl.prod_code, open, high, low, close, prev_close,
cdl.qty, cdl.turnover, cdl.instmnt_code);
}
}
}
@Override
public void doHouseClean(String tablename) {
IChartData pcd = null;
switch ( tablename ) {
case "tb.chartdata_ticker": pcd = cdtr; break;
case "tb.chartdata_second": pcd = cdsr; break;
case "tb.chartdata_minute": pcd = cdmr; break;
case "tb.chartdata_hour": pcd = cdhr; break;
case "tb.chartdata_daily": pcd = cddr; break;
default: pcd = null; break;
}
String days = SPConfig.getInstance().GetProperty(tablename);
Date date = Date.from(LocalDate.now().atStartOfDay()
.minusDays(Long.parseLong(days))
.atZone(ZoneId.systemDefault())
.toInstant());
pcd.purgeDataByDate(date);
pcd.optimizeTable();
SPCommon.ConsoleLog("%s purged before: %s", tablename, date);
}
@Override
public void consolidateChartDataTables(String updatetable, LocalDate dt, Long seconds) {
IChartData pcd = null;
Date date = Timestamp.valueOf(dt.atStartOfDay());
switch (updatetable ) {
case "ChartdataMinute": pcd = cdmr; break;
case "ChartdataHour": pcd = cdhr; break;
case "ChartdataDaily": pcd = cddr; break;
default: pcd = null; break;
}
// delete target rows first
pcd.deleteDataByDate(date);
Long sel_sec = seconds;
if (seconds == 86400L)
sel_sec = 1L;
// get records in ChartDataSecond table
List<Object[]> lcds = cdsr.getListChartDataObject(sel_sec, date, seconds);
List<ChartdataObject> lcdo = new ArrayList<ChartdataObject>();
Iterator<Object[]> it = lcds.iterator();
// massage data
while(it.hasNext()) {
Object[] row = (Object[]) it.next();
ChartdataObject cdo = new ChartdataObject();
Integer log_time = (Integer) row[0];
cdo.mkt_datetime = log_time.longValue();
cdo.prod_code = (String) row[1];
cdo.open = (double) row[2];
cdo.high = (double) row[3];
cdo.low = (double) row[4];
cdo.close = (double) row[5];
cdo.prev_close = (double) row[6];
cdo.qty = (long) row[7];
cdo.log_no_max = (BigInteger) row[8];
cdo.log_no_min = (BigInteger) row[9];
cdo.turnover = (BigInteger) row[10];
cdo.instmnt_code = (String) row[11];
// override open + close
cdo.last_close = cdsr.getChartDataClose(cdo.log_no_max);
cdo.first_open = cdsr.getChartDataOpen(cdo.log_no_min);
lcdo.add(cdo);
}
// replace into DB
for (ChartdataObject cdo : lcdo) {
pcd.replaceChartData(cdo.mkt_datetime, cdo.prod_code, cdo.first_open, cdo.high, cdo.low,
cdo.last_close, cdo.prev_close, cdo.qty.intValue(), cdo.turnover, cdo.instmnt_code);
}
SPCommon.ConsoleLog("%s consolidated for: %s", updatetable, date);
}
@Override
public Iterable<ChartdataTicker> getTickerData(String prodcode, Integer bars) {
String sql = "SELECT c FROM ChartdataTicker c WHERE c.prod_code = :prodcode ORDER BY c.mkt_datetime DESC";
final EntityManager em = JPAUtility.getEntityManager();
List<ChartdataTicker> lcdt = em.createQuery(sql, ChartdataTicker.class)
.setParameter("prodcode", prodcode)
.setMaxResults(bars)
.getResultList();
//em.close();
return lcdt;
}
@Override
@Cacheable("getCutOffTime")
@SuppressWarnings("deprecation")
public LocalTime getCutOffTime(String prodcode) {
LocalTime cutoff_time = null;
String instr_code = cdsr.getInstrumentCode(prodcode);
if (instr_code == null || instr_code.isEmpty())
return cutoff_time;
String mkt_code = cdgr.getMarketCode(instr_code);
List<Object[]> lirc = cdgr.getInstrumentRegionCutoff(instr_code);
if (lirc.size() > 0) {
Iterator<Object[]> it = lirc.iterator();
if (it.hasNext()) {
Object[] row = (Object[]) it.next();
Timestamp w = (Timestamp) row[0];
Timestamp s = (Timestamp) row[1];
String region_name = (String) row[2];
LocalTime win_time = LocalTime.of(w.getHours(), w.getMinutes(), w.getSeconds());
LocalTime sum_time = LocalTime.of(s.getHours(), s.getMinutes(), s.getSeconds());
List<Object[]> lrd = cdgr.getRegionDate(region_name);
if (lrd.size() > 0)
cutoff_time = sum_time;
else
cutoff_time = win_time;
}
}
if (cutoff_time == null) {
List<Object[]> lmrc = cdgr.getMarketRegionCutoff(mkt_code);
if (lmrc.size() > 0) {
Iterator<Object[]> it = lmrc.iterator();
if (it.hasNext()) {
Object[] row = (Object[]) it.next();
Timestamp w = (Timestamp) row[0];
Timestamp s = (Timestamp) row[1];
String region_name = (String) row[2];
LocalTime win_time = LocalTime.of(w.getHours(), w.getMinutes(), w.getSeconds());
LocalTime sum_time = LocalTime.of(s.getHours(), s.getMinutes(), s.getSeconds());
if (region_name == null || region_name.isEmpty())
cutoff_time = win_time;
else {
List<Object[]> lrd = cdgr.getRegionDate(region_name);
if (lrd.size() > 0)
cutoff_time = sum_time;
else
cutoff_time = win_time;
}
}
}
}
return cutoff_time;
}
//@Cacheable("GetOHLC")
private ChartdataSecond GetOHLC(List<ChartdataSecond> lcds) {
ChartdataSecond cds = new ChartdataSecond();
cds.setMktDatetime(lcds.get(0).getMktDatetime());
cds.setOpen(lcds.get(0).getOpen());
cds.setClose(lcds.get(lcds.size()-1).getClose());
cds.setPrevClose(lcds.get(lcds.size()-1).getPrevClose());
double high = lcds.get(0).getHigh();
double low = lcds.get(0).getLow();
BigInteger turnover = new BigInteger("0");
Integer qty = 0;
for (ChartdataSecond item : lcds) {
if (item.getHigh() > high) // >
high = item.getHigh();
if (item.getLow() < low) // <
low = item.getLow();
turnover = turnover.add(item.getTurnover());
qty += item.getQty();
// todo: cutoff
}
cds.setHigh(high);
cds.setLow(low);
cds.setQty(qty);
cds.setTurnover(turnover);
cds.setCutOff(0); // TODO: need fix once we have cutoff
return cds;
}
//@Cacheable("CDStoCDD")
private ChartdataData CDStoCDD(ChartdataSecond cds) {
ChartdataData cdd = new ChartdataData();
cdd.O = cds.getOpen();
cdd.H = cds.getHigh();
cdd.L = cds.getLow();
cdd.C = cds.getClose();
cdd.V = cds.getQty();
cdd.TO = cds.getTurnover();
cdd.CO = cds.getCutOff();
long t = cds.getMktDatetime().getTime() / 1000;
cdd.T = t;
return cdd;
}
@Cacheable("getChartDataMap")
private Map<Integer, List<ChartdataSecond>> getChartDataMap(List<ChartdataSecond> lcds, Integer second) {
// map items into [timestamp][list]
Map<Integer, List<ChartdataSecond>> mkeylist = new HashMap<Integer, List<ChartdataSecond>>();
lcds.forEach(item->{
Date dt = item.getMktDatetime();
Integer epochtime = (int) (dt.getTime() / 1000);
Integer key = ((int) Math.floor(epochtime / second)) * second;
if (!mkeylist.containsKey(key))
mkeylist.put(key, new ArrayList<>());
mkeylist.get(key).add(item);
//System.out.println("key: " + key + " value: " + dt.toString());
});
return mkeylist;
}
@Cacheable("getChartDataMapDaily")
private Map<Integer, List<ChartdataSecond>> getChartDataMap(List<ChartdataSecond> lcds, LocalTime lt, Integer second) {
// map items into [timestamp][list]
Map<Integer, List<ChartdataSecond>> mkeylist = new HashMap<Integer, List<ChartdataSecond>>();
Integer prev_time = 0;
lcds.forEach(item->{
Date dt = item.getMktDatetime();
Integer epochtime = (int) (dt.getTime() / 1000);
// TODO: May contain bug, probably rewrite code as logic is based on PHP code
LocalDateTime ldt = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
Calendar cal = Calendar.getInstance();
cal.set(ldt.getYear(), ldt.getMonthValue(), ldt.getDayOfMonth(), lt.getHour(), lt.getMinute(), lt.getSecond());
Integer cutofftime = (int) (cal.getTimeInMillis() / 1000);
// set cut off?
if (prev_time < cutofftime && epochtime >= cutofftime)
item.setCutOff(1);
else
item.setCutOff(0);
// why?
//prev_time = epochtime;
if (epochtime < cutofftime) {
cutofftime -= 86400;
}
Integer key = cutofftime; // may be error here?
if (!mkeylist.containsKey(key))
mkeylist.put(key, new ArrayList<>());
mkeylist.get(key).add(item);
//System.out.println("key: " + key + " value: " + dt.toString());
});
return mkeylist;
}
//@Cacheable("getChartDataMap")
private Map<Integer, List<ChartdataSecond>> getChartDataMap(String prodcode, Integer second, Date from, Date to) {
List<ChartdataSecond> lcds = new ArrayList<ChartdataSecond>();
List<Object[]> lcdo = cdsr.getListChartData(prodcode, from, to);
for(Object o: lcdo){
lcds.add((ChartdataSecond)o);
}
// TODO: may need to move to getChartDataHistory
// this is daily chart, need to make adjustment for OHLC base on different market trading hours
if (second >= 86400) {
LocalTime lt = this.getCutOffTime(prodcode);
return getChartDataMap(lcds, lt, second);
}
return getChartDataMap(lcds, second);
}
@Cacheable("getChartDataHistory")
private List<ChartdataData> getChartDataHistory(String prodcode, Integer second, Date from, Date to) {
IChartData pcd = null;
if (second < 3600) pcd = cdmr;
else if (second < 86400) pcd = cdhr;
else pcd = cddr;
List<ChartdataSecond> lcds = new ArrayList<ChartdataSecond>();
List<Object[]> lcdo = pcd.getListChartData(prodcode, from, to);
if (pcd instanceof ChartdataMinuteRepository) {
for(Object o: lcdo)
lcds.add(new ChartdataSecond((ChartdataMinute) o));
} else if (pcd instanceof ChartdataHourRepository) {
for(Object o: lcdo)
lcds.add(new ChartdataSecond((ChartdataHour) o));
} else {
for(Object o: lcdo)
lcds.add(new ChartdataSecond((ChartdataDaily) o));
}
Map<Integer, List<ChartdataSecond>> mkeylist = getChartDataMap(lcds, second);
// map items into [time][ChartdataSecond] with OHLC
return getListChartdataData(mkeylist);
}
@Cacheable("setCutOffLine")
@SuppressWarnings("deprecation")
private List<ChartdataData> setCutOffLine(String prodcode, List<ChartdataData> lcdj) {
// indicate cutoff time, fast hack, may need re-factoring
LocalTime lt = this.getCutOffTime(prodcode);
boolean cutoff_first_found = false;
if (lt != null) {
for(ChartdataData cdd: lcdj) {
Date date = SPCommon.GetDateTime(cdd.T);
if (date.getHours() == lt.getHour()) {
if (cutoff_first_found == true)
continue;
cutoff_first_found = true;
cdd.CO = 1;
}
else
cutoff_first_found = false;
//SPCommon.ConsoleLog("%s : %s : %s : %s", date, cdd.O, cdd.C, cdd.CO);
}
}
return lcdj;
}
//@Cacheable("sortChartDataData")
private List<ChartdataData> sortChartDataData(List<ChartdataData> lcdj) {
// sort by time
Collections.sort(lcdj, new Comparator<ChartdataData>(){
@Override
public int compare(ChartdataData o1, ChartdataData o2) {
Date d1 = new Date(o1.T * 1000);
Date d2 = new Date(o2.T * 1000);
return d1.compareTo(d2);
}
});
return lcdj;
}
@Cacheable("getListChartdataData")
private List<ChartdataData> getListChartdataData(Map<Integer, List<ChartdataSecond>> mkeylist) {
// map items into [time][ChartdataSecond] with OHLC
List<ChartdataData> lcdj = new ArrayList<>();
mkeylist.forEach((k, v)->{
ChartdataSecond cds = GetOHLC(v);
ChartdataData cdj = CDStoCDD(cds);
lcdj.add(cdj);
//System.out.println("key: " + k + " value: " + v.get(0).getMktDatetime().toString() + cdj.T.toString());
});
return lcdj;
}
@Override
@Cacheable("getChartDataCached")
public Iterable<ChartdataData> getChartDataCached(String prodcode, Integer second, Date from, Date to) {
Map<Integer, List<ChartdataSecond>> mkeylist = new HashMap<Integer, List<ChartdataSecond>>();
// get today data (fill the gap)
Date today = SPCommon.GetDateTime(SPCommon.GetDayStartDate(0));
mkeylist = getChartDataMap(prodcode, second, today, to);
// map items into [time][ChartdataSecond] with OHLC
List<ChartdataData> lcdj = getListChartdataData(mkeylist);
// get from history tables if needed
List<ChartdataData> lcdh = getChartDataHistory(prodcode, second, from, to);
// append history data to today's data
lcdj.addAll(lcdh);
// sort by time
lcdj = sortChartDataData(lcdj);
// set daily cutoff time (line in chart)
lcdj = setCutOffLine(prodcode, lcdj);
return lcdj;
}
@Override
public Iterable<ChartdataData> getChartDataCurrent(String prodcode, Integer second, Date from, Date to) {
Map<Integer, List<ChartdataSecond>> mkeylist = new HashMap<Integer, List<ChartdataSecond>>();
mkeylist = getChartDataMap(prodcode, second, from, to);
// map items into [time][ChartdataSecond] with OHLC
List<ChartdataData> lcdj = getListChartdataData(mkeylist);
// sort by time
lcdj = sortChartDataData(lcdj);
// set daily cutoff time (line in chart)
lcdj = setCutOffLine(prodcode, lcdj);
return lcdj;
}
}