Skip to content

Commit

Permalink
[MDAPI-42] [.NET] Implement OptionChain
Browse files Browse the repository at this point in the history
  • Loading branch information
Konstantin Ivaschenko committed Jul 26, 2024
1 parent 29a2f1b commit 2e35650
Show file tree
Hide file tree
Showing 12 changed files with 1,328 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ sudo /usr/bin/xattr -r -d com.apple.quarantine <directory_with_tools>
is a simple demonstration of how to subscribe to the Order event and handle snapshots and updates.
- [x] [MultipleMarketDepthSample](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/MultipleMarketDepthSample)
is a simple demonstration of how to use the `MarketDepthModel` to manage and display order books for multiple symbols.
- [x] [DXFeedOptionChain](https://github.com/dxFeed/dxfeed-graal-net-api/tree/main/samples/DXFeedOptionChain)
how to build option chains, and prints quotes for nearby option strikes.

## Current State

Expand Down
2 changes: 2 additions & 0 deletions ReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* [MDAPI-42] [.NET] Implement OptionChain

## Version 2.3.0

* [MDAPI-115] [.NET] Add IncOrderSnapshot sample
Expand Down
7 changes: 7 additions & 0 deletions dxfeed-graal-net-api.sln
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultipleMarketDepthSample",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{5F74BD34-C2D4-436B-8243-FB0F3BB9F0AC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DXFeedOptionChain", "samples\DXFeedOptionChain\DXFeedOptionChain.csproj", "{7E7BF3A7-C564-4B82-AAD6-6C1D1BCE3F19}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -138,6 +140,10 @@ Global
{C8F5013F-7F40-46D2-92AD-6B593524A1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8F5013F-7F40-46D2-92AD-6B593524A1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8F5013F-7F40-46D2-92AD-6B593524A1D0}.Release|Any CPU.Build.0 = Release|Any CPU
{7E7BF3A7-C564-4B82-AAD6-6C1D1BCE3F19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E7BF3A7-C564-4B82-AAD6-6C1D1BCE3F19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E7BF3A7-C564-4B82-AAD6-6C1D1BCE3F19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E7BF3A7-C564-4B82-AAD6-6C1D1BCE3F19}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{73597E04-D8A8-4991-A759-7F886CBE2A8F} = {C4490D74-2970-4A1B-8178-A724A06B140A}
Expand All @@ -160,5 +166,6 @@ Global
{5F74BD34-C2D4-436B-8243-FB0F3BB9F0AC} = {C4490D74-2970-4A1B-8178-A724A06B140A}
{B74E8A86-1AB7-4B36-AED3-292CDD95BF90} = {5F74BD34-C2D4-436B-8243-FB0F3BB9F0AC}
{930B1039-B76C-42C5-AD0F-9FA1A1FC9D84} = {5F74BD34-C2D4-436B-8243-FB0F3BB9F0AC}
{7E7BF3A7-C564-4B82-AAD6-6C1D1BCE3F19} = {C4490D74-2970-4A1B-8178-A724A06B140A}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions dxfeed-graal-net-api.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=libpthread/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=marshaler/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mbyte/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mdapi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mddqa/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=intraday/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Mmap/@EntryIndexedValue">True</s:Boolean>
Expand Down
21 changes: 21 additions & 0 deletions samples/DXFeedOptionChain/DXFeedOptionChain.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>DxFeed.Graal.Net.Samples</RootNamespace>
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>../../artifacts/Debug/Samples/</OutputPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>../../artifacts/Release/Samples/</OutputPath>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\DxFeed.Graal.Net\DxFeed.Graal.Net.csproj"/>
</ItemGroup>

</Project>
107 changes: 107 additions & 0 deletions samples/DXFeedOptionChain/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DxFeed.Graal.Net.Api;
using DxFeed.Graal.Net.Events.Market;
using DxFeed.Graal.Net.Ipf;
using DxFeed.Graal.Net.Ipf.Options;

namespace DxFeed.Graal.Net.Samples;

/// <summary>
/// A simple sample that shows how to build option chains, and prints quotes for nearby option strikes.
/// </summary>
[SuppressMessage("ReSharper", "MethodSupportsCancellation")]
internal abstract class Program
{
public static async Task Main(string[] args)
{
if (args.Length != 5)
{
Console.WriteLine("usage: DXFeedOptionChain <address> <ipf-file> <symbol> <nStrikes> <nMonths>");
Console.WriteLine(" <ipf-file> is endpoint address");
Console.WriteLine(" <ipf-file> is name of instrument profiles file");
Console.WriteLine(" <symbol> is the product or underlying symbol");
Console.WriteLine(" <nStrikes> number of strikes to print for each series");
Console.WriteLine(" <nMonths> number of months to print");
return;
}

var argAddress = args[0];
var argIpfFile = args[1];
var argSymbol = args[2];
var nStrikes = int.Parse(args[3], CultureInfo.InvariantCulture);
var nMonths = int.Parse(args[4], CultureInfo.InvariantCulture);

var feed = DXEndpoint.Create().Connect(argAddress).GetFeed();

// Subscribe to trade to learn instrument last price.
Console.WriteLine($"Waiting for price of {argSymbol} ...");
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(1));
var trade = await feed.GetLastEventAsync<Trade>(argSymbol, cts.Token);

var price = trade.Price;
Console.WriteLine($"Price of {argSymbol} is {price.ToString(CultureInfo.InvariantCulture)}");

Console.WriteLine($"Reading instruments from {argIpfFile} ...");
var instruments = new InstrumentProfileReader().ReadFromFile(argIpfFile).ToList();

Console.WriteLine("Building option chains ...");
var chains = OptionChainsBuilder<InstrumentProfile>.Build(instruments).Chains;
if (!chains.TryGetValue(argSymbol, out var chain))
{
Console.WriteLine($"No chain found for symbol {argSymbol}");
return;
}

nMonths = Math.Min(nMonths, chain.GetSeries().Count);
var seriesList = chain.GetSeries().Take(nMonths).ToList();

Console.WriteLine("Requesting option quotes ...");
var quotes = new Dictionary<InstrumentProfile, Task<Quote>>();
foreach (var series in seriesList)
{
var strikes = series.GetNStrikesAround(nStrikes, price);
foreach (var strike in strikes)
{
if (series.Calls.TryGetValue(strike, out var call))
{
quotes[call] = feed.GetLastEventAsync<Quote>(call.Symbol);
}

if (series.Puts.TryGetValue(strike, out var put))
{
quotes[put] = feed.GetLastEventAsync<Quote>(put.Symbol);
}
}
}

await Task.WhenAll(quotes.Values);

Console.WriteLine("Printing option series ...");
foreach (var series in seriesList)
{
Console.WriteLine($"Option series {series}");
var strikes = series.GetNStrikesAround(nStrikes, price);
Console.WriteLine($"{"C.BID",10} {"C.ASK",10} {"STRIKE",10} {"P.BID",10} {"P.ASK",10}");
foreach (var strike in strikes)
{
series.Calls.TryGetValue(strike, out var call);
series.Puts.TryGetValue(strike, out var put);
var callQuote = call != null && quotes.TryGetValue(call, out var callTask)
? callTask.Result
: new Quote();
var putQuote = put != null && quotes.TryGetValue(put, out var putTask)
? putTask.Result
: new Quote();
Console.WriteLine(
$"{callQuote.BidPrice,10:F3} {callQuote.AskPrice,10:F3} {strike,10:F3} {putQuote.BidPrice,10:F3} {putQuote.AskPrice,10:F3}");
}
}
}
}
78 changes: 78 additions & 0 deletions src/DxFeed.Graal.Net/Ipf/Options/OptionChain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// <copyright file="OptionChain.cs" company="Devexperts LLC">
// Copyright © 2024 Devexperts LLC. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// </copyright>

using System;
using System.Collections.Generic;

namespace DxFeed.Graal.Net.Ipf.Options;

/// <summary>
/// Represents a set of option series for a single product or underlying symbol.
///
/// <h3>Threads and locks</h3>
///
/// This class is <b>NOT</b> thread-safe and cannot be used from multiple threads without external synchronization.
/// </summary>
/// <typeparam name="T">The type of option instrument instances.</typeparam>
public sealed class OptionChain<T> : ICloneable
{
private readonly SortedDictionary<OptionSeries<T>, OptionSeries<T>> _seriesMap = new();

/// <summary>
/// Initializes a new instance of the <see cref="OptionChain{T}"/> class with the specified symbol.
/// </summary>
/// <param name="symbol">The symbol (product or underlying) of this option chain.</param>
internal OptionChain(string symbol) =>
Symbol = symbol;

/// <summary>
/// Gets the symbol (product or underlying) of this option chain.
/// </summary>
public string Symbol { get; }

/// <summary>
/// Returns a sorted set of option series in this option chain.
/// </summary>
/// <returns>A sorted set of option series in this option chain.</returns>
public SortedSet<OptionSeries<T>> GetSeries() =>
new(_seriesMap.Keys);

/// <summary>
/// Returns a shallow copy of this option chain.
/// All series are copied (cloned) themselves, but option instrument instances are shared with the original.
/// </summary>
/// <returns>A shallow copy of this option chain.</returns>
public object Clone()
{
var clone = new OptionChain<T>(Symbol);
foreach (var series in _seriesMap.Values)
{
var seriesClone = (OptionSeries<T>)series.Clone();
clone._seriesMap.Add(seriesClone, seriesClone);
}

return clone;
}

/// <summary>
/// Adds an option to the specified series in this option chain.
/// If the series does not exist, it is created.
/// </summary>
/// <param name="series">The option series to which the option will be added.</param>
/// <param name="isCall">Indicates whether the option is a call option.</param>
/// <param name="strike">The strike price of the option.</param>
/// <param name="option">The option to add.</param>
internal void AddOption(OptionSeries<T> series, bool isCall, double strike, T option)
{
if (!_seriesMap.TryGetValue(series, out var os))
{
os = new OptionSeries<T>(series);
_seriesMap[os] = os;
}

os.AddOption(isCall, strike, option);
}
}
Loading

0 comments on commit 2e35650

Please sign in to comment.