Skip to content
This repository was archived by the owner on Feb 7, 2022. It is now read-only.

Commit 53ca8ad

Browse files
committed
Release V0.3.0
1. Change the app's Chinese name. 2. The app now uses the specified font family instead of the system default. 3. The main window is by default maximised (previously restored down). 4. Redesign the date pickers, due to the bad performance of the previous ones. Now they have a calendar view to make it easier to pick a date. 5. Optimise the appearance of the search button and the outer borders of the display area for the data grid. 6. Add hints for the situation when the search access is denied by the data source. 7. Add a busy indicator to show the indication of data loading. 8. Round each day's volumes to the nearest integer, and keep 1 decimal place for total volumes. 9. Enable row selection of the data grid which can provide a better view especially when the row is long, and clearing row selection is also enabled. 10. Sorting is by default disabled for each day's volumes (previously enabled for all columns). Besides, it is allowed to sort data to its initial order other than ascending or descending order. 11. Columns are auto-sized before becoming visible. 12. Display weekday names for columns whose header text is a date. 13. The columns for Saturday's and Sunday's volumes are not displayed now. 14. Optimise the content style of the exported Excel files and automatically enable filters for the columns. 15. Enable printing the data grid. If the column width is too long, you can just return to adjust it and then click the print button again. 16. Solve a potential crash problem during multiple times of searching. 17. Fix a bug that the tooltips of the title bar's buttons for controlling the window state are not displayed in simplified Chinese. 18. Fix a bug that the font colour of the text box for the symbol does not change to red sometimes when the input is in a wrong format (e.g., SH6010065). 19. Fix a bug that the data grid may not be in the initial status (sorting, column width, etc.) after clicking the search button. 20. Fix a bug that the data grid cannot be correctly exported as an Excel file in XLS format. 21. Fix a bug that UI can get temporarily choppy when the search button is clicked.
1 parent 2d2204e commit 53ca8ad

22 files changed

+2305
-938
lines changed

ShSzStockHelper/App.xaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
<Application x:Class="ShSzStockHelper.App"
1+
<!-- The app initialiser. -->
2+
<Application x:Class="ShSzStockHelper.App"
23
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
xmlns:local="clr-namespace:ShSzStockHelper"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
56
StartupUri="HomeWindow.xaml">
67
<Application.Resources>
7-
8+
<local:StylePropertyViewModel x:Key="StyleProperties" />
89
</Application.Resources>
9-
</Application>
10+
</Application>
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/*
22
* @Description: helper class for processing data of strike prices and volumes collected
3-
* @Version: 1.0.3.20200719
3+
* @Version: 1.0.6.20200808
44
* @Author: Arvin Zhao
55
* @Date: 2020-07-15 18:25:42
66
* @Last Editors: Arvin Zhao
7-
* @LastEditTime: 2020-07-19 19:25:42
7+
* @LastEditTime: 2020-08-08 19:25:42
88
*/
99

1010
using HtmlAgilityPack;
@@ -15,10 +15,10 @@ namespace ShSzStockHelper
1515
/// <summary>
1616
/// A helper class for processing data of strike prices and volumes collected.
1717
/// </summary>
18-
class StrikePrice_VolumeDataProcessor
18+
class StrikePriceVolumeDataProcessor
1919
{
20+
private readonly StylePropertyViewModel _stylePropertyViewModel;
2021
private const string _cellNodePath = "//tbody/tr";
21-
private const string _dateStringFormat = "yyyy-M-d";
2222

2323
/// <summary>
2424
/// The symbol of a stock.
@@ -35,49 +35,62 @@ class StrikePrice_VolumeDataProcessor
3535
/// </summary>
3636
public DateTime EndDate { get; set; }
3737

38+
public StrikePriceVolumeDataProcessor()
39+
{
40+
_stylePropertyViewModel = new StylePropertyViewModel(); // Initialise the view model class for using the defined style properties.
41+
} // end constructor StrikePriceVolumeDataProcessor
42+
3843
/// <summary>
3944
/// Get data of strike prices and volumes.
4045
/// </summary>
4146
/// <returns>
42-
/// A 2D array of type decimal of size at least (1, 3) containing the data or of size (1, 1) if the date range is too long, or <c>null</c> if the filters are wrong.
43-
/// The first index of the array represents the index of rows (start from 0).
44-
/// The second index of the array represents the index of columns (start from 0).
45-
/// For the second index, Index 0 represents strike prices, while Index 1 represents total volumes. The other indexes represent each day's volumes.
47+
/// A jagged 2D array of type "decimal?" of size at least (1, 3) containing the data,
48+
/// or a 2D array of size (1, 1) if access is denied by the specified source providing data of strike prices and volumes,
49+
/// or a 2D array of size (1, 2) if the date range is too long,
50+
/// or <c>null</c> if the filters are wrong.
51+
/// <br /><br />
52+
/// The first number of the size represents the number of rows.
53+
/// The second number of the size represents the number of columns.
54+
/// For columns, Index 0 represents strike prices, while Index 1 represents total volumes. These two columns should not have <c>null</c> data. The other indexes represent each day's volumes.
55+
/// <br /><br />
56+
/// Each "row" of the 2D array will have the same number of elements, so it can be seen as a rectangular one.
57+
/// The 2D array containing the data is sorted by strike prices in descending order.
4658
/// </returns>
47-
public decimal?[,] GetStrikePrice_VolumeData()
59+
public decimal?[][] GetStrikePriceVolumeData()
4860
{
4961
HtmlNode rootNode = GetRootNode(StartDate, EndDate);
5062

5163
if (rootNode.SelectNodes("//body/div") != null)
5264
{
53-
HtmlNodeCollection strikePrice_TotalVolumeNodes = rootNode.SelectNodes(_cellNodePath);
65+
HtmlNodeCollection strikePriceTotalVolumeNodes = rootNode.SelectNodes(_cellNodePath);
5466

55-
if (strikePrice_TotalVolumeNodes != null)
67+
if (strikePriceTotalVolumeNodes != null)
5668
{
5769
int dayTotalCount = EndDate.Subtract(StartDate).Days + 1; // Calculate the number of days (>= 1) from the start date to the end date.
58-
decimal?[,] strikePrice_VolumeRows = new decimal?[strikePrice_TotalVolumeNodes.Count, 2 + dayTotalCount]; // The table of strike prices and volumes should have at least 3 columns (strike price, total volume, and each day's volume).
70+
decimal?[][] strikePriceVolumeRowCollection = new decimal?[strikePriceTotalVolumeNodes.Count][]; // The table of strike prices and volumes should have at least 3 columns (strike price, total volume, and each day's volume).
71+
decimal?[] strikePriceVolumeRow;
5972
int nodeIndex = 0; // Represent the index of rows (start from 0).
6073
int elementIndex; // Represent the index of columns (start from 0).
6174

6275
// Get data of strike prices and total volumes.
63-
foreach (HtmlNode node in strikePrice_TotalVolumeNodes)
76+
foreach (HtmlNode node in strikePriceTotalVolumeNodes)
6477
{
6578
elementIndex = 0;
79+
strikePriceVolumeRow = new decimal?[2 + dayTotalCount];
6680

6781
foreach (HtmlNode element in node.Elements("td"))
6882
{
69-
// Index 0 represents strike price, while Index 1 represents total volume. (Determined by the specified data source.)
83+
// Index 0 represents strike prices, while Index 1 represents total volumes. (Determined by the specified data source.)
7084
if (elementIndex < 2)
7185
{
7286
decimal elementValue = Convert.ToDecimal(element.InnerText);
73-
strikePrice_VolumeRows[nodeIndex, elementIndex] = elementIndex == 0 ? elementValue : elementValue / 100m; // 总成交量:1手 = 100股。
74-
elementIndex++;
87+
strikePriceVolumeRow[elementIndex++] = elementValue; // Here the unit of total volumes is "股".
7588
}
7689
else
7790
break;
7891
} // end foreach
7992

80-
nodeIndex++;
93+
strikePriceVolumeRowCollection[nodeIndex++] = strikePriceVolumeRow;
8194
} // end foreach
8295

8396
if (dayTotalCount > 1)
@@ -86,36 +99,36 @@ class StrikePrice_VolumeDataProcessor
8699
for (int dayCount = 0; dayCount < dayTotalCount; dayCount++)
87100
{
88101
DateTime dayVolumeDate = Convert.ToDateTime(StartDate.ToShortDateString()).AddDays(dayCount);
89-
HtmlNodeCollection strikePrice_DayVolumeNodes = GetRootNode(dayVolumeDate, dayVolumeDate).SelectNodes(_cellNodePath);
102+
HtmlNodeCollection strikePriceDayVolumeNodes = GetRootNode(dayVolumeDate, dayVolumeDate).SelectNodes(_cellNodePath);
90103

91104
/*
92105
* Execute the code block if the specified node collection is not null.
93106
* Otherwise, just keep the initial element value (0 of type decimal) of the 2D array containing data of strike prices and volume.
94107
*/
95-
if (strikePrice_DayVolumeNodes != null)
108+
if (strikePriceDayVolumeNodes != null)
96109
{
97110
nodeIndex = 0;
98111

99-
foreach (HtmlNode node in strikePrice_DayVolumeNodes)
112+
foreach (HtmlNode node in strikePriceDayVolumeNodes)
100113
{
101114
elementIndex = 0;
102115
decimal strikePrice = 0m;
103116

104117
foreach (HtmlNode element in node.Elements("td"))
105118
{
106-
// Index 0 represents strike price. (Determined by the specified data source.)
119+
// Index 0 represents strike prices. (Determined by the specified data source.)
107120
if (elementIndex == 0)
108121
{
109122
strikePrice = Convert.ToDecimal(element.InnerText);
110123
elementIndex++;
111124
}
112-
// Index 1 represents the specified day's volume. (Determined by the specified data source.)
125+
// Index 1 represents the specified day's volumes. (Determined by the specified data source.)
113126
else if (elementIndex == 1)
114127
{
115-
for (int nodeCount = 0; nodeCount < strikePrice_TotalVolumeNodes.Count; nodeCount++)
116-
if (strikePrice_VolumeRows[nodeCount, 0] == strikePrice)
128+
for (int nodeCount = 0; nodeCount < strikePriceTotalVolumeNodes.Count; nodeCount++)
129+
if (strikePriceVolumeRowCollection[nodeCount][0] == strikePrice)
117130
{
118-
strikePrice_VolumeRows[nodeCount, 2 + dayCount] = Convert.ToDecimal(element.InnerText) / 100m; // 每日成交量:1手 = 100股。
131+
strikePriceVolumeRowCollection[nodeCount][2 + dayCount] = Convert.ToDecimal(element.InnerText); // Here the unit of the specified day's volumes is "股".
119132
break;
120133
} // end if
121134

@@ -129,23 +142,29 @@ class StrikePrice_VolumeDataProcessor
129142
} // end foreach
130143
}
131144
else
132-
for (int nodeCount = 0; nodeCount < strikePrice_TotalVolumeNodes.Count; nodeCount++)
133-
strikePrice_VolumeRows[nodeCount, 2 + dayCount] = null;
145+
for (int nodeCount = 0; nodeCount < strikePriceTotalVolumeNodes.Count; nodeCount++)
146+
strikePriceVolumeRowCollection[nodeCount][2 + dayCount] = null;
134147
} // end for
135148
}
136149
else
137-
for (int nodeCount = 0; nodeCount < strikePrice_TotalVolumeNodes.Count; nodeCount++)
138-
strikePrice_VolumeRows[nodeCount, 2] = strikePrice_VolumeRows[nodeCount, 1];
150+
for (int nodeCount = 0; nodeCount < strikePriceTotalVolumeNodes.Count; nodeCount++)
151+
strikePriceVolumeRowCollection[nodeCount][2] = strikePriceVolumeRowCollection[nodeCount][1];
152+
153+
// TODO: sorting may not be needed when getting data from the web.
154+
// Array.Sort(strikePriceVolumeRowCollection, (x, y) => Comparer<decimal>.Default.Compare((decimal) y[0], (decimal) x[0])); // Sort by strike prices in descending order.
139155

140-
return strikePrice_VolumeRows;
156+
return strikePriceVolumeRowCollection;
141157
}
142158
// Wrong filters (symbol/start date/end date).
143159
else
144160
return null;
145161
}
162+
// Access is denied by the specified data source.
163+
else if (rootNode.SelectNodes("//body/h1") != null)
164+
return new decimal?[1][] { new decimal?[1] };
146165
// The date range is too long.
147166
else
148-
return new decimal?[1, 1];
167+
return new decimal?[1][] { new decimal?[2] };
149168
} // end method GetStrikePrice_VolumeData
150169

151170
/// <summary>
@@ -157,8 +176,8 @@ class StrikePrice_VolumeDataProcessor
157176
private HtmlNode GetRootNode(DateTime startDate, DateTime endDate)
158177
{
159178
return new HtmlWeb()
160-
.Load(@"http://market.finance.sina.com.cn/pricehis.php?symbol=" + Symbol + "&startdate=" + startDate.ToString(_dateStringFormat) + "&enddate=" + endDate.ToString(_dateStringFormat))
179+
.Load(@"http://market.finance.sina.com.cn/pricehis.php?symbol=" + Symbol + "&startdate=" + startDate.ToString(_stylePropertyViewModel.DateFormat) + "&enddate=" + endDate.ToString(_stylePropertyViewModel.DateFormat))
161180
.DocumentNode;
162181
} // end method GetRootNode
163-
} // end class StrikePrice_VolumeDataProcessor
182+
} // end class StrikePriceVolumeDataProcessor
164183
} // end namespace ShSzStockHelper

0 commit comments

Comments
 (0)