Skip to content

Commit 0cee452

Browse files
author
albc4l
committed
- Added console user interface to choose between Russell / CH stocks
- Refactoring of the managers to unify the calls
1 parent 66ac1ba commit 0cee452

File tree

5 files changed

+75
-61
lines changed

5 files changed

+75
-61
lines changed

App.config

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
<add key="ModelTopDividendsCount" value="200"/>
1616
<add key="ModelStockPerSectorLimit" value="5"/>
1717
<add key="ModelPortfolioStockCount" value="20"/>
18-
<add key="ModelPortfolioExcelFile" value="portfolio.xlsx"/>
1918

2019
</appSettings>
2120
</configuration>

DataAccess/BaseDataAccessManager.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using System.Threading.Tasks;
2+
using RussellScreener.Entities;
3+
14
namespace RussellScreener.DataAccess {
25
public abstract class BaseDataAccessManager {
36

@@ -10,5 +13,8 @@ public abstract class BaseDataAccessManager {
1013
public abstract string GetCacheFileName();
1114

1215
#endregion Methods
16+
17+
public abstract Task<StockRepository> Process();
18+
1319
}
1420
}

DataAccess/IexManager.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,27 @@
1010
namespace RussellScreener.DataAccess {
1111
public class IexManager : BaseDataAccessManager {
1212

13+
#region Methods
14+
15+
public override string GetCacheFileName() {
16+
return "russel-iex-stocks.json";
17+
}
18+
19+
public override async Task<StockRepository> Process() {
20+
const string etfFilename = "russell.csv";
21+
DownloadISharesComposition(etfFilename);
22+
List<string> tickers = ExtractISharesTickers(etfFilename);
23+
return await DownloadAllStocksFromIex(tickers);
24+
}
25+
1326
/// <summary>
1427
/// Extract all tickers of the holdings within the ETF
1528
/// It simply goes through the CSV, starting at a given position where data is located (EtfCsvDataLineNumber).
1629
/// For each line, the first cell (the ticker) is extract and quotes chars are discarded.
1730
/// </summary>
1831
/// <param name="csvFilename">Name of the iShares CSV containing the holdings info</param>
1932
/// <returns>A list of string of tickers</returns>
20-
public List<string> ExtractISharesTickers(string csvFilename) {
33+
protected List<string> ExtractISharesTickers(string csvFilename) {
2134
List<string> tickers = new List<string>();
2235

2336
var rawCsvLines = File.ReadLines(csvFilename).ToList();
@@ -35,7 +48,7 @@ public List<string> ExtractISharesTickers(string csvFilename) {
3548
/// Download the holdings as a CSV from iShares page (as of Nov. 2017)
3649
/// </summary>
3750
/// <param name="csvFilename">URL of the CSV - Stored in the app.config</param>
38-
public void DownloadISharesComposition(string csvFilename) {
51+
protected void DownloadISharesComposition(string csvFilename) {
3952
var etfCompositionUrl = ConfigurationManager.AppSettings["EtfCompositionUrl"];
4053
using (WebClient wc = new WebClient()) {
4154
wc.DownloadFile(etfCompositionUrl, csvFilename);
@@ -47,7 +60,7 @@ public void DownloadISharesComposition(string csvFilename) {
4760
/// </summary>
4861
/// <param name="tickers">List of tickers to download</param>
4962
/// <returns>A stock repository with all stocks corresponding to the input tickers (except those that failed during fetching)</returns>
50-
public async Task<StockRepository> DownloadAllStocksFromIex(List<string> tickers) {
63+
protected async Task<StockRepository> DownloadAllStocksFromIex(List<string> tickers) {
5164
var stockRepository = new StockRepository();
5265
var iexStockStatsUrl = ConfigurationManager.AppSettings["IexStockStatsUrl"];
5366
var iexStockCompanyUrl = ConfigurationManager.AppSettings["IexStockCompanyUrl"];
@@ -74,7 +87,7 @@ public async Task<StockRepository> DownloadAllStocksFromIex(List<string> tickers
7487
/// <param name="iexStockCompanyUrl">IEX API URL for the stock company info</param>
7588
/// <param name="stockRepository">Repository object where the stock will be added</param>
7689
/// <returns>A task performing the download of the stock info</returns>
77-
public async Task DownloadStockAsync(string ticker, string iexStockStatsUrl, string iexStockCompanyUrl, StockRepository stockRepository) {
90+
protected async Task DownloadStockAsync(string ticker, string iexStockStatsUrl, string iexStockCompanyUrl, StockRepository stockRepository) {
7891
using (var wc = new WebClient()) {
7992
try {
8093
Stock s = new Stock(ticker);
@@ -94,9 +107,6 @@ public async Task DownloadStockAsync(string ticker, string iexStockStatsUrl, str
94107
}
95108
}
96109

97-
public override string GetCacheFileName() {
98-
return "russel-iex-stocks.json";
99-
}
100-
110+
#endregion Methods
101111
}
102112
}

DataAccess/SixSwissManager.cs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,24 @@ public class SixSwissManager : BaseDataAccessManager {
1414

1515
#region Methods
1616

17+
/// <summary>
18+
/// Return the name of the cache to be used for this manager.
19+
/// </summary>
20+
/// <returns>Name of the cache to use for this manager</returns>
21+
public override string GetCacheFileName() {
22+
return "swiss-stocks.json";
23+
}
24+
25+
public override async Task<StockRepository> Process() {
26+
return await DownloadSwissStockInfos(@".\countries\switzerland\swiss-listings.csv");
27+
}
28+
1729
/// <summary>
1830
/// Asynchronously download Swiss stock information by extracting necessary values from HTML pages
1931
/// retrieved from SIX.
2032
/// </summary>
2133
/// <param name="swissListingsCsv">CSV with Swiss tickers. Prepared in advance.</param>
22-
public async Task<StockRepository> DownloadSwissStockInfos(string swissListingsCsv) {
34+
protected async Task<StockRepository> DownloadSwissStockInfos(string swissListingsCsv) {
2335
var rawCsvLines = File.ReadAllLines(swissListingsCsv).ToList();
2436

2537
// Forced to use Regex to extract the values from the raw HTML page returned by SIX.
@@ -55,7 +67,15 @@ public async Task<StockRepository> DownloadSwissStockInfos(string swissListingsC
5567
try {
5668
Console.WriteLine(name, ticker);
5769
// we are forced to do HTML scrapping, no known CSV or free API for Swiss stocks
58-
var stockStatsRawHtml = await wc.DownloadStringTaskAsync(new Uri(string.Format(swissStatsUrl, isin)));
70+
string url = string.Format(swissStatsUrl, isin);
71+
72+
var stockStatsRawHtml = await wc.DownloadStringTaskAsync(new Uri(url));
73+
if (stockStatsRawHtml.Contains("No data is available at present")) {
74+
stockStatsRawHtml = await wc.DownloadStringTaskAsync(new Uri(string.Format(swissStatsUrl.Replace("{0}CHF4", "{0}CHF1"), isin)));
75+
if (stockStatsRawHtml.Contains("No data is available at present")) {
76+
Console.WriteLine("This ticker has some issues with data. Manual checking? " + ticker);
77+
}
78+
}
5979

6080
// extract dividend yield from the page
6181
stock.Stats.DividendYield = ParseSwissRegexResult(divYieldSwissStockRegex, stockStatsRawHtml);
@@ -87,15 +107,6 @@ private double ParseSwissRegexResult(Regex regex, string rawHtml) {
87107
return value;
88108
}
89109

90-
/// <summary>
91-
/// Return the name of the cache to be used for this manager.
92-
/// </summary>
93-
/// <returns>Name of the cache to use for this manager</returns>
94-
public override string GetCacheFileName() {
95-
return "swiss-stocks.json";
96-
}
97-
98110
#endregion Methods
99-
100111
}
101112
}

Program.cs

Lines changed: 29 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Configuration;
4-
using System.Diagnostics;
52
using System.IO;
63
using System.Threading.Tasks;
74
using RussellScreener.DataAccess;
@@ -18,32 +15,34 @@ public class Program {
1815
/// <param name="args">Command-line arguments. Not used</param>
1916
/// <returns>Task for async running</returns>
2017
public static async Task Main(string[] args) {
21-
StockRepositoryManager manager = new StockRepositoryManager();
22-
23-
SixSwissManager swissManager = new SixSwissManager();
24-
// StockRepository stockRepository = await swissManager.DownloadSwissStockInfos(@".\countries\switzerland\swiss-listings.csv");
25-
26-
// manager.WriteStockRepositoryCache(stockRepository, swissManager.GetCacheFileName());
27-
var stockRepository = manager.ReadStockRepositoryFromCache(swissManager.GetCacheFileName());
28-
29-
var screener = new Screener();
30-
Console.WriteLine("Analyzing data...");
31-
var finalPortfolio = screener.PickStocks(stockRepository);
32-
string xlxsFilename = ConfigurationManager.AppSettings["ModelPortfolioExcelFile"];
33-
34-
ExcelManager excelManager = new ExcelManager();
35-
excelManager.WriteExcelFile(xlxsFilename, finalPortfolio, stockRepository.Stocks);
18+
Console.WriteLine("1. Russell 1000");
19+
Console.WriteLine("2. Swiss Stocks");
20+
Console.WriteLine("-- Other keys to Exit --");
21+
var choice = Console.ReadKey().Key;
22+
23+
BaseDataAccessManager manager = null;
24+
string xlsxFilename = null;
25+
if (choice == ConsoleKey.D1) {
26+
manager = new IexManager();
27+
xlsxFilename = "russell_portfolio.xlsx";
28+
} else if (choice == ConsoleKey.D2) {
29+
manager = new SixSwissManager();
30+
xlsxFilename = "six_swiss_portfolio.xlsx";
31+
} else {
32+
return;
33+
}
3634

37-
// await ApplyRussell1000();
35+
await Process(manager, xlsxFilename);
3836
}
3937

40-
private static async Task ApplyRussell1000() {
41-
IexManager iex = new IexManager();
42-
var cacheName = iex.GetCacheFileName();
38+
private static async Task Process(BaseDataAccessManager dataAccessManager, string outputXlsFilename) {
39+
var cacheName = dataAccessManager.GetCacheFileName();
4340
FileInfo cacheFileInfo = new FileInfo(cacheName);
4441

45-
// Main workflow
46-
// 1. Load data from iShares (the Russell 100 current composition. See DownloadISharesComposition method)
42+
Screener screener = new Screener();
43+
44+
// Main workflow for Russell 1000. Workflows with other data access managers are similar.
45+
// 1. Load data from iShares (the Russell 1000 current composition. See DownloadISharesComposition method)
4746
// 2. Extract tickers
4847
// 3. Load data from IEX for each ticker (metrics + company info)
4948
// 4. Store these info into a "StockRepository" which is then saved on disk and used as a cache
@@ -52,8 +51,6 @@ private static async Task ApplyRussell1000() {
5251
// 6. Apply the strategy rules, get a list of stocks for the portfolio
5352
// 7. Generate an Excel file with results
5453

55-
Screener screener = new Screener();
56-
5754
bool refreshCache;
5855
if (!cacheFileInfo.Exists) {
5956
Console.WriteLine("No cache with stocks information exists.");
@@ -65,14 +62,12 @@ private static async Task ApplyRussell1000() {
6562
}
6663

6764
StockRepositoryManager manager = new StockRepositoryManager();
68-
6965
StockRepository stockRepository;
7066

7167
if (refreshCache) {
7268
Console.WriteLine("Refreshing the cache. Download will start now, please wait...");
73-
iex.DownloadISharesComposition(ETF_FILENAME);
74-
List<string> tickers = iex.ExtractISharesTickers(ETF_FILENAME);
75-
stockRepository = await iex.DownloadAllStocksFromIex(tickers);
69+
70+
stockRepository = await dataAccessManager.Process();
7671
manager.WriteStockRepositoryCache(stockRepository, cacheName);
7772
} else {
7873
Console.WriteLine("Reading cache...");
@@ -81,19 +76,18 @@ private static async Task ApplyRussell1000() {
8176

8277
Console.WriteLine("Analyzing data...");
8378
var finalPortfolio = screener.PickStocks(stockRepository);
84-
string xlxsFilename = ConfigurationManager.AppSettings["ModelPortfolioExcelFile"];
8579

8680
ExcelManager excelManager = new ExcelManager();
87-
excelManager.WriteExcelFile(xlxsFilename, finalPortfolio, stockRepository.Stocks);
81+
excelManager.WriteExcelFile(outputXlsFilename, finalPortfolio, stockRepository.Stocks);
8882

89-
Console.WriteLine($"An excel file has been generated with your portfolio selection. Name: {xlxsFilename}");
83+
Console.WriteLine($"An excel file has been generated with your portfolio selection. Name: {outputXlsFilename}");
9084
bool showExcel = AskQuestion("Do you want to open this Excel file now?");
9185
if (showExcel) {
92-
Process.Start(xlxsFilename);
86+
System.Diagnostics.Process.Start(outputXlsFilename);
9387
}
9488

9589
Console.WriteLine();
96-
Console.WriteLine("Press a key to close this application.");
90+
Console.WriteLine("Press Enter to close this application.");
9791
Console.ReadLine();
9892
}
9993

@@ -109,12 +103,6 @@ public static bool AskQuestion(string question) {
109103

110104
#endregion Methods
111105

112-
#region Fields
113-
114-
const string ETF_FILENAME = "russell.csv";
115-
116-
117-
#endregion Fields
118106

119107
}
120108
}

0 commit comments

Comments
 (0)