Skip to content

Commit

Permalink
New actual portfolio CSV + New tickers
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmyshah83 committed Oct 21, 2018
1 parent a46da02 commit 9a6a1b2
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 51 deletions.
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,19 @@
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
</dependency>

<dependency>
<groupId>com.bloombergblp</groupId>
<artifactId>blpapi</artifactId>
<version>3.8.8-2</version>
</dependency>

<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public void run(String... args) {
List<DailyTransaction> dailyTransactions = null;
try {
dailyTransactions = outputGenerator.process();
createOrder.placeOrder(dailyTransactions);
if (null != dailyTransactions)
createOrder.placeOrder(dailyTransactions);
} catch (FileNotFoundException e) {
logger.error("FILE UNAVAILABLE --", e);
e.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.quant.backtest.multi.strategy.enums;

public enum ActualPortfolioHeader {

Company("Company"),
Ticker("Ticker"),
MarketValue("Market Value");

private String value;

private ActualPortfolioHeader(String value) {
this.value = value;
}

public String getValue() {
return this.value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.quant.backtest.multi.strategy.utils.CsvUtils;
import com.quant.backtest.multi.strategy.utils.DateUtils;
import com.quant.backtest.multi.strategy.utils.Defaults;
import com.quant.backtest.multi.strategy.utils.PortfolioUtils;

@Component
public class MultiDayOptimalMultiStrategyProcessor {
Expand All @@ -36,11 +37,13 @@ public class MultiDayOptimalMultiStrategyProcessor {
private OptimalMultiStrategyProcessor optimalMultiStrategyProcessor;
@Autowired
private CsvUtils csvUtils;
@Autowired
private PortfolioUtils portfolioUtils;

public Map<String, BigDecimal> process() throws FileNotFoundException {
Map<String, Double> allStrategyWeights = inputCalculator.calculateWeights(inputPropertiesLoader.getSortino(), inputPropertiesLoader.getFlag());
Double totalWeight = DEFAULT_DOUBLE;
for (Double strategyWeight : allStrategyWeights.values()) {
for (Double strategyWeight : allStrategyWeights.values()) {
totalWeight = totalWeight + strategyWeight;
}
logger.info("Total Strategy Weight = {} ", totalWeight);
Expand All @@ -61,7 +64,7 @@ public Map<String, BigDecimal> process() throws FileNotFoundException {
}
Map<String, BigDecimal> optimalPortfolio = new HashMap<>();
for (Entry<String, Double> finalAveragedTickerEntry : finalAveragedTickers.entrySet()) {
optimalPortfolio.put(finalAveragedTickerEntry.getKey(), new BigDecimal((finalAveragedTickerEntry.getValue() / numberOfDays)).setScale(Defaults.SCALE, RoundingMode.HALF_EVEN));
optimalPortfolio.put(portfolioUtils.findBloombergTicker(finalAveragedTickerEntry.getKey()), new BigDecimal((finalAveragedTickerEntry.getValue() / numberOfDays)).setScale(Defaults.SCALE, RoundingMode.HALF_EVEN));
}
logger.info("Final set of tickers are: {}", optimalPortfolio);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ public class OptimalMultiStrategyProcessor {
private int minCashTickers;

public Map<String, Double> process(Map<String, Double> allStrategyWeights, Double totalWeight, String date) throws FileNotFoundException {
Map<String, List<String>> allStrategyTickers = new HashMap<>();
Map<String, List<String>> allStrategyTickers = new HashMap<>();
for (String strategyName : inputPropertiesLoader.getStrategy().values()) {
if (DEFAULT_DOUBLE.equals(allStrategyWeights.get(strategyName)))
continue;
List<String> tickers = csvUtils.readBacktestedCsv(inputPropertiesLoader.getFilePath() + strategyName + "/" + strategyName + ".BUY.STG." + date + ".csv");
if (inputPropertiesLoader.isUseCash() && tickers.size() < minCashTickers) {
IntStream.range(tickers.size(), minCashTickers).forEach(i -> tickers.add(Tickers.CASH.toString()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package com.quant.backtest.multi.strategy.processors;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -19,64 +16,60 @@
import com.quant.backtest.multi.strategy.enums.Side;
import com.quant.backtest.multi.strategy.models.DailyTransaction;
import com.quant.backtest.multi.strategy.properties.InputPropertiesLoader;
import com.quant.backtest.multi.strategy.utils.CsvUtils;
import com.quant.backtest.multi.strategy.utils.DateUtils;
import com.quant.backtest.multi.strategy.utils.Defaults;
import com.quant.backtest.multi.strategy.utils.EmailUtils;
import com.quant.backtest.multi.strategy.utils.FileUtils;
import com.quant.backtest.multi.strategy.utils.PortfolioUtils;

@Component
public class OutputGenerator {

private static final Logger logger = LoggerFactory.getLogger(OutputGenerator.class);
private BigDecimal multiplier;

@Autowired
private MultiDayOptimalMultiStrategyProcessor multiDayOptimalMultiStrategyProcessor;
@Autowired
private InputPropertiesLoader inputPropertiesLoader;
@Autowired
private CsvUtils csvUtils;
private PortfolioUtils portfolioUtils;
@Autowired
private DateUtils dateUtils;
@Autowired
private FileUtils fileUtils;
@Autowired
private EmailUtils emailUtils;

@PostConstruct
public void init() {
multiplier = inputPropertiesLoader.getCapital().divide(new BigDecimal("100").setScale(Defaults.SCALE, RoundingMode.HALF_EVEN));
}

public List<DailyTransaction> process() throws FileNotFoundException {
Map<String, BigDecimal> currentOptimals = multiDayOptimalMultiStrategyProcessor.process();
Map<String, BigDecimal> previousActuals = null;
Map<String, BigDecimal> actualPortfolio = null;
String filePath = inputPropertiesLoader.getOutputFilePath() + "actual-" + dateUtils.getPreviousWorkingDay() + ".csv";
if (!fileUtils.doesFileExists(filePath)) {
throw new FileNotFoundException("Cannot find file : " + filePath);
}
logger.info("Fetching Previous File from Path {}", filePath);
try {
previousActuals = csvUtils.readCsvToMap(filePath);
} catch (IOException e) {
actualPortfolio = portfolioUtils.createActualPortfolio(filePath);
} catch (Exception e) {
logger.error("Error reading previous file {}", e);
e.printStackTrace();
}


BigDecimal multiplier = new BigDecimal(portfolioUtils.getTotalMarketValueOfPortfolio()).divide(new BigDecimal("100").setScale(Defaults.SCALE, RoundingMode.HALF_EVEN));

List<DailyTransaction> dailyTransactions = new ArrayList<>();
StringBuilder builder = new StringBuilder("Daily Details \n");
for (Entry<String, BigDecimal> currentActual : currentOptimals.entrySet()) {
if (!previousActuals.containsKey(currentActual.getKey())) {
BigDecimal finalValue = multiplier.multiply(currentActual.getValue());
if (!actualPortfolio.containsKey(currentActual.getKey())) {
BigDecimal finalValue = multiplier.multiply(currentActual.getValue()).setScale(Defaults.SCALE, RoundingMode.HALF_EVEN);
dailyTransactions.add(new DailyTransaction(Side.BUY, currentActual.getKey(), finalValue));
builder.append(Side.BUY.getName() + " " + currentActual.getKey() + " worth $" + finalValue + "\n");
logger.info("BUY {} worth ${}", currentActual.getKey(), finalValue);
}
}

BigDecimal deltaVal = inputPropertiesLoader.getDelta();
for (Entry<String, BigDecimal> previousActual : previousActuals.entrySet()) {
for (Entry<String, BigDecimal> previousActual : actualPortfolio.entrySet()) {
if (!currentOptimals.containsKey(previousActual.getKey())) {
BigDecimal finalValue = multiplier.multiply(previousActual.getValue());
dailyTransactions.add(new DailyTransaction(Side.SELL, previousActual.getKey(), finalValue));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ public class InputPropertiesLoader {
@NonNull
private String outputFilePath;
@NonNull
private BigDecimal capital;
@NonNull
private int numberOfDays;
@NonNull
private boolean useCash;
Expand Down Expand Up @@ -80,13 +78,6 @@ public String getOutputFilePath() {
public void setOutputFilePath(String outputFilePath) {
this.outputFilePath = outputFilePath;
}
public void setCapital(String capital) {
this.capital = new BigDecimal(capital).setScale(Defaults.SCALE, RoundingMode.HALF_EVEN);
}

public BigDecimal getCapital() {
return capital;
}
public int getNumberOfDays() {
return numberOfDays;
}
Expand Down
75 changes: 64 additions & 11 deletions src/main/java/com/quant/backtest/multi/strategy/utils/CsvUtils.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,54 @@
package com.quant.backtest.multi.strategy.utils;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.NumberFormat;
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 java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.PostConstruct;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.CsvListWriter;
import org.supercsv.io.CsvMapReader;
import org.supercsv.io.ICsvListWriter;
import org.supercsv.io.ICsvMapReader;
import org.supercsv.prefs.CsvPreference;

import com.quant.backtest.multi.strategy.enums.ActualPortfolioHeader;

@Component
public class CsvUtils {

private final String[] header = new String[] { "Ticker", "Percent Weight" };

@Autowired
private DateUtils dateUtils;
private Double totalMarketCap;
private final NumberFormat numberFormat = NumberFormat.getInstance();

private final Map<String, CellProcessor> DESIRED_COLUMNS = new HashMap<>();

@PostConstruct
public void init() {
DESIRED_COLUMNS.put(ActualPortfolioHeader.Company.getValue(), new NotNull());
DESIRED_COLUMNS.put(ActualPortfolioHeader.Ticker.getValue(), new Optional());
DESIRED_COLUMNS.put(ActualPortfolioHeader.MarketValue.getValue(), new Optional());
}

public List<String> readBacktestedCsv(String filePath) throws FileNotFoundException {
List<String> tickers = null;
Expand All @@ -41,20 +64,50 @@ public List<String> readBacktestedCsv(String filePath) throws FileNotFoundExcept
}

public void writeMapToCsv(String filePath, Map<String, BigDecimal> map) throws IOException {
try (ICsvListWriter listWriter = new CsvListWriter(
new FileWriter(filePath + "optimal-" + dateUtils.getCurrentDate() + ".csv"),
CsvPreference.STANDARD_PREFERENCE)) {
final String[] header = new String[] { "Ticker", "Percent Weight" };
try (ICsvListWriter listWriter = new CsvListWriter(new FileWriter(filePath + "optimal-" + dateUtils.getCurrentDate() + ".csv"), CsvPreference.STANDARD_PREFERENCE)) {
listWriter.write(header[0], header[1]);
for (Entry<String, BigDecimal> row : map.entrySet()) {
listWriter.write(row.getKey(), row.getValue());
}
}
}

public Map<String, BigDecimal> readCsvToMap(String filePath) throws IOException {
try (Stream<String> lines = Files.lines(Paths.get(filePath))) {
return lines.map(line -> line.split(",")).skip(1)
.collect(Collectors.toMap(line -> line[0], line -> new BigDecimal(line[1]).setScale(Defaults.SCALE, RoundingMode.HALF_EVEN)));

public Set<Map<String, Object>> fetchActualPortfolioFromCsv(String filePath) throws Exception {
ICsvMapReader mapReader = null;
Map<String, Object> actualPortfolioRow;
Set<Map<String, Object>> actualPortfolioSet = new HashSet<>();
try {
mapReader = new CsvMapReader(new FileReader(filePath), CsvPreference.STANDARD_PREFERENCE);
final String[] header = mapReader.getHeader(true);
final CellProcessor[] processors = new CellProcessor[header.length];
for (int i = 0; i < header.length; i++) {
final CellProcessor processor = DESIRED_COLUMNS.get(header[i]);
if (processor != null) {
processors[i] = processor; // set up processor for desired columns
} else {
header[i] = null; // skip undesired columns
}
}
while ((actualPortfolioRow = mapReader.read(header, processors)) != null) {
// Check to end the loop when we have the market Value
if (StringUtils.equalsIgnoreCase("Total", StringUtils.trim((String) actualPortfolioRow.get(ActualPortfolioHeader.Company.getValue())))) {
totalMarketCap = numberFormat.parse(StringUtils.trim((String) actualPortfolioRow.get(ActualPortfolioHeader.MarketValue.getValue()))).doubleValue();
break;
}
actualPortfolioSet.add(actualPortfolioRow);
System.out.println(String.format("lineNo=%s, rowNo=%s, autal Portfolio row=%s", mapReader.getLineNumber(), mapReader.getRowNumber(), actualPortfolioRow));
}
} finally {
if (mapReader != null) {
mapReader.close();
}
}
return actualPortfolioSet;
}

public Double getTotalMarketValue() {
return totalMarketCap;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

public class Defaults {

public static final int SCALE = 1;
public static final int SCALE = 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.quant.backtest.multi.strategy.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.quant.backtest.multi.strategy.enums.ActualPortfolioHeader;

@Component
public class PortfolioUtils {

@Autowired
private CsvUtils csvUtils;

private static final String SEPARATOR = ".";
private final NumberFormat numberFormat = NumberFormat.getInstance();
private final String BBG_TICKER_APPENDER = " CN EQUITY";

private Function<String, String> bbgTickerFinderFunction = a -> StringUtils.substringAfter(a, SEPARATOR).length() == 0 ? new StringBuilder(a).append(BBG_TICKER_APPENDER).toString()
: StringUtils.substringAfter(a, SEPARATOR).length() == 1 ?
new StringBuilder(StringUtils.substringBefore(a, SEPARATOR)).append("/").append(StringUtils.substringAfter(a, SEPARATOR)).append(BBG_TICKER_APPENDER).toString() :
new StringBuilder(StringUtils.substringBefore(a, SEPARATOR)).append("-").append(StringUtils.substringAfter(a, SEPARATOR).charAt(0)).append(BBG_TICKER_APPENDER).toString();


public Map<String, BigDecimal> createActualPortfolio(String filePath) throws Exception {
Set<Map<String, Object>> actualPortfolioTickers = csvUtils.fetchActualPortfolioFromCsv(filePath);
Double totalMarketValue = csvUtils.getTotalMarketValue();
Map<String, BigDecimal> actualPortfolio = new HashMap<>();
for (Map<String, Object> actualPortfolioTicker : actualPortfolioTickers) {
String portfolioMarketValue = (String) actualPortfolioTicker.get(ActualPortfolioHeader.MarketValue.getValue());
BigDecimal value = null;
if (StringUtils.isNotBlank(portfolioMarketValue)) {
Double portfolioTicker = (numberFormat.parse(portfolioMarketValue).doubleValue()/totalMarketValue)*100;
value = new BigDecimal(portfolioTicker).setScale(Defaults.SCALE, RoundingMode.HALF_EVEN);

} else
value = new BigDecimal(0d).setScale(Defaults.SCALE, RoundingMode.HALF_EVEN);
actualPortfolio.put((String) actualPortfolioTicker.get(ActualPortfolioHeader.Ticker.getValue()), value);

}
return actualPortfolio;
}

public String findBloombergTicker(String optimalPortfolioTicker) {
return bbgTickerFinderFunction.apply(optimalPortfolioTicker);
}

public Double getTotalMarketValueOfPortfolio() {
return csvUtils.getTotalMarketValue();
}
}
Loading

0 comments on commit 9a6a1b2

Please sign in to comment.