diff --git a/MonoStockPortfolio.Core/AndroidSqliteBase.cs b/MonoStockPortfolio.Core/AndroidSqliteBase.cs new file mode 100644 index 0000000..ba939a6 --- /dev/null +++ b/MonoStockPortfolio.Core/AndroidSqliteBase.cs @@ -0,0 +1,46 @@ +using Android.Content; +using Android.Database.Sqlite; +using Android.Util; + +namespace MonoStockPortfolio.Core +{ + public abstract class AndroidSqliteBase : SQLiteOpenHelper + { + public const string PORTFOLIO_TABLE_NAME = "Portfolios"; + public const string POSITION_TABLE_NAME = "Positions"; + public const string CONFIG_TABLE_NAME = "Config"; + + public const string DATABASE_NAME = "stockportfolio.db"; + public const int DATABASE_VERSION = 1; + + protected AndroidSqliteBase(Context context) + : base(context, DATABASE_NAME, null, DATABASE_VERSION) + { + } + + protected SQLiteDatabase Db { get { return WritableDatabase; } } + + public override void OnCreate(SQLiteDatabase db) + { + db.ExecSQL("CREATE TABLE " + PORTFOLIO_TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT)"); + db.ExecSQL("CREATE TABLE " + POSITION_TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, Ticker TEXT, Shares REAL, PricePerShare REAL, ContainingPortfolioID INT)"); + db.ExecSQL("CREATE TABLE " + CONFIG_TABLE_NAME + " (StockItems TEXT)"); + + db.ExecSQL("INSERT INTO " + CONFIG_TABLE_NAME + " (StockItems) VALUES ('2,0,1,3')"); + db.ExecSQL("INSERT INTO " + PORTFOLIO_TABLE_NAME + " (Name) VALUES ('Sample portfolio')"); + db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('GOOG', '500', '593.97', 1)"); + db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('AMZN', '500', '180.00', 1)"); + db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('AAPL', '500', '322.56', 1)"); + db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('MSFT', '500', '27.91', 1)"); + db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('NOVL', '500', '5.92', 1)"); + db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('S', '500', '4.23', 1)"); + db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('VZ', '500', '35.78', 1)"); + db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('T', '500', '29.38', 1)"); + } + + public override void OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + Log.Warn("Upgrade", "Nothing to upgrade"); + } + } +} \ No newline at end of file diff --git a/MonoStockPortfolio.Core/Config/AndroidSqliteConfigRepository.cs b/MonoStockPortfolio.Core/Config/AndroidSqliteConfigRepository.cs index aa80e5c..588347e 100644 --- a/MonoStockPortfolio.Core/Config/AndroidSqliteConfigRepository.cs +++ b/MonoStockPortfolio.Core/Config/AndroidSqliteConfigRepository.cs @@ -1,11 +1,44 @@ using System.Collections.Generic; +using System.Linq; +using Android.Content; using MonoStockPortfolio.Entities; namespace MonoStockPortfolio.Core.Config { - public class AndroidSqliteConfigRepository : IConfigRepository + public class AndroidSqliteConfigRepository : AndroidSqliteBase, IConfigRepository { + public AndroidSqliteConfigRepository(Context context) : base(context) + { } + public IEnumerable GetStockItems() + { + var cursor = Db.Query(CONFIG_TABLE_NAME, new[] { "StockItems" }, null, null, null, null, null); + string stockItemsCsv = null; + if (cursor.Count > 0) + { + cursor.MoveToNext(); + stockItemsCsv = cursor.GetString(0); + if (!cursor.IsClosed) cursor.Close(); + } + + if (string.IsNullOrEmpty(stockItemsCsv)) + { + return DefaultItems(); + } + + return stockItemsCsv.Split(',').Select(i => (StockDataItem)int.Parse(i)); + } + + public void UpdateStockItems(List stockDataItems) + { + var stockItemsCsv = string.Join(",", stockDataItems.Select(i => ((int) i).ToString()).ToArray()); + var contentValues = new ContentValues(); + contentValues.Put("StockItems", stockItemsCsv); + Db.Update(CONFIG_TABLE_NAME, contentValues, null, null); + } + + // this should never be called, but it's here anyway in case of some catastrophe + private static IEnumerable DefaultItems() { var items = new List(); items.Add(StockDataItem.Ticker); diff --git a/MonoStockPortfolio.Core/Config/IConfigRepository.cs b/MonoStockPortfolio.Core/Config/IConfigRepository.cs index d8e2cbf..2cd9755 100644 --- a/MonoStockPortfolio.Core/Config/IConfigRepository.cs +++ b/MonoStockPortfolio.Core/Config/IConfigRepository.cs @@ -6,5 +6,6 @@ namespace MonoStockPortfolio.Core.Config public interface IConfigRepository { IEnumerable GetStockItems(); + void UpdateStockItems(List stockDataItems); } } \ No newline at end of file diff --git a/MonoStockPortfolio.Core/EnumExtensions.cs b/MonoStockPortfolio.Core/EnumExtensions.cs index dd1e98d..fa8a8a5 100644 --- a/MonoStockPortfolio.Core/EnumExtensions.cs +++ b/MonoStockPortfolio.Core/EnumExtensions.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using MonoStockPortfolio.Entities; namespace MonoStockPortfolio.Core @@ -21,5 +24,16 @@ namespace MonoStockPortfolio.Core } return string.Empty; } + + public static IEnumerable GetValues(this Enum value) + { + var enumerations = new List(); + var fields = value.GetType().GetFields(BindingFlags.Static | BindingFlags.Public); + foreach (var fieldInfo in fields) + { + enumerations.Add((Enum)fieldInfo.GetValue(value)); + } + return enumerations.Cast(); + } } } \ No newline at end of file diff --git a/MonoStockPortfolio.Core/MonoStockPortfolio.Core.csproj b/MonoStockPortfolio.Core/MonoStockPortfolio.Core.csproj index eaad376..034c91e 100644 --- a/MonoStockPortfolio.Core/MonoStockPortfolio.Core.csproj +++ b/MonoStockPortfolio.Core/MonoStockPortfolio.Core.csproj @@ -31,11 +31,7 @@ 4 - - ..\libs\FileHelpers.dll - - @@ -46,14 +42,15 @@ + + + - - diff --git a/MonoStockPortfolio.Core/PortfolioRepositories/AndroidSqlitePortfolioRepository.cs b/MonoStockPortfolio.Core/PortfolioRepositories/AndroidSqlitePortfolioRepository.cs index 8881496..f1a8369 100644 --- a/MonoStockPortfolio.Core/PortfolioRepositories/AndroidSqlitePortfolioRepository.cs +++ b/MonoStockPortfolio.Core/PortfolioRepositories/AndroidSqlitePortfolioRepository.cs @@ -7,41 +7,32 @@ using MonoStockPortfolio.Entities; namespace MonoStockPortfolio.Core.PortfolioRepositories { - public class AndroidSqlitePortfolioRepository : IPortfolioRepository + public class AndroidSqlitePortfolioRepository : AndroidSqliteBase, IPortfolioRepository { - private OpenHelper _dbHelper; - private SQLiteDatabase _db; - private const string PORTFOLIO_TABLE_NAME = "Portfolios"; - private const string DATABASE_NAME = "stockportfolio.db"; - private const int DATABASE_VERSION = 1; - private const string POSITION_TABLE_NAME = "Positions"; - public AndroidSqlitePortfolioRepository(Context context) - { - _dbHelper = new OpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION); - _db = _dbHelper.WritableDatabase; - } + : base(context) + { } public IList GetAllPortfolios() { var list = new List(); - var cursor = _db.Query(PORTFOLIO_TABLE_NAME, new[] {"id", "Name"}, null, null, null, null, null); - if(cursor.Count > 0) + var cursor = Db.Query(PORTFOLIO_TABLE_NAME, new[] { "id", "Name" }, null, null, null, null, null); + if (cursor.Count > 0) { - while(cursor.MoveToNext()) + while (cursor.MoveToNext()) { var portfolio = new Portfolio(cursor.GetInt(0)); portfolio.Name = cursor.GetString(1); list.Add(portfolio); } } - if(!cursor.IsClosed) cursor.Close(); + if (!cursor.IsClosed) cursor.Close(); return list; } public Portfolio GetPortfolioById(long portfolioId) { - var cursor = _db.Query(PORTFOLIO_TABLE_NAME, new[] { "id", "Name" }, " ID = " + portfolioId, null, null, null, null); + var cursor = Db.Query(PORTFOLIO_TABLE_NAME, new[] { "id", "Name" }, " ID = " + portfolioId, null, null, null, null); if (cursor.Count > 0) { cursor.MoveToNext(); @@ -67,26 +58,26 @@ namespace MonoStockPortfolio.Core.PortfolioRepositories public void DeletePortfolioById(int portfolioId) { - _db.BeginTransaction(); + Db.BeginTransaction(); try { - _db.Delete(PORTFOLIO_TABLE_NAME, "id = " + portfolioId, null); - _db.Delete(POSITION_TABLE_NAME, "ContainingPortfolioID = " + portfolioId, null); - _db.SetTransactionSuccessful(); + Db.Delete(PORTFOLIO_TABLE_NAME, "id = " + portfolioId, null); + Db.Delete(POSITION_TABLE_NAME, "ContainingPortfolioID = " + portfolioId, null); + Db.SetTransactionSuccessful(); } catch (SQLiteException) { - Log.E("DeletePortfolio", "SQLiteException => Id = " + portfolioId); + Log.Error("DeletePortfolio", "SQLiteException => Id = " + portfolioId); } finally { - _db.EndTransaction(); + Db.EndTransaction(); } } public Portfolio GetPortfolioByName(string portfolioName) { - var cursor = _db.Query(PORTFOLIO_TABLE_NAME, new[] { "id", "Name" }, " Name = '" + portfolioName + "'", null, null, null, null); + var cursor = Db.Query(PORTFOLIO_TABLE_NAME, new[] { "id", "Name" }, " Name = '" + portfolioName + "'", null, null, null, null); if (cursor.Count > 0) { cursor.MoveToNext(); @@ -100,19 +91,19 @@ namespace MonoStockPortfolio.Core.PortfolioRepositories public void DeletePositionById(long positionId) { - _db.Delete(POSITION_TABLE_NAME, "id = " + positionId, null); + Db.Delete(POSITION_TABLE_NAME, "id = " + positionId, null); } public Position GetPositionById(long positionId) { Position position = null; - var cursor = _db.Query(POSITION_TABLE_NAME, new[] { "id", "Ticker", "Shares", "PricePerShare" }, " id = " + positionId, null, null, null, null); + var cursor = Db.Query(POSITION_TABLE_NAME, new[] { "id", "Ticker", "Shares", "PricePerShare" }, " id = " + positionId, null, null, null, null); if (cursor.Count > 0) { while (cursor.MoveToNext()) { - position= new Position(cursor.GetInt(0)); + position = new Position(cursor.GetInt(0)); position.Ticker = cursor.GetString(1); position.Shares = Convert.ToDecimal(cursor.GetFloat(2)); position.PricePerShare = Convert.ToDecimal(cursor.GetFloat(3)); @@ -126,7 +117,7 @@ namespace MonoStockPortfolio.Core.PortfolioRepositories { var list = new List(); - var cursor = _db.Query(POSITION_TABLE_NAME, new[] { "id", "Ticker", "Shares", "PricePerShare" }, " ContainingPortfolioID = " + portfolioId, null, null, null, null); + var cursor = Db.Query(POSITION_TABLE_NAME, new[] { "id", "Ticker", "Shares", "PricePerShare" }, " ContainingPortfolioID = " + portfolioId, null, null, null, null); if (cursor.Count > 0) { while (cursor.MoveToNext()) @@ -157,23 +148,23 @@ namespace MonoStockPortfolio.Core.PortfolioRepositories private void UpdateExistingPortfolio(Portfolio portfolio) { var portfolioID = portfolio.ID ?? -1; - Log.E("UpdateExistingPortfolio", "Portfolios updated: " + _db.Update(PORTFOLIO_TABLE_NAME, GetPortfolioContentValues(portfolio), "id = " + portfolioID, null)); + Log.Error("UpdateExistingPortfolio", "Portfolios updated: " + Db.Update(PORTFOLIO_TABLE_NAME, GetPortfolioContentValues(portfolio), "id = " + portfolioID, null)); } private void InsertNewPortfolio(Portfolio portfolio) { - Log.E("InsertNewPortfolio", "Portfolios inserted: " + _db.Insert(PORTFOLIO_TABLE_NAME, null, GetPortfolioContentValues(portfolio))); + Log.Error("InsertNewPortfolio", "Portfolios inserted: " + Db.Insert(PORTFOLIO_TABLE_NAME, null, GetPortfolioContentValues(portfolio))); } private void UpdateExistingPosition(Position position) { var positionID = position.ID ?? -1; - Log.E("UpdateExistingPosition", "Positions updated: " + _db.Update(POSITION_TABLE_NAME, GetPositionContentValues(position), "id = " + positionID, null)); + Log.Error("UpdateExistingPosition", "Positions updated: " + Db.Update(POSITION_TABLE_NAME, GetPositionContentValues(position), "id = " + positionID, null)); } private void InsertNewPosition(Position position) { - Log.E("InsertNewPosition", "Positions inserted: " + _db.Insert(POSITION_TABLE_NAME, null, GetPositionContentValues(position))); + Log.Error("InsertNewPosition", "Positions inserted: " + Db.Insert(POSITION_TABLE_NAME, null, GetPositionContentValues(position))); } private static ContentValues GetPortfolioContentValues(Portfolio portfolio) @@ -192,41 +183,5 @@ namespace MonoStockPortfolio.Core.PortfolioRepositories positionValues.Put("ContainingPortfolioID", position.ContainingPortfolioID); return positionValues; } - - - - - - - - - private class OpenHelper : SQLiteOpenHelper - { - public OpenHelper(Context context, string name, SQLiteDatabase.ICursorFactory factory, int version) - : base(context, name, factory, version) - { - } - - public override void OnCreate(SQLiteDatabase db) - { - db.ExecSQL("CREATE TABLE " + PORTFOLIO_TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT)"); - db.ExecSQL("CREATE TABLE " + POSITION_TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, Ticker TEXT, Shares REAL, PricePerShare REAL, ContainingPortfolioID INT)"); - - db.ExecSQL("INSERT INTO " + PORTFOLIO_TABLE_NAME + " (Name) VALUES ('Sample portfolio')"); - db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('GOOG', '500', '593.97', 1)"); - db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('AMZN', '500', '180.00', 1)"); - db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('AAPL', '500', '322.56', 1)"); - db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('MSFT', '500', '27.91', 1)"); - db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('NOVL', '500', '5.92', 1)"); - db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('S', '500', '4.23', 1)"); - db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('VZ', '500', '35.78', 1)"); - db.ExecSQL("INSERT INTO " + POSITION_TABLE_NAME + " (Ticker, Shares, PricePerShare, ContainingPortfolioID) VALUES ('T', '500', '29.38', 1)"); - } - - public override void OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) - { - Log.W("Upgrade", "Nothing to upgrade"); - } - } } } \ No newline at end of file diff --git a/MonoStockPortfolio.Core/Services/PortfolioService.cs b/MonoStockPortfolio.Core/Services/PortfolioService.cs index 2edd6ec..8780cfd 100644 --- a/MonoStockPortfolio.Core/Services/PortfolioService.cs +++ b/MonoStockPortfolio.Core/Services/PortfolioService.cs @@ -50,7 +50,7 @@ namespace MonoStockPortfolio.Core.Services } catch (Exception ex) { - Log.E("GetDetailedItems", ex.ToString()); + Log.Error("GetDetailedItems", ex.ToString()); throw; } } diff --git a/MonoStockPortfolio.Core/StockData/CsvParser.cs b/MonoStockPortfolio.Core/StockData/CsvParser.cs new file mode 100644 index 0000000..1353f30 --- /dev/null +++ b/MonoStockPortfolio.Core/StockData/CsvParser.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Android.Util; + +namespace MonoStockPortfolio.Core.StockData +{ + public class CsvParser + { + public static IEnumerable ParseCsvIntoStockQuotes(string csvText) + { + using (var sr = new StringReader(csvText)) + { + var lines = new List(); + + try + { + string line; + while ((line = sr.ReadLine()) != null) + { + var tokens = line.Split(','); + for (int i = 0; i < tokens.Length; i++) + { + tokens[i] = tokens[i].Trim('\"'); + } + lines.Add(tokens); + } + } + catch (Exception) + { + Log.Error("ParseCSV", "Error in retrieving/parsing stock information"); + } + + return lines; + } + } + } +} \ No newline at end of file diff --git a/MonoStockPortfolio.Core/StockData/GoogleStockDataProvider.cs b/MonoStockPortfolio.Core/StockData/GoogleStockDataProvider.cs new file mode 100644 index 0000000..630aab6 --- /dev/null +++ b/MonoStockPortfolio.Core/StockData/GoogleStockDataProvider.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using MonoStockPortfolio.Entities; + +namespace MonoStockPortfolio.Core.StockData +{ + public class GoogleStockDataProvider : IStockDataProvider + { + private const string BASE_URL = "http://www.google.com/finance/info?infotype=infoquoteall&q="; + /* + http://code.google.com/p/qsb-mac-plugins/source/browse/trunk/stock-quoter/trunk/StockQuoter.py?r=4 + The Google Finance feed can return some or all of the following keys: + + avvo * Average volume (float with multiplier, like '3.54M') + beta * Beta (float) + c * Amount of change while open (float) + ccol * (unknown) (chars) + cl Last perc. change + cp * Change perc. while open (float) + e * Exchange (text, like 'NASDAQ') + ec * After hours last change from close (float) + eccol * (unknown) (chars) + ecp * After hours last chage perc. from close (float) + el * After. hours last quote (float) + el_cur * (unknown) (float) + elt After hours last quote time (unknown) + eo * Exchange Open (0 or 1) + eps * Earnings per share (float) + fwpe Forward PE ratio (float) + hi * Price high (float) + hi52 * 52 weeks high (float) + id * Company id (identifying number) + l * Last value while open (float) + l_cur * Last value at close (like 'l') + lo * Price low (float) + lo52 * 52 weeks low (float) + lt Last value date/time + ltt Last trade time (Same as "lt" without the data) + mc * Market cap. (float with multiplier, like '123.45B') + name * Company name (text) + op * Open price (float) + pe * PE ratio (float) + t * Ticker (text) + type * Type (i.e. 'Company') + vo * Volume (float with multiplier, like '3.54M') + */ + + public IEnumerable GetStockQuotes(IEnumerable tickers) + { + var tickerCsv = string.Join(",", tickers.ToArray()); + var url = BASE_URL + tickerCsv; + var jsonResults = ScrapeUrl(url).Split('}'); + + return jsonResults.Select(MapJsonToStockitems); + } + + protected StockQuote MapJsonToStockitems(string jsonResults) + { + using(var sr = new StringReader(jsonResults)) + { + var sq = new StockQuote(); + string line; + while((line = sr.ReadLine()) != null) + { + if(line.StartsWith(",\"t\"")) + { + sq.Ticker = line.Replace(",\"t\" : ", "").Trim().Trim('"'); + continue; + } + if(line.StartsWith(",\"c\"")) + { + sq.Change = decimal.Parse(line.Replace(",\"c\" : ", "").Trim().Trim('"')); + continue; + } + if(line.StartsWith(",\"l\"")) + { + sq.LastTradePrice = decimal.Parse(line.Replace(",\"l\" : ", "").Trim().Trim('"')); + continue; + } + if(line.StartsWith(",\"ltt\"")) + { + sq.LastTradeTime = line.Replace(",\"ltt\":", "").Trim().Trim('"').Replace("EST","").Trim(); + continue; + } + } + return sq; + } + } + + private static string ScrapeUrl(string url) + { + string resultCsv; + var req = WebRequest.Create(url); + var resp = req.GetResponse(); + using (var sr = new StreamReader(resp.GetResponseStream())) + { + resultCsv = sr.ReadToEnd(); + sr.Close(); + } + return resultCsv; + } + } +} \ No newline at end of file diff --git a/MonoStockPortfolio.Core/StockData/YahooFinanceStockData.cs b/MonoStockPortfolio.Core/StockData/YahooFinanceStockData.cs deleted file mode 100644 index 16462d4..0000000 --- a/MonoStockPortfolio.Core/StockData/YahooFinanceStockData.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FileHelpers; - -namespace MonoStockPortfolio.Core.StockData -{ - [DelimitedRecord(",")] - public class YahooFinanceStockData - { - [FieldQuoted(QuoteMode.OptionalForBoth)] - public string Ticker; - - public decimal LastTradePrice; - - [FieldQuoted(QuoteMode.OptionalForBoth)] - public string Name; - - public string Volume; - - public decimal Change; - - [FieldQuoted(QuoteMode.OptionalForBoth)] - public string LastTradeTime; - - [FieldQuoted(QuoteMode.OptionalForBoth)] - public string RealTimeLastTradeWithTime; - - [FieldQuoted(QuoteMode.OptionalForBoth)] - public string ChangeRealTime; - } -} \ No newline at end of file diff --git a/MonoStockPortfolio.Core/StockData/YahooStockDataProvider.cs b/MonoStockPortfolio.Core/StockData/YahooStockDataProvider.cs deleted file mode 100644 index 871c317..0000000 --- a/MonoStockPortfolio.Core/StockData/YahooStockDataProvider.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using Android.Util; -using FileHelpers; -using MonoStockPortfolio.Entities; - -namespace MonoStockPortfolio.Core.StockData -{ - public class YahooStockDataProvider : IStockDataProvider - { - private const string LAST_TRADE_PRICE_ONLY = "l1"; - private const string NAME = "n"; - private const string VOLUME = "v"; - private const string TICKER_SYMBOL = "s"; - private const string CHANGE = "c1"; - private const string LAST_TRADE_TIME = "t1"; - private const string REAL_TIME_LAST_TRADE_WITH_TIME = "k1"; - private const string REAL_TIME_CHANGE = "c6"; - - // http://www.gummy-stuff.org/Yahoo-data.htm - // http://finance.yahoo.com/d/quotes.csv?s= a BUNCH of - // STOCK SYMBOLS separated by "+" &f=a bunch of special tags - public IEnumerable GetStockQuotes(IEnumerable tickers) - { - string url = "http://finance.yahoo.com/d/quotes.csv?s="; - url += string.Join("+", tickers.ToArray()); - url += "&f="; - url += TICKER_SYMBOL; - url += LAST_TRADE_PRICE_ONLY; - url += NAME; - url += VOLUME; - url += CHANGE; - url += LAST_TRADE_TIME; - url += REAL_TIME_LAST_TRADE_WITH_TIME; - url += REAL_TIME_CHANGE; - - string resultCsv = ScrapeUrl(url); - - var yahooQuoteData = ParseCsvIntoStockQuotes(resultCsv); - - foreach (var quote in yahooQuoteData) - { - yield return MapYahooData(quote); - } - } - - private static StockQuote MapYahooData(YahooFinanceStockData data) - { - if(data == null) - { - return null; - } - var stock = new StockQuote(); - stock.Name = data.Name; - stock.LastTradePrice = data.LastTradePrice; - stock.Ticker = data.Ticker; - stock.Volume = data.Volume; - stock.Change = data.Change; - stock.LastTradeTime = data.LastTradeTime; - stock.RealTimeLastTradePrice = decimal.Parse(data.RealTimeLastTradeWithTime - .Replace("", "") - .Replace("", "") - .Replace("N/A -", "") - .Trim() - ); - stock.ChangeRealTime = data.ChangeRealTime; - return stock; - } - - private static IList ParseCsvIntoStockQuotes(string csv) - { - var engine = new FileHelperEngine(typeof(YahooFinanceStockData)); - var stockQuotes = engine.ReadString(csv) as YahooFinanceStockData[]; - if(stockQuotes == null) - { - throw new ArgumentException("Could not parse CSV input"); - } - return stockQuotes; - } - - private static string ScrapeUrl(string url) - { - try - { - string resultCsv; - var req = WebRequest.Create(url); - var resp = req.GetResponse(); - using (var sr = new StreamReader(resp.GetResponseStream())) - { - resultCsv = sr.ReadToEnd(); - sr.Close(); - } - return resultCsv; - } - catch (Exception ex) - { - Log.E("ScrapeUrlException", ex.ToString()); - throw; - } - } - } -} \ No newline at end of file diff --git a/MonoStockPortfolio.Entities/MonoStockPortfolio.Entities.csproj b/MonoStockPortfolio.Entities/MonoStockPortfolio.Entities.csproj index a64137f..a5c0882 100644 --- a/MonoStockPortfolio.Entities/MonoStockPortfolio.Entities.csproj +++ b/MonoStockPortfolio.Entities/MonoStockPortfolio.Entities.csproj @@ -31,7 +31,6 @@ 4 - diff --git a/MonoStockPortfolio.Entities/StockDataItem.cs b/MonoStockPortfolio.Entities/StockDataItem.cs index 929dd94..f1020d5 100644 --- a/MonoStockPortfolio.Entities/StockDataItem.cs +++ b/MonoStockPortfolio.Entities/StockDataItem.cs @@ -3,22 +3,22 @@ namespace MonoStockPortfolio.Entities public enum StockDataItem { [StringValue("Change")] - Change, + Change = 0, [StringValue("Gain/Loss")] - GainLoss, + GainLoss = 1, [StringValue("Ticker")] - Ticker, + Ticker = 2, [StringValue("Time")] - Time, + Time = 3, [StringValue("Volume")] - Volume, + Volume = 4, [StringValue("Price")] - LastTradePrice, + LastTradePrice = 5, [StringValue("Price-RT")] - RealTimeLastTradeWithTime, + RealTimeLastTradeWithTime = 6, [StringValue("Change-RT")] - ChangeRealTime, + ChangeRealTime = 7, [StringValue("Gain/Loss-RT")] - GainLossRealTime + GainLossRealTime = 8 } } \ No newline at end of file diff --git a/MonoStockPortfolio.Tests/CsvParserTests.cs b/MonoStockPortfolio.Tests/CsvParserTests.cs new file mode 100644 index 0000000..275a844 --- /dev/null +++ b/MonoStockPortfolio.Tests/CsvParserTests.cs @@ -0,0 +1,47 @@ +using System.Linq; +using MonoStockPortfolio.Core.StockData; +using Xunit; + +namespace MonoStockPortfolio.Tests +{ + public class CsvParserTests + { + [Fact] + public void Can_parse_a_single_line_with_a_single_token() + { + var line = "XIN"; + var result = CsvParser.ParseCsvIntoStockQuotes(line); + + Assert.Equal(result.Count(), 1); + Assert.Equal(result.First()[0], "XIN"); + } + + [Fact] + public void Can_parse_two_lines_with_a_single_token_each() + { + var line = "XIN\nMSFT"; + var result = CsvParser.ParseCsvIntoStockQuotes(line); + + Assert.Equal(result.Count(), 2); + Assert.Equal(result.First()[0], "XIN"); + Assert.Equal(result.ElementAt(1)[0], "MSFT"); + } + + [Fact] + public void Can_parse_a_more_complex_set() + { + var line = @"""XIN"",2.41,""Xinyuan Real Esta"",269244,-0.03,""4:00pm"",""N/A - 2.41"",""-0.03"""; + var result = CsvParser.ParseCsvIntoStockQuotes(line); + + Assert.Equal(result.Count(), 1); + Assert.Equal(result.First()[0], "XIN"); + Assert.Equal(result.First()[1], "2.41"); + Assert.Equal(result.First()[2], "Xinyuan Real Esta"); + Assert.Equal(result.First()[3], "269244"); + Assert.Equal(result.First()[4], "-0.03"); + Assert.Equal(result.First()[5], "4:00pm"); + Assert.Equal(result.First()[6], "N/A - 2.41"); + Assert.Equal(result.First()[7], "-0.03"); + } + } +} diff --git a/MonoStockPortfolio.Tests/GoogleStockQuoteTests.cs b/MonoStockPortfolio.Tests/GoogleStockQuoteTests.cs new file mode 100644 index 0000000..36e35f5 --- /dev/null +++ b/MonoStockPortfolio.Tests/GoogleStockQuoteTests.cs @@ -0,0 +1,170 @@ +using MonoStockPortfolio.Core.StockData; +using Xunit; + +namespace MonoStockPortfolio.Tests +{ + public class GoogleStockQuoteTests : GoogleStockDataProvider + { + #region ExampleJson : Example Json Result + private string ExampleJson = + @" +// [ { +""id"": ""720780"" +,""t"" : ""XIN"" +,""e"" : ""NYSE"" +,""l"" : ""2.41"" +,""l_cur"" : ""2.41"" +,""s"": ""0"" +,""ltt"":""4:00PM EST"" +,""lt"" : ""Feb 4, 4:00PM EST"" +,""c"" : ""-0.03"" +,""cp"" : ""-1.23"" +,""ccol"" : ""chr"" +,""eo"" : """" +,""delay"": """" +,""op"" : ""2.45"" +,""hi"" : ""2.45"" +,""lo"" : ""2.39"" +,""vo"" : ""269,244.00"" +,""avvo"" : ""352,270.00"" +,""hi52"" : ""4.30"" +,""lo52"" : ""2.20"" +,""mc"" : ""182.78M"" +,""pe"" : ""3.47"" +,""fwpe"" : """" +,""beta"" : ""1.30"" +,""eps"" : ""0.69"" +,""name"" : ""Xinyuan Real Estate Co., Ltd. (ADR)"" +,""type"" : ""Company"" +} +,{ +""id"": ""358464"" +,""t"" : ""MSFT"" +,""e"" : ""NASDAQ"" +,""l"" : ""27.77"" +,""l_cur"" : ""27.77"" +,""s"": ""2"" +,""ltt"":""4:01PM EST"" +,""lt"" : ""Feb 4, 4:01PM EST"" +,""c"" : ""+0.12"" +,""cp"" : ""0.43"" +,""ccol"" : ""chg"" +,""el"": ""27.72"" +,""el_cur"": ""27.72"" +,""elt"" : ""Feb 4, 7:39PM EST"" +,""ec"" : ""-0.05"" +,""ecp"" : ""-0.18"" +,""eccol"" : ""chr"" +,""div"" : ""0.16"" +,""yld"" : ""2.30"" +,""eo"" : """" +,""delay"": """" +,""op"" : ""27.73"" +,""hi"" : ""27.84"" +,""lo"" : ""27.51"" +,""vo"" : ""40.42M"" +,""avvo"" : ""55.50M"" +,""hi52"" : ""31.58"" +,""lo52"" : ""22.73"" +,""mc"" : ""233.33B"" +,""pe"" : ""11.77"" +,""fwpe"" : """" +,""beta"" : ""1.06"" +,""eps"" : ""2.36"" +,""name"" : ""Microsoft Corporation"" +,""type"" : ""Company"" +} +,{ +""id"": ""22144"" +,""t"" : ""AAPL"" +,""e"" : ""NASDAQ"" +,""l"" : ""346.50"" +,""l_cur"" : ""346.50"" +,""s"": ""2"" +,""ltt"":""4:02PM EST"" +,""lt"" : ""Feb 4, 4:02PM EST"" +,""c"" : ""+3.06"" +,""cp"" : ""0.89"" +,""ccol"" : ""chg"" +,""el"": ""346.48"" +,""el_cur"": ""346.48"" +,""elt"" : ""Feb 4, 7:59PM EST"" +,""ec"" : ""-0.02"" +,""ecp"" : ""-0.01"" +,""eccol"" : ""chr"" +,""div"" : """" +,""yld"" : """" +,""eo"" : """" +,""delay"": """" +,""op"" : ""343.76"" +,""hi"" : ""346.70"" +,""lo"" : ""343.51"" +,""vo"" : ""11.49M"" +,""avvo"" : ""15.58M"" +,""hi52"" : ""348.60"" +,""lo52"" : ""190.85"" +,""mc"" : ""319.22B"" +,""pe"" : ""19.35"" +,""fwpe"" : """" +,""beta"" : ""1.38"" +,""eps"" : ""17.91"" +,""name"" : ""Apple Inc."" +,""type"" : ""Company"" +} +] +"; + #endregion + + private string[] SplitResults + { + get + { + return ExampleJson.Split('}'); + } + } + + [Fact] + public void Test_ticker() + { + var results = base.MapJsonToStockitems(SplitResults[0]); + Assert.Equal(results.Ticker,"XIN"); + results = base.MapJsonToStockitems(SplitResults[1]); + Assert.Equal(results.Ticker,"MSFT"); + results = base.MapJsonToStockitems(SplitResults[2]); + Assert.Equal(results.Ticker,"AAPL"); + } + + [Fact] + public void Test_change() + { + var results = base.MapJsonToStockitems(SplitResults[0]); + Assert.Equal(results.Change, -0.03M); + results = base.MapJsonToStockitems(SplitResults[1]); + Assert.Equal(results.Change, 0.12M); + results = base.MapJsonToStockitems(SplitResults[2]); + Assert.Equal(results.Change, 3.06M); + } + + [Fact] + public void Test_last_price() + { + var results = base.MapJsonToStockitems(SplitResults[0]); + Assert.Equal(results.LastTradePrice, 2.41M); + results = base.MapJsonToStockitems(SplitResults[1]); + Assert.Equal(results.LastTradePrice, 27.77M); + results = base.MapJsonToStockitems(SplitResults[2]); + Assert.Equal(results.LastTradePrice, 346.50M); + } + + [Fact] + public void Test_time() + { + var results = base.MapJsonToStockitems(SplitResults[0]); + Assert.Equal(results.LastTradeTime, "4:00PM"); + results = base.MapJsonToStockitems(SplitResults[1]); + Assert.Equal(results.LastTradeTime, "4:01PM"); + results = base.MapJsonToStockitems(SplitResults[2]); + Assert.Equal(results.LastTradeTime, "4:02PM"); + } + } +} \ No newline at end of file diff --git a/MonoStockPortfolio.Tests/MonoStockPortfolio.Tests.csproj b/MonoStockPortfolio.Tests/MonoStockPortfolio.Tests.csproj new file mode 100644 index 0000000..597edbc --- /dev/null +++ b/MonoStockPortfolio.Tests/MonoStockPortfolio.Tests.csproj @@ -0,0 +1,86 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {C2797FAB-AFAB-49F6-9131-FC9BF03CAB9D} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + MonoStockPortfolio.Tests + MonoStockPortfolio.Tests + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + True + + + True + + + True + + + True + + + True + + + True + + + ..\libs\xunit.dll + + + + + + + + + + {251E7BB4-CFE2-4DE4-9E2A-AAE1AF41C8CB} + MonoStockPortfolio.Core + + + {05A57650-3B41-46FF-9EAD-9112B5EFBEED} + MonoStockPortfolio.Entities + + + {E23D8575-CE4E-4716-B9C7-70115D23ADBB} + MonoStockPortfolio + + + + + + + + \ No newline at end of file diff --git a/MonoStockPortfolio.Tests/PostSharp.Custom.targets b/MonoStockPortfolio.Tests/PostSharp.Custom.targets new file mode 100644 index 0000000..38368c7 --- /dev/null +++ b/MonoStockPortfolio.Tests/PostSharp.Custom.targets @@ -0,0 +1,6 @@ + + + + Silverlight20 + + \ No newline at end of file diff --git a/MonoStockPortfolio.Tests/Properties/AssemblyInfo.cs b/MonoStockPortfolio.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2c0dd27 --- /dev/null +++ b/MonoStockPortfolio.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MonoStockPortfolio.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("MonoStockPortfolio.Tests")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MonoStockPortfolio.sln b/MonoStockPortfolio.sln index 8c834bf..10045dc 100644 --- a/MonoStockPortfolio.sln +++ b/MonoStockPortfolio.sln @@ -8,15 +8,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{643BA3D4-E libs\FileHelpers.dll = libs\FileHelpers.dll EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{4EE7F6AD-B8A9-4402-800E-E4C8AE0FF8FB}" - ProjectSection(SolutionItems) = preProject - assets\UInotes.txt = assets\UInotes.txt - EndProjectSection -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoStockPortfolio.Core", "MonoStockPortfolio.Core\MonoStockPortfolio.Core.csproj", "{251E7BB4-CFE2-4DE4-9E2A-AAE1AF41C8CB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoStockPortfolio.Entities", "MonoStockPortfolio.Entities\MonoStockPortfolio.Entities.csproj", "{05A57650-3B41-46FF-9EAD-9112B5EFBEED}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoStockPortfolio.Tests", "MonoStockPortfolio.Tests\MonoStockPortfolio.Tests.csproj", "{C2797FAB-AFAB-49F6-9131-FC9BF03CAB9D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +32,10 @@ Global {05A57650-3B41-46FF-9EAD-9112B5EFBEED}.Debug|Any CPU.Build.0 = Debug|Any CPU {05A57650-3B41-46FF-9EAD-9112B5EFBEED}.Release|Any CPU.ActiveCfg = Release|Any CPU {05A57650-3B41-46FF-9EAD-9112B5EFBEED}.Release|Any CPU.Build.0 = Release|Any CPU + {C2797FAB-AFAB-49F6-9131-FC9BF03CAB9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2797FAB-AFAB-49F6-9131-FC9BF03CAB9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2797FAB-AFAB-49F6-9131-FC9BF03CAB9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2797FAB-AFAB-49F6-9131-FC9BF03CAB9D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MonoStockPortfolio/Activites/ConfigActivity.cs b/MonoStockPortfolio/Activites/ConfigActivity.cs new file mode 100644 index 0000000..e4b5f32 --- /dev/null +++ b/MonoStockPortfolio/Activites/ConfigActivity.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Preferences; +using MonoStockPortfolio.Core.Config; +using MonoStockPortfolio.Entities; +using MonoStockPortfolio.Framework; +using MonoStockPortfolio.Core; + +namespace MonoStockPortfolio.Activites +{ + [Activity(Label = "Config")] + public class ConfigActivity : PreferenceActivity + { + [IoC] private IConfigRepository _repo; + private StockItemPreference[] _stockItemsConfig; + + protected override void OnCreate(Bundle bundle) + { + base.OnCreate(bundle); + + AddPreferencesFromResource(Resource.Layout.config); + + _stockItemsConfig = StockItemPreference.BuildList(_repo.GetStockItems()).ToArray(); + + var customPref = FindPreference("customStockItems"); + customPref.PreferenceClick += customPref_PreferenceClick; + } + + bool customPref_PreferenceClick(Preference preference) + { + IEnumerable[] stockItemsDisplay = _stockItemsConfig.OrderBy(i => i.StockDataItem).Select(i => i.StockDataItem.GetStringValue()).ToArray(); + bool[] allitemschecked = _stockItemsConfig.OrderBy(i => i.StockDataItem).Select(i => i.IsChecked).ToArray(); + + var dialog = new AlertDialog.Builder(this); + dialog.SetMultiChoiceItems(stockItemsDisplay, allitemschecked, clickCallback); + dialog.SetTitle("Select columns"); + dialog.SetPositiveButton("Save", okCallback); + dialog.Create().Show(); + return true; + } + + private void okCallback(object sender, DialogClickEventArgs e) + { + var list = _stockItemsConfig.Where(i => i.IsChecked).Select(i => i.StockDataItem).ToList(); + _repo.UpdateStockItems(list); + } + + private void clickCallback(object sender, DialogMultiChoiceClickEventArgs e) + { + _stockItemsConfig[e.Which].IsChecked = e.IsChecked; + } + + public static string ClassName { get { return "monostockportfolio.activites.ConfigActivity"; } } + + private class StockItemPreference + { + public static IEnumerable BuildList(IEnumerable checkedItems) + { + var allitems = StockDataItem.Change.GetValues(); + + return allitems.Select(item => new StockItemPreference {StockDataItem = item, IsChecked = checkedItems.Contains(item)}); + } + public StockDataItem StockDataItem { get; private set; } + public bool IsChecked { get; set; } + } + } +} \ No newline at end of file diff --git a/MonoStockPortfolio/Activites/EditPortfolioActivity.cs b/MonoStockPortfolio/Activites/EditPortfolioActivity.cs index 6afcf8a..cf9203c 100644 --- a/MonoStockPortfolio/Activites/EditPortfolioActivity.cs +++ b/MonoStockPortfolio/Activites/EditPortfolioActivity.cs @@ -16,7 +16,7 @@ namespace MonoStockPortfolio.Activites { base.OnCreate(bundle); - SetContentView(Resource.layout.addportfolio); + SetContentView(Resource.Layout.addportfolio); WireUpEvents(); diff --git a/MonoStockPortfolio/Activites/EditPortfolioActivity.designer.cs b/MonoStockPortfolio/Activites/EditPortfolioActivity.designer.cs index 460f96a..bc6555d 100644 --- a/MonoStockPortfolio/Activites/EditPortfolioActivity.designer.cs +++ b/MonoStockPortfolio/Activites/EditPortfolioActivity.designer.cs @@ -8,7 +8,7 @@ namespace MonoStockPortfolio.Activites public static string Extra_PortfolioID { get { return "monoStockPortfolio.EditPortfolioActivity.PortfolioID"; } } public long ThisPortfolioId { get { return Intent.GetLongExtra(Extra_PortfolioID, -1); } } - protected Button SaveButton { get { return FindViewById