using System.Text.Json; using XFEEDToolBox.Core.Models; namespace XFEEDToolBox.Core.Execution; public sealed record AutomaticTradeRunSession(string Id,DateTimeOffset StartedAt,decimal BaselineNetProfit,bool Active) { public decimal Profit(decimal confirmedTotal)=>confirmedTotal-BaselineNetProfit; public static AutomaticTradeRunSession Open(string root,decimal confirmedTotal) { var path=Path.Combine(root,"run-session.json"); if(File.Exists(path)&&JsonSerializer.Deserialize(File.ReadAllText(path),ToolboxSettings.JsonOptions) is {Active:true} existing)return existing; var created=new AutomaticTradeRunSession(Guid.NewGuid().ToString("N"),DateTimeOffset.UtcNow,confirmedTotal,true);Save(root,created);return created; } public static void Finish(string root) { var path=Path.Combine(root,"run-session.json");if(!File.Exists(path))return; var current=JsonSerializer.Deserialize(File.ReadAllText(path),ToolboxSettings.JsonOptions);if(current is not null)Save(root,current with{Active=false}); } private static void Save(string root,AutomaticTradeRunSession value) {Directory.CreateDirectory(root);var path=Path.Combine(root,"run-session.json");File.WriteAllText(path+".tmp",JsonSerializer.Serialize(value,ToolboxSettings.JsonOptions));File.Move(path+".tmp",path,true);} } public sealed record AutomaticTradeEstimate(decimal NetProfit,double Minutes,string Basis) { public decimal Hourly=>Minutes>0?NetProfit*60/(decimal)Minutes:0; public static AutomaticTradeEstimate For(TradeLeg leg,string root,long? ship) { var samples=new List<(DateTimeOffset At,double Minutes,decimal Costs)>(); foreach(var directory in Directory.EnumerateDirectories(root,"journey-*")) { var r=Path.Combine(directory,"result.json");var p=Path.Combine(directory,"leg.json");if(!File.Exists(r)||!File.Exists(p))continue; var result=JsonSerializer.Deserialize(File.ReadAllText(r),ToolboxSettings.JsonOptions); if(result?.Passed!=true||result.Checkpoint.ShipId!=ship)continue; var prior=JsonSerializer.Deserialize(File.ReadAllText(p),ToolboxSettings.JsonOptions)!;var duration=(result.Finished-result.Checkpoint.StartedAt).TotalMinutes; if(prior.From.MarketId==leg.From.MarketId&&prior.To.MarketId==leg.To.MarketId&&duration is >=1 and <=30)samples.Add((result.Finished,duration,result.Profit.Expenses)); } var recent=samples.OrderByDescending(s=>s.At).Take(5).ToArray(); var fallback=2+1.5+1+Math.Sqrt(Math.Max(0,leg.To.DistanceToArrivalLs??1000))/32; var minutes=recent.Length>0?recent.Average(s=>s.Minutes):leg.EstimatedMinutes>0?leg.EstimatedMinutes:fallback; var costs=recent.Length>0?recent.Average(s=>s.Costs):leg.OperatingCost; return new(leg.GrossProfit-costs,minutes,recent.Length>0?"按同船同航段近期实测估算,实际利润以成交及费用回执为准。":"按路线参数估算,尚无同船完整实测;实际费用以回执为准。"); } }