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<AutomaticTradeRunSession>(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<AutomaticTradeRunSession>(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<NativeTradeJourneyResult>(File.ReadAllText(r),ToolboxSettings.JsonOptions);
if(result?.Passed!=true||result.Checkpoint.ShipId!=ship)continue;
var prior=JsonSerializer.Deserialize<TradeLeg>(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?"按同船同航段近期实测估算,实际利润以成交及费用回执为准。":"按路线参数估算,尚无同船完整实测;实际费用以回执为准。");
}
}
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<AutomaticTradeRunSession>(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<AutomaticTradeRunSession>(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<NativeTradeJourneyResult>(File.ReadAllText(r),ToolboxSettings.JsonOptions);
if(result?.Passed!=true||result.Checkpoint.ShipId!=ship)continue;
var prior=JsonSerializer.Deserialize<TradeLeg>(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?"按同船同航段近期实测估算,实际利润以成交及费用回执为准。":"按路线参数估算,尚无同船完整实测;实际费用以回执为准。");
}
}