From 08825894f4b87a1c741cdc94427181c779963081 Mon Sep 17 00:00:00 2001 From: caspar Date: Tue, 17 Mar 2026 20:08:31 -0400 Subject: [PATCH] Create API for price data. --- Backend.cs | 43 +++++++ Backend.csproj | 5 + Controller/MarketDataController.cs | 89 +++++++++++++++ MarketDataRequest/HistoricalEquityRequest.cs | 12 +- Program.cs | 100 ---------------- Properties/launchSettings.json | 4 +- ServiceHandler/MarketDataHander.cs | 113 +++++++++++++++++++ 7 files changed, 263 insertions(+), 103 deletions(-) create mode 100644 Backend.cs create mode 100644 Controller/MarketDataController.cs delete mode 100644 Program.cs create mode 100644 ServiceHandler/MarketDataHander.cs diff --git a/Backend.cs b/Backend.cs new file mode 100644 index 0000000..77e8a42 --- /dev/null +++ b/Backend.cs @@ -0,0 +1,43 @@ +using Backend.ServiceHander; +using Microsoft.AspNetCore.HttpOverrides; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddSingleton(); + +builder.Services.AddCors(options => +{ + options.AddPolicy("Frontend", policy => + { + policy + // ---- DEVELOPMENT ---- + .AllowAnyOrigin() + // ---- PRODUCTION ---- + // .WithOrigins("https://app.dongfeng-systems.org") + .AllowAnyHeader() + .AllowAnyMethod(); + }); +}); + +builder.Services.Configure(options => +{ + options.ForwardedHeaders = + ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; +}); + +var app = builder.Build(); + +app.UseSwagger(); +app.UseSwaggerUI(); + +app.UseForwardedHeaders(); +app.UseHttpsRedirection(); + +app.UseCors("Frontend"); +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Backend.csproj b/Backend.csproj index 4d2f476..309f8bb 100644 --- a/Backend.csproj +++ b/Backend.csproj @@ -10,5 +10,10 @@ + + + + + diff --git a/Controller/MarketDataController.cs b/Controller/MarketDataController.cs new file mode 100644 index 0000000..7cff173 --- /dev/null +++ b/Controller/MarketDataController.cs @@ -0,0 +1,89 @@ +using Backend.ServiceHander; +using Backend.MarketDataRequest; +using Microsoft.AspNetCore.Mvc; +using IBApi; + +namespace BackendApi.Controller +{ + [ApiController] + [Route("api/market")] + public class MarketController : ControllerBase + { + private readonly MarketDataService _marketDataService; + + public MarketController(MarketDataService marketDataService) + { + _marketDataService = marketDataService; + } + + [HttpGet("live/{symbol}")] + public IActionResult GetLive(string symbol) + { + try + { + var data = _marketDataService.GetLiveQuote(symbol.ToUpperInvariant()); + return Ok(data); + } + catch (Exception ex) + { + return StatusCode(500, new { error = ex.ToString() }); + } + } + + [HttpGet("historical")] + public IActionResult GetHistorical( + [FromQuery] string symbol, + [FromQuery] string endDateTime, + [FromQuery] string duration, + [FromQuery] HistoricalBarSize barSize, + [FromQuery] HistoricalWhatToShow whatToShow = HistoricalWhatToShow.Trades, + [FromQuery] bool useRth = true) + { + try + { + var data = _marketDataService.GetHistorical( + symbol.ToUpperInvariant(), + endDateTime, + duration, + barSize, + whatToShow, + useRth + ); + + return Ok(data); + } + catch (Exception ex) + { + return StatusCode(500, new { error = ex.ToString() }); + } + } + + [HttpGet("historical-line")] + public IActionResult GetHistoricalLine( + [FromQuery] string symbol, + [FromQuery] string endDateTime, + [FromQuery] string duration, + [FromQuery] HistoricalBarSize barSize, + [FromQuery] HistoricalWhatToShow whatToShow = HistoricalWhatToShow.Trades, + [FromQuery] bool useRth = true) + { + try + { + var data = _marketDataService.GetHistoricalLine( + symbol.ToUpperInvariant(), + endDateTime, + duration, + barSize, + whatToShow, + useRth + ); + + return Ok(data); + } + catch (Exception ex) + { + return StatusCode(500, new { error = ex.ToString() }); + } + } + } +} \ No newline at end of file diff --git a/MarketDataRequest/HistoricalEquityRequest.cs b/MarketDataRequest/HistoricalEquityRequest.cs index 1fd9af2..8a8b9f2 100644 --- a/MarketDataRequest/HistoricalEquityRequest.cs +++ b/MarketDataRequest/HistoricalEquityRequest.cs @@ -6,7 +6,9 @@ namespace Backend.MarketDataRequest FiveMins, FifteenMins, OneHour, - OneDay + OneDay, + FiveDays, + OneMonth } public enum HistoricalWhatToShow @@ -17,6 +19,12 @@ namespace Backend.MarketDataRequest Midpoint } + public sealed class HistoricalLinePoint + { + public string Timestamp { get; init; } = string.Empty; + public double Price { get; init; } + } + public sealed class HistoricalEquityRequest { public int RequestId { get; init; } @@ -62,6 +70,8 @@ namespace Backend.MarketDataRequest HistoricalBarSize.FifteenMins => "15 mins", HistoricalBarSize.OneHour => "1 hour", HistoricalBarSize.OneDay => "1 day", + HistoricalBarSize.FiveDays => "5 days", + HistoricalBarSize.OneMonth => "1 month", _ => throw new ArgumentOutOfRangeException(nameof(BarSize)) }; diff --git a/Program.cs b/Program.cs deleted file mode 100644 index 6959d5f..0000000 --- a/Program.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Backend.MarketDataRequest; -using IBApi; -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Backend -{ - public static class Program - { - public static void Main(string[] args) - { - var Gateway = new IBGatewayControl(); - try - { - Gateway.Start("127.0.0.1", 4002, 1); - } - catch - { - Console.WriteLine("Invalid connection to IB Gateway. Please ensure gateway is running and connected."); - Environment.Exit(1); - } - - while (!Gateway.IsApiReady) - { - Thread.Sleep(50); - } - - // ---- TEST HISTORICAL DATA REQUEST ---- - - var HistoricalRequest = new HistoricalEquityRequest - { - RequestId = 1, - Symbol = "NVDA", - EndDateTime = "20260311 11:00:00 US/Eastern", - Duration = "3600 S", - BarSize = HistoricalBarSize.FiveMins, - WhatToShow = HistoricalWhatToShow.Trades, - UseRth = true, - KeepUpToDate = false, - FormatDate = 1 - }; - - List Bars = Gateway.RequestHistoricalBlocking(HistoricalRequest, TimeSpan.FromSeconds(15)); - - Console.WriteLine(); - Console.WriteLine($"Received {Bars.Count} bars. Continue program here."); - - // ---- TEST LIVE DATA REQUEST ---- - - var liveRequest = new LiveEquityRequest - { - RequestId = 2, - Symbol = "NVDA" - }; - - var live = Gateway.RequestLiveEquityBlocking( - liveRequest, - TimeSpan.FromSeconds(15)); - - Console.WriteLine(); - Console.WriteLine("=== Live Quote ==="); - Console.WriteLine($"Symbol: {live.Symbol}"); - Console.WriteLine($"Last Price: {live.LastPrice}"); - Console.WriteLine($"Volume: {live.Volume}"); - Console.WriteLine($"Previous Close: {live.PreviousClose}"); - Console.WriteLine($"Open: {live.Open}"); - - - // ---- FUNDAMENTALS DATA REQUEST ---- - // DOES NOT WORK, NEEDS EXTRA DATA SUBSCRIPTION -/* - var fundamentalsRequest = new FundamentalsEquityRequest - { - RequestId = 3, - Symbol = "NVDA" - }; - - try - { - var fundamentals = Gateway.RequestFundamentalsBlocking( - fundamentalsRequest, - TimeSpan.FromSeconds(15)); - - Console.WriteLine(); - Console.WriteLine("=== Fundamentals ==="); - Console.WriteLine($"Symbol: {fundamentals.Symbol}"); - Console.WriteLine($"Market Cap: {fundamentals.MarketCap}"); - Console.WriteLine($"P/E Ratio: {fundamentals.PERatio}"); - Console.WriteLine($"Dividend Yield: {fundamentals.DividendYield}"); - } - catch (Exception ex) - { - Console.WriteLine($"Fundamentals request failed."); - } -*/ - Gateway.Disconnect(); - } - } -} \ No newline at end of file diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 3cbd1d4..c232bc9 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -5,7 +5,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "http://localhost:5246", + "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -14,7 +14,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "https://localhost:7039;http://localhost:5246", + "applicationUrl": "https://localhost:7039;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/ServiceHandler/MarketDataHander.cs b/ServiceHandler/MarketDataHander.cs new file mode 100644 index 0000000..ef99d15 --- /dev/null +++ b/ServiceHandler/MarketDataHander.cs @@ -0,0 +1,113 @@ +using Backend.MarketDataRequest; +using IBApi; + +namespace Backend.ServiceHander +{ + public sealed class MarketDataService + { + private readonly IBGatewayControl Gateway; + private int NextRequestId = 1; + private readonly object Lock = new(); + + public MarketDataService() + { + Gateway = new IBGatewayControl(); + try + { + Gateway.Start("127.0.0.1", 4002, 1); + } + catch + { + Console.WriteLine("Invalid connection to IB Gateway. Please ensure gateway is running and connected."); + Environment.Exit(1); + } + } + + private int GetNextRequestId() + { + lock (Lock) + { + return NextRequestId++; + } + } + + public LiveEquitySnapshot GetLiveQuote(string Symbol) // + { + var Request = new LiveEquityRequest + { + RequestId = GetNextRequestId(), + Symbol = Symbol, + SecType = "STK", + Exchange = "SMART", + PrimaryExchange = "NASDAQ", + Currency = "USD", + RegulatorySnapshot = false + }; + + return Gateway.RequestLiveEquityBlocking(Request, TimeSpan.FromSeconds(10)); + } + + public List GetHistorical(string symbol, string endDateTime, string duration, HistoricalBarSize barSize, HistoricalWhatToShow whatToShow = HistoricalWhatToShow.Trades, bool useRth = true) + { + var Request = new HistoricalEquityRequest + { + RequestId = GetNextRequestId(), + Symbol = symbol, + EndDateTime = endDateTime, + Duration = duration, + BarSize = barSize, + WhatToShow = whatToShow, + UseRth = useRth, + KeepUpToDate = false, + FormatDate = 1 + }; + + var Bars = Gateway.RequestHistoricalBlocking(Request, TimeSpan.FromSeconds(20)); + + return Bars.Select(b => new + { + time = b.Time, + open = b.Open, + high = b.High, + low = b.Low, + close = b.Close, + volume = b.Volume + }).Cast().ToList(); + } + + public List GetHistoricalLine(string symbol, string endDateTime, string duration, HistoricalBarSize barSize, HistoricalWhatToShow whatToShow = HistoricalWhatToShow.Trades, bool useRth = true) + { + var Request = new HistoricalEquityRequest + { + RequestId = GetNextRequestId(), + Symbol = symbol, + EndDateTime = endDateTime, + Duration = duration, + BarSize = barSize, + WhatToShow = whatToShow, + UseRth = useRth, + KeepUpToDate = false, + FormatDate = 1 + }; + + var Bars = Gateway.RequestHistoricalBlocking(Request, TimeSpan.FromSeconds(20)); + + return Bars.Select(b => new + { + timestamp = b.Time, + price = b.Close, + volume = b.Volume + }).Cast().ToList(); + } + + public void Dispose() + { + Gateway.Dispose(); + } + + public void Disconnect() + { + Gateway.Disconnect(); + } + } +} \ No newline at end of file