-1

I have over 1,000 records and I am using this to find the highest value of (profit * volume). In this case its "DEF" but then I have to open excel and sort by volume and find the range that produces the highest profit.. say excel column 200 to column 800 and then I'm left with say from volume 13450 to volume 85120 is the best range for profits.. how can I code something like that in C# so that I can stop using excel.

    public class Stock {
        public string StockSymbol { get; set; }
        public double Profit { get; set; }
        public int Volume { get; set; }

        public Stock(string Symbol, double p, int v) {
            StockSymbol = Symbol;
            Profit = p;
            Volume = v;
        }
    }

    private ConcurrentDictionary<string, Stock> StockData = new();

    private void button1_Click(object sender, EventArgs e) {

        StockData["ABC"] = new Stock("ABC", 50, 14000);
        StockData["DEF"] = new Stock("DEF", 50, 105000);
        StockData["GHI"] = new Stock("GHI", -70, 123080);
        StockData["JKL"] = new Stock("JKL", -70, 56500);
        StockData["MNO"] = new Stock("MNO", 50, 23500);

        var DictionaryItem = StockData.OrderByDescending((u) => u.Value.Profit * u.Value.Volume).First();

        MessageBox.Show( DictionaryItem.Value.StockSymbol + " " + DictionaryItem.Value.Profit);
    }
XK8ER
  • 748
  • 1
  • 8
  • 23
  • Ah, you aren't Adham - my bad. Weird to have him as your avatar. – mjwills May 25 '21 at 23:01
  • @mjwills: FWIW, [Google says there are at least five people](https://www.google.com/search?tbs=simg:CAQS_1wEJvVt-1RoOBuUa8wELELCMpwgaOwo5CAQSFPYewTOaI-MtpAW2GMcbyC77JfkYGhvG5ZWQ0RkUJQQkGC4XLPgpa07274AgTHl6f0wgBTAEDAsQjq7-CBoKCggIARIEaLW7jQwLEJ3twQkakgEKIAoNbm8gZXhwcmVzc2lvbtqliPYDCwoJL2ovOV9iaHBuChoKB2ZvciBtZW7apYj2AwsKCS9hLzU2emZjcgobCglmaW5lIGFydHPapYj2AwoKCC9tLzBtZzF3ChkKBnNrZXRjaNqliPYDCwoJL20vMDdnbHpxChoKCHBhaW50aW5n2qWI9gMKCggvbS8wNXFkaAw&tbm=isch&sa=X&ved=2ahUKEwji6c-k-OXwAhXKIDQIHS-DCvkQ2A4oAXoECAEQMw&biw=1536&bih=793&dpr=2.5) ... – Peter Duniho May 25 '21 at 23:09
  • ... using that graphic as an avatar. One of them going by "Adnan K." I guess they all liked [the graphic on Adham's site](https://www.adhamdannaway.com/)? – Peter Duniho May 25 '21 at 23:09
  • 1
    Indeed, Bing says [there are lots more than just five](https://www.bing.com/images/search?view=detailV2&insightstoken=bcid_Ty60ZNOVFMkCdQ*ccid_LrRk05UU&form=SBIIRP&iss=SBIUPLOADGET&sbisrc=ImgPaste&idpbck=1&sbifsz=280+x+279+%c2%b7+9.99+kB+%c2%b7+png&sbifnm=image.png&thw=280&thh=279&ptime=58&dlen=13644&expw=280&exph=279&selectedindex=0&id=-149647963&ccid=LrRk05UU&vt=2&sim=15). Popular graphic, it seems. Heck, [three](https://stackoverflow.com/users/1860959) [other](https://stackoverflow.com/users/3195410) [SO users](https://stackoverflow.com/users/5367368) use the same graphic. – Peter Duniho May 25 '21 at 23:13
  • 4
    Anyway... I am a bit confused as to what exactly you are trying to do. You reference an excel file from which you select the highest profit from currently, and show an example in c# of how to obtain a record with the highest profitability. How do you determine this range currently in excel? Am i missing something, or is there some missing details from your question. – hijinxbassist May 25 '21 at 23:13
  • @hijinxbassist its a tedious process where I go selecting cells and look at the profit total from excel.. this is why I would like to automate this – XK8ER May 25 '21 at 23:17
  • 1
    I understand wanting to automate a tedious process such as this, but without some well defined rules it would be impossible. How do you determine what "Highest Profit" is with regards to a range. ` from volume 13450 to volume 85120 is the best range for profits` How do you determine the from volume and to volume? Breaking down the steps you take to reach that conclusion should yield a working algorithm. – hijinxbassist May 25 '21 at 23:24
  • @hijinxbassist so the first thing i do in excel is select the entire column to see total profit and is usually -500 then I select say 100 to 900 and i'm positive 200 then I select say 200 to 800 and the sum is positive 1500 then I select say 300 to 700 and positive 1300 so I know the range – XK8ER May 25 '21 at 23:35
  • The column in this case is the 'volume * profit' value? Each row represents a stock entry? – hijinxbassist May 25 '21 at 23:59
  • @hijinxbassist I just sort by volume and start selecting profit columns until I see highest sum – XK8ER May 26 '21 at 00:11

3 Answers3

1

I wrote up something that may or may not meet your requirements. It uses random to seed a set of test data (you can ignore all of that).

private void GetStockRange()
{
    var stocks = new Stock[200];
    var stockChars = Enumerable.Range(0, 26).Select(n => ((char)n + 64).ToString()).ToArray();
    var random = new Random(DateTime.Now.Millisecond);

    for (int i = 0; i < stocks.Length; i++)
    {
        stocks[i] = new Stock(stockChars[random.Next(0, 26)], random.NextDouble() * random.Next(-250, 250), random.Next(1, 2000));
    }


    var highestPerformaceSelectionCount = 3;
    var highestPerformanceIndices = stocks
        .OrderByDescending(stock => stock.Performance)
        .Take(Math.Max(2, highestPerformaceSelectionCount))
        .Select(stock => Array.IndexOf(stocks, stock))
        .OrderBy(i => i);

    var startOfRange = highestPerformanceIndices.First();
    var endOfRange = highestPerformanceIndices.Last();
    var rangeCount = endOfRange - startOfRange + 1;

    var stocksRange = stocks
        .Skip(startOfRange)
        .Take(rangeCount);


    var totalPerformance = stocks.Sum(stock => stock.Performance);
    var rangedPerformance = stocksRange.Sum(stock => stock.Performance);

    MessageBox.Show(
        "Range Start: " + startOfRange + "\r\n" + 
        "Range End: " + endOfRange + "\r\n" + 
        "Range Cnt: " + rangeCount + "\r\n" + 
        "Total P: " + totalPerformance + "\r\n" + 
        "Range P: " + rangedPerformance
    );
}

The basics of this algorithm to get some of the highest performance points (configured using highestPerformanceSelectionCount, min of 2), and using those indices, construct a range which contains those items. Then take a sum of that range to get the total for that range.

Not sure if I am way off from your question. This may also not be the best way to handle the range. I wanted to post what I had before heading home.

I also added a Performance property to the stock class, which is simply Profit * Volume

EDIT

There is a mistake in the use of the selected indices. The indices selected should be used against the ordered set in order to produce correct ranged results.

Rather than taking the stocksRange from the original unsorted array, instead create the range from the ordered set.

var stocksRange = stocks
    .OrderByDescending(stock => stock.Performance)
    .Skip(startOfRange)
    .Take(rangeCount);

The indices should be gathered from the ordered set as well. Caching the ordered set is probably the easiest route to go.

hijinxbassist
  • 731
  • 11
  • 17
0

As is generally the case, there are any number of ways you can go about this.

First, your sorting code above (the OrderByDescending call). It does what you appear to want, more or less, in that it produces an ordered sequence of KeyValuePair<string, Stock> that you can then choose from. Personally I'd just have sorted StockData.Values to avoid all that .Value indirection. Once sorted you can take the top performer as you're doing, or use the .Take() method to grab the top n items:

var byPerformance = StockData.Values.OrderByDescending(s => s.Profit * s.Volume);
var topPerformer = byPerformance.First();
var top10 = byPerformance.Take(10).ToArray();

If you want to filter by a particular performance value or range then it helps to pre-calculate the number and do your filtering on that. Either store (or calculate) the Performance value in the class, calculate it in the class with a computed property, or tag the Stock records with a calculated performance using an intermediate type:

  1. Store in the class
    public class Stock {
        // marking these 'init' makes them read-only after creation
        public string StockSymbol { get; init; }
        public double Profit { get; init; }
        public int Volume { get; init; }

        public double Performance { get; init; }

        public Stock(string symbol, double profit, int volume)
        {
            StockSymbol = symbol;
            Profit = profit;
            Volume = volume;
            Performance = profit * volume;
        }
    }
  1. Calculate in class
    public class Stock 
    {
        public string StockSymbol { get; set; }
        public double Profit { get; set; }
        public int Volume { get; set; }
        public double Performance => Profit * Volume;

        // you know what the constructor looks like
    }
  1. Intermediate Type (with range filtering)
    // let's look for those million-dollar items
    var minPerformance = 1000000d;

    var highPerformance = StockData.Values
        // create a stream of intermediate types with the performance
        .Select(s => new { Performance = s.Profit * s.Volume, Stock = s })
        // sort them
        .OrderByDescending(i => i.Performance)
        // filter by our criteria
        .Where(i => i.Performance >= minPerformance)
        // pull out the stocks themselves
        .Select(i => i.Value)
        // and fix into an array so we don't have to do this repeatedly
        .ToArray();

Ultimately though you'll probably end up looking for ways to store the data between runs, update the values and so forth. I strongly suggest looking at starting with a database and learning how to do things there. It's basically the same, you just end up with a lot more flexibility in the way you handle the data. The code to do the actual queries looks basically the same.


Once you have the data in your program, there are very few limits on how you can manipulate it. Anything you can do in Excel with the data, you can do in C#. Usually easily, sometimes with a little work.

LINQ (Language-Integrated Native Query) makes a lot of those manipulations trivial, with extensions for all sorts of things. You can take the average performance (with .Average()) and then filter on those that perform 10% above it with some simple math. If the data follows some sort of Normal Distribution you can add your own extension (or use this one) to figure out the standard deviation... and now we're doing statistics!

The basic concepts of LINQ, and the database languages it was roughly based on, give you plenty of expressive power. And Stack Overflow is full of people who can help you figure out how to get there.

Corey
  • 13,462
  • 1
  • 29
  • 60
-1

try following :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        List<Stock> stocks = null;
        public Form1()
        {
            InitializeComponent();
            stocks = new List<Stock>() {
                new Stock("ABC", 50, 14000),
                new Stock("DEF", 50, 105000),
                new Stock("GHI", -70, 123080),
                new Stock("JKL", -70, 56500),
                new Stock("MNO", 50, 23500)
            };

        }
 

        private void button1_Click(object sender, EventArgs e)
        {
            DataTable dt = Stock.GetTable(stocks);

            dataGridView1.DataSource = dt;
        }
    }
    public class Stock {
        public string StockSymbol { get; set; }
        public double Profit { get; set; }
        public int Volume { get; set; }

        public Stock(string Symbol, double p, int v) {
            StockSymbol = Symbol;
            Profit = p;
            Volume = v;
        }
        public static DataTable GetTable(List<Stock> stocks)
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("Symbol", typeof(string));
            dt.Columns.Add("Profit", typeof(int));
            dt.Columns.Add("Volume", typeof(int));
            dt.Columns.Add("Volume x Profit", typeof(int));

            foreach(Stock stock in stocks)
            {
                dt.Rows.Add(new object[] { stock.StockSymbol, stock.Profit, stock.Volume, stock.Profit * stock.Volume });
            }
            dt = dt.AsEnumerable().OrderByDescending(x => x.Field<int>("Volume x Profit")).CopyToDataTable();
            return dt;
        }
    }


}
jdweng
  • 28,546
  • 2
  • 13
  • 18