diff --git a/Alpha.sln b/Alpha.sln new file mode 100644 index 0000000..b4b501f --- /dev/null +++ b/Alpha.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35617.110 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIShell", "UIShell\UIShell.csproj", "{25136FF5-345F-FE98-37AF-CBF690B70510}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Alpha", "Alpha\Alpha.csproj", "{EE9CA942-E12D-9230-D3D5-1B63E2F95A73}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {25136FF5-345F-FE98-37AF-CBF690B70510}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25136FF5-345F-FE98-37AF-CBF690B70510}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25136FF5-345F-FE98-37AF-CBF690B70510}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25136FF5-345F-FE98-37AF-CBF690B70510}.Release|Any CPU.Build.0 = Release|Any CPU + {EE9CA942-E12D-9230-D3D5-1B63E2F95A73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE9CA942-E12D-9230-D3D5-1B63E2F95A73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE9CA942-E12D-9230-D3D5-1B63E2F95A73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE9CA942-E12D-9230-D3D5-1B63E2F95A73}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1132CA7C-2F80-4134-9EBC-7EC9A58C8769} + EndGlobalSection +EndGlobal diff --git a/Alpha/Alpha.csproj b/Alpha/Alpha.csproj new file mode 100644 index 0000000..6fd6f2f --- /dev/null +++ b/Alpha/Alpha.csproj @@ -0,0 +1,22 @@ + + + + WinExe + net9.0-windows + enable + enable + true + true + + + + + + + + + + + + + diff --git a/Alpha/App.xaml b/Alpha/App.xaml new file mode 100644 index 0000000..ee25f3c --- /dev/null +++ b/Alpha/App.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/Alpha/App.xaml.cs b/Alpha/App.xaml.cs new file mode 100644 index 0000000..60a484f --- /dev/null +++ b/Alpha/App.xaml.cs @@ -0,0 +1,14 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace Alpha +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } + +} diff --git a/Alpha/AssemblyInfo.cs b/Alpha/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/Alpha/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Alpha/Converters/AlphaResultToColorConverter.cs b/Alpha/Converters/AlphaResultToColorConverter.cs new file mode 100644 index 0000000..7a7f70e --- /dev/null +++ b/Alpha/Converters/AlphaResultToColorConverter.cs @@ -0,0 +1,36 @@ +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; +using Alpha.Models; + +namespace Alpha.Converters +{ + public class AlphaResultToColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is AlphaResponse response) + { + List? checks; + checks = response.Is?.Checks; + if (checks != null) + { + return !checks.Any(delegate (AlphaCheck check) + { + string? result; + result = check.Result; + return result != null && (result.ToUpper()?.Equals("FAIL", StringComparison.OrdinalIgnoreCase)).GetValueOrDefault(); + }) + ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2E7D32")) + : (object)new SolidColorBrush((Color)ColorConverter.ConvertFromString("#C62828")); + } + } + return new SolidColorBrush(Colors.Transparent); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Alpha/Converters/AlphaResultToTextConverter.cs b/Alpha/Converters/AlphaResultToTextConverter.cs new file mode 100644 index 0000000..81ee8be --- /dev/null +++ b/Alpha/Converters/AlphaResultToTextConverter.cs @@ -0,0 +1,35 @@ +using System.Globalization; +using System.Windows.Data; +using Alpha.Models; + +namespace Alpha.Converters +{ + public class AlphaResultToTextConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is AlphaResponse response) + { + List? checks; + checks = response.Is?.Checks; + if (checks != null) + { + return !checks.Any(delegate (AlphaCheck check) + { + string? result; + result = check.Result; + return result != null && (result.ToUpper()?.Equals("FAIL", StringComparison.OrdinalIgnoreCase)).GetValueOrDefault(); + }) + ? "已通过" + : (object)"未通过"; + } + } + return "未知状态"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Alpha/Converters/CheckResultColorConverter.cs b/Alpha/Converters/CheckResultColorConverter.cs new file mode 100644 index 0000000..85b3ba1 --- /dev/null +++ b/Alpha/Converters/CheckResultColorConverter.cs @@ -0,0 +1,27 @@ +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace Alpha.Converters +{ + public class CheckResultColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is string result + ? result.ToUpper() switch + { + "PASS" => new SolidColorBrush(Color.FromRgb(40, 167, 69)), + "FAIL" => new SolidColorBrush(Color.FromRgb(220, 53, 69)), + "PENDING" => new SolidColorBrush(Color.FromRgb(byte.MaxValue, 193, 7)), + _ => new SolidColorBrush(Color.FromRgb(108, 117, 125)), + } + : (object)new SolidColorBrush(Color.FromRgb(108, 117, 125)); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Alpha/Converters/ExpiryToDateTimeConverter.cs b/Alpha/Converters/ExpiryToDateTimeConverter.cs new file mode 100644 index 0000000..00959ed --- /dev/null +++ b/Alpha/Converters/ExpiryToDateTimeConverter.cs @@ -0,0 +1,39 @@ +using Newtonsoft.Json; + +namespace Alpha.Converters +{ + public class ExpiryToDateTimeConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + if (value is DateTime) + { + writer.WriteValue(((DateTime)value - new DateTime(1970, 1, 1)).TotalSeconds); + } + else + { + writer.WriteNull(); + } + } + + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + if ((uint)(reader.TokenType - 7) <= 1u) + { + double totalSeconds; + totalSeconds = Convert.ToDouble(reader.Value); + return DateTime.Now.AddSeconds(totalSeconds); + } + throw new JsonSerializationException("Unexpected token type: " + reader.TokenType); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(DateTime); + } + } +} diff --git a/Alpha/Converters/IgnoreStatusVisibilityMultiConverter.cs b/Alpha/Converters/IgnoreStatusVisibilityMultiConverter.cs new file mode 100644 index 0000000..0380f11 --- /dev/null +++ b/Alpha/Converters/IgnoreStatusVisibilityMultiConverter.cs @@ -0,0 +1,87 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using Alpha.Models; + +namespace Alpha.Converters +{ + public class IgnoreStatusVisibilityMultiConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length == 2 && values[0] is IgnoreStatus status && values[1] is SimulationCompletedData param) + { + if ((status & IgnoreStatus.Stop) == IgnoreStatus.Stop) + { + AlphaResponse? alphaResult; + alphaResult = param.AlphaResult; + if (alphaResult != null && (alphaResult.Is?.Checks?.Any(delegate (AlphaCheck check) + { + string? result2; + result2 = check.Result; + return result2 != null && (result2.ToUpper()?.Equals("FAIL", StringComparison.OrdinalIgnoreCase)).GetValueOrDefault(); + })).GetValueOrDefault()) + { + return Visibility.Collapsed; + } + } + if ((status & IgnoreStatus.Pass) == IgnoreStatus.Pass) + { + AlphaResponse? alphaResult2; + alphaResult2 = param.AlphaResult; + if (alphaResult2 != null && alphaResult2.Is?.Checks?.Any(delegate (AlphaCheck check) + { + string? result; + result = check.Result; + return result != null && (result.ToUpper()?.Equals("FAIL", StringComparison.OrdinalIgnoreCase)).GetValueOrDefault(); + }) == false) + { + return Visibility.Collapsed; + } + } + if ((status & IgnoreStatus.Failure) == IgnoreStatus.Failure) + { + string? status2; + status2 = param.Status; + if (status2 != null && (status2.ToUpper()?.Equals("FAIL", StringComparison.OrdinalIgnoreCase)).GetValueOrDefault()) + { + return Visibility.Collapsed; + } + } + if ((status & IgnoreStatus.Warning) == IgnoreStatus.Warning) + { + string? status3; + status3 = param.Status; + if (status3 != null && (status3.ToUpper()?.Equals("WARNING", StringComparison.OrdinalIgnoreCase)).GetValueOrDefault()) + { + return Visibility.Collapsed; + } + } + if ((status & IgnoreStatus.Complete) == IgnoreStatus.Complete) + { + string? status4; + status4 = param.Status; + if (status4 != null && (status4.ToUpper()?.Equals("COMPLETE", StringComparison.OrdinalIgnoreCase)).GetValueOrDefault()) + { + return Visibility.Collapsed; + } + } + if ((status & IgnoreStatus.Error) == IgnoreStatus.Error) + { + string? status5; + status5 = param.Status; + if (status5 != null && (status5.ToUpper()?.Equals("ERROR", StringComparison.OrdinalIgnoreCase)).GetValueOrDefault()) + { + return Visibility.Collapsed; + } + } + } + return Visibility.Visible; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Alpha/Converters/InverseUniversalComparisonConverter.cs b/Alpha/Converters/InverseUniversalComparisonConverter.cs new file mode 100644 index 0000000..adad0dd --- /dev/null +++ b/Alpha/Converters/InverseUniversalComparisonConverter.cs @@ -0,0 +1,86 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Alpha.Converters +{ + public class InverseUniversalComparisonConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (parameter is string paramStr && paramStr == "NullToVisible") + { + return (value == null) ? Visibility.Collapsed : Visibility.Visible; + } + return parameter is string paramStr2 && paramStr2 == "NullToHidden" + ? (value != null) ? Visibility.Hidden : Visibility.Visible + : value == null + ? Visibility.Visible + : parameter == null + ? value is bool + ? ((bool)value) ? Visibility.Collapsed : Visibility.Visible + : value is int + ? ((int)value != 0) ? Visibility.Collapsed : Visibility.Visible + : value is double + ? (Math.Abs((double)value) > 0.0001) ? Visibility.Collapsed : Visibility.Visible + : value is string strValue2 + ? (!string.IsNullOrEmpty(strValue2)) ? Visibility.Collapsed : Visibility.Visible + : value.GetType().IsEnum + ? (!value.Equals(Enum.GetValues(value.GetType()).GetValue(0))) ? Visibility.Collapsed : Visibility.Visible + : (object)Visibility.Collapsed + : (((value is not int && value is not double && value is not float) || 1 == 0) ? ((value is bool boolValue && parameter is bool) ? (boolValue == (bool)parameter) : ((value is string strValue && parameter is string paramStr3) ? string.Equals(strValue, paramStr3, StringComparison.InvariantCulture) : ((value.GetType().IsEnum && parameter.GetType().IsEnum) ? value.Equals(parameter) : value.Equals(parameter)))) : (Math.Abs(System.Convert.ToDouble(value) - System.Convert.ToDouble(parameter)) < 0.0001)) ? Visibility.Collapsed : Visibility.Visible; + } + + public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Visibility) + { + if ((Visibility)value - 1 <= Visibility.Hidden) + { + if (targetType == typeof(bool)) + { + return true; + } + if (targetType == typeof(int)) + { + return parameter ?? 1; + } + if (targetType == typeof(double)) + { + return parameter ?? 1.0; + } + if (targetType == typeof(string)) + { + return parameter?.ToString() ?? string.Empty; + } + if (targetType.IsEnum && parameter != null) + { + return Enum.Parse(targetType, parameter.ToString() ?? string.Empty); + } + } + else + { + if (targetType == typeof(bool)) + { + return false; + } + if (targetType == typeof(int) || targetType == typeof(double)) + { + return 0; + } + if (targetType == typeof(string)) + { + return string.Empty; + } + if (targetType.IsEnum) + { + Array values; + values = Enum.GetValues(targetType); + return values.Length <= 0 ? null : values.GetValue(0); + } + } + } + throw new InvalidOperationException("ConvertBack can only convert from Visibility."); + } + } +} diff --git a/Alpha/Converters/NullToVisibilityConverter.cs b/Alpha/Converters/NullToVisibilityConverter.cs new file mode 100644 index 0000000..989f577 --- /dev/null +++ b/Alpha/Converters/NullToVisibilityConverter.cs @@ -0,0 +1,19 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Alpha.Converters +{ + public class NullToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (value == null) ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Alpha/Converters/StatusToColorConverter.cs b/Alpha/Converters/StatusToColorConverter.cs new file mode 100644 index 0000000..3dde17e --- /dev/null +++ b/Alpha/Converters/StatusToColorConverter.cs @@ -0,0 +1,26 @@ +using System.Globalization; +using System.Windows.Data; + +namespace Alpha.Converters +{ + public class StatusToColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is string status + ? status.ToUpper() switch + { + "COMPLETE" => "#2E7D32", + "WARNING" => "#F57C00", + "ERROR" => "#C62828", + _ => "#757575", + } + : (object)"#757575"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Alpha/Converters/UniversalComparisonConverter.cs b/Alpha/Converters/UniversalComparisonConverter.cs new file mode 100644 index 0000000..5fcc0f9 --- /dev/null +++ b/Alpha/Converters/UniversalComparisonConverter.cs @@ -0,0 +1,84 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Alpha.Converters +{ + public class UniversalComparisonConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return parameter is string paramStr && paramStr == "NullToVisible" + ? (value != null) ? Visibility.Collapsed : Visibility.Visible + : parameter is string paramStr2 && paramStr2 == "NullToHidden" + ? (value == null) ? Visibility.Hidden : Visibility.Visible + : value == null + ? Visibility.Collapsed + : parameter == null + ? value is bool + ? (!(bool)value) ? Visibility.Collapsed : Visibility.Visible + : value is int + ? ((int)value == 0) ? Visibility.Collapsed : Visibility.Visible + : value is double + ? (!(Math.Abs((double)value) > 0.0001)) ? Visibility.Collapsed : Visibility.Visible + : value is string strValue2 + ? string.IsNullOrEmpty(strValue2) ? Visibility.Collapsed : Visibility.Visible + : value.GetType().IsEnum + ? value.Equals(Enum.GetValues(value.GetType()).GetValue(0)) ? Visibility.Collapsed : Visibility.Visible + : (object)Visibility.Visible + : (!(((value is not int && value is not double && value is not float) || 1 == 0) ? ((value is bool boolValue && parameter is bool) ? (boolValue == (bool)parameter) : ((value is string strValue && parameter is string paramStr3) ? string.Equals(strValue, paramStr3, StringComparison.InvariantCulture) : ((value.GetType().IsEnum && parameter.GetType().IsEnum) ? value.Equals(parameter) : value.Equals(parameter)))) : (Math.Abs(System.Convert.ToDouble(value) - System.Convert.ToDouble(parameter)) < 0.0001))) ? Visibility.Collapsed : Visibility.Visible; + } + + public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Visibility visibility) + { + if (visibility == Visibility.Visible) + { + if (targetType == typeof(bool)) + { + return true; + } + if (targetType == typeof(int)) + { + return parameter ?? 1; + } + if (targetType == typeof(double)) + { + return parameter ?? 1.0; + } + if (targetType == typeof(string)) + { + return parameter?.ToString() ?? string.Empty; + } + if (targetType.IsEnum && parameter != null) + { + return Enum.Parse(targetType, parameter.ToString() ?? string.Empty); + } + } + else + { + if (targetType == typeof(bool)) + { + return false; + } + if (targetType == typeof(int) || targetType == typeof(double)) + { + return 0; + } + if (targetType == typeof(string)) + { + return string.Empty; + } + if (targetType.IsEnum) + { + Array values; + values = Enum.GetValues(targetType); + return values.Length <= 0 ? null : values.GetValue(0); + } + } + } + throw new InvalidOperationException("ConvertBack只能从Visibility转换。"); + } + } +} diff --git a/Alpha/FodyWeavers.xml b/Alpha/FodyWeavers.xml new file mode 100644 index 0000000..63fc148 --- /dev/null +++ b/Alpha/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Alpha/Helpers/DirectiveReplacerExtension.cs b/Alpha/Helpers/DirectiveReplacerExtension.cs new file mode 100644 index 0000000..bb99c91 --- /dev/null +++ b/Alpha/Helpers/DirectiveReplacerExtension.cs @@ -0,0 +1,48 @@ +using System.Text.RegularExpressions; + +namespace Alpha.Helpers +{ + public static class DirectiveReplacerExtension + { + public static IEnumerable ExpandPlaceholders(string input, Dictionary> replacements) + { + MatchCollection matches = Regex.Matches(input, @"\$(\d+)"); + List placeholderKeys = []; + foreach (Match match in matches) + { + int key = int.Parse(match.Groups[1].Value); + if (!placeholderKeys.Contains(key)) + { + placeholderKeys.Add(key); + } + } + + // Start with an empty combination + IEnumerable> combos = + new List> { Enumerable.Empty<(int, string)>() }; + + // Build combinations + foreach (int key in placeholderKeys) + { + combos = combos.SelectMany(c => + replacements[key].Select(val => c.Append((key, val)))); + } + + // Replace placeholders in each combination + foreach (IEnumerable<(int Key, string Value)> combo in combos) + { + string result = input; + foreach ((int k, string v) in combo) + { + result = Regex.Replace(result, @"\$" + k + @"\b", v); + } + yield return result; + } + } + + public static bool HasPlaceholders(string input, int id) + { + return Regex.IsMatch(input, @"\$" + id + @"\b"); + } + } +} diff --git a/Alpha/Helpers/PasswordBoxHelper.cs b/Alpha/Helpers/PasswordBoxHelper.cs new file mode 100644 index 0000000..9f6e9b2 --- /dev/null +++ b/Alpha/Helpers/PasswordBoxHelper.cs @@ -0,0 +1,71 @@ +using System.Windows; +using System.Windows.Controls; + +namespace Alpha.Helpers +{ + public static class PasswordBoxHelper + { + public static readonly DependencyProperty BoundPassword = + DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxHelper), new PropertyMetadata(string.Empty, OnBoundPasswordChanged)); + + public static readonly DependencyProperty BindPassword = + DependencyProperty.RegisterAttached("BindPassword", typeof(bool), typeof(PasswordBoxHelper), new PropertyMetadata(false, OnBindPasswordChanged)); + + private static readonly DependencyProperty UpdatingPassword = + DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxHelper), new PropertyMetadata(false)); + + private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is PasswordBox passwordBox && !(bool)passwordBox.GetValue(UpdatingPassword)) + { + passwordBox.Password = (string)e.NewValue; + } + } + + private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e) + { + if (dp is PasswordBox passwordBox) + { + if ((bool)e.OldValue) + { + passwordBox.PasswordChanged -= HandlePasswordChanged; + } + + if ((bool)e.NewValue) + { + passwordBox.PasswordChanged += HandlePasswordChanged; + } + } + } + + private static void HandlePasswordChanged(object sender, RoutedEventArgs e) + { + if (sender is PasswordBox passwordBox) + { + passwordBox.SetValue(UpdatingPassword, true); + passwordBox.SetValue(BoundPassword, passwordBox.Password); + passwordBox.SetValue(UpdatingPassword, false); + } + } + + public static string GetBoundPassword(DependencyObject dp) + { + return (string)dp.GetValue(BoundPassword); + } + + public static void SetBoundPassword(DependencyObject dp, string value) + { + dp.SetValue(BoundPassword, value); + } + + public static bool GetBindPassword(DependencyObject dp) + { + return (bool)dp.GetValue(BindPassword); + } + + public static void SetBindPassword(DependencyObject dp, bool value) + { + dp.SetValue(BindPassword, value); + } + } +} diff --git a/Alpha/Helpers/TimeHelper.cs b/Alpha/Helpers/TimeHelper.cs new file mode 100644 index 0000000..fbd8aa4 --- /dev/null +++ b/Alpha/Helpers/TimeHelper.cs @@ -0,0 +1,37 @@ +namespace Alpha.Helpers +{ + public static class TimeHelper + { + public static string GetTimeSpan(DateTime start, DateTime end) + { + TimeSpan timeSpan = end - start; + return timeSpan.ToString(@"hh\:mm\:ss"); + } + + public static string FormatTime(this TimeSpan time) + { + if (time.TotalSeconds < 60) + { + return $"{time.Seconds}秒"; + } + else if (time.TotalSeconds < 3600) + { + int minutes = time.Minutes; + int seconds = time.Seconds; + return $"{minutes}分 {seconds}秒"; + } + else if (time.TotalSeconds < 86400) + { + int hours = time.Hours; + int minutes = time.Minutes; + return $"{hours}小时 {minutes}分"; + } + else + { + int days = time.Days; + int hours = time.Hours; + return $"{days}天 {hours}小时"; + } + } + } +} diff --git a/Alpha/Models/AlphaResponse.cs b/Alpha/Models/AlphaResponse.cs new file mode 100644 index 0000000..d78a66d --- /dev/null +++ b/Alpha/Models/AlphaResponse.cs @@ -0,0 +1,217 @@ +using Newtonsoft.Json; + +namespace Alpha.Models +{ + public class AlphaResponse + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("type")] + public string? Type { get; set; } + + [JsonProperty("author")] + public string? Author { get; set; } + + [JsonProperty("settings")] + public AlphaSettings? Settings { get; set; } + + [JsonProperty("regular")] + public AlphaRegular? Regular { get; set; } + + [JsonProperty("dateCreated")] + public DateTime DateCreated { get; set; } + + [JsonProperty("dateSubmitted")] + public DateTime? DateSubmitted { get; set; } + + [JsonProperty("dateModified")] + public DateTime DateModified { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + + [JsonProperty("favorite")] + public bool Favorite { get; set; } + + [JsonProperty("hidden")] + public bool Hidden { get; set; } + + [JsonProperty("color")] + public string? Color { get; set; } + + [JsonProperty("category")] + public string? Category { get; set; } + + [JsonProperty("tags")] + public List? Tags { get; set; } + + [JsonProperty("classifications")] + public List? Classifications { get; set; } + + [JsonProperty("grade")] + public string? Grade { get; set; } + + [JsonProperty("stage")] + public string? Stage { get; set; } + + [JsonProperty("status")] + public string? Status { get; set; } + + [JsonProperty("is")] + public AlphaPerformanceMetrics? Is { get; set; } + + [JsonProperty("os")] + public object? Os { get; set; } + + [JsonProperty("train")] + public AlphaPerformanceMetrics? Train { get; set; } + + [JsonProperty("test")] + public AlphaPerformanceMetrics? Test { get; set; } + + [JsonProperty("prod")] + public object? Prod { get; set; } + + [JsonProperty("competitions")] + public object? Competitions { get; set; } + + [JsonProperty("themes")] + public object? Themes { get; set; } + + [JsonProperty("pyramids")] + public object? Pyramids { get; set; } + + [JsonProperty("team")] + public object? Team { get; set; } + } + + public class AlphaSettings + { + [JsonProperty("instrumentType")] + public string? InstrumentType { get; set; } + + [JsonProperty("region")] + public string? Region { get; set; } + + [JsonProperty("universe")] + public string? Universe { get; set; } + + [JsonProperty("delay")] + public int Delay { get; set; } + + [JsonProperty("decay")] + public int Decay { get; set; } + + [JsonProperty("neutralization")] + public string? Neutralization { get; set; } + + [JsonProperty("truncation")] + public double Truncation { get; set; } + + [JsonProperty("pasteurization")] + public string? Pasteurization { get; set; } + + [JsonProperty("unitHandling")] + public string? UnitHandling { get; set; } + + [JsonProperty("nanHandling")] + public string? NanHandling { get; set; } + + [JsonProperty("language")] + public string? Language { get; set; } + + [JsonProperty("visualization")] + public bool Visualization { get; set; } + + [JsonProperty("testPeriod")] + public string? TestPeriod { get; set; } + } + + public class AlphaRegular + { + [JsonProperty("code")] + public string? Code { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + [JsonProperty("operatorCount")] + public int OperatorCount { get; set; } + } + + public class AlphaClassification + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + } + + public class AlphaPerformanceMetrics + { + [JsonProperty("pnl")] + public int Pnl { get; set; } + + [JsonProperty("bookSize")] + public int BookSize { get; set; } + + [JsonProperty("longCount")] + public int LongCount { get; set; } + + [JsonProperty("shortCount")] + public int ShortCount { get; set; } + + [JsonProperty("turnover")] + public double Turnover { get; set; } + + [JsonProperty("returns")] + public double Returns { get; set; } + + [JsonProperty("drawdown")] + public double Drawdown { get; set; } + + [JsonProperty("margin")] + public double Margin { get; set; } + + [JsonProperty("sharpe")] + public double Sharpe { get; set; } + + [JsonProperty("fitness")] + public double Fitness { get; set; } + + [JsonProperty("startDate")] + public DateTime StartDate { get; set; } + + [JsonProperty("checks")] + public List? Checks { get; set; } + } + + public class AlphaCheck + { + [JsonProperty("name")] + public string? Name { get; set; } + + [JsonProperty("result")] + public string? Result { get; set; } + + [JsonProperty("limit")] + public double? Limit { get; set; } + + [JsonProperty("value")] + public double? Value { get; set; } + + [JsonProperty("competitions")] + public List? Competitions { get; set; } + } + + public class AlphaCompetition + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + } +} diff --git a/Alpha/Models/DatasetResponse.cs b/Alpha/Models/DatasetResponse.cs new file mode 100644 index 0000000..289cbc7 --- /dev/null +++ b/Alpha/Models/DatasetResponse.cs @@ -0,0 +1,91 @@ +using Newtonsoft.Json; + +namespace Alpha.Models +{ + public class DatasetResponse + { + [JsonProperty("count")] + public int Count { get; set; } + + [JsonProperty("results")] + public List? Results { get; set; } + } + + public class Dataset + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + [JsonProperty("category")] + public Category? Category { get; set; } + + [JsonProperty("subcategory")] + public Subcategory? Subcategory { get; set; } + + [JsonProperty("region")] + public string? Region { get; set; } + + [JsonProperty("delay")] + public int Delay { get; set; } + + [JsonProperty("universe")] + public string? Universe { get; set; } + + [JsonProperty("coverage")] + public double Coverage { get; set; } + + [JsonProperty("valueScore")] + public double ValueScore { get; set; } + + [JsonProperty("userCount")] + public int UserCount { get; set; } + + [JsonProperty("alphaCount")] + public int AlphaCount { get; set; } + + [JsonProperty("fieldCount")] + public int FieldCount { get; set; } + + [JsonProperty("themes")] + public List? Themes { get; set; } + + [JsonProperty("researchPapers")] + public List? ResearchPapers { get; set; } + } + + public class Category + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + } + + public class Subcategory + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + } + + public class ResearchPaper + { + [JsonProperty("type")] + public string? Type { get; set; } + + [JsonProperty("title")] + public string? Title { get; set; } + + [JsonProperty("url")] + public string? Url { get; set; } + } +} diff --git a/Alpha/Models/FieldResponse.cs b/Alpha/Models/FieldResponse.cs new file mode 100644 index 0000000..cc2f7fa --- /dev/null +++ b/Alpha/Models/FieldResponse.cs @@ -0,0 +1,64 @@ +using Newtonsoft.Json; + +namespace Alpha.Models +{ + public class FieldResponse + { + [JsonProperty("count")] + public int Count { get; set; } + + [JsonProperty("results")] + public List? Results { get; set; } + } + + public class Field + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + [JsonProperty("dataset")] + public DatasetInfo? Dataset { get; set; } + + [JsonProperty("category")] + public Category? Category { get; set; } + + [JsonProperty("subcategory")] + public Subcategory? Subcategory { get; set; } + + [JsonProperty("region")] + public string? Region { get; set; } + + [JsonProperty("delay")] + public int Delay { get; set; } + + [JsonProperty("universe")] + public string? Universe { get; set; } + + [JsonProperty("type")] + public string? Type { get; set; } + + [JsonProperty("coverage")] + public double Coverage { get; set; } + + [JsonProperty("userCount")] + public int UserCount { get; set; } + + [JsonProperty("alphaCount")] + public int AlphaCount { get; set; } + + [JsonProperty("themes")] + public List? Themes { get; set; } + } + + public class DatasetInfo + { + [JsonProperty("id")] + public string? Id { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + } +} diff --git a/Alpha/Models/IgnoreStatus.cs b/Alpha/Models/IgnoreStatus.cs new file mode 100644 index 0000000..51d50e2 --- /dev/null +++ b/Alpha/Models/IgnoreStatus.cs @@ -0,0 +1,14 @@ +namespace Alpha.Models +{ + [Flags] + public enum IgnoreStatus + { + None = 0, + Warning = 1, + Error = 2, + Stop = 4, + Pass = 8, + Complete = 16, + Failure = 32 + } +} diff --git a/Alpha/Models/Knowledge.cs b/Alpha/Models/Knowledge.cs new file mode 100644 index 0000000..70e80a3 --- /dev/null +++ b/Alpha/Models/Knowledge.cs @@ -0,0 +1,10 @@ +namespace Alpha.Models +{ + public class Knowledge + { + public string? Title { get; set; } + public string? Description { get; set; } + public int FieldCount { get; set; } + public List? Fields { get; set; } + } +} \ No newline at end of file diff --git a/Alpha/Models/SearchScope.cs b/Alpha/Models/SearchScope.cs new file mode 100644 index 0000000..23eff8b --- /dev/null +++ b/Alpha/Models/SearchScope.cs @@ -0,0 +1,26 @@ +namespace Alpha.Models +{ + public class SearchScope + { + public string? Region { get; set; } + public string? Delay { get; set; } + public string? Universe { get; set; } + public string? InstrumentType { get; set; } + + public SearchScope() + { + Region = null; + Delay = null; + Universe = null; + InstrumentType = null; + } + + public SearchScope(string? region, string? delay, string? universe, string? instrumentType) + { + Region = region; + Delay = delay; + Universe = universe; + InstrumentType = instrumentType; + } + } +} diff --git a/Alpha/Models/SimulationCompletedData.cs b/Alpha/Models/SimulationCompletedData.cs new file mode 100644 index 0000000..86e3f26 --- /dev/null +++ b/Alpha/Models/SimulationCompletedData.cs @@ -0,0 +1,113 @@ +using Newtonsoft.Json; + +namespace Alpha.Models +{ + public class SimulationCompletedSettings + { + [JsonProperty("instrumentType")] + public string? InstrumentType { get; set; } + + [JsonProperty("region")] + public string? Region { get; set; } + + [JsonProperty("universe")] + public string? Universe { get; set; } + + [JsonProperty("delay")] + public int Delay { get; set; } + + [JsonProperty("decay")] + public int Decay { get; set; } + + [JsonProperty("neutralization")] + public string? Neutralization { get; set; } + + [JsonProperty("truncation")] + public double Truncation { get; set; } + + [JsonProperty("pasteurization")] + public string? Pasteurization { get; set; } + + [JsonProperty("unitHandling")] + public string? UnitHandling { get; set; } + + [JsonProperty("nanHandling")] + public string? NanHandling { get; set; } + + [JsonProperty("language")] + public string? Language { get; set; } + + [JsonProperty("visualization")] + public bool Visualization { get; set; } + } + + public class SimulationCompletedData + { + [JsonProperty("type")] + public string? Type { get; set; } + + [JsonProperty("regular")] + public string? Regular { get; set; } + + public string? Time { get; set; } + + [JsonProperty("settings")] + public SimulationCompletedSettings? Settings { get; set; } + + [JsonProperty("status")] + public string? Status { get; set; } + + [JsonProperty("message")] + public string? Message { get; set; } + + [JsonProperty("location")] + public SimulationCompletedLocation? Location { get; set; } + + [JsonProperty("alpha")] + public string? Alpha { get; set; } + + public AlphaResponse? AlphaResult { get; set; } + + public static SimulationCompletedData CreateDefault() + { + return new SimulationCompletedData + { + Type = "REGULAR", + Regular = "liabilities/assets", + Settings = new SimulationCompletedSettings + { + InstrumentType = "EQUITY", + Region = "USA", + Universe = "TOP3000", + Delay = 1, + Decay = 0, + Neutralization = "INDUSTRY", + Truncation = 0.08, + Pasteurization = "ON", + UnitHandling = "VERIFY", + NanHandling = "OFF", + Language = "FASTEXPR", + Visualization = false + } + }; + } + } + + public class SimulationCompletedLocation + { + [JsonProperty("line")] + public int Line { get; set; } + + [JsonProperty("start")] + public int Start { get; set; } + + [JsonProperty("end")] + public int End { get; set; } + + [JsonProperty("type")] + public string? Type { get; set; } + + [JsonProperty("property")] + public string? Property { get; set; } + } +} diff --git a/Alpha/Models/SimulationData.cs b/Alpha/Models/SimulationData.cs new file mode 100644 index 0000000..f69d78b --- /dev/null +++ b/Alpha/Models/SimulationData.cs @@ -0,0 +1,79 @@ +using Newtonsoft.Json; + +namespace Alpha.Models +{ + public class SimulationSettings + { + [JsonProperty("instrumentType")] + public string? InstrumentType { get; set; } + + [JsonProperty("region")] + public string? Region { get; set; } + + [JsonProperty("universe")] + public string? Universe { get; set; } + + [JsonProperty("delay")] + public int Delay { get; set; } + + [JsonProperty("decay")] + public int Decay { get; set; } + + [JsonProperty("neutralization")] + public string? Neutralization { get; set; } + + [JsonProperty("truncation")] + public double Truncation { get; set; } + + [JsonProperty("pasteurization")] + public string? Pasteurization { get; set; } + + [JsonProperty("unitHandling")] + public string? UnitHandling { get; set; } + + [JsonProperty("nanHandling")] + public string? NanHandling { get; set; } + + [JsonProperty("language")] + public string? Language { get; set; } + + [JsonProperty("visualization")] + public bool Visualization { get; set; } + } + + public class SimulationData + { + [JsonProperty("type")] + public string? Type { get; set; } + + [JsonProperty("regular")] + public string? Regular { get; set; } + + [JsonProperty("settings")] + public SimulationSettings? Settings { get; set; } + + public static SimulationData CreateDefault() + { + return new SimulationData + { + Type = "REGULAR", + Regular = "liabilities/assets", + Settings = new SimulationSettings + { + InstrumentType = "EQUITY", + Region = "USA", + Universe = "TOP3000", + Delay = 1, + Decay = 0, + Neutralization = "INDUSTRY", + Truncation = 0.08, + Pasteurization = "ON", + UnitHandling = "VERIFY", + NanHandling = "OFF", + Language = "FASTEXPR", + Visualization = false + } + }; + } + } +} diff --git a/Alpha/Models/SimulationSettingsModel.cs b/Alpha/Models/SimulationSettingsModel.cs new file mode 100644 index 0000000..6e972c4 --- /dev/null +++ b/Alpha/Models/SimulationSettingsModel.cs @@ -0,0 +1,35 @@ +using Alpha.ViewModels; +using ReactiveUI.Fody.Helpers; + +namespace Alpha.Models +{ + public class SimulationSettingsModel : ViewModelBase + { + [Reactive] + public string? ProgrammingLanguage { get; set; } + [Reactive] + public string? AssetClass { get; set; } + [Reactive] + public string? Region { get; set; } + [Reactive] + public string? Liquidity { get; set; } + [Reactive] + public string? DecisionDelay { get; set; } + [Reactive] + public string? AlphaWeight { get; set; } + [Reactive] + public string? DataCleaning { get; set; } + [Reactive] + public string? WarningThrowing { get; set; } + [Reactive] + public string? MissingValueHandling { get; set; } + [Reactive] + public string? LinearDecayWeightedAverage { get; set; } = "4"; + [Reactive] + public string? TruncationWeight { get; set; } = "0.08"; + [Reactive] + public string? TimeEvaluationYear { get; set; } = "1"; + [Reactive] + public string? TimeEvaluationMonth { get; set; } = "0"; + } +} diff --git a/Alpha/Models/WorldQuantAccountLoginResponse.cs b/Alpha/Models/WorldQuantAccountLoginResponse.cs new file mode 100644 index 0000000..d679385 --- /dev/null +++ b/Alpha/Models/WorldQuantAccountLoginResponse.cs @@ -0,0 +1,28 @@ +using Alpha.Converters; +using Newtonsoft.Json; + +namespace Alpha.Models +{ + public class WorldQuantAccountLoginResponse + { + public User? User { get; set; } + public Token? Token { get; set; } + public List Permissions { get; set; } + + public WorldQuantAccountLoginResponse() + { + Permissions = []; + } + } + + public class User + { + public string? Id { get; set; } + } + + public class Token + { + [JsonConverter(typeof(ExpiryToDateTimeConverter))] + public DateTime Expiry { get; set; } + } +} diff --git a/Alpha/Processing/SyntaxChecker.cs b/Alpha/Processing/SyntaxChecker.cs new file mode 100644 index 0000000..110bc8d --- /dev/null +++ b/Alpha/Processing/SyntaxChecker.cs @@ -0,0 +1,561 @@ +using System.Text.RegularExpressions; +using Newtonsoft.Json; + +namespace Alpha.Processing +{ + // 数据类型枚举 + public enum DataType + { + Matrix, + Group, + Universe, + Vector + } + + // 参数定义 + public class ParameterDefinition + { + public string? Name { get; set; } + public DataType Type { get; set; } + public bool HasDefault { get; set; } + public string? DefaultValue { get; set; } + } + + // 函数定义 + public class FunctionDefinition + { + public string? Name { get; set; } + public List? Parameters { get; set; } + + // 解析函数定义字符串 + public static FunctionDefinition FromDefinitionString(string definition) + { + // 示例: "add(x: Vector, y: Vector, filter: Vector = false)" + string pattern = @"(\w+)\((.*?)\)"; + Match match = Regex.Match(definition, pattern); + if (!match.Success) + { + throw new Exception($"Invalid definition format: {definition}"); + } + + string funcName = match.Groups[1].Value; + string paramsString = match.Groups[2].Value; + + List parameters = []; + List paramsSplit = SplitParameters(paramsString); + + foreach (string param in paramsSplit) + { + string p = param.Trim(); + ParameterDefinition paramDef = new(); + if (p.Contains("=")) + { + string[] parts = p.Split('='); + paramDef.HasDefault = true; + paramDef.DefaultValue = parts[1].Trim(); + string[] nameType = parts[0].Trim().Split(':'); + paramDef.Name = nameType[0].Trim(); + paramDef.Type = ParseDataType(nameType[1].Trim()); + } + else + { + paramDef.HasDefault = false; + string[] nameType = p.Split(':'); + paramDef.Name = nameType[0].Trim(); + paramDef.Type = ParseDataType(nameType[1].Trim()); + } + parameters.Add(paramDef); + } + + return new FunctionDefinition + { + Name = funcName, + Parameters = parameters + }; + } + + private static List SplitParameters(string paramsString) + { + List result = []; + string current = ""; + bool inQuotes = false; + foreach (char c in paramsString) + { + if (c is '"' or '\'') + { + inQuotes = !inQuotes; + } + if (c == ',' && !inQuotes) + { + result.Add(current.Trim()); + current = ""; + } + else + { + current += c; + } + } + if (!string.IsNullOrWhiteSpace(current)) + { + result.Add(current.Trim()); + } + + return result; + } + + private static DataType ParseDataType(string typeStr) + { + return typeStr.ToLower() switch + { + "matrix" => DataType.Matrix, + "group" => DataType.Group, + "universe" => DataType.Universe, + "vector" => DataType.Vector, + _ => throw new Exception($"Unsupported data type: {typeStr}") + }; + } + } + + // 令牌类型枚举 + public enum TokenType + { + FunctionName, + Parameter, + Symbol, // 如 '=', ',' + ParenthesisOpen, + ParenthesisClose, + Value, // 值,如数字、布尔值 + VariableName, + EndOfLine + } + + // 令牌类 + public class Token + { + public TokenType Type { get; set; } + public string? Value { get; set; } + public int Position { get; set; } // 位置,用于错误报告 + + public override string ToString() + { + return $"{Type}: {Value}"; + } + } + + // 词法分析器 + public class Tokenizer + { + private readonly string _input; + private int _position; + private readonly int _length; + + public Tokenizer(string input) + { + _input = input; + _position = 0; + _length = input.Length; + } + + public List Tokenize() + { + List tokens = []; + + while (_position < _length) + { + char current = _input[_position]; + + if (char.IsWhiteSpace(current)) + { + _position++; + continue; + } + + if (current == '(') + { + tokens.Add(new Token { Type = TokenType.ParenthesisOpen, Value = "(", Position = _position }); + _position++; + continue; + } + if (current == ')') + { + tokens.Add(new Token { Type = TokenType.ParenthesisClose, Value = ")", Position = _position }); + _position++; + continue; + } + if (current == ',') + { + tokens.Add(new Token { Type = TokenType.Symbol, Value = ",", Position = _position }); + _position++; + continue; + } + if (current == '=') + { + tokens.Add(new Token { Type = TokenType.Symbol, Value = "=", Position = _position }); + _position++; + continue; + } + + if (char.IsLetter(current) || current == '_') + { + string identifier = ReadWhile(c => char.IsLetterOrDigit(c) || c == '_'); + // Determine if it's a function name or a variable/parameter + tokens.Add(new Token { Type = TokenType.FunctionName, Value = identifier, Position = _position - identifier.Length }); + continue; + } + + if (char.IsDigit(current) || current == '.' || current == '-') + { + string number = ReadWhile(c => char.IsDigit(c) || c == '.' || c == '-'); + tokens.Add(new Token { Type = TokenType.Value, Value = number, Position = _position - number.Length }); + continue; + } + + if (current is '"' or '\'') + { + string str = ReadString(current); + tokens.Add(new Token { Type = TokenType.Value, Value = str, Position = _position - str.Length - 2 }); + continue; + } + + // 未识别的符号 + tokens.Add(new Token { Type = TokenType.Symbol, Value = current.ToString(), Position = _position }); + _position++; + } + + tokens.Add(new Token { Type = TokenType.EndOfLine, Value = "EOF", Position = _position }); + return tokens; + } + + private string ReadWhile(Func condition) + { + int start = _position; + while (_position < _length && condition(_input[_position])) + { + _position++; + } + return _input[start.._position]; + } + + private string ReadString(char quote) + { + _position++; // 跳过开始的引号 + int start = _position; + while (_position < _length && _input[_position] != quote) + { + _position++; + } + if (_position >= _length) + { + throw new Exception("未闭合的字符串字面量"); + } + + string str = _input[start.._position]; + _position++; // 跳过结束的引号 + return str; + } + } + + // 语法检查器 + public class SyntaxChecker + { + private readonly List _functionDefinitions; + private readonly Dictionary _variables; // 变量名及其类型 + + public SyntaxChecker(List functionDefinitions) + { + _functionDefinitions = functionDefinitions; + _variables = []; + } + + public List CheckSyntax(List codeLines) + { + List errors = []; + + foreach (string line in codeLines) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + try + { + Tokenizer tokenizer = new(line); + List tokens = tokenizer.Tokenize(); + if (tokens.Count == 0) + { + continue; + } + + // 简单判断是否是变量赋值 + int assignIndex = tokens.FindIndex(t => t.Type == TokenType.Symbol && t.Value == "="); + if (assignIndex != -1) + { + // 变量赋值 + Token varNameToken = tokens[0]; + if (varNameToken.Type != TokenType.FunctionName) + { + errors.Add($"错误: 无效的变量名 '{varNameToken.Value}' 在行: {line}"); + continue; + } + string? varName = varNameToken.Value; + + // 表达式部分 + List exprTokens = tokens.GetRange(assignIndex + 1, tokens.Count - assignIndex - 2); // 排除EOF + DataType? exprType = EvaluateExpression(exprTokens, line, errors); + if (!string.IsNullOrEmpty(varName) && exprType != null) + { + if (_variables.ContainsKey(varName)) + { + // 可选:检查类型一致性 + if (_variables[varName] != exprType) + { + errors.Add($"错误: 变量 '{varName}' 类型不一致。在行: {line}"); + } + } + else + { + + _variables[varName] = exprType.Value; + } + } + } + else + { + // 仅函数调用 + DataType? exprType = EvaluateExpression(tokens, line, errors); + } + } + catch (Exception ex) + { + errors.Add($"错误: {ex.Message} 在行: {line}"); + } + } + + return errors; + } + + private DataType? EvaluateExpression(List tokens, string line, List errors) + { + if (tokens.Count == 0) + { + return null; + } + + // 假设表达式是一个函数调用 + if (tokens[0].Type != TokenType.FunctionName) + { + errors.Add($"错误: 表达式必须以函数调用开始。在行: {line}"); + return null; + } + + string? funcName = tokens[0]?.Value; + FunctionDefinition? funcDef = _functionDefinitions.Find(f => f.Name == funcName); + if (funcDef == null) + { + errors.Add($"错误: 未定义的函数 '{funcName}' 在行: {line}"); + return null; + } + + // 检查括号 + if (tokens.Count < 2 || tokens[1].Type != TokenType.ParenthesisOpen) + { + errors.Add($"错误: 函数调用缺少 '(' 在行: {line}"); + return null; + } + + // 查找关闭括号 + int closeParenIndex = tokens.FindIndex(t => t.Type == TokenType.ParenthesisClose); + if (closeParenIndex == -1) + { + errors.Add($"错误: 函数调用缺少 ')' 在行: {line}"); + return null; + } + + // 提取参数令牌 + List argTokens = tokens.GetRange(2, closeParenIndex - 2); + List args = SplitArguments(argTokens); + + // 验证参数数量 + int minParams = 0; + foreach (ParameterDefinition p in funcDef.Parameters ?? []) + { + if (!p.HasDefault) + { + minParams++; + } + } + + if (args.Count < minParams || args.Count > funcDef.Parameters?.Count) + { + errors.Add($"错误: 函数 '{funcName}' 参数数量不匹配。期望最少 {minParams} 个参数,最多 {funcDef.Parameters?.Count} 个参数。在行: {line}"); + return null; + } + + // 验证每个参数 + for (int i = 0; i < args.Count; i++) + { + string arg = args[i]; + ParameterDefinition? paramDef = funcDef.Parameters?[i]; + DataType? argType = GetArgumentType(arg, line, errors); + if (argType == null || paramDef == null) + { + // 错误已记录 + continue; + } + + if (argType != paramDef.Type) + { + errors.Add($"错误: 参数 '{paramDef.Name}' 期望类型为 '{paramDef.Type}', 但接收到类型 '{argType}' 在行: {line}"); + } + } + + // 假设函数的返回类型与最后一个参数类型相关(根据实际需求调整) + // 这里需要根据具体的函数定义来决定返回类型 + // 为简化,假设所有函数返回 Vector 类型 + return DataType.Vector; + } + + private List SplitArguments(List tokens) + { + List args = []; + string current = ""; + int parenDepth = 0; + + foreach (Token token in tokens) + { + if (token.Type == TokenType.ParenthesisOpen) + { + parenDepth++; + current += token.Value; + } + else if (token.Type == TokenType.ParenthesisClose) + { + parenDepth--; + current += token.Value; + } + else if (token.Type == TokenType.Symbol && token.Value == "," && parenDepth == 0) + { + args.Add(current.Trim()); + current = ""; + } + else + { + current += token.Value + " "; + } + } + + if (!string.IsNullOrWhiteSpace(current)) + { + args.Add(current.Trim()); + } + + return args; + } + + private DataType? GetArgumentType(string arg, string line, List errors) + { + // 简单判断参数类型 + if (double.TryParse(arg, out _)) + { + return DataType.Matrix; // 假设数值为 Matrix 类型 + } + + if (arg.Equals("true", StringComparison.OrdinalIgnoreCase) || arg.Equals("false", StringComparison.OrdinalIgnoreCase)) + { + return DataType.Vector; // 布尔值为 Vector 类型 + } + + if (_variables.ContainsKey(arg)) + { + return _variables[arg]; + } + // 可能是一个函数调用 + if (Regex.IsMatch(arg, @"^\w+\(.*\)$")) + { + // 递归检查 + Tokenizer tokenizer = new(arg); + List tokens = tokenizer.Tokenize(); + return EvaluateExpression(tokens, line, errors); + } + + errors.Add($"错误: 未知的参数或变量 '{arg}' 在行: {line}"); + return null; + } + } + + internal class SyntaxCheckerProgram + { + private static void Test() + { + // 从提供的JSON加载函数定义 + string json = @"[ + { + 'name': 'add', + 'definition': 'add(x: Vector, y: Vector, filter: Vector = false)' + }, + { + 'name': 'log', + 'definition': 'log(x: Vector)' + }, + { + 'name': 'subtract', + 'definition': 'subtract(x: Vector, y: Vector, filter: Vector = false)' + }, + { + 'name': 'multiply', + 'definition': 'multiply(x: Vector, y: Vector, filter: Vector = false)' + }, + { + 'name': 'divide', + 'definition': 'divide(x: Vector, y: Vector)' + }, + // 添加其他函数定义... + ]"; + + List funcList = []; + List funcs = JsonConvert.DeserializeObject>(json) ?? []; + foreach (dynamic func in funcs) + { + string name = func.name; + string definition = func.definition; + try + { + FunctionDefinition funcDef = FunctionDefinition.FromDefinitionString(definition); + funcList.Add(funcDef); + } + catch (Exception ex) + { + Console.WriteLine($"错误解析函数 '{name}': {ex.Message}"); + } + } + + // 初始化语法检测器 + SyntaxChecker checker = new(funcList); + + Console.WriteLine("请输入要检查的代码(输入空行结束):"); + List codeLines = []; + string? line; + while (!string.IsNullOrEmpty(line = Console.ReadLine())) + { + codeLines.Add(line); + } + + // 检查语法 + List errors = checker.CheckSyntax(codeLines); + + if (errors.Count == 0) + { + Console.WriteLine("语法检查通过!"); + } + else + { + Console.WriteLine("语法检查发现以下错误:"); + foreach (string error in errors) + { + Console.WriteLine(error); + } + } + } + } +} diff --git a/Alpha/ViewModels/MainViewModel.cs b/Alpha/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..0eaf4cb --- /dev/null +++ b/Alpha/ViewModels/MainViewModel.cs @@ -0,0 +1,776 @@ +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reactive; +using System.Reactive.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Windows.Input; +using Alpha.Helpers; +using Alpha.Models; +using Alpha.Views; +using Newtonsoft.Json; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; + +namespace Alpha.ViewModels +{ + public class MainViewModel : ViewModelBase + { + [Reactive] + public string? Email { get; set; } + [Reactive] + public string? Password { get; set; } + [Reactive] + public string? TestSummary { get; set; } + [Reactive] + public string? Code { get; set; } + [Reactive] + public string? BackTestButtonText { get; set; } + [Reactive] + public string? PauseBackTestButtonText { get; set; } + [Reactive] + public int BackTestModeIndex { get; set; } + [Reactive] + public bool AutoSubmit { get; set; } + [Reactive] + public bool IsPaused { get; set; } + [Reactive] + public bool HasBackTestStarted { get; set; } + [Reactive] + public WorldQuantAccountLoginResponse? WorldQuantAccountLoginResponse { get; set; } + [Reactive] + public ObservableCollection? KnowledgeList { get; set; } + [Reactive] + public ObservableCollection? SimulationCompletedDatas { get; set; } + [Reactive] + public Knowledge? SelectedKnowledge { get; set; } + [Reactive] + public bool IsKnowledgeLoading { get; set; } + [Reactive] + public SimulationSettingsModel SimulationSettings { get; set; } = new SimulationSettingsModel(); + + private bool _ignoreWarnings; + public bool IgnoreWarnings + { + get => _ignoreWarnings; + set + { + _ = this.RaiseAndSetIfChanged(ref _ignoreWarnings, value); + UpdateStatus(); + } + } + + private bool _ignoreErrors; + public bool IgnoreErrors + { + get => _ignoreErrors; + set + { + _ = this.RaiseAndSetIfChanged(ref _ignoreErrors, value); + UpdateStatus(); + } + } + + private bool _ignoreFailures; + public bool IgnoreFailures + { + get => _ignoreFailures; + set + { + _ = this.RaiseAndSetIfChanged(ref _ignoreFailures, value); + UpdateStatus(); + } + } + + private bool _ignorePasses; + public bool IgnorePasses + { + get => _ignorePasses; + set + { + _ = this.RaiseAndSetIfChanged(ref _ignorePasses, value); + UpdateStatus(); + } + } + + private bool _ignoreCompleted; + public bool IgnoreCompleted + { + get => _ignoreCompleted; + set + { + _ = this.RaiseAndSetIfChanged(ref _ignoreCompleted, value); + UpdateStatus(); + } + } + + private bool _ignoreStop; + public bool IgnoreStop + { + get => _ignoreStop; + set + { + _ = this.RaiseAndSetIfChanged(ref _ignoreStop, value); + UpdateStatus(); + } + } + + private IgnoreStatus _currentStatus; + public IgnoreStatus CurrentStatus + { + get => _currentStatus; + private set => this.RaiseAndSetIfChanged(ref _currentStatus, value); + } + + public ICommand LoginCommand { get; } + public ICommand BackTestCommand { get; } + public ICommand GetKnowledgeCommand { get; } + public ICommand ViewAlphaDetailsCommand { get; } + public ICommand PauseBackTestCommand { get; } + + private HttpClientHandler? _httpClientHandler; + private HttpClient? _httpClient; + private List? _alphaList; + private CancellationTokenSource? _cts; + + public MainViewModel() + { + base.PropertyChanged += MainViewModel_PropertyChanged; + LoginCommand = ReactiveCommand.CreateFromTask(Login, this.WhenAnyValue(x => x.Email, x => x.Password, (email, password) => !string.IsNullOrWhiteSpace(email) && !string.IsNullOrWhiteSpace(password)), this); + BackTestCommand = ReactiveCommand.Create(BackTest, this.WhenAny(x => x.WorldQuantAccountLoginResponse, x => x.Value != null), this); + GetKnowledgeCommand = ReactiveCommand.CreateFromTask(LoadKnowledge, this.WhenAnyValue(x => x.WorldQuantAccountLoginResponse, y => y.IsKnowledgeLoading, (x, y) => x != null && !y), this); + ViewAlphaDetailsCommand = ReactiveCommand.Create(ViewAlphaDetails, outputScheduler: this); + PauseBackTestCommand = ReactiveCommand.Create(PauseBackTest, this.WhenAny(x => x.WorldQuantAccountLoginResponse, x => x.Value != null), this); + PauseBackTestButtonText = "暂停"; + BackTestButtonText = "回测"; + Initialize(); + } + + private void PauseBackTest() + { + IsPaused = !IsPaused; + PauseBackTestButtonText = IsPaused ? "继续" : "暂停"; + } + + private void Initialize() + { + TestSummary = "未回测"; + PreventSleep(); + } + + private Unit ViewAlphaDetails(AlphaResponse response) + { + AlphaInfoWindow alphaDetailsViewModel = new(response); + alphaDetailsViewModel.Show(); + return default; + } + + private void UpdateStatus() + { + CurrentStatus = IgnoreStatus.None; + + if (IgnoreWarnings) + { + CurrentStatus |= IgnoreStatus.Warning; + } + if (IgnoreErrors) + { + CurrentStatus |= IgnoreStatus.Error; + } + if (IgnoreFailures) + { + CurrentStatus |= IgnoreStatus.Failure; + } + if (IgnorePasses) + { + CurrentStatus |= IgnoreStatus.Pass; + } + if (IgnoreCompleted) + { + CurrentStatus |= IgnoreStatus.Complete; + } + if (IgnoreStop) + { + CurrentStatus |= IgnoreStatus.Stop; + } + } + + private async Task LoadKnowledge() + { + KnowledgeList = []; + IsKnowledgeLoading = true; + + DatasetResponse? datasetResponse = await GetDataset(); + + if (datasetResponse?.Results != null) + { + foreach (Dataset dataset in datasetResponse.Results) + { + FieldResponse? fieldResponse = await GetField(dataset.Id); + + if (fieldResponse?.Results != null) + { + Knowledge knowledge = new() + { + Title = dataset.Name, + Description = dataset.Description, + Fields = [], + FieldCount = dataset.FieldCount + }; + KnowledgeList.Add(knowledge); + foreach (Field field in fieldResponse.Results) + { + knowledge.Fields.Add(field); + } + } + } + } + IsKnowledgeLoading = false; + } + + private async Task Relogin() + { + if (WorldQuantAccountLoginResponse?.Token?.Expiry.AddHours(-0.5) < DateTime.Now) + { + await Login(); + } + } + + private async Task BackTest() + { + HasBackTestStarted = true; + await Relogin(); + if (_httpClient == null || _httpClientHandler == null || WorldQuantAccountLoginResponse == null) + { + return; + } + BackTestButtonText = "停止"; + + if (_cts != null && !_cts.IsCancellationRequested) + { + await _cts.CancelAsync(); + _cts.Dispose(); + _cts = null; + BackTestButtonText = "回测"; + TestSummary = "未回测"; + IsPaused = false; + PauseBackTestButtonText = "暂停"; + return; + } + _cts = new(); + CancellationToken token = _cts.Token; + + int index = 0; + TestSummary = "正在生成表达式..."; + await GenerateSimulations(token); + if (_alphaList == null) + { + TestSummary = "请重试"; + return; + } + + Stopwatch stopwatch = Stopwatch.StartNew(); + SimulationCompletedDatas = []; + SemaphoreSlim semaphore = WorldQuantAccountLoginResponse.Permissions.Contains("CONSULTANT") ? new(100) : new(3); + List incoming = []; + + List tasks = _alphaList.Select(async item => + { + await semaphore.WaitAsync(); + if (token.IsCancellationRequested) + { + return; + } + int incomingIndex = index + 1; + try + { + await Relogin(); + + Stopwatch taskStopwatch = Stopwatch.StartNew(); + index++; + int progress = index * 100 / _alphaList.Count; + incoming.Add(incomingIndex); + TestSummary = $"正在模拟第 {string.Join("、", incoming)} 个表达式,共 {_alphaList.Count} 个,进度{progress}% (用时 {stopwatch.Elapsed.FormatTime()})"; + string json = JsonConvert.SerializeObject(item, Formatting.Indented); + StringContent content = new(json, Encoding.UTF8, "application/json"); + + HttpResponseMessage simResponse = await _httpClient.PostAsync("https://api.worldquantbrain.com/simulations", content); + while (simResponse.StatusCode == HttpStatusCode.TooManyRequests && !token.IsCancellationRequested) + { + TestSummary = $"请求太频繁,20 秒后自动重试 (用时 {stopwatch.Elapsed.FormatTime()})"; + while (IsPaused) + { + TestSummary = $"已暂停 (用时 {stopwatch.Elapsed.FormatTime()})"; + await Task.Delay(TimeSpan.FromSeconds(1)); + } + await Task.Delay(TimeSpan.FromSeconds(20)); + simResponse = await _httpClient.PostAsync("https://api.worldquantbrain.com/simulations", content); + } + + if (simResponse.Headers.TryGetValues("Location", out IEnumerable? simProgressUrls) && !token.IsCancellationRequested) + { + string? simProgressUrl = simProgressUrls?.FirstOrDefault(); + int retryCount = 0; + const int maxRetries = 30; + + while (retryCount < maxRetries && !token.IsCancellationRequested) + { + while (IsPaused) + { + TestSummary = $"已暂停 (用时 {stopwatch.Elapsed.FormatTime()})"; + await Task.Delay(TimeSpan.FromSeconds(1)); + } + + HttpResponseMessage progressResponse = await _httpClient.GetAsync(simProgressUrl); + + if (progressResponse.Headers.TryGetValues("Retry-After", out IEnumerable? retryAfterValues)) + { + string retryAfterStr = retryAfterValues.FirstOrDefault() ?? "0"; + double retryAfterSec = double.Parse(retryAfterStr); + + if (retryAfterSec == 0) + { + string resultContent = await progressResponse.Content.ReadAsStringAsync(); + SimulationCompletedData? result = JsonConvert.DeserializeObject(resultContent); + if (result != null) + { + HttpResponseMessage alphaResponse = await _httpClient.GetAsync($"https://api.worldquantbrain.com/alphas/{result.Alpha}"); + string alphaResponseContent = await alphaResponse.Content.ReadAsStringAsync(); + AlphaResponse? alpha = JsonConvert.DeserializeObject(alphaResponseContent); + result.AlphaResult = alpha; + result.Time = taskStopwatch.Elapsed.FormatTime(); + SimulationCompletedDatas.Add(result); + break; + } + } + await Task.Delay(TimeSpan.FromSeconds(retryAfterSec * 2)); + } + else + { + string resultContent = await progressResponse.Content.ReadAsStringAsync(); + SimulationCompletedData? result = JsonConvert.DeserializeObject(resultContent); + if (result != null) + { + HttpResponseMessage alphaResponse = await _httpClient.GetAsync($"https://api.worldquantbrain.com/alphas/{result.Alpha}"); + string alphaResponseContent = await alphaResponse.Content.ReadAsStringAsync(); + AlphaResponse? alpha = JsonConvert.DeserializeObject(alphaResponseContent); + result.Regular ??= item.Regular; + result.AlphaResult = alpha?.Id == null ? null : alpha; + result.Time = taskStopwatch.Elapsed.FormatTime(); + SimulationCompletedDatas.Add(result); + if (AutoSubmit && alpha?.Is?.Checks?.Any(check => check.Result?.ToUpper()?.Equals("FAIL", StringComparison.OrdinalIgnoreCase) == true) == false) + { + _ = await SubmitAlpha(result.Alpha); + } + break; + } + } + retryCount++; + } + } + taskStopwatch.Stop(); + } + finally + { + _ = incoming.Remove(incomingIndex); + _ = semaphore.Release(); + } + }).ToList(); + + await Task.WhenAll(tasks); + await _cts.CancelAsync(); + stopwatch.Stop(); + TestSummary = $"完成 (用时 {stopwatch.Elapsed.FormatTime()})"; + BackTestButtonText = "回测"; + HasBackTestStarted = false; + } + + private async Task GenerateSimulations(CancellationToken token = default) + { + _alphaList = []; + + if (KnowledgeList == null) + { + return; + } + + if (BackTestModeIndex == 1) + { + if (string.IsNullOrWhiteSpace(Code)) + { + return; + } + + //string alphaExpression = $"group_rank({datafield.Id}/cap, subindustry)"; + SimulationData simulationData = new() + { + Type = "REGULAR", + Settings = new SimulationSettings + { + InstrumentType = SimulationSettings.AssetClass?.ToUpper(), + Region = SimulationSettings.Region?.ToUpper(), + Universe = SimulationSettings.Liquidity?.ToUpper(), + Delay = int.Parse(SimulationSettings.DecisionDelay ?? "1"), + Decay = int.Parse(SimulationSettings.LinearDecayWeightedAverage ?? "0"), + Neutralization = SimulationSettings.AlphaWeight?.ToUpper(), + Truncation = double.Parse(SimulationSettings.TruncationWeight ?? "0.08"), + Pasteurization = SimulationSettings.DataCleaning?.ToUpper(), + UnitHandling = SimulationSettings.WarningThrowing?.ToUpper(), + NanHandling = SimulationSettings.MissingValueHandling?.ToUpper(), + Language = "FASTEXPR", + Visualization = false + }, + Regular = Code + }; + + _alphaList.Add(simulationData); + } + else if (BackTestModeIndex == 2) + { + if (string.IsNullOrWhiteSpace(Code)) + { + return; + } + + SearchScope searchScope = new(SimulationSettings.Region?.ToUpper(), SimulationSettings.DecisionDelay?.ToUpper(), SimulationSettings.Liquidity?.ToUpper(), SimulationSettings.Liquidity?.ToUpper()); + List c1 = DirectiveReplacerExtension.HasPlaceholders(Code, 1) ? await GetDataFields(searchScope, search: "analyst", token: token) : []; + List c2 = DirectiveReplacerExtension.HasPlaceholders(Code, 2) ? await GetDataFields(searchScope, search: "fundamental", token: token) : []; + List c3 = DirectiveReplacerExtension.HasPlaceholders(Code, 3) ? await GetDataFields(searchScope, search: "model", token: token) : []; + List c4 = DirectiveReplacerExtension.HasPlaceholders(Code, 4) ? await GetDataFields(searchScope, search: "news", token: token) : []; + List c5 = DirectiveReplacerExtension.HasPlaceholders(Code, 5) ? await GetDataFields(searchScope, search: "option", token: token) : []; + List c6 = DirectiveReplacerExtension.HasPlaceholders(Code, 6) ? await GetDataFields(searchScope, search: "price volume", token: token) : []; + List c7 = DirectiveReplacerExtension.HasPlaceholders(Code, 7) ? await GetDataFields(searchScope, search: "social media", token: token) : []; + + Dictionary> replacements = new() + { + { 1, c1 }, + { 2, c2 }, + { 3, c3 }, + { 4, c4 }, + { 5, c5 }, + { 6, c6 }, + { 7, c7 } + }; + foreach (string item in DirectiveReplacerExtension.ExpandPlaceholders(Code, replacements)) + { + SimulationData simulationData = new() + { + Type = "REGULAR", + Settings = new SimulationSettings + { + InstrumentType = SimulationSettings.AssetClass?.ToUpper(), + Region = SimulationSettings.Region?.ToUpper(), + Universe = SimulationSettings.Liquidity?.ToUpper(), + Delay = int.Parse(SimulationSettings.DecisionDelay ?? "1"), + Decay = int.Parse(SimulationSettings.LinearDecayWeightedAverage ?? "0"), + Neutralization = SimulationSettings.AlphaWeight?.ToUpper(), + Truncation = double.Parse(SimulationSettings.TruncationWeight ?? "0.08"), + Pasteurization = SimulationSettings.DataCleaning?.ToUpper(), + UnitHandling = SimulationSettings.WarningThrowing?.ToUpper(), + NanHandling = SimulationSettings.MissingValueHandling?.ToUpper(), + Language = "FASTEXPR", + Visualization = false + }, + Regular = item + }; + _alphaList.Add(simulationData); + } + } + else + { + List _groupCompareOp = + [ + "group_zscore", + //"group_backfill", + //"group_mean", + "group_rank", + "group_neutralize" + ]; + List _tsCompareOp = + [ + "ts_rank", // 时间序列排名 + "ts_zscore", // 时间序列Z-score + "ts_av_diff", // 时间序列平均差值 + "ts_mean", // 时间序列均值 + "ts_std_dev", // 时间序列标准差 + "ts_sum", // 时间序列总和 + "ts_delta", // 时间序列变化量 + "ts_scale", // 时间序列缩放 + "ts_product", + //"ts_corr", // 时间序列相关性 + "ts_decay_linear", // 时间序列线性衰减 + "ts_arg_min", + "ts_arg_max", + ]; + List _group = + [ + "market", + "industry", + "subindustry", + "sector", + "country", + "currency", + "exchange", + ]; + List _days = + [ + "200", + "20" + ]; + List alphaExpressions = []; + List companyFundamentals = await GetDataFields(new SearchScope(SimulationSettings.Region?.ToUpper(), SimulationSettings.DecisionDelay?.ToUpper(), SimulationSettings.Liquidity?.ToUpper(), SimulationSettings.Liquidity?.ToUpper()), datasetId: "fundamental6", token: token); + + await Parallel.ForEachAsync(_groupCompareOp, async (gco, cancellationToken) => + { + await Parallel.ForEachAsync(_tsCompareOp, async (tco, cancellationToken) => + { + await Parallel.ForEachAsync(companyFundamentals, async (cf, cancellationToken) => + { + await Parallel.ForEachAsync(_days, async (d, cancellationToken) => + { + await Parallel.ForEachAsync(_group, async (grp, cancellationToken) => + { + string expression = $"{gco}({tco}({cf}, {d}), {grp})"; + alphaExpressions.Add(expression); + await Task.CompletedTask; + }); + }); + }); + }); + }); + + foreach (string alphaExpression in alphaExpressions) + { + //string alphaExpression = $"group_rank({datafield.Id}/cap, subindustry)"; + SimulationData simulationData = new() + { + Type = "REGULAR", + Settings = new SimulationSettings + { + InstrumentType = SimulationSettings.AssetClass?.ToUpper(), + Region = SimulationSettings.Region?.ToUpper(), + Universe = SimulationSettings.Liquidity?.ToUpper(), + Delay = int.Parse(SimulationSettings.DecisionDelay ?? "1"), + Decay = int.Parse(SimulationSettings.LinearDecayWeightedAverage ?? "0"), + Neutralization = SimulationSettings.AlphaWeight?.ToUpper(), + Truncation = double.Parse(SimulationSettings.TruncationWeight ?? "0.08"), + Pasteurization = SimulationSettings.DataCleaning?.ToUpper(), + UnitHandling = SimulationSettings.WarningThrowing?.ToUpper(), + NanHandling = SimulationSettings.MissingValueHandling?.ToUpper(), + Language = "FASTEXPR",//FASTEXPR + Visualization = false + }, + Regular = alphaExpression + }; + + _alphaList.Add(simulationData); + } + + Random random = new(); + await Task.Run(() => + { + if (token.IsCancellationRequested) + { + return; + } + + _alphaList = [.. _alphaList.OrderBy(x => random.Next())]; + }, token); + } + } + + private async Task Login() + { + _httpClientHandler ??= new HttpClientHandler(); + _httpClientHandler.CookieContainer ??= new(); + _httpClient ??= new(_httpClientHandler); + string url = "https://api.worldquantbrain.com/authentication"; + string authToken = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{Email}:{Password}")); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authToken); + _httpClient.DefaultRequestHeaders.Add("sec-ch-ua", "\"Microsoft Edge\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\""); + _httpClient.DefaultRequestHeaders.Add("sec-ch-ua-mobile", "?0"); + _httpClient.DefaultRequestHeaders.Add("sec-ch-ua-platform", "\"Windows\""); + _httpClient.DefaultRequestHeaders.Add("sec-fetch-dest", "empty"); + _httpClient.DefaultRequestHeaders.Add("sec-fetch-mode", "cors"); + _httpClient.DefaultRequestHeaders.Add("sec-fetch-site", "same-site"); + _httpClient.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"); + + HttpResponseMessage? response = null; + try + { + response = await _httpClient.PostAsync(url, null); + _ = response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); + WorldQuantAccountLoginResponse = JsonConvert.DeserializeObject(responseBody); + Debug.WriteLine("Login successful: " + responseBody); + await LoadKnowledge(); + } + catch (HttpRequestException) + { + string errorContent = response != null ? await response.Content.ReadAsStringAsync() : "No response content"; + Debug.WriteLine("Login failed: " + errorContent); + } + } + + private void MainViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + + } + + public async Task GetDataset() + { + if (_httpClient == null || _httpClientHandler == null) + { + return null; + } + + HttpResponseMessage response = await _httpClient.GetAsync("https://api.worldquantbrain.com/data-sets?delay=1&instrumentType=EQUITY&limit=20&offset=0®ion=USA&universe=TOP3000"); + string responseBody = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(responseBody); + } + + public async Task GetField(string? id) + { + if (_httpClient == null || _httpClientHandler == null) + { + return null; + } + + HttpResponseMessage response = await _httpClient.GetAsync($"https://api.worldquantbrain.com/data-fields?dataset.id={id}&delay=1&instrumentType=EQUITY&limit=50&offset=0®ion=USA&universe=TOP3000"); + string responseBody = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(responseBody); + } + + public async Task> GetDataFields(SearchScope searchScope, string datasetId = "", string search = "", CancellationToken token = default) + { + if (_httpClient == null || _httpClientHandler == null) + { + return []; + } + + string? instrumentType = searchScope.InstrumentType; + string? region = searchScope.Region; + string? delay = searchScope.Delay; + string? universe = searchScope.Universe; + + string urlTemplate; + + urlTemplate = string.IsNullOrWhiteSpace(search) + ? "https://api.worldquantbrain.com/data-fields?" + + $"&instrumentType={instrumentType}" + + $"®ion={region}&delay={delay}&universe={universe}" + + $"&dataset.id={datasetId}&limit=50" + + "&offset={0}" + : "https://api.worldquantbrain.com/data-fields?" + + $"&instrumentType={instrumentType}" + + $"®ion={region}&delay={delay}&universe={universe}" + + $"&limit=50&search={search}" + + "&offset={0}"; + + // Get count from initial request + HttpResponseMessage countResponse = await _httpClient.GetAsync(string.Format(urlTemplate, 0)); + string countJson = await countResponse.Content.ReadAsStringAsync(); + int count = JsonDocument.Parse(countJson).RootElement.GetProperty("count").GetInt32(); + + List> datafieldsList = []; + JsonElement results = JsonDocument.Parse(countJson).RootElement.GetProperty("results"); + + List batch = []; + foreach (JsonElement result in results.EnumerateArray()) + { + string? item = result.GetProperty("id").GetString(); + if (!string.IsNullOrWhiteSpace(item)) + { + batch.Add(item); + } + } + datafieldsList.Add(batch); + + // Fetch data in batches + for (int x = 50; x < count; x += 50) + { + if (token.IsCancellationRequested) + { + break; + } + HttpResponseMessage response = await _httpClient.GetAsync(string.Format(urlTemplate, x)); + string content = await response.Content.ReadAsStringAsync(); + results = JsonDocument.Parse(content).RootElement.GetProperty("results"); + + batch.Clear(); + foreach (JsonElement result in results.EnumerateArray()) + { + string? item = result.GetProperty("id").GetString(); + if (!string.IsNullOrWhiteSpace(item)) + { + batch.Add(item); + } + } + datafieldsList.Add(batch); + } + + // Flatten the list + List datafieldsListFlat = datafieldsList.SelectMany(x => x).ToList(); + return datafieldsListFlat; + } + + private async Task SubmitAlpha(string? alphaId) + { + if (_httpClient == null || _httpClientHandler == null || string.IsNullOrEmpty(alphaId)) + { + return false; + } + + string url = $"https://api.worldquantbrain.com/alphas/{alphaId}/submit"; + HttpResponseMessage response = await _httpClient.PostAsync(url, null); + + if (response.StatusCode != HttpStatusCode.Created) + { + return false; + } + + string? getUrl = response.Headers.Location?.ToString(); + if (string.IsNullOrEmpty(getUrl)) + { + return false; + } + + while (true) + { + HttpResponseMessage getResponse = await _httpClient.GetAsync(getUrl); + + if (getResponse.Headers.TryGetValues("Retry-After", out IEnumerable? retryAfterValues)) + { + string retryAfterStr = retryAfterValues.FirstOrDefault() ?? "0"; + double retryAfterSec = double.Parse(retryAfterStr); + await Task.Delay(TimeSpan.FromSeconds(retryAfterSec * 5)); + } + else + { + return getResponse.StatusCode == HttpStatusCode.OK || await SubmitAlpha(alphaId); + } + } + } + + [DllImport("kernel32.dll")] + private static extern uint SetThreadExecutionState(ExecutionState esFlags); + + [Flags] + private enum ExecutionState : uint + { + ES_CONTINUOUS = 0x80000000, + ES_DISPLAY_REQUIRED = 0x00000002, + ES_SYSTEM_REQUIRED = 0x00000001, + ES_AWAYMODE_REQUIRED = 0x00000040 + } + + private void PreventSleep() + { + _ = SetThreadExecutionState(ExecutionState.ES_CONTINUOUS | ExecutionState.ES_DISPLAY_REQUIRED); + } + } +} diff --git a/Alpha/ViewModels/ViewModelBase.cs b/Alpha/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..ea447d2 --- /dev/null +++ b/Alpha/ViewModels/ViewModelBase.cs @@ -0,0 +1,33 @@ +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using System.Windows; +using ReactiveUI; + +namespace Alpha.ViewModels +{ + public class ViewModelBase : ReactiveObject, IScheduler + { + public DateTimeOffset Now => DateTimeOffset.Now; + + public IDisposable Schedule(TState state, Func action) + { + return Application.Current.Dispatcher.CheckAccess() + ? action(this, state) + : Application.Current.Dispatcher.Invoke(() => action(this, state)); + } + + public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + return new SingleAssignmentDisposable + { + Disposable = new Timer(_ => action(this, state), null, dueTime, TimeSpan.FromMilliseconds(-1)) + }; + } + + public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) + { + TimeSpan delay = dueTime - DateTimeOffset.Now; + return Schedule(state, delay, action); + } + } +} diff --git a/Alpha/Views/AlphaInfoWindow.xaml b/Alpha/Views/AlphaInfoWindow.xaml new file mode 100644 index 0000000..35cc726 --- /dev/null +++ b/Alpha/Views/AlphaInfoWindow.xaml @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Alpha/Views/AlphaInfoWindow.xaml.cs b/Alpha/Views/AlphaInfoWindow.xaml.cs new file mode 100644 index 0000000..ff0d6e4 --- /dev/null +++ b/Alpha/Views/AlphaInfoWindow.xaml.cs @@ -0,0 +1,21 @@ +using Alpha.Models; +using UIShell.Controls; + +namespace Alpha.Views +{ + /// + /// AlphaInfoWindow.xaml 的交互逻辑 + /// + public partial class AlphaInfoWindow : MetroWindow + { + public AlphaInfoWindow() + { + InitializeComponent(); + } + + public AlphaInfoWindow(AlphaResponse dataContext) : this() + { + DataContext = dataContext; + } + } +} diff --git a/Alpha/Views/MainWindow.xaml b/Alpha/Views/MainWindow.xaml new file mode 100644 index 0000000..d6ba016 --- /dev/null +++ b/Alpha/Views/MainWindow.xaml @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +