676 lines
20 KiB
C#
676 lines
20 KiB
C#
using YLErp.BLL.MarginCalculation;
|
|
using YLErp.DBModels;
|
|
using YLErp.Enums;
|
|
using YLErp.Modules;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.EodModule.SettlementModule;
|
|
using YLErp.Modules.PricingModule;
|
|
using YLErp.Modules.TradeModule;
|
|
using YLErp.Modules.VolatilityModule;
|
|
|
|
namespace YLErp.Forms
|
|
{
|
|
public partial class FormOption : Form
|
|
{
|
|
readonly RichTextBoxWriter _rbWriter;
|
|
|
|
FormJsonView _formJsonView;
|
|
|
|
public FormOption()
|
|
{
|
|
InitializeComponent();
|
|
|
|
_rbWriter = new RichTextBoxWriter(richTextBoxLog);
|
|
}
|
|
|
|
private void ShowJson(object obj)
|
|
{
|
|
var json = JsonHelper.Serialize(obj);
|
|
|
|
_rbWriter.WriteLine(json);
|
|
|
|
if (_formJsonView == null)
|
|
{
|
|
_formJsonView = new FormJsonView();
|
|
}
|
|
|
|
_formJsonView.SetJson(json).ShowDialog();
|
|
}
|
|
|
|
//查看交易
|
|
private void btnViewTrade_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcData(null);
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
_rbWriter.Clear().WriteLine(btnViewTrade.Text);
|
|
ShowJson(data.trade);
|
|
}
|
|
|
|
//定价计算
|
|
private void btnPricing_Click(object sender, EventArgs e)
|
|
{
|
|
ExecPricing();
|
|
}
|
|
|
|
private void btnPricingV2_Click(object sender, EventArgs e)
|
|
{
|
|
ExecPricing("V2");
|
|
}
|
|
|
|
private void ExecPricing(string calcVersion = "V1")
|
|
{
|
|
var data = GetCalcData(null);
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var tdFull = data.tradeFull;
|
|
|
|
ResetTrade(tdFull);
|
|
|
|
_rbWriter.Clear().Write(DateTime.Now.ToString() + " ").WriteLine(btnPricing.Text);
|
|
|
|
tdFull.OptionType = "看涨";
|
|
|
|
var result = new PriceCalcService(GetOptUserInfo()).CalcOptionPrice(tdFull, calcMargin: false, CalcScenarioEnum.Pricing, calcVersion: calcVersion);
|
|
|
|
ShowJson(result);
|
|
|
|
_rbWriter.WriteLine(DateTime.Now);
|
|
}
|
|
|
|
private void btnViewPosition_Click(object sender, EventArgs e)
|
|
{
|
|
var valueDate = GetPosCalcValueDate();
|
|
var data = GetCalcData(valueDate);
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
_rbWriter.Clear().WriteLine(btnViewPosition.Text);
|
|
ShowJson(data.trade);
|
|
}
|
|
|
|
//计算持仓PV
|
|
private void btnCalcPv_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcDataV2();
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_rbWriter.Clear().WriteLine(btnCalcPv.Text);
|
|
|
|
var sw = new System.Diagnostics.Stopwatch();
|
|
sw.Start();
|
|
|
|
var td = data.trade;
|
|
var req = new OptionValueCalcRequest(td.NoRiskRate ?? BLL.valuedateBLL.SysRiskFreeRate())
|
|
{
|
|
vols = new[] { data.vol },
|
|
spotPrices = new[] { data.underlyingPrice },
|
|
preciseTimeMode = checkRealTime.Checked,
|
|
calcDeltaT1 = true,
|
|
calcScenario = Enums.CalcScenarioEnum.RealtimeRisk
|
|
};
|
|
|
|
var result = OptionCalculatorV2.GetOptionValueResult(data.valueDate, td, req, out _);
|
|
|
|
sw.Stop();
|
|
|
|
_rbWriter.WriteLine($"耗时{sw.ElapsedMilliseconds / 1000}秒,pv:{result.Pv}");
|
|
|
|
ShowJson(result);
|
|
}
|
|
|
|
//计算持仓保证金
|
|
private void btnCalcMargin_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcDataV2();
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_rbWriter.Clear().WriteLine(btnCalcMargin.Text);
|
|
|
|
var sw = new System.Diagnostics.Stopwatch();
|
|
sw.Start();
|
|
|
|
var td = data.trade;
|
|
|
|
var priceProvider = new ManualPriceProvider();
|
|
|
|
priceProvider.SetPrice(td.UnderlyingCode, data.underlyingPrice);
|
|
|
|
|
|
var req = new RunMarginCalculationReq(OptUserInfo.UnitTestUser)
|
|
{
|
|
forOtherSide = false,
|
|
hasOptionInfo = true,
|
|
PriceProvider = priceProvider,
|
|
settleDate = data.valueDate,
|
|
tradeList = new List<trade> { td },
|
|
volType = "持仓",
|
|
CalcMarginType = checkRealTime.Checked
|
|
? Enums.CalcMarginTypeEnum.None : Enums.CalcMarginTypeEnum.EodMargin,
|
|
};
|
|
|
|
var list = MarginDefault.RunMarginCalculation(req);
|
|
|
|
sw.Stop();
|
|
|
|
_rbWriter.WriteLine($"耗时{sw.ElapsedMilliseconds / 1000}秒");
|
|
|
|
if (list.Count < 1)
|
|
{
|
|
_rbWriter.WriteLine($"没有取到日期'{data.valueDate:yyyy-MM-dd}'的保证金");
|
|
}
|
|
else
|
|
{
|
|
ShowJson(list[0]);
|
|
}
|
|
}
|
|
|
|
//是否实时风险
|
|
private void checkRealTime_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
if (checkRealTime.Checked)
|
|
{
|
|
dateTimePicker1.Enabled = false;
|
|
dateTimePicker1.Value = BLL.valuedateBLL.ValueDate;
|
|
|
|
btnViewPosition.Text = "查看实时持仓";
|
|
btnCalcPv.Text = "计算实时PV";
|
|
btnCalcMargin.Text = "计算实时保证金";
|
|
}
|
|
else
|
|
{
|
|
dateTimePicker1.Enabled = true;
|
|
|
|
btnViewPosition.Text = "查看日终持仓";
|
|
btnCalcPv.Text = "计算日终PV";
|
|
btnCalcMargin.Text = "计算日终保证金";
|
|
}
|
|
}
|
|
|
|
//获取实时行情标的价格
|
|
private void menuGetCurUmPrice_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcData(null);
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var um = DbContextFactory.GetYLDbContext().underlying_manager
|
|
.Where(n => n.UnderlyingCode == data.trade.UnderlyingCode)
|
|
.Select(n => new { n.Price }).FirstOrDefault();
|
|
|
|
if (um == null)
|
|
{
|
|
tbUnderlyingPrice.Text = string.Empty;
|
|
_rbWriter.WriteLine("标的数据不存在:" + data.trade.UnderlyingCode);
|
|
}
|
|
else
|
|
{
|
|
tbUnderlyingPrice.Text = um.Price?.ToString("F4");
|
|
}
|
|
}
|
|
|
|
//日终收盘价
|
|
private void menuGetEodClosePrice_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcData(null);
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var valueDate = GetPosCalcValueDate();
|
|
var provider = new EodPriceProvider(valueDate);
|
|
|
|
if (provider.TryGetEodPrice(data.trade.UnderlyingCode, out var eodPrice))
|
|
{
|
|
tbUnderlyingPrice.Text = eodPrice.ClosePrice.ToString("F4");
|
|
}
|
|
else
|
|
{
|
|
tbUnderlyingPrice.Text = string.Empty;
|
|
_rbWriter.WriteLine($"日期'{valueDate:yyyy-MM-dd}',标的收盘价不存在:{data.trade.UnderlyingCode}");
|
|
}
|
|
}
|
|
|
|
//日终结算价
|
|
private void menuGetEodSettlePrice_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcData(null);
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var valueDate = GetPosCalcValueDate();
|
|
var provider = new EodPriceProvider(valueDate);
|
|
|
|
if (provider.TryGetEodPrice(data.trade.UnderlyingCode, out var eodPrice))
|
|
{
|
|
tbUnderlyingPrice.Text = eodPrice.SettlePrice.ToString("F4");
|
|
}
|
|
else
|
|
{
|
|
tbUnderlyingPrice.Text = string.Empty;
|
|
_rbWriter.WriteLine($"日期'{valueDate:yyyy-MM-dd}',标的结算价不存在:{data.trade.UnderlyingCode}");
|
|
}
|
|
}
|
|
|
|
//日终参考价
|
|
private void menuGetEodRefPrice_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcData(null);
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var valueDate = GetPosCalcValueDate();
|
|
var provider = new EodPriceProvider(valueDate);
|
|
|
|
if (provider.TryGetEodPrice(data.trade.UnderlyingCode, out var eodPrice))
|
|
{
|
|
tbUnderlyingPrice.Text = eodPrice.GetPrice(SettlementTypeEnum.ReferencePrice).ToString("F4");
|
|
}
|
|
else
|
|
{
|
|
tbUnderlyingPrice.Text = string.Empty;
|
|
_rbWriter.WriteLine($"日期'{valueDate:yyyy-MM-dd}',标的参考价不存在:{data.trade.UnderlyingCode}");
|
|
}
|
|
}
|
|
|
|
//持仓波动率
|
|
private void menuGetPositionVol_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcData(null);
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var td = data.trade;
|
|
var valueDate = GetPosCalcValueDate();
|
|
var provider = new TradeVolitalityProvider(valueDate) { Initialized = true };
|
|
if (provider.TryGetVol(td.id, td.ExerciseDate ?? valueDate, out var vol))
|
|
{
|
|
tbVol.Text = vol.ToString("P4");
|
|
}
|
|
else
|
|
{
|
|
_rbWriter.WriteLine($"未获取到持仓波动率,交易编号:{td.TradeNumber},交易ID:{td.id},日期:{valueDate:yyyy-MM-dd}");
|
|
}
|
|
}
|
|
|
|
//对冲波动率
|
|
private void menuGetHedgeVol_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcData(null);
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var td = data.trade;
|
|
var valueDate = GetPosCalcValueDate();
|
|
var provider = new TradeHedgeVolProvider(valueDate) { Initialized = true };
|
|
if (provider.TryGetVol(td.id, out var vol))
|
|
{
|
|
tbVol.Text = vol.ToString("P4");
|
|
}
|
|
else
|
|
{
|
|
_rbWriter.WriteLine($"未获取到对冲波动率,交易编号:{td.TradeNumber},交易ID:{td.id},日期:{valueDate:yyyy-MM-dd}");
|
|
}
|
|
}
|
|
|
|
//开仓波动率
|
|
private void menuGetOpenVol_Click(object sender, EventArgs e)
|
|
{
|
|
var data = GetCalcData(null);
|
|
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
tbVol.Text = data.trade.TradeOpenVolatility?.ToString("P4");
|
|
}
|
|
|
|
#region----计算数据处理----
|
|
|
|
private DateTime GetPosCalcValueDate()
|
|
{
|
|
return QdpModule.QdpCalendarHelper.GetNonHoliday(dateTimePicker1.Value.Date);
|
|
}
|
|
|
|
private CalcDataV2 GetCalcDataV2()
|
|
{
|
|
var valueDate = GetPosCalcValueDate();
|
|
|
|
var data = GetCalcData(checkRealTime.Checked ? null : valueDate);
|
|
|
|
if (data == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!double.TryParse(tbUnderlyingPrice.Text, out var underlyingPrice))
|
|
{
|
|
MessageBox.Show("解析标的价格失败:" + tbUnderlyingPrice.Text);
|
|
return null;
|
|
}
|
|
|
|
if (!NumberHelper.TryParse(tbVol.Text, out var vol, out var isPercent))
|
|
{
|
|
MessageBox.Show("解析波动率失败:" + tbVol.Text);
|
|
return null;
|
|
}
|
|
|
|
return new CalcDataV2(data)
|
|
{
|
|
vol = vol,
|
|
underlyingPrice = underlyingPrice,
|
|
valueDate = valueDate
|
|
};
|
|
}
|
|
|
|
private CalcData GetCalcData(DateTime? eodValueDate)
|
|
{
|
|
try
|
|
{
|
|
var tdInput = richTextBoxTrade.Text.Trim();
|
|
|
|
trade td = null;
|
|
OtcOptionTradeFull tdFull = null;
|
|
string errMsg = null;
|
|
|
|
switch (cmbTradeDataSource.Text)
|
|
{
|
|
case "交易ID":
|
|
errMsg = GetDataByTradeId(tdInput, out td, out tdFull, eodValueDate);
|
|
break;
|
|
case "交易编号":
|
|
errMsg = GetDataByTradeNumber(tdInput, out td, out tdFull, eodValueDate);
|
|
break;
|
|
case "表单数据(定价请求)":
|
|
tdFull = ParseFormData(richTextBoxTrade.Lines);
|
|
break;
|
|
case "JSON数据(trade)":
|
|
td = JsonHelper.Deserialize<trade>(tdInput);
|
|
break;
|
|
case "JSON数据(tradeFull)":
|
|
tdFull = JsonHelper.Deserialize<OtcOptionTradeFull>(tdInput);
|
|
break;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(errMsg))
|
|
{
|
|
MessageBox.Show(errMsg);
|
|
return null;
|
|
}
|
|
|
|
if (td == null && tdFull == null)
|
|
{
|
|
MessageBox.Show("交易数据不存在");
|
|
return null;
|
|
}
|
|
|
|
if (td == null)
|
|
{
|
|
td = TradeConverter.ConvertOptionTrade(tdFull);
|
|
}
|
|
|
|
if (tdFull == null)
|
|
{
|
|
tdFull = TradeConverter.ConvertToOptionTrade(td);
|
|
}
|
|
|
|
return new CalcData(td, tdFull);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show(ex.ToString());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static string GetDataByTradeId(string tradeIdStr, out trade td, out OtcOptionTradeFull tdFull, DateTime? eodValueDate)
|
|
{
|
|
td = null;
|
|
tdFull = null;
|
|
|
|
if (string.IsNullOrEmpty(tradeIdStr))
|
|
{
|
|
return "请填写交易ID";
|
|
}
|
|
|
|
if (!int.TryParse(tradeIdStr, out var tradeId))
|
|
{
|
|
return "交易ID未能正确解析成整数:" + tradeIdStr;
|
|
}
|
|
|
|
if (eodValueDate.HasValue)
|
|
{
|
|
td = GetEodTrade(eodValueDate.Value, tradeId, null);
|
|
}
|
|
else
|
|
{
|
|
tdFull = new TradeDetailsQueryService(GetOptUserInfo()).GetOtcTradeFull(tradeId);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private string GetDataByTradeNumber(string tradeNumber, out trade td, out OtcOptionTradeFull tdFull, DateTime? eodValueDate)
|
|
{
|
|
td = null;
|
|
tdFull = null;
|
|
|
|
if (string.IsNullOrEmpty(tradeNumber))
|
|
{
|
|
return "请填写交易编号";
|
|
}
|
|
|
|
if (eodValueDate.HasValue)
|
|
{
|
|
td = GetEodTrade(eodValueDate.Value, 0, tradeNumber);
|
|
}
|
|
else
|
|
{
|
|
tdFull = new TradeDetailsQueryService(GetOptUserInfo()).GetOtcTradeFull(tradeNumber);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static trade GetEodTrade(DateTime valueDate, int tradeId, string tradeNumber)
|
|
{
|
|
var predicateBuilder = new EodQueryPredicateBuilder(new EodSettlementRequest(GetOptUserInfo())
|
|
{
|
|
SettleDate = valueDate,
|
|
UseClosePrice = true,
|
|
VolType = "持仓"
|
|
});
|
|
|
|
var predicate = string.IsNullOrEmpty(tradeNumber)
|
|
? PredicateBuilder.Create<trade>(t => t.id == tradeId)
|
|
: PredicateBuilder.Create<trade>(t => t.TradeNumber == tradeNumber);
|
|
|
|
predicate = predicate.And(predicateBuilder.GetOtcTradePredicate());
|
|
|
|
var otcTrades = new EodSettleDataQueryService(GetOptUserInfo())
|
|
.GetOtcTrades(valueDate, predicate, out var tradeCashProvider);
|
|
|
|
if (valueDate != BLL.valuedateBLL.ValueDate && otcTrades.Any())
|
|
{
|
|
var hisDataProvider = new TradeHisDataProvider(valueDate).Initialize();
|
|
|
|
foreach (var item in otcTrades)
|
|
{
|
|
item.NoRiskRate = hisDataProvider.GetNoRiskRate(item.id) ?? item.NoRiskRate ?? BLL.valuedateBLL.RiskFreeRate;
|
|
item.DividendRate = hisDataProvider.GetDividendRate(item.id);
|
|
if (item.DividendRate == null)
|
|
{
|
|
item.DividendRate = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode).DividendRate ?? item.NoRiskRate;
|
|
}
|
|
}
|
|
}
|
|
|
|
return otcTrades.FirstOrDefault();
|
|
}
|
|
|
|
private static OtcOptionTradeFull ParseFormData(string[] lines)
|
|
{
|
|
var list = new List<KeyValuePair<string, string>>();
|
|
|
|
foreach (var line in lines)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(line))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var index = line.IndexOf(':');
|
|
|
|
if (index < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
list.Add(new KeyValuePair<string, string>(line.Substring(0, index).Trim(), line.Substring(index + 1).Trim()));
|
|
}
|
|
|
|
//var fc = new FormDataCollection(list);
|
|
|
|
//var data = fc.ReadAs<InnerFormData>();
|
|
|
|
//return data?.trades != null ? data.trades.FirstOrDefault() : null;
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
private static void ResetTrade(OtcTradeBase otcTrade)
|
|
{
|
|
if (otcTrade is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
otcTrade.id = 0;
|
|
otcTrade.TradeNumber = string.Empty;
|
|
otcTrade.Notional = otcTrade.OriginalNotional ?? 0;
|
|
otcTrade.StockEqvNotional = otcTrade.OriginalStockEqvNotional ?? 0;
|
|
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(otcTrade.UnderlyingCode);
|
|
otcTrade.TradeAmount = otcTrade.Notional / (um?.CountRatio ?? 1);
|
|
otcTrade.ParentTradeId = 0;
|
|
otcTrade.UnWindDate = null;
|
|
otcTrade.FinalPrice = null;
|
|
otcTrade.UnWindNotional = null;
|
|
otcTrade.HasPartialUnWind = null;
|
|
otcTrade.CheckStatus = null;
|
|
otcTrade.ProcessOrderId = 0;
|
|
otcTrade.ProcessOptDate = null;
|
|
otcTrade.ProcessStatus = null;
|
|
otcTrade.DividendDate = new DateTime(2000, 1, 1);
|
|
otcTrade.StructureType = string.Empty;
|
|
otcTrade.StructureIntroduction = string.Empty;
|
|
|
|
otcTrade.StockEqvNotional = otcTrade.StockEqvNotional.OtcFormatValue(OtcFormatFlag.StockEqvNotional);
|
|
otcTrade.StockEqvNotionalReal = otcTrade.StockEqvNotionalReal.OtcFormatValue(OtcFormatFlag.StockEqvNotional);
|
|
}
|
|
|
|
private static OptUserInfo GetOptUserInfo()
|
|
{
|
|
return OptUserInfo.SystemUser;
|
|
}
|
|
|
|
class CalcData
|
|
{
|
|
public trade trade { get; }
|
|
|
|
public OtcOptionTradeFull tradeFull { get; }
|
|
|
|
public DateTime timestamp { get; private set; }
|
|
|
|
public CalcData(trade trade, OtcOptionTradeFull tradeFull)
|
|
{
|
|
timestamp = DateTime.Now;
|
|
|
|
this.trade = trade ?? throw new ArgumentNullException(nameof(trade));
|
|
|
|
this.tradeFull = tradeFull ?? throw new ArgumentNullException(nameof(tradeFull));
|
|
}
|
|
|
|
public void UpdateTimeStampe()
|
|
{
|
|
timestamp = DateTime.Now;
|
|
}
|
|
|
|
public CalcData Clone()
|
|
{
|
|
return new CalcData(trade.Clone(), tradeFull.Clone());
|
|
}
|
|
}
|
|
|
|
class CalcDataV2
|
|
{
|
|
public DateTime valueDate;
|
|
|
|
public double underlyingPrice;
|
|
|
|
public double vol;
|
|
|
|
readonly CalcData _data;
|
|
|
|
public CalcDataV2(CalcData data)
|
|
{
|
|
_data = data ?? throw new ArgumentNullException(nameof(data));
|
|
}
|
|
|
|
public trade trade => _data.trade;
|
|
|
|
public OtcOptionTradeFull tradeFull => _data.tradeFull;
|
|
|
|
}
|
|
|
|
class InnerFormData
|
|
{
|
|
public IEnumerable<OtcOptionTradeFull> trades { get; set; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void FormOption_Load(object sender, EventArgs e)
|
|
{
|
|
cmbTradeDataSource.SelectedIndex = 1;
|
|
|
|
menuStrip1.AllowMerge = false;
|
|
}
|
|
}
|
|
}
|