基金净值估算程序 C#实现
By
jerrxjr1220
at 2023-07-24 • 0人收藏 • 1225人看过
原来天天基金或者支付宝上都能直接看到当天的基金估值,从今天开始基金的当天估值全部下架了(真是把股民/基民当韭菜了,而且是套着麻袋割
)
为了能实时看到基金的净值估值,只能自己通过基金公布的重仓股票份额自行估算涨跌。
于是就有了下面的代码,C#实现 Linqpad版,基金的持股份额保存在SQLite数据库中,其实也可以通过查询获取。
var jj = new JiJin("002910");
var stocks = Stocks.Select(x=>x);
foreach (var item in stocks) {
var change = GetStockChange(item.Stockname);
jj.Stock[item.Stockname] = ((double)item.Percentage, change*0.01);
}
jj.Calculate();
jj.Dump();
$"{DateTime.Now.ToShortDateString()} 基金002910的净值估计变动{jj.TotalChange*100}%".Dump();
double GetStockChange(string code)
{
double change = 0;
var wc = new WebClient();
wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183");
var str = wc.DownloadString("http://qt.gtimg.cn/q=" + code);
var data = str.Split("~");
change = Convert.ToDouble(data[32].Trim());
return change;
}
class JiJin
{
public string Name { get; private set; }
public Dictionary<string, (double, double)> Stock {get; set;} = new();
public double TotalChange {get; set;}
public JiJin(string name) {
Name = name;
}
public double Calculate() {
double total=0;
double totalpercent=0;
double totalchange=0;
double count=0;
foreach (var item in Stock) {
total += item.Value.Item1 * item.Value.Item2;
totalpercent += item.Value.Item1;
totalchange += item.Value.Item2;
count++;
}
totalchange /= count;
total += (1-totalpercent)*totalchange;
TotalChange = total;
return TotalChange;
}
}
13 个回复 | 最后更新于 2023-10-20
2023-07-24
#2
改进了一下,直接输入基金代码可以自动查询持仓并计算净值。
通过解析天天基金的网页进行计算。
var jj = new JiJin("012414");
jj.GetChiCang();
jj.Calculate();
$"{DateTime.Now.ToShortDateString()} 基金{jj.Name}的净值估计{jj.TotalChange*100}%".Dump();
class JiJin
{
public string Name { get; private set; }
public Dictionary<string, (double, double)> Stock { get; set; } = new();
public double TotalChange { get; set; }
public JiJin(string name)
{
Name = name;
}
public double Calculate()
{
double total = 0;
double totalpercent = 0;
double totalchange = 0;
double count = 0;
foreach (var item in Stock)
{
total += item.Value.Item1 * item.Value.Item2;
totalpercent += item.Value.Item1;
totalchange += item.Value.Item2;
count++;
}
totalchange /= count;
total += (1 - totalpercent) * totalchange;
TotalChange = total;
return TotalChange;
}
public void GetChiCang()
{
var wc = new WebClient();
wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183");
var str = wc.DownloadString($"http://fund.eastmoney.com/{Name}.html");
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(str);
var node = doc.DocumentNode.SelectSingleNode("//*[@id='position_shares']/div[1]/table");
var content = node.InnerText;
var c = content.Split(" ").Where(x => x.Length > 0);
var list = c.ToArray()[4..];
for (var i=0; i<list.Length;i++) {
if (list[i].EndsWith("%") && list[i+1].EndsWith("%")) {
Stock[list[i-1]] = (Convert.ToDouble(list[i][..^1])*0.01, Convert.ToDouble(list[i+1][..^1])*0.01);
}
}
}
}2023/7/24 星期一 基金012414的净值估计-1.1309255000000003%


对比实际当天净值,基本上还可以。
2023-07-26
#6
回复#5 @鸿湖重工业株式会社 :
完整工程文件下载:
aardio不是可以直接调用C#的类的吗?
import console;
console.setTitle("MyJiJin");
import dotNet;
var dll = dotNet.load("\res\JiJin.dll");
var JiJin = dll.import("JiJin");
var jj = JiJin.JiJin("002910");
jj.GetChiCang();
jj.Calculate();
console.log(jj.TotalChange);
console.pause();
2023-07-27
#7
换了个异步写法,调用天天基金的api接口并解析json列表
var myjj = new JiJin("002910");
myjj.Adjustment = 0.9;
myjj.GetStocks();
myjj.Calculate();
myjj.Dump();
public class JiJin {
public string _Code { get; private set; }
public string _Name { get; private set; }
public double TotalChange {get; set;}
public double Adjustment {get; set;} = 1;
public List<Dictionary<string, (double, double)>> Stocks = new List<Dictionary<string, (double, double)>>();
public JiJin (string code) {
_Code = code;
}
JToken GetChiCang() {
var wc = new HttpClient();
wc.DefaultRequestHeaders.Add("User-Agent", "EMProjJijin/6.2.8 (iPhone; iOS 13.6; Scale/2.00)");
wc.DefaultRequestHeaders.Add("GTOKEN", "98B423068C1F4DEF9842F82ADF08C5db");
wc.DefaultRequestHeaders.Add("clientInfo", "ttjj-iPhone10,1-iOS-iOS13.6");
wc.DefaultRequestHeaders.Add("ContentType", "application/x-www-form-urlencoded");
wc.DefaultRequestHeaders.Add("Host", "fundmobapi.eastmoney.com");
wc.DefaultRequestHeaders.Add("Referer", "https://mpservice.com/516939c37bdb4ba2b1138c50cf69a2e1/release/pages/FundHistoryNetWorth");
var resp = wc.GetStringAsync($"https://fundmobapi.eastmoney.com/FundMNewApi/FundMNInverstPosition?FCODE={_Code}&OSVersion=14.3&appVersion=6.3.8&deviceid=3EA024C2-7F22-408B-95E4-383D38160FB3&plat=Iphone&product=EFund&serverVersion=6.3.6&version=6.3.8");
return JConstructor.Parse(resp.Result)["Datas"]["fundStocks"];
}
async Task<double> GetStockChange(string code)
{
double change = 0;
var wc = new HttpClient();
wc.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183");
var bytes = await wc.GetByteArrayAsync("http://qt.gtimg.cn/q=" + code);
var str = Encoding.UTF8.GetString(bytes);
var data = str.Split("~");
change = Convert.ToDouble(data[32].Trim());
return change;
}
public void GetStocks() {
var chicang = GetChiCang();
foreach (var each in chicang) {
var stock = ((string)each["GPDM"]).StartsWith("6") ? "sh"+(string)each["GPDM"] : "sz"+(string)each["GPDM"] ;
var ccbl = (double)each["JZBL"];
var change = GetStockChange(stock).Result;
Dictionary<string, (double, double)> s = new Dictionary<string, (double, double)>();
s[stock] = (ccbl, change);
Stocks.Add(s);
}
}
public double Calculate()
{
double total = 0;
double totalpercent = 0;
double totalchange = 0;
int count = 0;
foreach (var stock in Stocks)
{
KeyValuePair<string, (double, double)> pair = stock.FirstOrDefault();
total += pair.Value.Item1 * pair.Value.Item2 * 0.0001;
totalpercent += pair.Value.Item1 * 0.01;
totalchange += pair.Value.Item2 * 0.01;
count++;
}
totalchange /= count;
total += (1 - totalpercent) * totalchange;
TotalChange = total * Adjustment;
return TotalChange;
}
}
2023-07-28
#9
回复#8 @鸿湖重工业株式会社 :
这个还要看你调用的dll,有些比如申明了允许外部调用,有些可能是私有的,还有些可能是受保护的,或者是只读或者只写的属性。
例如,上面的这个类
public class JiJin {
public string _Code { get; private set; }
public string _Name { get; private set; }
public double TotalChange {get; set;}
public double Adjustment {get; set;} = 1;
public List<Dictionary<string, (double, double)>> Stocks = new List<Dictionary<string, (double, double)>>();
public JiJin (string code) {
_Code = code;
}
...这个类的“_Code”的写属性就是私有的,只能在创建时修改“_Code”的值,外部调用时只能是只读的,你要写就会报错。
2023-10-19
#10
<hc:GlowWindow x:Class="WPFFundation.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WPFFundation.ViewModels"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:hc="https://handyorg.github.io/handycontrol"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
mc:Ignorable="d"
Title="MainWindow" Height="750" Width="600"
FontFamily="JetBrains Mono"
FontSize="{StaticResource TextFontSize}" Background="White">
<Window.Resources>
</Window.Resources>
<hc:SimplePanel>
<ScrollViewer x:Name="scrollViewer" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="5*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<WrapPanel Grid.ColumnSpan="3" VerticalAlignment="Center">
<TextBlock Text="Fundation" Style="{StaticResource TextBlockDefaultDanger}"
FontSize="20" Margin="20" />
<TextBox hc:InfoElement.Placeholder="Input Fundation Code ..." FontSize="20" Foreground="Brown"
Style="{StaticResource TextBoxExtend}" Height="45" Width="300"
Text="{Binding FundationCode, Mode=TwoWay}" />
<Button Style="{StaticResource ButtonDashedDanger}" Margin="20"
Content="Search" FontSize="20" Height="40"
Command="{Binding SearchCommand}" />
</WrapPanel>
<StackPanel Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Center">
<TextBox hc:InfoElement.Title="Adjustment" hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="1" hc:TitleElement.Foreground="{StaticResource PrimaryBrush}"
Style="{StaticResource TextBoxExtend}" Width="400" Margin="0,10"
Text="{Binding FundationAdjustment, Mode=TwoWay}" />
<TextBox hc:InfoElement.Title="Fundation Name" hc:InfoElement.TitlePlacement="Left"
hc:TitleElement.Foreground="{StaticResource PrimaryBrush}"
Style="{StaticResource TextBoxExtend}" Width="400" Margin="0,5"
Text="{Binding FundationName}" />
<TextBox x:Name="estimate" hc:InfoElement.Title="Fundation Estimation"
hc:InfoElement.TitlePlacement="Left"
hc:TitleElement.Foreground="{StaticResource PrimaryBrush}"
Style="{StaticResource TextBoxExtend}" Width="400" Margin="0,5"
Text="{Binding FundationEstimation}" />
<i:Interaction.Triggers>
<i:DataTrigger Binding="{Binding FundationColor}" Value="green">
<i:ChangePropertyAction TargetName="estimate" PropertyName="Foreground" Value="Green" />
</i:DataTrigger>
<i:DataTrigger Binding="{Binding FundationColor}" Value="red">
<i:ChangePropertyAction TargetName="estimate" PropertyName="Foreground" Value="Red" />
</i:DataTrigger>
</i:Interaction.Triggers>
</StackPanel>
<ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3">
<ListBox ItemsSource="{Binding Fundation.Stocks}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock x:Name="name" Text="{Binding StockName}" Margin="70,10,10,10"
FontSize="20" Width="150" />
<TextBlock x:Name="price" Text="{Binding StockPrice}" Margin="10"
FontSize="16" Width="80" />
<TextBlock x:Name="percent"
Text="{Binding StockChangePercent, StringFormat={}{0} %}" Margin="10"
FontSize="16" Width="80" />
<TextBlock x:Name="volume"
Text="{Binding StockVolumePercentage, StringFormat={}{0} %}" Margin="10"
FontSize="14" />
<i:Interaction.Triggers>
<i:DataTrigger Binding="{Binding StockColor}" Value="green">
<i:ChangePropertyAction TargetName="percent" PropertyName="Foreground"
Value="Green" />
</i:DataTrigger>
<i:DataTrigger Binding="{Binding StockColor}" Value="red">
<i:ChangePropertyAction TargetName="percent" PropertyName="Foreground"
Value="Red" />
</i:DataTrigger>
</i:Interaction.Triggers>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</ScrollViewer>
</hc:SimplePanel>
</hc:GlowWindow>using System;
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace WPFFundation.Models;
public partial class StockModel : ObservableObject
{
[ObservableProperty] private double _stockChangePercent;
[ObservableProperty] private string _stockColor = "Grey";
[ObservableProperty] private string? _stockName;
[ObservableProperty] private double _stockPrice;
[ObservableProperty] private double _stockVolumePercentage;
public StockModel()
{
StockName = string.Empty;
}
}
public partial class FundationModel : ObservableObject
{
[ObservableProperty] private double _adjustment;
[ObservableProperty] private double? _fundationChange;
[ObservableProperty] private string _fundationName;
[ObservableProperty] private double? _fundationValue;
[ObservableProperty] private BindingList<StockModel> _stocks;
[ObservableProperty] private DateTime _updateDateTime;
public FundationModel()
{
Stocks = new BindingList<StockModel>();
}
}using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Newtonsoft.Json.Linq;
using WPFFundation.Models;
namespace WPFFundation.ViewModels;
public partial class MainViewModel : ObservableObject
{
[ObservableProperty] private FundationModel _fundation;
[ObservableProperty] private double _fundationAdjustment = 1;
[ObservableProperty] private string? _fundationCode;
[ObservableProperty] private string _fundationColor = "black";
[ObservableProperty] private string? _fundationEstimation;
[ObservableProperty] private string? _fundationName;
public MainViewModel()
{
Fundation = new FundationModel();
//发送消息
//WeakReferenceMessenger.Default.Send(new CustomizedMessage(new MessageModel()));
}
[RelayCommand]
public async Task SearchAsync()
{
if(FundationCode.Length>=6)
await GetStocksAsync(FundationCode);
}
public async Task<JToken> GetChiCangAsync(string fundationCode)
{
var wc = new HttpClient();
wc.DefaultRequestHeaders.Add("User-Agent", "EMProjJijin/6.2.8 (iPhone; iOS 13.6; Scale/2.00)");
wc.DefaultRequestHeaders.Add("GTOKEN", "98B423068C1F4DEF9842F82ADF08C5db");
wc.DefaultRequestHeaders.Add("clientInfo", "ttjj-iPhone10,1-iOS-iOS13.6");
wc.DefaultRequestHeaders.Add("ContentType", "application/x-www-form-urlencoded");
wc.DefaultRequestHeaders.Add("Host", "fundmobapi.eastmoney.com");
wc.DefaultRequestHeaders.Add("Referer",
"https://mpservice.com/516939c37bdb4ba2b1138c50cf69a2e1/release/pages/FundHistoryNetWorth");
var resp = await wc.GetStringAsync(
$"https://fundmobapi.eastmoney.com/FundMNewApi/FundMNInverstPosition?FCODE={fundationCode}&OSVersion=14.3&appVersion=6.3.8&deviceid=3EA024C2-7F22-408B-95E4-383D38160FB3&plat=Iphone&product=EFund&serverVersion=6.3.6&version=6.3.8");
return JObject.Parse(resp)["Datas"]!["fundStocks"]!;
}
public async Task<(double, double)> GetStockChangeAsync(string code)
{
double change;
double price;
var wc = new HttpClient();
wc.DefaultRequestHeaders.Add("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183");
var bytes = await wc.GetByteArrayAsync("http://qt.gtimg.cn/q=" + code);
var str = Encoding.UTF8.GetString(bytes);
var data = str.Split("~");
change = Convert.ToDouble(data[32].Trim());
price = Convert.ToDouble(data[3]);
return (price, change);
}
public async Task GetStocksAsync(string fundationCode)
{
var chicang = await GetChiCangAsync(fundationCode);
Fundation.Stocks.Clear();
foreach (var each in (JArray)chicang)
{
double stockPrice;
double stockChangePercent;
var stockCode = ((string)each["GPDM"]!).StartsWith("6")
? string.Format("sh{0:D6}", int.Parse((string)each["GPDM"]!))
: string.Format("sz{0:D6}", int.Parse((string)each["GPDM"]!));
var ccbl = (double)each["JZBL"]!;
var stockName = each["GPJC"]!.ToString();
(stockPrice, stockChangePercent) = await GetStockChangeAsync(stockCode);
var color = stockChangePercent > 0 ? "red" : "green";
Fundation.Stocks.Add(new StockModel
{
StockName = stockName, StockPrice = stockPrice, StockChangePercent = stockChangePercent,
StockVolumePercentage = ccbl, StockColor = color
});
Calculate();
}
}
public void Calculate()
{
double sum = 0;
double restPercentage = 100;
double averageChangePercent = 0;
foreach (var stock in Fundation.Stocks)
{
sum += stock.StockChangePercent * stock.StockVolumePercentage * 0.01;
restPercentage -= stock.StockVolumePercentage;
averageChangePercent += stock.StockChangePercent;
}
averageChangePercent /= Fundation.Stocks.Count;
sum += restPercentage * averageChangePercent * 0.01;
FundationEstimation = $"{sum * FundationAdjustment:F2} %";
FundationColor = sum > 0 ? "red" : "green";
}
}
//定义消息
// public class CustomizedMessage : ValueChangedMessage<MessageModel> // PropertyChangedMessage // RequestMessage ( Async + ..)
// {
// public CustomizedMessage(MessageModel value) : base(value)
// {
// }
// }using HandyControl.Controls;
using WPFFundation.ViewModels;
namespace WPFFundation.Views;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : GlowWindow
{
private readonly MainViewModel mainViewModel;
public MainWindow()
{
InitializeComponent();
#region 注册并接受消息
//WeakReferenceMessenger.Default.Register<CustomizedMessage>(this, (o, m) => { MessageBox.Show("Received!"); });
#endregion
mainViewModel = new MainViewModel();
DataContext = mainViewModel;
}
}登录后方可回帖



估计的净值,难免是有误差,但是总好过被套着麻袋割韭菜