Yahoo! Managed
使用 .NET 下载金融数据、管理在线投资组合或使用 Yahoo! 的 Search BOSS。
引言
Yahoo! 提供的 Web 服务很不错,但您需要知道正确的标签和符号以及如何构建 URL 来使用它。这个库将承担这项繁琐的任务,并将收到的数据以托管类(managed classes)的形式返回。
本文借助一些示例,提供了该库的基本理解和应用。有关实际信息、发布版或文档等,请参阅项目主页。
Using the Code
本文中的每个示例都需要以下导入:
using MaasOne;
using MaasOne.Base;
下载类和命名空间
本节中的每个示例都需要以下导入:
using MaasOne.Finance.YahooFinance;
该库提供了许多不同的类用于下载数据,但每个下载类都遵循相同的原理。每个类都继承自通用 `Base.DownloadClient
QuotesDownload dl = new QuotesDownload();
DownloadClient<QuotesResult> baseDl = dl;
QuotesDownloadSettings settings = dl.Settings;
settings.IDs = new string[] { "MSFT", "GOOG", "YHOO" };
settings.Properties = new QuoteProperty[] { QuoteProperty.Symbol,
QuoteProperty.Name,
QuoteProperty.LastTradePriceOnly
};
SettingsBase baseSettings = baseDl.Settings;
在此示例中,您需要设置要获取数据的股票 ID。并且您必须为数据行设置行情属性。`SettingsBase` 类仅提供内部功能,在库外部,它仅对标准化很重要。
现在您可以开始下载数据了。为此,`Base.DownloadClient
Response<QuotesResult> resp = baseDl.Download();
每个专用下载类,如 `QuotesDownload`,都可以有重载的 `Download` 和 `DownloadAsync` 方法。如果您使用标准的无参数方法(此处不将 `userArgs` 计为参数),则该类将使用 `Settings` 属性对象进行下载。通过重载方法,可能会创建一个新的设置对象,该对象是通过传递的参数创建的。`Settings` 属性对象也可以被克隆,并且只将传递的参数新设置到克隆的对象中。
`Base.Response
ConnectionInfo connInfo = resp.Connection;
if (connInfo.State == ConnectionState.Success)
{
QuotesResult result = resp.Result;
//...
}
else
{
Exception ex = connInfo.Exception;
Debug.WriteLine(ex.Message);
}
如果您想启动异步下载,情况几乎相同。您可以使用单个下载类的实例启动多个下载过程。`Base.DownloadClient
object userArgs = (double)1.5;
dl.AsyncDownloadCompleted += this.QuotesDownload_Completed;
dl.DownloadAsync(userArgs);
private void QuotesDownload_Completed(DownloadClient<QuotesResult> sender,
DownloadCompletedEventArgs<QuotesResult> e)
{
sender.AsyncDownloadCompleted -= this.QuotesDownload_Completed;
object userArgs = e.UserArgs;
double dbl = (double)userArgs;
SettingsBase baseSettings = e.Settings;
QuotesDownloadSettings settings = (QuotesDownloadSettings)baseSettings;
Response<QuotesResult> resp = e.Response;
QuotesResult result = resp.Result;
//...
}
委托传递 `DownloadCompletedEventArgs
第二个事件是非通用的 `AsyncDownloadCompletedEvent`,它由 `IDownload` 接口实现。`IDownload` 的功能几乎与 `DownloadClient
object userArgs = (double)1.5;
dl.AsyncDownloadCompletedEvent += this.IDownload_Completed;
dl.DownloadAsync(userArgs);
private void IDownload_Completed(IDownload sender, IDownloadCompletedEventArgs e)
{
sender.AsyncDownloadCompletedEvent -= this.IDownload_Completed;
object userArgs = e.UserArgs;
double dbl = (double)userArgs;
SettingsBase baseSettings = e.Settings;
QuotesDownloadSettings settings = (QuotesDownloadSettings)baseSettings;
IResponse resp = e.GetResponse();
object baseResult = resp.GetObjectResult();
QuotesResult result = (QuotesResult)baseResult;
//...
}
在此示例中,我使用了 `Finance.YahooFinance.QuotesDownload` 类。借助以下概述,您可以查看此库中最专业的下载类。
- Finance.YahooFinance
AlphabeticIDIndexDownload
:按索引(A-Z)下载有效的 Yahoo! Finance ID。ChartDownload
:下载技术分析图表。CompanyInfoDownload
:下载公司的主要信息。CompanyProfileDownload
:下载公司简介数据。CompanyStatisticsDownload
:下载公司的财务统计数据。FuturesChainDownload
:下载期货链。HistQuotesDownload
:下载历史行情。IDSearchDownload
:按关键字搜索有效的 Yahoo! Finance ID。MarketDownload
:下载包含行业、细分行业和公司的市场概览。MarketQuotesDownload
:下载特定行业、细分行业或公司包含的市场行情。QuotesDownload
:下载多达 85 个行情属性。QuoteOptionsDownload
:下载看跌期权和看涨期权。
- Finance.YahooPortfolio
YPortfolioManager
:管理您的 Yahoo! 投资组合(创建、编辑和删除投资组合、视图和组件)。HoldingsDownload
:下载持股数据。PortfolioInfoDownload
:下载所有投资组合的概览,不含组件。PortfolioDownload
:下载投资组合视图的组件。
- Finance.YahooScreener
BondScreenerDownload
:根据特定标准监控债券。StockScreenerDownload
:根据特定标准监控股票。
- Search.BOSS
SearchDownload
:下载 Web、图像、新闻和拼写服务的搜索查询数据。RelatedSuggestionsDownload
:下载相关的搜索建议。
- Geo.GeoPlanet
PlacesDownload
:下载 Geo Planet API 的地理数据(Yahoo! 已弃用)。
- Geo.PlaceFinder
PlaceFinderDownload
:下载 Place Finder API 的地理数据。
- Weather.YahooWeather
LocationIDSearchDownload
:下载天气 API 的位置 ID。WeatherFeedDownload
:下载天气预报。
除了这些 Yahoo! 命名空间外,还有一个 RSS 命名空间。一些 Web 服务提供 RSS Feed,此类可用于下载 RSS 2.0 Feed。
- RSS
FeedDownload
:下载 RSS 2.0 Feed。
该项目最初是为了封装 Yahoo! Web 服务而开始的,但也可以扩展到其他 Web 服务。目前已经实现了对 MSN Money(Microsoft)的支持。
- Finance.MSNMoney
ChartDownload
:下载技术分析图表。HistQuotesDownload
:下载历史行情。IDSearchDownload
:按关键字搜索有效的 Yahoo! Finance ID。QuotesDownload
:下载行情属性。
由于扩展的可能性,命名空间结构如下:MaasOne.[KindOfWebService].[NameOfWebService]。因此,可以利用同类 Web 服务但不同提供商的协同效应。
Finance.YahooFinance
本节中的每个示例都需要以下导入:
using MaasOne.Finance;
using MaasOne.Finance.YahooFinance;
`Finance.YahooFinance` 命名空间包含 Yahoo! Finance Web 服务的类。下载原理始终相同,但我将展示一些示例以方便理解。
首先,我们想获取一些 ID。为此有不同的类。其中一个就是 `IDSearchDownload`。
IDSearchDownload dl = new IDSearchDownload();
IDQuerySearchDownloadSettings settings = new IDQuerySearchDownloadSettings();
settings.Query = "dow";
settings.Markets = FinancialMarket.AllMarkets;
settings.ResultsIndex = 0;
settings.Server = YahooServer.Germany;
settings.Type = SecurityType.Any;
dl.Settings = settings;
Response<IDSearchResult> resp = dl.Download();
foreach (IDSearchData id in resp.Result.Items)
{
string idStr = id.ID;
}
使用 `IDQuerySearchDownloadSettings`,您每次请求最多可以下载 20 条结果,并且可以设置起始索引(如果总共有超过 20 条结果)。使用 `IDInstantSearchDownloadSettings`,您最多可以下载 10 条结果,而无需起始索引,也没有服务器、排名或类型等指定。
按字母索引搜索 ID 有点特殊,因为它结合了 `AlphabeticIDIndexDownload` 和 `IDSearchDownload`。
//Download TopIndex (e.g. [A], [B], [1-9])
AlphabeticIDIndexDownload dl = new AlphabeticIDIndexDownload();
dl.Settings.TopIndex = null;
Response<AlphabeticIDIndexResult> resp = dl.Download();
//Download Index (e.g. [C], [Cl], [Ca(1/10)])
AlphabeticalTopIndex topIndex = (AlphabeticalTopIndex)resp.Result.Items[2];
dl.Settings.TopIndex = topIndex;
Response<AlphabeticIDIndexResult> resp2 = dl.Download();
//Download ID Search Results
AlphabeticalIndex index = resp.Result.Items[0];
IDSearchDownload dl2 = new IDSearchDownload();
Response<IDSearchResult> resp3 = dl2.Download(index);
IDSearchData[] idResults = resp3.Result.Items;
利用收到的 ID,可以使用其他类下载数据。我已经在上面的“下载类”部分展示了一个示例。这里是另一个使用 `HistQuotesDownload` 的示例。
HistQuotesDownload dl = new HistQuotesDownload();
dl.Settings.IDs = new string[] { "GOOG" };
dl.Settings.FromDate = new DateTime(2010, 1, 1);
dl.Settings.ToDate = DateTime.Today;
dl.Settings.Interval = HistQuotesInterval.Weekly;
Response<HistQuotesResult> resp = dl.Download();
foreach (HistQuotesDataChain hqc in resp.Result.Chains)
{
foreach (HistQuotesData hqd in hqc)
{
double close = hqd.Close;
double closeAdj = hqd.CloseAdjusted;
long volume = hqd.Volume;
//...
}
}
此命名空间中的其他下载类(以及该库中的大多数类)都遵循相同的原理:实例化、设置、下载响应、使用结果数据。
Finance.YahooFinance.Support
`Support` 命名空间提供了许多对于直接获取数据不是必需的类。此命名空间仅用于更轻松地处理这些数据。为此,您有不同的数据类。`Support.YID` 类管理所有与 Yahoo! Finance ID 相关的信息。这样的 ID 构建得相当结构化。要获取 Apple 股票(在 XETRA 交易所交易)的数据,您需要 ID "AAPL.DE
"。在 NYSE 交易的股票只有一个 ID "AAPL
"。这表明在大多数情况下,您可以从 ID 后缀推断出股票交易所(但有多个美国股票交易所没有后缀)。为此,`YID` 类提供了 `StockExchange` 属性来存储名称、ID 或交易时间等信息。您可以证明在机器本地时区或 UTC 时区是否在特定时间进行交易。您可以使用 `IDSearchResult` 创建 `YID` 的新实例。在大多数情况下,搜索结果会提供一个已知的股票交易所,并且 `YID` 构造函数将自动管理结果数据。`Support.WorldMarket` 类管理所有已知的、Yahoo! 支持的股票交易所以及一些指数的集合。您有一个大陆、国家和指数的结构来反映世界市场状况(由 Yahoo! 提供)。股票指数将由 `YIndexID` 类表示,该类继承自 `YID`。这里有一个额外的属性 `DownloadComponents`,它指示下载器是加载指数本身的数据还是指数中股票的数据。另一个 `IID` 类是 `YCurrencyID`。此类表示基准货币和相对货币之间的货币关系。为此,有 `Currency` 属性 `BaseCurrency` 和 `DepCurrency`。您下载的结果(`QuotesDownload` -> `LastTradePriceOnly`)格式为 1 `BaseCurrency` : X `DepCurrency`。
Finance.YahooPortfolio
本节中的每个示例都需要以下导入:
using MaasOne.Finance.YahooPortfolio;
使用该库,您可以管理您的在线投资组合。通常,您会借助 `YPortfolioManager` 类来完成此操作。此类提供了用于创建、编辑和删除投资组合、视图和组件的各种方法。`YPortfolioManager` 继承自 `YAccountManager`,后者提供了管理登录状态的基本方法。
private YAccountManager mManager = new YAccountManager();
private void LogIn()
{
System.Net.NetworkCredential cred = new System.Net.NetworkCredential();
cred.UserName = "username@yahoo.com";
cred.Password = "password";
bool isLoggeIn = mManager.LogIn(cred);
}
要下载您的投资组合概览,您只需要 `PortfolioInfoDownload` 类和 `YAccountManager`。
if (mManager.IsLoggedIn)
{
PortfolioInfoDownload dl = new PortfolioInfoDownload();
dl.Settings.Account = mManager;
Response<PortfolioInfoResult> resp = dl.Download();
foreach (PortfolioInfo pfi in resp.Result.Items)
{
string id = pfi.ID;
string name = pfi.Name;
}
}
如果您想下载单个投资组合的特定视图,您还需要 `YAccountManager` 和 `PortfolioDownload` 设置的特定投资组合 ID。实时视图和基本面视图是静态的。它们不能被编辑或删除。
if (mManager.IsLoggedIn)
{
PortfolioDownload dl = new PortfolioDownload();
dl.Settings.Account = mManager;
dl.Settings.PortfolioID = "your_portfolio_id";
dl.Settings.ViewIndex = 0; //The first view
//dl.Settings.DownloadRealTimeView = true; //Real-Time
//dl.Settings.DownloadFundamentalsView = true; //Fundamentals
Response<Portfolio> resp = dl.Download();
Portfolio pf = resp.Result;
string[] allViews = pf.AvailableViews;
string selView = pf.SelectedView;
foreach (IID id in pf.IDs)
{
string idStr = id.ID;
}
PortfolioColumnType[] columns = pf.Columns;
foreach (PortfolioColumnType clm in columns)
{
Debug.Write(clm.ToString() + "|");
}
Debug.Write("\n");
foreach (PortfolioDataRow row in pf.Rows)
{
foreach (PortfolioColumnType clm in row.AvailableColumns)
{
object cellValue = row[clm];
Debug.Write(cellValue.ToString());
}
Debug.Write("\n");
}
}
这些示例仅仅是简单的下载工作。要编辑您的投资组合,您需要上传要更改的新数据。为此,您有 `YPortfolioManager`。这与其他下载类不同。您没有 `Settings` 属性,并且有多个事件用于不同的操作。
private YPortfolioManager mPfManager = new YPortfolioManager();
登录后,您可以添加一个新的投资组合。
Response<Portfolio> createResp = mPfManager.CreatePortfolio("MyPortfolio");
Portfolio pf = createResp.Result;
Debug.WriteLine("New Portfolio: " + pf.Info.Name);
现在您可以更改名称。
mPfManager.EditPortfolio(pf.Info.ID, "MyPortfolio_NewName");
Response<Portfolio> editResp = mPfManager.DownloadPortfolio(pf.Info.ID);
pf = editResp.Result;
Debug.WriteLine("Edit Name: " + pf.Info.Name);
或者再次删除它。
Response<PortfolioInfoResult> deleteResp = mPfManager.DeletePortfolio(pf.Info.ID);
PortfolioInfo[] restPortfolios = deleteResp.Result.Items;
下一步是向投资组合添加新股票。
Response<Portfolio> addResp = mPfManager.AddPortfolioItem("portfolioID", "GOOG");
Portfolio pf = addResp.Result;
foreach (IID id in pf.IDs)
{
if (id.ID == "GOOG")
{
Debug.WriteLine("found");
}
}
当然,您也可以删除它们。
mPfManager.DeletePortfolioItem(pf.Info.ID, "GOOG");
在 Yahoo! Portfolio 中,您可以为您的股票设置持股。首先,您必须下载实际数据。您可以使用 `HoldingsDownload`/`YAccountManager` 或 `YPortfolioManager` 来完成此操作。
HoldingsDownload dl = new HoldingsDownload();
dl.Settings.Account = mManager;
dl.Settings.PortfolioID = "portfolioID";
Response<HoldingsResult> resp = dl.Download();
Holding[] holdings = resp.Result.Items;
foreach (Holding h in holdings)
{
string id = h.ID;
int shares = h.Shares;
double pricePaid = h.PricePaid;
Nullable<DateTime> tradeDate = h.TradeDate;
//...
}
现在您可以编辑和更新这些值。
holdings[0].PricePaid = 35.29;
holdings[0].Shares = 100;
holdings[0].TradeDate = DateTime.Today;
mPfManager.EditHoldings("portfolioID", holdings);
提示:您可以使用 `EditHoldings` 方法添加或删除投资组合中的多个项目/ID,或者完全清空投资组合(空数组)。投资组合将由传递的 `Holding` 数组完全更新。
创建、编辑和删除投资组合视图几乎相同,因此我现在跳转到 Yahoo! Search BOSS。
Search.BOSS
本节中的每个示例都需要以下导入:
using MaasOne.Search.BOSS;
Yahoo! Search BOSS 是使用 Yahoo! 的 Web、图像和新闻搜索(还包括拼写和博客)的最新 Web 服务。该库使用 BOSS v2。如果您想使用此服务,您必须注册并创建一个 OAuth 密钥,并为此付费。获得该 OAuth 密钥后,您就可以使用 Yahoo! Managed 的这部分来轻松自动化您的 BOSS 查询。
为此,您可以使用 `Search.BOSS.SearchDownload` 类。主要有一个常规的 `SearchDownloadSettings` 类用于设置 OAuth 凭据等。
SearchDownload dl = new SearchDownload();
SearchDownloadSettings settings = dl.Settings;
dl.Settings.ConsumerKey = "your_oauth_key";
dl.Settings.ConsumerSecret = "your_oauth_secret";
dl.Settings.HttpsUsed = true;
设置通用选项后,您必须设置要使用的服务。可以在单个查询中使用多种服务。
Culture culture = new Culture(Language.en, Country.US);
string queryText = "test";
SearchService service = null;
WebSearchService web = new WebSearchService();
web.LimitedWeb = true;
service = web;
service.Culture = culture;
service.Query = queryText;
service.Index = 0;
service.Count = 10;
dl.Settings.Services.Add(service);
NewsSearchService news = new NewsSearchService();
news.AlwaysLatestDateNow = true;
service = news;
service.Culture = culture;
service.Query = queryText;
service.Index = 0;
service.Count = 10;
dl.Settings.Services.Add(service);
ImageSearchService images = new ImageSearchService();
images.Dimensions = ImageSearchDimensions.All;
service = web;
service.Culture = culture;
service.Query = queryText;
service.Index = 0;
service.Count = 10;
dl.Settings.Services.Add(service);
然后您可以开始下载数据。结果被分成不同的 `SearchDataConatiner` 类,分别对应每种服务类型。在那里,您有一个 `Type` 属性,指示服务类型和专用容器类的类型,例如 `WebSearchDataContainer`。对于 Items,您有 `SearchData` 类及其专用的服务继承类,例如 `WebSearchData`。
Response<SearchResult> resp = dl.Download();
foreach (SearchDataContainer container in resp.Result.Containers)
{
if (container.Type == SearchResultType.Web)
{
WebSearchDataContainer webContainer = (WebSearchDataContainer)container;
foreach (WebSearchData wsd in webContainer.Items)
{
SearchData sd = wsd;
string title = sd.Title;
string description = sd.Description;
Uri url = sd.Url;
Uri clickUrl = sd.ClickUrl;
string displyUrl = wsd.DisplayUrl;
Language lang = wsd.Language;
DateTime crawlDate = wsd.CrawlingDate;
}
}
}
参与项目
我期待将此项目扩展到其他 Web 服务。如果您也有兴趣,可以参与该项目。主要工作将是创建设置类(和 URL)以及结果解析方法。下载数据的框架已经可用。
如果您有兴趣,请发送邮件。
鸣谢
- Angelo Cresta,感谢他出色的 bug 修复/测试工作。
- Zvonimir Digas,感谢他提供的股票交易所时间表。
- Alain Dionne,感谢他提供的加拿大指数信息。
历史
- 2012 年 5 月 1 日
- 版本 0.11.2
- 已更改 `YPortfolioManager`
- 已更改 `YAccountManager`
- 已更改 `HistQuotesData`
- 版本 0.11.2
- 版本历史记录从 0.7.8 到 0.11.2
- 2010 年 11 月 26 日
- 版本 0.7.8
- 内部更改 `QuotesDownload`
- 已更改 `HistQuoteData`
- 版本 0.7.8
- 2010 年 11 月 12 日
- 版本 0.7.7
- 次要更改
- 内部优化
- 杂项 bug 修复
- CF 3.5 版本
- 版本 0.7.7
- 2010 年 9 月 15 日
- 版本 0.7.6
- 添加了 `Culture` 类
- 添加了 `Language` 枚举
- 添加了 `Region` 枚举
- 已修复 `Base.Download`
- 已更改 `Support.YID`
- 已扩展 `ChartDownload`
- 已扩展 `FeedDownload`
- 已修复 `KeyStatisticsDownload`
- 内部优化
- 杂项 bug 修复
- 更新 `market.xml`
- 版本 0.7.6
- 2010 年 7 月 22 日
- 版本 0.7.5
- 已更改 `Country` 枚举
- 已更改 `WorldMarket`
- 已更改 `CountryInfo`
- 已更改 `ContinentInfo`
- 已更改 `NonAPI.IDSearchDownload`
- 杂项 bug 修复
- 更新 `market.xml`
- 版本 0.7.5
- 2010 年 7 月 21 日
- 版本 0.7.4
- 已扩展 `HistQuotesDownload`
- 添加了 `HistQuotesDataChain`
- 已修复 `CompanyStatisticsDownload`
- `Base.Download` 方法现在是 `Protected` 而不是 `Friend`
- 已更改 `QuoteData.Values`
- 内部优化
- 杂项 bug 修复
- 版本 0.7.4
- 2010 年 7 月 20 日
- 版本 0.7.3
- 已更改 `IDSearchDownloadChangedEventArgs`
- 已更改 `FinancialSecurityType`
- 已更改 `Feed` 和 `FeedDownload`
- `Base.Download` 方法现在是 `Protected` 而不是 `Friend`
- XML 自动文本编码
- 内部下载结构已更改
- 内部优化
- 杂项 bug 修复
- 版本 0.7.3
- 2010 年 7 月 14 日
- 版本 0.7.2
- 已更改 `API.IDSearch`
- 已更改 `Base.ConnectionInfo`
- 杂项 bug 修复
- 版本 0.7.2
- 2010 年 6 月 29 日
- 版本 0.7.1
- 已更改 `RSS.Feed`
- 杂项 bug 修复
- 内部优化
- 更新 `market.xml`
- 版本 0.7.1
- 2010 年 6 月 24 日
- 版本 0.7
- 下载结构已更改
- 线程安全的下载
- 添加了 `Base.Response`
- 添加了 `Base.ConnectionInfo`
- 添加了 `CompanyStatisticsDownload`
- 命名空间结构已更改(Finance 项目的从属关系)
- 已更改 `MarketDownload`
- 杂项 bug 修复
- 内部优化
- 版本 0.7
- 2010 年 4 月 18 日
- 版本 0.6
- 添加了 `QuotesBaseData`
- 添加了 `QuotesBaseDownload`
- 添加了 `QuoteOption`
- 添加了 `QuoteOptionsDownload`
- 数据导入/导出已更改
- 已更改 `StockExchange`
- 优化了 `MarketDownload`
- 杂项 bug 修复
- 内部优化
- 更新 `market.xml`
- 版本 0.6
- 2010 年 3 月 24 日
- 版本 0.5
- 命名空间结构已更改
- Compact Framework 2.0 版本
- 实现了 ID 搜索
- 实现了字母 ID 列表下载
- 添加了 `ISIN` 类
- 修正了 `DownloadFailure` 参数(已弃用)
- 数据导入/导出已更改
- 次要 bug 修复
- 内部优化
- 更新 `market.xml`
- 版本 0.5
- 2010 年 1 月 14 日
- 版本 0.4.3
- YQL URL 更改
- 版本 0.4.3
- 2010 年 1 月 6 日
- 版本 0.4.2
- 添加了代理支持
- 版本 0.4.2
- 2009 年 12 月 14 日
- 版本 0.4.1
- 添加了商品
- 添加了特殊货币
- 版本 0.4.1
- 2009 年 12 月 10 日
- 版本 0.4
- YQL 实现
- 数据导入/导出
- 添加了 `StockExchange`
- 添加了 `IID` 接口
- 添加了 `MarketDownload`
- 次要 bug 修复
- 更新 `market.xml`
- 版本 0.4
- 2009 年 11 月 22 日
- 版本 0.3
- 货币兑换结构已更改
- 添加了 `YID`
- 添加了股票交易所
- 下载器 bug 修复
- 完成了 `Servers`
- 版本 0.3
- 2009 年 10 月 27 日
- 版本 0.2
- CSV 读取器 bug 修复
- 清理下载对象的处置
- 已修改 `QuoteData`
- 多个指数的行情下载
- 添加了指数
- 完成了 `Currencies`
- 枚举描述
- 版本 0.2
- 2009 年 9 月 26 日
- 版本 0.1
- 添加了 `Base.Download`
- 添加了服务器
- 版本 0.1
- 2009 年 9 月 24 日
- 更新了源代码
- 2009 年 9 月 23 日
- 初次发布