From 0e1e0bc4e76d61b0c1c7b251ab569b24c3139db5 Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Fri, 13 Sep 2024 15:56:25 +0100 Subject: [PATCH 01/13] initial commit - added in MyAlgoLogic --- .idea/compiler.xml | 20 +++++ .idea/encodings.xml | 25 ++++++ .idea/inspectionProfiles/Project_Default.xml | 6 ++ .idea/jarRepositories.xml | 20 +++++ .idea/misc.xml | 12 +++ .idea/vcs.xml | 6 ++ .../algo/AddCancelAlgoLogic.java | 4 + .../codingblackfemales/algo/AlgoLogic.java | 1 + .../algo/PassiveAlgoLogic.java | 6 +- .../algo/SniperAlgoLogic.java | 27 +++++- .../sotw/SimpleAlgoState.java | 2 + .../dictionary/EncodingDecodingTest.java | 20 +++-- .../gettingstarted/MyAlgoLogic.java | 53 ++++++++++-- .../gettingstarted/MyAlgoTest.java | 83 +++++++++++++++---- 14 files changed, 251 insertions(+), 34 deletions(-) create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..38033fb2 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000..6860377a --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..03d9549e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..712ab9d9 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..9dc782bb --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java index bfdc28cd..06cd445e 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java @@ -53,4 +53,8 @@ public Action evaluate(SimpleAlgoState state) { return new CreateChildOrder(Side.BUY, quantity, price); } } + @Override + public long evaluate(SimpleAlgoState state, long size) { + return 0; + } } diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AlgoLogic.java index f9d56f7c..a838c9ea 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AlgoLogic.java @@ -5,4 +5,5 @@ public interface AlgoLogic { Action evaluate(final SimpleAlgoState state); + long evaluate(SimpleAlgoState state, long size); } diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/PassiveAlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/PassiveAlgoLogic.java index 59594b9b..bb4da188 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/PassiveAlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/PassiveAlgoLogic.java @@ -40,4 +40,8 @@ public Action evaluate(SimpleAlgoState state) { } } -} + @Override + public long evaluate(SimpleAlgoState state, long size) { + return 0; + } +} \ No newline at end of file diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/SniperAlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/SniperAlgoLogic.java index f011db26..e2dfba1a 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/SniperAlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/SniperAlgoLogic.java @@ -10,27 +10,34 @@ import org.slf4j.LoggerFactory; import static codingblackfemales.action.NoAction.NoAction; - +// declating SniperAlgoLogic class which implements AlgoLogic interface public class SniperAlgoLogic implements AlgoLogic { + // creating a logger for SniperAlgoLogic class to log info private static final Logger logger = LoggerFactory.getLogger(SniperAlgoLogic.class); @Override public Action evaluate(SimpleAlgoState state) { + // logging information that the algo has started logger.info("[SNIPERALGO] In Algo Logic...."); + // converting order book into a string format final String book = Util.orderBookToString(state); + // logging the current state of order book logger.info("[SNIPERALGO] Algo Sees Book as:\n" + book); + // retrieving the best ask price (the lowst price seller is willing to accept) from the order book + // state.getAskAt(0) represents best or lowest price in the market known as fatTuch final AskLevel farTouch = state.getAskAt(0); - //take as much as we can from the far touch.... + //setting the quantity and price for this order long quantity = farTouch.quantity; long price = farTouch.price; - //until we have three child orders.... + // algo checks how many child orders it currently has in the market. + // if theres fewer 5, it will create more. if (state.getChildOrders().size() < 5) { //then keep creating a new one logger.info("[SNIPERALGO] Have:" + state.getChildOrders().size() + " children, want 5, sniping far touch of book with: " + quantity + " @ " + price); @@ -40,4 +47,18 @@ public Action evaluate(SimpleAlgoState state) { return NoAction; } } + @Override + public long evaluate(SimpleAlgoState state, long size) { + return 0; + } } +// goal: sniper algo aims to "snipe" the best ask price, so tryig to buy as much as possible at the lowest price(the best ask) +// until it has 5 child orders. +// flow: +// 1. logs the current state of the order book. +// 2. retrieves the best ask price. +// 3. if fewer than 5 chld orders exists, it creates a new buy order at the best ask price for the available quantity. +// 4. if 5 or more child orders exist, it does not hing. + +// this algo follows a "sniping" strategy, where it aggressively tries to buy at the best available price on the ask side +// until it reaches a treshold of 5 active orders. \ No newline at end of file diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/sotw/SimpleAlgoState.java b/algo-exercise/algo/src/main/java/codingblackfemales/sotw/SimpleAlgoState.java index b27d0972..bec37374 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/sotw/SimpleAlgoState.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/sotw/SimpleAlgoState.java @@ -20,4 +20,6 @@ public interface SimpleAlgoState { public List getActiveChildOrders(); public long getInstrumentId(); + + } diff --git a/algo-exercise/dictionary/src/test/java/codingblackfemales/dictionary/EncodingDecodingTest.java b/algo-exercise/dictionary/src/test/java/codingblackfemales/dictionary/EncodingDecodingTest.java index cf7a5a9d..59727bb2 100644 --- a/algo-exercise/dictionary/src/test/java/codingblackfemales/dictionary/EncodingDecodingTest.java +++ b/algo-exercise/dictionary/src/test/java/codingblackfemales/dictionary/EncodingDecodingTest.java @@ -4,9 +4,17 @@ import org.agrona.concurrent.UnsafeBuffer; import org.junit.Assert; import org.junit.Test; +// import uk.co.real_logic.sbe.ir.generated.MessageHeaderDecoder; +// import uk.co.real_logic.sbe.ir.generated.MessageHeaderEncoder; +// import messages.marketdata.BookUpdateEncoder; +// import messages.marketdata.BookUpdateDecoder; + import java.nio.ByteBuffer; +// import static com.sun.org.apache.xml.internal.serializer.utils.Utils.messages; + + public class EncodingDecodingTest { private final MessageHeaderDecoder headerDecoder = new MessageHeaderDecoder(); @@ -31,14 +39,14 @@ public void encodingDecoding() throws Exception { encoder.instrumentId(123L); encoder.askBookCount(3) - .next().price(100l).size(101l) - .next().price(90l).size(200l) - .next().price(80l).size(300); + .next().price(100L).size(101L) + .next().price(90L).size(200L) + .next().price(80L).size(300); encoder.bidBookCount(3) - .next().price(110l).size(100) - .next().price(210l).size(200) - .next().price(310l).size(300); + .next().price(110L).size(100) + .next().price(210L).size(200) + .next().price(310L).size(300); encoder.instrumentStatus(InstrumentStatus.CONTINUOUS); encoder.source(Source.STREAM); diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index f0259d0a..05b5218a 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -1,10 +1,14 @@ package codingblackfemales.gettingstarted; import codingblackfemales.action.Action; +import codingblackfemales.action.CreateChildOrder; import codingblackfemales.action.NoAction; import codingblackfemales.algo.AlgoLogic; import codingblackfemales.sotw.SimpleAlgoState; +import codingblackfemales.sotw.marketdata.AskLevel; +import codingblackfemales.sotw.marketdata.BidLevel; import codingblackfemales.util.Util; +import messages.order.Side; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,19 +16,54 @@ public class MyAlgoLogic implements AlgoLogic { private static final Logger logger = LoggerFactory.getLogger(MyAlgoLogic.class); + private long position = 0; // Variable to track how many shares we currently hold + private long entryBuyPrice = 0; // Variable to track the price at which we first bought shares + @Override public Action evaluate(SimpleAlgoState state) { var orderBookAsString = Util.orderBookToString(state); + logger.info("[MYALGO] The state of the order book is:\n{}", orderBookAsString); + + // Retrieve the best bid and ask prices + BidLevel bestBid = state.getBidAt(0); // the highest bid price - the price buyers are willing to pay + AskLevel bestAsk = state.getAskAt(0); // the lowest ask price - the price sellers are asking for + + // setting a threshold price below which we are willing to buy + long buyThreshold = 100; // the max price we are willing to buy shares + + // Checking if we hold no shares, and the current ask price is low enough to buy + if (position == 0 && bestAsk.price < buyThreshold) { + logger.info("[MYALGO] No shares held and ask price (" + bestAsk.price + ") is below threshold (" + buyThreshold + "). Buying " + bestAsk.quantity + " shares."); + + // Update position and entry buy price after buying + position = bestAsk.quantity; + entryBuyPrice = bestAsk.price; + + return new CreateChildOrder(Side.BUY, bestAsk.quantity, bestAsk.price); + } else { + logger.info("[MYALGO] Ask price (" + bestAsk.price + ") is too high. Not buying at this time."); + } - logger.info("[MYALGO] The state of the order book is:\n" + orderBookAsString); + // Checking if we hold shares and the bid price is high enough to sell + if (position > 0 && bestBid.price > entryBuyPrice) { + logger.info("[MYALGO] Holding shares, and bid price (" + bestBid.price + ") is above entry price (" + entryBuyPrice + "). Selling."); - /******** - * - * Add your logic here.... - * - */ + // Reset position after selling + long sharesToSell = position; + long position = 0; - return NoAction.NoAction; + return new CreateChildOrder(Side.SELL, sharesToSell, bestBid.price); + } else { + logger.info("[MYALGO] Bid price (" + bestBid.price + ") is not high enough. Holding position for now."); + } + return NoAction.NoAction; // no buy or sell conditions are met, take no action + } + + @Override + public long evaluate(SimpleAlgoState state, long size) { + return 0; } } + + diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index c16427bb..8ddf1949 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -1,35 +1,84 @@ package codingblackfemales.gettingstarted; -import codingblackfemales.algo.AlgoLogic; +import codingblackfemales.container.Actioner; +import codingblackfemales.container.AlgoContainer; +import codingblackfemales.container.RunTrigger; +import codingblackfemales.sequencer.DefaultSequencer; +import codingblackfemales.sequencer.Sequencer; +import codingblackfemales.sequencer.consumer.LoggingConsumer; +import codingblackfemales.sequencer.marketdata.SequencerTestCase; +import codingblackfemales.sequencer.net.TestNetwork; +import codingblackfemales.service.MarketDataService; +import codingblackfemales.service.OrderService; +import messages.marketdata.*; +import org.agrona.concurrent.UnsafeBuffer; import org.junit.Test; +import java.nio.ByteBuffer; -/** - * This test is designed to check your algo behavior in isolation of the order book. - * - * You can tick in market data messages by creating new versions of createTick() (ex. createTick2, createTickMore etc..) - * - * You should then add behaviour to your algo to respond to that market data by creating or cancelling child orders. - * - * When you are comfortable you algo does what you expect, then you can move on to creating the MyAlgoBackTest. - * - */ -public class MyAlgoTest extends AbstractAlgoTest { +import static org.junit.Assert.assertEquals; + +public class MyAlgoTest extends SequencerTestCase { + + private final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder(); + private final BookUpdateEncoder encoder = new BookUpdateEncoder(); + + private AlgoContainer container; @Override - public AlgoLogic createAlgoLogic() { - //this adds your algo logic to the container classes - return new MyAlgoLogic(); + public Sequencer getSequencer() { + final TestNetwork network = new TestNetwork(); + final Sequencer sequencer = new DefaultSequencer(network); + + final RunTrigger runTrigger = new RunTrigger(); + final Actioner actioner = new Actioner(sequencer); + + container = new AlgoContainer(new MarketDataService(runTrigger), new OrderService(runTrigger), runTrigger, actioner); + //set my algo logic + container.setLogic(new MyAlgoLogic()); + + network.addConsumer(new LoggingConsumer()); + network.addConsumer(container.getMarketDataService()); + network.addConsumer(container.getOrderService()); + network.addConsumer(container); + + return sequencer; } + private UnsafeBuffer createSampleMarketDataTick(){ + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); + final UnsafeBuffer directBuffer = new UnsafeBuffer(byteBuffer); + + //write the encoded output to the direct buffer + encoder.wrapAndApplyHeader(directBuffer, 0, headerEncoder); + + //set the fields to desired values + encoder.venue(Venue.XLON); + encoder.instrumentId(123L); + + encoder.askBookCount(3) + .next().price(100L).size(101L) + .next().price(110L).size(200L) + .next().price(115L).size(5000L); + + encoder.bidBookCount(3) + .next().price(98L).size(100L) + .next().price(95L).size(200L) + .next().price(91L).size(300L); + + encoder.instrumentStatus(InstrumentStatus.CONTINUOUS); + encoder.source(Source.STREAM); + + return directBuffer; + } @Test public void testDispatchThroughSequencer() throws Exception { //create a sample market data tick.... - send(createTick()); + send(createSampleMarketDataTick()); //simple assert to check we had 3 orders created - //assertEquals(container.getState().getChildOrders().size(), 3); + assertEquals(container.getState().getChildOrders().size(), 3); } } From d6b7dcb279230a9dfb744bf3fc65133058b9ac93 Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Sat, 14 Sep 2024 15:08:11 +0100 Subject: [PATCH 02/13] second commit - commented out override method - made no difference to output --- .../codingblackfemales/algo/AddCancelAlgoLogic.java | 8 ++++---- .../main/java/codingblackfemales/algo/AlgoLogic.java | 2 +- .../codingblackfemales/algo/PassiveAlgoLogic.java | 8 ++++---- .../codingblackfemales/algo/SniperAlgoLogic.java | 8 ++++---- .../gettingstarted/MyAlgoLogic.java | 12 ++++++------ 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java index 06cd445e..8f66e7ba 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java @@ -53,8 +53,8 @@ public Action evaluate(SimpleAlgoState state) { return new CreateChildOrder(Side.BUY, quantity, price); } } - @Override - public long evaluate(SimpleAlgoState state, long size) { - return 0; - } + //@Override + //public long evaluate(SimpleAlgoState state, long size) { + // return 0; + //} } diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AlgoLogic.java index a838c9ea..5978743c 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AlgoLogic.java @@ -5,5 +5,5 @@ public interface AlgoLogic { Action evaluate(final SimpleAlgoState state); - long evaluate(SimpleAlgoState state, long size); + // long evaluate(SimpleAlgoState state, long size); } diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/PassiveAlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/PassiveAlgoLogic.java index bb4da188..af5ae07d 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/PassiveAlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/PassiveAlgoLogic.java @@ -40,8 +40,8 @@ public Action evaluate(SimpleAlgoState state) { } } - @Override - public long evaluate(SimpleAlgoState state, long size) { - return 0; - } + // @Override + //public long evaluate(SimpleAlgoState state, long size) { + // return 0; + //} } \ No newline at end of file diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/SniperAlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/SniperAlgoLogic.java index e2dfba1a..204e8b3f 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/SniperAlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/SniperAlgoLogic.java @@ -47,10 +47,10 @@ public Action evaluate(SimpleAlgoState state) { return NoAction; } } - @Override - public long evaluate(SimpleAlgoState state, long size) { - return 0; - } + // @Override + //public long evaluate(SimpleAlgoState state, long size) { + // return 0; + //} } // goal: sniper algo aims to "snipe" the best ask price, so tryig to buy as much as possible at the lowest price(the best ask) // until it has 5 child orders. diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index 05b5218a..83a2d0fd 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -30,7 +30,7 @@ public Action evaluate(SimpleAlgoState state) { AskLevel bestAsk = state.getAskAt(0); // the lowest ask price - the price sellers are asking for // setting a threshold price below which we are willing to buy - long buyThreshold = 100; // the max price we are willing to buy shares + long buyThreshold = 200; // the max price we are willing to buy shares // Checking if we hold no shares, and the current ask price is low enough to buy if (position == 0 && bestAsk.price < buyThreshold) { @@ -51,7 +51,7 @@ public Action evaluate(SimpleAlgoState state) { // Reset position after selling long sharesToSell = position; - long position = 0; + position = 0; return new CreateChildOrder(Side.SELL, sharesToSell, bestBid.price); } else { @@ -60,10 +60,10 @@ public Action evaluate(SimpleAlgoState state) { return NoAction.NoAction; // no buy or sell conditions are met, take no action } - @Override - public long evaluate(SimpleAlgoState state, long size) { - return 0; - } + //@Override + // public long evaluate(SimpleAlgoState state, long size) { + // return 0; + //} } From 1cc60e16f9d23a4c53ee34bc46d7d7aef2fce9b0 Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Sat, 28 Sep 2024 17:01:11 +0100 Subject: [PATCH 03/13] Algo is running but sell condition not working yet --- .idea/trading-algorithm-assessment.iml | 9 + .../algo/AddCancelAlgoLogic.java | 2 +- .../gettingstarted/MyAlgoLogic.java | 93 +- .../gettingstarted/MyAlgoBackTest.java | 1 + .../gettingstarted/MyAlgoTest.java | 85 +- ui-front-end/package-lock.json | 4139 +++++++++++++++++ .../market-depth/MarketDepthFeature.tsx | 45 +- .../market-depth/MarketDepthPanel.tsx | 0 .../src/components/market-depth/PriceCell.tsx | 21 + .../components/market-depth/QuantityCell.tsx | 17 + .../market-depth/useMarketDepthData.ts | 5 +- 11 files changed, 4304 insertions(+), 113 deletions(-) create mode 100644 .idea/trading-algorithm-assessment.iml create mode 100644 ui-front-end/package-lock.json create mode 100644 ui-front-end/src/components/market-depth/MarketDepthPanel.tsx create mode 100644 ui-front-end/src/components/market-depth/PriceCell.tsx create mode 100644 ui-front-end/src/components/market-depth/QuantityCell.tsx diff --git a/.idea/trading-algorithm-assessment.iml b/.idea/trading-algorithm-assessment.iml new file mode 100644 index 00000000..d6ebd480 --- /dev/null +++ b/.idea/trading-algorithm-assessment.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java index 8f66e7ba..306fd779 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java @@ -26,7 +26,7 @@ public Action evaluate(SimpleAlgoState state) { var totalOrderCount = state.getChildOrders().size(); - //make sure we have an exit condition... + //make sure we have a we have an xit condition... if (totalOrderCount > 20) { return NoAction.NoAction; } diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index 83a2d0fd..1078ecdd 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -1,9 +1,11 @@ package codingblackfemales.gettingstarted; import codingblackfemales.action.Action; +import codingblackfemales.action.CancelChildOrder; import codingblackfemales.action.CreateChildOrder; import codingblackfemales.action.NoAction; import codingblackfemales.algo.AlgoLogic; +import codingblackfemales.sotw.ChildOrder; import codingblackfemales.sotw.SimpleAlgoState; import codingblackfemales.sotw.marketdata.AskLevel; import codingblackfemales.sotw.marketdata.BidLevel; @@ -12,58 +14,87 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + public class MyAlgoLogic implements AlgoLogic { private static final Logger logger = LoggerFactory.getLogger(MyAlgoLogic.class); - private long position = 0; // Variable to track how many shares we currently hold - private long entryBuyPrice = 0; // Variable to track the price at which we first bought shares + // constraints for the price limit + private static final double priceLimit = 99.00; + private static final double sellLimit = 105.00; // sell when the price reaches £105 + private static final int maxOrders = 10; // max allowed orders in the orderbook + private static final int quantity = 50; // quantity for each order @Override public Action evaluate(SimpleAlgoState state) { + logger.info("[MYALGO] In Algo Logic...."); + // log the current state of the order book var orderBookAsString = Util.orderBookToString(state); - logger.info("[MYALGO] The state of the order book is:\n{}", orderBookAsString); + logger.info("[MYALGO] The state of the order book is:\n" + orderBookAsString); + + // Retrieve the total Order count + var totalOrderCount = state.getChildOrders().size(); + logger.info("[MYALGO] Total child orders: " + totalOrderCount); + + // Retrieve active orders count + List activeOrders = state.getActiveChildOrders(); + int activeOrdersCount = activeOrders.size(); + logger.info("[MYALGO] Active child orders: " + activeOrdersCount); + + //make sure we have an exit condition + if (totalOrderCount >= maxOrders) { + logger.info("[MYALGO] Maximum order count reached. No more buy orders will be placed."); + return NoAction.NoAction; + } // Retrieve the best bid and ask prices BidLevel bestBid = state.getBidAt(0); // the highest bid price - the price buyers are willing to pay AskLevel bestAsk = state.getAskAt(0); // the lowest ask price - the price sellers are asking for - // setting a threshold price below which we are willing to buy - long buyThreshold = 200; // the max price we are willing to buy shares + // Actual price + long bidPrice = bestBid.price; // the highest bid price + long askPrice = bestAsk.price; // the lowest ask price - // Checking if we hold no shares, and the current ask price is low enough to buy - if (position == 0 && bestAsk.price < buyThreshold) { - logger.info("[MYALGO] No shares held and ask price (" + bestAsk.price + ") is below threshold (" + buyThreshold + "). Buying " + bestAsk.quantity + " shares."); + // Place buy orders until you reach 10 + // bidPrice < priceLimit && - not working + if (activeOrdersCount < maxOrders) { + logger.info("[MYALGO] The current price is below the price limit. Placing buy order"); + return new CreateChildOrder(Side.BUY, quantity, bidPrice); + } - // Update position and entry buy price after buying - position = bestAsk.quantity; - entryBuyPrice = bestAsk.price; + // cancel all buy orders if the price has exceeded the priceLimit and start selling + // bidPrice >= priceLimit && + else if (bidPrice >= priceLimit && activeOrdersCount > maxOrders) { + logger.info("[MYALGO] Price exceeded limit. Cancelling all buy orders"); + final var option = activeOrders.stream().findFirst(); + if (option.isPresent()) { + var childOrder = option.get(); + logger.info("[ADDCANCELALGO] Cancelling order:" + childOrder); + return new CancelChildOrder(childOrder); + } else{ + return NoAction.NoAction; + } + } - return new CreateChildOrder(Side.BUY, bestAsk.quantity, bestAsk.price); - } else { - logger.info("[MYALGO] Ask price (" + bestAsk.price + ") is too high. Not buying at this time."); + // sell if the price exceeds the sell limit + else if (bidPrice > sellLimit) { + logger.info("[MYALGO] The price exceeds the sell limit. Placing sell order"); + return new CreateChildOrder(Side.SELL, quantity, bidPrice); } + // if no conditions are met + else { + logger.info("[MYALGO] No condtions met. Take no action"); + return NoAction.NoAction; + } + } +} + - // Checking if we hold shares and the bid price is high enough to sell - if (position > 0 && bestBid.price > entryBuyPrice) { - logger.info("[MYALGO] Holding shares, and bid price (" + bestBid.price + ") is above entry price (" + entryBuyPrice + "). Selling."); - // Reset position after selling - long sharesToSell = position; - position = 0; - return new CreateChildOrder(Side.SELL, sharesToSell, bestBid.price); - } else { - logger.info("[MYALGO] Bid price (" + bestBid.price + ") is not high enough. Holding position for now."); - } - return NoAction.NoAction; // no buy or sell conditions are met, take no action - } - //@Override - // public long evaluate(SimpleAlgoState state, long size) { - // return 0; - //} -} diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java index 1c1b3fd7..f821393a 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java @@ -19,6 +19,7 @@ public class MyAlgoBackTest extends AbstractAlgoBackTest { @Override public AlgoLogic createAlgoLogic() { + return new MyAlgoLogic(); } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index 8ddf1949..bf167495 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -1,84 +1,45 @@ package codingblackfemales.gettingstarted; -import codingblackfemales.container.Actioner; -import codingblackfemales.container.AlgoContainer; -import codingblackfemales.container.RunTrigger; -import codingblackfemales.sequencer.DefaultSequencer; -import codingblackfemales.sequencer.Sequencer; -import codingblackfemales.sequencer.consumer.LoggingConsumer; -import codingblackfemales.sequencer.marketdata.SequencerTestCase; -import codingblackfemales.sequencer.net.TestNetwork; -import codingblackfemales.service.MarketDataService; -import codingblackfemales.service.OrderService; -import messages.marketdata.*; -import org.agrona.concurrent.UnsafeBuffer; +import codingblackfemales.algo.AlgoLogic; import org.junit.Test; -import java.nio.ByteBuffer; - import static org.junit.Assert.assertEquals; -public class MyAlgoTest extends SequencerTestCase { - - private final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder(); - private final BookUpdateEncoder encoder = new BookUpdateEncoder(); - private AlgoContainer container; +/** + * This test is designed to check your algo behavior in isolation of the order book. + * + * You can tick in market data messages by creating new versions of createTick() (ex. createTick2, createTickMore etc..) + * + * You should then add behaviour to your algo to respond to that market data by creating or cancelling child orders. + * + * When you are comfortable you algo does what you expect, then you can move on to creating the MyAlgoBackTest. + * + */ +public class MyAlgoTest extends AbstractAlgoTest { @Override - public Sequencer getSequencer() { - final TestNetwork network = new TestNetwork(); - final Sequencer sequencer = new DefaultSequencer(network); - - final RunTrigger runTrigger = new RunTrigger(); - final Actioner actioner = new Actioner(sequencer); - - container = new AlgoContainer(new MarketDataService(runTrigger), new OrderService(runTrigger), runTrigger, actioner); - //set my algo logic - container.setLogic(new MyAlgoLogic()); - - network.addConsumer(new LoggingConsumer()); - network.addConsumer(container.getMarketDataService()); - network.addConsumer(container.getOrderService()); - network.addConsumer(container); - - return sequencer; + public AlgoLogic createAlgoLogic() { + //this adds your algo logic to the container classes + return new MyAlgoLogic(); } - private UnsafeBuffer createSampleMarketDataTick(){ - final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); - final UnsafeBuffer directBuffer = new UnsafeBuffer(byteBuffer); - - //write the encoded output to the direct buffer - encoder.wrapAndApplyHeader(directBuffer, 0, headerEncoder); - - //set the fields to desired values - encoder.venue(Venue.XLON); - encoder.instrumentId(123L); - - encoder.askBookCount(3) - .next().price(100L).size(101L) - .next().price(110L).size(200L) - .next().price(115L).size(5000L); - - encoder.bidBookCount(3) - .next().price(98L).size(100L) - .next().price(95L).size(200L) - .next().price(91L).size(300L); - - encoder.instrumentStatus(InstrumentStatus.CONTINUOUS); - encoder.source(Source.STREAM); + @Test + public void testMaxOrderLimit() throws Exception { + // create a sample market data tick to test the maxOrder Limit of 10 + send(createTick()); - return directBuffer; + // Assert that no more than 10 orders were created + assertEquals(container.getState().getChildOrders().size(), 10); } @Test public void testDispatchThroughSequencer() throws Exception { //create a sample market data tick.... - send(createSampleMarketDataTick()); + send(createTick()); //simple assert to check we had 3 orders created - assertEquals(container.getState().getChildOrders().size(), 3); + assertEquals(container.getState().getChildOrders().size(), 10); } } diff --git a/ui-front-end/package-lock.json b/ui-front-end/package-lock.json new file mode 100644 index 00000000..ff17e6a4 --- /dev/null +++ b/ui-front-end/package-lock.json @@ -0,0 +1,4139 @@ +{ + "name": "algo-prices", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "algo-prices", + "version": "0.0.0", + "dependencies": { + "@vuu-ui/vuu-data-remote": "0.8.81", + "@vuu-ui/vuu-data-test": "0.8.81", + "@vuu-ui/vuu-datatable": "0.8.81", + "@vuu-ui/vuu-icons": "0.8.81", + "@vuu-ui/vuu-layout": "0.8.81", + "@vuu-ui/vuu-shell": "0.8.81", + "@vuu-ui/vuu-table": "0.8.81", + "@vuu-ui/vuu-theme": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", + "@vitejs/plugin-react": "^4.3.1", + "@vuu-ui/vuu-data-types": "0.8.81", + "@vuu-ui/vuu-protocol-types": "0.8.81", + "@vuu-ui/vuu-table-types": "0.8.81", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.7", + "typescript": "^5.2.2", + "vite": "^5.3.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz", + "integrity": "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + }, + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.2.tgz", + "integrity": "sha512-Fq7eWOl1Rcbrfn6jD8FPCj9Auaxdm5nIK5RYOeW7ughnd/rY5AmPg6b+CfsG39ZHdwiwe8lde3q8uR7CF5S0yQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz", + "integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", + "license": "MIT" + }, + "node_modules/@codemirror/view": { + "version": "6.33.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.33.0.tgz", + "integrity": "sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", + "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.24", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.24.tgz", + "integrity": "sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@internationalized/date": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.5.tgz", + "integrity": "sha512-H+CfYvOZ0LTJeeLOqm19E3uj/4YjrmOFtBufDHPfvtI80hFAMqtrp7oCACpe4Cil5l8S0Qu/9dYfZc/5lY8WQQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", + "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", + "integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz", + "integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz", + "integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz", + "integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz", + "integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz", + "integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz", + "integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz", + "integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz", + "integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz", + "integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz", + "integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz", + "integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz", + "integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz", + "integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz", + "integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz", + "integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@salt-ds/core": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@salt-ds/core/-/core-1.27.1.tgz", + "integrity": "sha512-9lAJc/6i3phrWm4Vgag7rvv6jVmmbOchJeDP82/LKxd0WrhVT6RflsB4XMH+3xIUsE2ggu1H2+G+zLFqRIOFDA==", + "license": "Apache-2.0", + "dependencies": { + "@floating-ui/react": "^0.26.5", + "@salt-ds/icons": "^1.11.2", + "@salt-ds/styles": "^0.2.1", + "@salt-ds/window": "^0.1.1", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "@types/react": ">=16.14.0", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@salt-ds/icons": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@salt-ds/icons/-/icons-1.12.1.tgz", + "integrity": "sha512-YSFBXEh+knGLWFVMoYqnQMUgWKyJk8CcsiW6OPCS4f4IEkT8p/CoSkgnK5gMe01m6DtL6Cby++IXCSZbMzQwmw==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/styles": "^0.2.1", + "@salt-ds/window": "^0.1.1", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "@types/react": ">=16.14.0", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@salt-ds/styles": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@salt-ds/styles/-/styles-0.2.1.tgz", + "integrity": "sha512-/GYQLY+ILzGyd2/KndCmoEfLw/t3pcYwihJn3ofe4yd6nhLYHPkvl4TXXzq6NnfD3NHmQWnWh3jQicLsYcvdXg==", + "license": "Apache-2.0", + "peerDependencies": { + "@types/react": ">=16.14.0", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@salt-ds/window": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@salt-ds/window/-/window-0.1.1.tgz", + "integrity": "sha512-DKVRbu7YeHdqFECGhC4W3KOF1eWCyGkFyZUEUNZyK4bvPLK1NI8z5JoxGU70dLVsFgjhk4wj3i1MmAVhdXu4lA==", + "license": "Apache-2.0", + "peerDependencies": { + "@types/react": ">=16.14.0", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@thomaschaplin/cusip-generator": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@thomaschaplin/cusip-generator/-/cusip-generator-1.0.22.tgz", + "integrity": "sha512-162DBgkPGQI0otk/aW8z7XjyYM1fEOUf+p4yiEgJZhKupSWuwSNcsIcAi4fDZvP/XkI/pBF83aXUlYkWFMqA2Q==", + "license": "MIT" + }, + "node_modules/@thomaschaplin/isin-generator": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@thomaschaplin/isin-generator/-/isin-generator-1.0.3.tgz", + "integrity": "sha512-M1vm7MsTdLhOybs21dR1M0/aUAwpM2B7qZvcVMTzxBxnjzwaqCr1EtxMUjecCOoxViIA6A/HO7emlfKUGODBNQ==", + "license": "MIT", + "dependencies": { + "@thomaschaplin/cusip-generator": "^1.0.1" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.7.tgz", + "integrity": "sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", + "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.5", + "@babel/plugin-transform-react-jsx-self": "^7.24.5", + "@babel/plugin-transform-react-jsx-source": "^7.24.1", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/@vuu-ui/vuu-codemirror": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-codemirror/-/vuu-codemirror-0.8.81.tgz", + "integrity": "sha512-ZYTVUxuJjkbzzuue8uXGFrzdgnESS76qFJ8nkhVmBoy1n0wAq2R7HglkqQlHXHFgOMGKRDYM290bcdt+dGVl1g==", + "license": "Apache-2.0", + "dependencies": { + "@codemirror/autocomplete": "^6.4.2", + "@codemirror/commands": "^6.2.1", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.9.3", + "@lezer/common": "1.0.3", + "@lezer/highlight": "^1.1.3", + "@vuu-ui/vuu-utils": "0.8.81" + } + }, + "node_modules/@vuu-ui/vuu-codemirror/node_modules/@lezer/common": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.3.tgz", + "integrity": "sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA==", + "license": "MIT" + }, + "node_modules/@vuu-ui/vuu-data-local": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-data-local/-/vuu-data-local-0.8.81.tgz", + "integrity": "sha512-dw9gfmVv3VJAhke7itSzqcJXrUl99aRlDGC6MtkzwQzIMjcT7EmDOTBEQIlh/Gk7E3TmJDaAqmoTl0xpGrL+WA==", + "license": "Apache-2.0", + "dependencies": { + "@vuu-ui/vuu-filter-parser": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + } + }, + "node_modules/@vuu-ui/vuu-data-react": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-data-react/-/vuu-data-react-0.8.81.tgz", + "integrity": "sha512-lQWQq8gLPQp8iIF0qirH6JlNkEaTONJcPMFQeytLYMN51wcKtum1kwN5I9bQWuI1jJmQIfWbThLQowBafaAB/g==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/core": "1.27.1", + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-data-remote": "0.8.81", + "@vuu-ui/vuu-filter-parser": "0.8.81", + "@vuu-ui/vuu-popups": "0.8.81", + "@vuu-ui/vuu-table": "0.8.81", + "@vuu-ui/vuu-ui-controls": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + }, + "peerDependencies": { + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-data-remote": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-data-remote/-/vuu-data-remote-0.8.81.tgz", + "integrity": "sha512-vr8L9mqi/UGZCtrPAsDP/kUWX91OY6IHeI9T8E6WGlKvWkgXsrh0nOLcR+aKk9TXdmoePmKjXcznbvrDvProzg==", + "license": "Apache-2.0", + "dependencies": { + "@vuu-ui/vuu-filter-parser": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + } + }, + "node_modules/@vuu-ui/vuu-data-test": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-data-test/-/vuu-data-test-0.8.81.tgz", + "integrity": "sha512-eAKp9/f3nGuW3lGPhFYLnOUt9XWVG+ECq/KG4GM0tCVqdQTmj5mSQgnipIRna7GtzmVyvAq4rWlLTtCD+BrOCA==", + "license": "Apache-2.0", + "dependencies": { + "@thomaschaplin/isin-generator": "1.0.3", + "@vuu-ui/vuu-data-local": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + } + }, + "node_modules/@vuu-ui/vuu-data-types": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-data-types/-/vuu-data-types-0.8.81.tgz", + "integrity": "sha512-OjmisSt8BqSAUoHt2ol8kkdIhJ8ww1FnVCnvE3U9kUxLeySkoNn8AkogCUQvhIEk5HYcK/qVFI8hTgSsMr1T4Q==", + "license": "Apache-2.0" + }, + "node_modules/@vuu-ui/vuu-datatable": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-datatable/-/vuu-datatable-0.8.81.tgz", + "integrity": "sha512-QwSIUJqxnSb5Uvq/DT5tq7+l0nxOn0VOOExW90QH6XaUTxwkNaPFqk+UcMntgFeLtSbZCGP+LJZtbIkSIVBtMg==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-data-local": "0.8.81", + "@vuu-ui/vuu-filters": "0.8.81", + "@vuu-ui/vuu-layout": "0.8.81", + "@vuu-ui/vuu-popups": "0.8.81", + "@vuu-ui/vuu-shell": "0.8.81", + "@vuu-ui/vuu-table": "0.8.81", + "@vuu-ui/vuu-table-extras": "0.8.81", + "@vuu-ui/vuu-table-types": "0.8.81", + "@vuu-ui/vuu-ui-controls": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + }, + "peerDependencies": { + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-filter-parser": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-filter-parser/-/vuu-filter-parser-0.8.81.tgz", + "integrity": "sha512-Bc6WSJ3tBXXCmo9F0+2Z3W7uSStYB3q3NTIxw5TrKrpzse46b1Tia8CaWYt95zJx038gvtfR4mnsZjYxG9kO9g==", + "license": "Apache-2.0", + "dependencies": { + "@lezer/common": "^1.0.2", + "@lezer/lr": "^1.3.3", + "@vuu-ui/vuu-data-types": "0.8.81", + "@vuu-ui/vuu-filter-types": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + } + }, + "node_modules/@vuu-ui/vuu-filter-types": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-filter-types/-/vuu-filter-types-0.8.81.tgz", + "integrity": "sha512-hOXido1MKpmfEtwIU60zY/fsITL1HNuAApNy+89QvQOv6k/OxP6owAL43vbJNddA4cmx3OqxDPQz3rzRnPkngQ==", + "license": "Apache-2.0" + }, + "node_modules/@vuu-ui/vuu-filters": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-filters/-/vuu-filters-0.8.81.tgz", + "integrity": "sha512-DLQ2OvU4e90vj69q0XIkNWmfJs+7ahPMfVnVPwO5y8K/XqKKUo0/TY/lC0eIxqpXFj3RDOQJn+XJxzpHlx1GLQ==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/core": "1.27.1", + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-data-react": "0.8.81", + "@vuu-ui/vuu-filter-parser": "0.8.81", + "@vuu-ui/vuu-popups": "0.8.81", + "@vuu-ui/vuu-table": "0.8.81", + "@vuu-ui/vuu-ui-controls": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81", + "uuid": "9.0.0" + }, + "peerDependencies": { + "@internationalized/date": "^3.0.0", + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-icons": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-icons/-/vuu-icons-0.8.81.tgz", + "integrity": "sha512-6dsIRLC3CLg2HXpS99D8n7B/Kzp9BAgYAnDcnVYh+urzKK1XJGnfX4HD1yP4RsQa+YLlFvutxWmHLC/2v6XaVg==", + "license": "Apache-2.0", + "peerDependencies": { + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-layout": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-layout/-/vuu-layout-0.8.81.tgz", + "integrity": "sha512-TnjXo0502gwoKuYgAJW6VBXJIeFrtbchl+O/RoOYZUN3fxwuPwHUh4GHSBVT7Tu+N9c7idwS5i7xmkJxhHT1xw==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/core": "1.27.1", + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-filters": "0.8.81", + "@vuu-ui/vuu-popups": "0.8.81", + "@vuu-ui/vuu-table": "0.8.81", + "@vuu-ui/vuu-ui-controls": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + }, + "peerDependencies": { + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-popups": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-popups/-/vuu-popups-0.8.81.tgz", + "integrity": "sha512-+4S7Je16Ybe3y9uyqCIMR0MwntAhxIwcW0k6UbDTCASK47Nb199RxL8bP8dwYhtAHscSuZPkWz3CatrWYlW9xA==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/core": "1.27.1", + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-data-types": "0.8.81", + "@vuu-ui/vuu-layout": "0.8.81", + "@vuu-ui/vuu-ui-controls": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + }, + "peerDependencies": { + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-protocol-types": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-protocol-types/-/vuu-protocol-types-0.8.81.tgz", + "integrity": "sha512-8ZrIcmR/VZ7rpy1/XF8onp6UnXy4B4DpDGavZdCJ29i5fVx6OVMyBP/2SC5RXXNWVui8DZi3dzZ+kI80LU/0Pg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@vuu-ui/vuu-shell": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-shell/-/vuu-shell-0.8.81.tgz", + "integrity": "sha512-U++v7mH3qpcJqzL7b4ZBJJMocIQe33AirXzhYWtc9AzDhBc/DFpUy6DtHux2Z7KAbMSq0TQHm/Xo8cXnijolMA==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/core": "1.27.1", + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-data-react": "0.8.81", + "@vuu-ui/vuu-data-remote": "0.8.81", + "@vuu-ui/vuu-icons": "0.8.81", + "@vuu-ui/vuu-layout": "0.8.81", + "@vuu-ui/vuu-popups": "0.8.81", + "@vuu-ui/vuu-table": "0.8.81", + "@vuu-ui/vuu-ui-controls": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81", + "html-to-image": "^1.11.11" + }, + "peerDependencies": { + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-table": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-table/-/vuu-table-0.8.81.tgz", + "integrity": "sha512-kcV2HNSskxpwI+CyJsa5HK1n014UWpUv4DP3qAqC92z7MA60+EWQXLKeSRWyNRtC5oyXehRaivQZ6818ZnGrlg==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/core": "1.27.1", + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-data-react": "0.8.81", + "@vuu-ui/vuu-layout": "0.8.81", + "@vuu-ui/vuu-popups": "0.8.81", + "@vuu-ui/vuu-ui-controls": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + }, + "peerDependencies": { + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-table-extras": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-table-extras/-/vuu-table-extras-0.8.81.tgz", + "integrity": "sha512-WFOszTVewB1ZNLd4+bYEmjOZmJ21s2pewCB19/DFiISgd1dJRfzicQgewuS8Y8NGjI5egUh6OrRJ/cpRLkjL/Q==", + "license": "Apache-2.0", + "dependencies": { + "@lezer/lr": "1.3.4", + "@salt-ds/core": "1.27.1", + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-codemirror": "0.8.81", + "@vuu-ui/vuu-data-react": "0.8.81", + "@vuu-ui/vuu-data-types": "0.8.81", + "@vuu-ui/vuu-layout": "0.8.81", + "@vuu-ui/vuu-popups": "0.8.81", + "@vuu-ui/vuu-table": "0.8.81", + "@vuu-ui/vuu-table-types": "0.8.81", + "@vuu-ui/vuu-ui-controls": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + }, + "peerDependencies": { + "@floating-ui/react": "^0.26.5", + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-table-extras/node_modules/@lezer/lr": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.4.tgz", + "integrity": "sha512-7o+e4og/QoC/6btozDPJqnzBhUaD1fMfmvnEKQO1wRRiTse1WxaJ3OMEXZJnkgT6HCcTVOctSoXK9jGJw2oe9g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@vuu-ui/vuu-table-types": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-table-types/-/vuu-table-types-0.8.81.tgz", + "integrity": "sha512-WWi1g4vPop1cL2SgHi/+0WiwvKhPrdqvnGEaqK6FfDjdaxplvG6boj6/zAuNKCeo/VIBC9bkDZSX07TLD/8hgw==", + "license": "Apache-2.0" + }, + "node_modules/@vuu-ui/vuu-theme": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-theme/-/vuu-theme-0.8.81.tgz", + "integrity": "sha512-D8zNBJQxsmf8qb67mq60GD2SCTdPJOqrqwa0vcLJLHx86RgByJhdip9VO1FhU3PzvDt/uItQIKeNukEbviEVPw==", + "license": "Apache-2.0" + }, + "node_modules/@vuu-ui/vuu-ui-controls": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-ui-controls/-/vuu-ui-controls-0.8.81.tgz", + "integrity": "sha512-idEuMmomzIyxOUPf+H+rmZdHLUnAfbXaei9cEwmD3LbcENtxWRrzWk+/QY7DMMryX79z6lg/fA6BZiXqiUJI+w==", + "license": "Apache-2.0", + "dependencies": { + "@floating-ui/react": "0.26.5", + "@salt-ds/core": "1.27.1", + "@salt-ds/icons": "1.9.1", + "@salt-ds/styles": "0.2.1", + "@salt-ds/window": "0.1.1", + "@vuu-ui/vuu-layout": "0.8.81", + "@vuu-ui/vuu-popups": "0.8.81", + "@vuu-ui/vuu-table": "0.8.81", + "@vuu-ui/vuu-utils": "0.8.81" + }, + "peerDependencies": { + "@internationalized/date": "^3.0.0", + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/@vuu-ui/vuu-ui-controls/node_modules/@floating-ui/react": { + "version": "0.26.5", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.5.tgz", + "integrity": "sha512-LJeSQa+yOwV0Tdpc/C3Vr92QMrwRqRMTk4yOwsRJKc57x3Lcw317GE0EV+ECM7+Z89yEAPBe7nzbDEWfkWCrBA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.5", + "@floating-ui/utils": "^0.2.0", + "tabbable": "^6.0.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@vuu-ui/vuu-ui-controls/node_modules/@salt-ds/icons": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@salt-ds/icons/-/icons-1.9.1.tgz", + "integrity": "sha512-ck1tL+vmBFUziFKB973pVLcLLvZIh5N1gDwS1dwm9DRKefXSS0ZtaB4KjrYvw/7LhVG8rqrcwtCSmvqo/3M4rg==", + "license": "Apache-2.0", + "dependencies": { + "@salt-ds/styles": "^0.2.1", + "@salt-ds/window": "^0.1.1", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "@types/react": ">=16.14.0", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@vuu-ui/vuu-utils": { + "version": "0.8.81", + "resolved": "https://registry.npmjs.org/@vuu-ui/vuu-utils/-/vuu-utils-0.8.81.tgz", + "integrity": "sha512-qMujVVNSlYJtKvnufxOa6RgWEua8X/qeJs1UXc4sbQs8tlwfmQhlcI0Rc5RGjWojCmaL1yhl5YOP12YxntXTtA==", + "license": "Apache-2.0", + "peerDependencies": { + "@internationalized/date": "^3.0.0", + "@vuu-ui/vuu-filter-parser": "0.8.81", + "clsx": "^2.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001660", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz", + "integrity": "sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.12.tgz", + "integrity": "sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/html-to-image": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz", + "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz", + "integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.3", + "@rollup/rollup-android-arm64": "4.21.3", + "@rollup/rollup-darwin-arm64": "4.21.3", + "@rollup/rollup-darwin-x64": "4.21.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.3", + "@rollup/rollup-linux-arm-musleabihf": "4.21.3", + "@rollup/rollup-linux-arm64-gnu": "4.21.3", + "@rollup/rollup-linux-arm64-musl": "4.21.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.3", + "@rollup/rollup-linux-riscv64-gnu": "4.21.3", + "@rollup/rollup-linux-s390x-gnu": "4.21.3", + "@rollup/rollup-linux-x64-gnu": "4.21.3", + "@rollup/rollup-linux-x64-musl": "4.21.3", + "@rollup/rollup-win32-arm64-msvc": "4.21.3", + "@rollup/rollup-win32-ia32-msvc": "4.21.3", + "@rollup/rollup-win32-x64-msvc": "4.21.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD", + "peer": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", + "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx b/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx index 6fb946a1..11cce0a0 100644 --- a/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx +++ b/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx @@ -1,25 +1,34 @@ -import { Placeholder } from "../placeholder"; -//import { useMarketDepthData } from "./useMarketDepthData"; -//import { schemas } from "../../data/algo-schemas"; +// import { Placeholder } from "../placeholder"; +import React from 'react'; +// import { useMarketDepthData } from "./useMarketDepthData"; +// import { schemas } from "../../data/algo-schemas"; -// prettier-ignore -// const testData: MarketDepthRow[] = [ -// { symbolLevel:"1230", level: 0, bid: 1000, bidQuantity: 500, offer: 1010, offerQuantity: 700 }, -// { symbolLevel:"1231", level: 1, bid: 990, bidQuantity: 700, offer: 1012, offerQuantity: 400 }, -// { symbolLevel:"1232", level: 2, bid: 985, bidQuantity: 1200, offer: 1013, offerQuantity: 800 }, -// { symbolLevel:"1233", level: 3, bid: 984, bidQuantity: 1300, offer: 1018, offerQuantity: 750 }, -// { symbolLevel:"1234", level: 4, bid: 970, bidQuantity: 800, offer: 1021, offerQuantity: 900 }, -// { symbolLevel:"1235", level: 5, bid: 969, bidQuantity: 700, offer: 1026, offerQuantity: 1500 }, -// { symbolLevel:"1236", level: 6, bid: 950, bidQuantity: 750, offer: 1027, offerQuantity: 1500 }, -// { symbolLevel:"1237", level: 7, bid: 945, bidQuantity: 900, offer: 1029, offerQuantity: 2000 }, -// { symbolLevel:"1238", level: 8, bid: 943, bidQuantity: 500, offer: 1031, offerQuantity: 500 }, -// { symbolLevel:"1239", level: 9, bid: 940, bidQuantity: 200, offer: 1024, offerQuantity: 800 }, -// ]; + + +// Example test data +const testData: MarketDepthRow[] = [ +{ symbolLevel:"1230", level: 0, bid: 1000, bidQuantity: 500, offer: 1010, offerQuantity: 700 }, +{ symbolLevel:"1231", level: 1, bid: 990, bidQuantity: 700, offer: 1012, offerQuantity: 400 }, +{ symbolLevel:"1232", level: 2, bid: 985, bidQuantity: 1200, offer: 1013, offerQuantity: 800 }, +{ symbolLevel:"1233", level: 3, bid: 984, bidQuantity: 1300, offer: 1018, offerQuantity: 750 }, +{ symbolLevel:"1234", level: 4, bid: 970, bidQuantity: 800, offer: 1021, offerQuantity: 900 }, +{ symbolLevel:"1235", level: 5, bid: 969, bidQuantity: 700, offer: 1026, offerQuantity: 1500 }, +{ symbolLevel:"1236", level: 6, bid: 950, bidQuantity: 750, offer: 1027, offerQuantity: 1500 }, +{ symbolLevel:"1237", level: 7, bid: 945, bidQuantity: 900, offer: 1029, offerQuantity: 2000 }, +{ symbolLevel:"1238", level: 8, bid: 943, bidQuantity: 500, offer: 1031, offerQuantity: 500 }, +{ symbolLevel:"1239", level: 9, bid: 940, bidQuantity: 200, offer: 1024, offerQuantity: 800 }, + ]; /** * TODO */ export const MarketDepthFeature = () => { - // const data = useMarketDepthData(schemas.prices); - return ; + // instead of fetching real data (useMarketDepthData) we will use the testData for now + const data = testData; + + // fetching the real market depth data using the hook +// const data = useMarketDepthData(schemas.prices); + + return ; + }; diff --git a/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx b/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx new file mode 100644 index 00000000..e69de29b diff --git a/ui-front-end/src/components/market-depth/PriceCell.tsx b/ui-front-end/src/components/market-depth/PriceCell.tsx new file mode 100644 index 00000000..4d8ff0cc --- /dev/null +++ b/ui-front-end/src/components/market-depth/PriceCell.tsx @@ -0,0 +1,21 @@ +import React, { useEffect, useState } from 'react'; // importing react library +// UseState hook allows component to remember values between renders (current price or prev price) +// UseEffect hook helps perform actions after a component has rendered (certain values have changed) + +interface PriceCellProps { + price: number; +} + +// PriceCell component will display the price in the table, with a logic implemented to show if the price +// is going up or down compared to the previous value. + +// PriceCell will: +// track the price +// compare prices +// show a direction arrow: +// update the previous price: + +// STEPS: +// import the needed hooks and libraries +// need to define the structure for my price component props +// setting up a logic \ No newline at end of file diff --git a/ui-front-end/src/components/market-depth/QuantityCell.tsx b/ui-front-end/src/components/market-depth/QuantityCell.tsx new file mode 100644 index 00000000..92b3d875 --- /dev/null +++ b/ui-front-end/src/components/market-depth/QuantityCell.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +interface QuantityCellProps { + quantity: number; +} + +// QuantityCell recieves the quantity value as a prop and decides what to display or do with that value. + +// logic to implement: +// basic function : show the quantity value in the cell. + +// conditional formatting: +// change how the quantity will be showed based on it's value. +// so if the quantity if high, i could highlight in green +// if the quantity is low, i could highlight in red. + + diff --git a/ui-front-end/src/components/market-depth/useMarketDepthData.ts b/ui-front-end/src/components/market-depth/useMarketDepthData.ts index ee55cc23..39feb8e6 100644 --- a/ui-front-end/src/components/market-depth/useMarketDepthData.ts +++ b/ui-front-end/src/components/market-depth/useMarketDepthData.ts @@ -14,10 +14,12 @@ export type MarketDepthRow = { }; const byLevel = ( - { level: levelA }: MarketDepthRow, + { level: levelA }: MarketDepthRow, { level: levelB }: MarketDepthRow ) => (levelA > levelB ? 1 : -1); + + class MarketPriceLevelStore { #data: Map = new Map(); #columnMap: ColumnMap; @@ -38,6 +40,7 @@ class MarketPriceLevelStore { bid: vuuRow[this.#columnMap.bid] as number, offer: vuuRow[this.#columnMap.offer] as number, offerQuantity: vuuRow[this.#columnMap.offerQuantity] as number, + }; }; From 7b231982d6b3bbe7a1a5884a83f897d970aff16d Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Wed, 2 Oct 2024 11:53:28 +0100 Subject: [PATCH 04/13] revised version - all actions are now working --- .../gettingstarted/MyAlgoLogic.java | 61 +++++++++++-------- .../gettingstarted/MyAlgoTest.java | 55 ++++++++++++++--- 2 files changed, 81 insertions(+), 35 deletions(-) diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index 1078ecdd..0a7b926b 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -1,5 +1,6 @@ package codingblackfemales.gettingstarted; + import codingblackfemales.action.Action; import codingblackfemales.action.CancelChildOrder; import codingblackfemales.action.CreateChildOrder; @@ -14,19 +15,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.List; public class MyAlgoLogic implements AlgoLogic { - private static final Logger logger = LoggerFactory.getLogger(MyAlgoLogic.class); // constraints for the price limit private static final double priceLimit = 99.00; - private static final double sellLimit = 105.00; // sell when the price reaches £105 + private static final double sellLimit = 99.00; // sell when the price reaches £105 private static final int maxOrders = 10; // max allowed orders in the orderbook private static final int quantity = 50; // quantity for each order + private static boolean clearActiveOrders = false; // when enabled will clear all active orders + private static boolean shouldBuy = true; // when enabled, place buy orders, else sell orders + @Override public Action evaluate(SimpleAlgoState state) { logger.info("[MYALGO] In Algo Logic...."); @@ -44,12 +46,6 @@ public Action evaluate(SimpleAlgoState state) { int activeOrdersCount = activeOrders.size(); logger.info("[MYALGO] Active child orders: " + activeOrdersCount); - //make sure we have an exit condition - if (totalOrderCount >= maxOrders) { - logger.info("[MYALGO] Maximum order count reached. No more buy orders will be placed."); - return NoAction.NoAction; - } - // Retrieve the best bid and ask prices BidLevel bestBid = state.getBidAt(0); // the highest bid price - the price buyers are willing to pay AskLevel bestAsk = state.getAskAt(0); // the lowest ask price - the price sellers are asking for @@ -58,35 +54,47 @@ public Action evaluate(SimpleAlgoState state) { long bidPrice = bestBid.price; // the highest bid price long askPrice = bestAsk.price; // the lowest ask price + // cancel all buy orders if the price has exceeded the priceLimit and start selling + if (clearActiveOrders && activeOrdersCount > 0) { + // go over order book and cancel when a cancellable order is found + for (ChildOrder order : activeOrders) { + // cancel order if it's above the price limit + if (bidPrice >= priceLimit) { + logger.info("[MYALGO] Price exceeded limit. Cancelling all buy orders"); + if (order != null) { + logger.info("[ADDCANCELALGO] Cancelling order:" + order); + return new CancelChildOrder(order); + } + } + } + } else if (clearActiveOrders && activeOrdersCount == 0) { + // Stop cancelling orders and now place sell orders + clearActiveOrders = false; + shouldBuy = false; + } + // Place buy orders until you reach 10 - // bidPrice < priceLimit && - not working - if (activeOrdersCount < maxOrders) { + if (shouldBuy && activeOrdersCount < maxOrders) { logger.info("[MYALGO] The current price is below the price limit. Placing buy order"); + if (activeOrdersCount + 1 >= maxOrders) { + clearActiveOrders = true; + } return new CreateChildOrder(Side.BUY, quantity, bidPrice); } - // cancel all buy orders if the price has exceeded the priceLimit and start selling - // bidPrice >= priceLimit && - else if (bidPrice >= priceLimit && activeOrdersCount > maxOrders) { - logger.info("[MYALGO] Price exceeded limit. Cancelling all buy orders"); - final var option = activeOrders.stream().findFirst(); - if (option.isPresent()) { - var childOrder = option.get(); - logger.info("[ADDCANCELALGO] Cancelling order:" + childOrder); - return new CancelChildOrder(childOrder); - } else{ + // sell if the price exceeds the sell limit + else if (shouldBuy == false && bidPrice > sellLimit) { + // FOR TESTING + if (activeOrdersCount >= maxOrders) { + logger.info("[MYALGO] Max orders created. End Algo"); return NoAction.NoAction; } - } - - // sell if the price exceeds the sell limit - else if (bidPrice > sellLimit) { logger.info("[MYALGO] The price exceeds the sell limit. Placing sell order"); return new CreateChildOrder(Side.SELL, quantity, bidPrice); } // if no conditions are met else { - logger.info("[MYALGO] No condtions met. Take no action"); + logger.info("[MYALGO] No conditions met. Take no action"); return NoAction.NoAction; } } @@ -97,4 +105,3 @@ else if (bidPrice > sellLimit) { - diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index bf167495..3a6d9a9a 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -1,9 +1,14 @@ package codingblackfemales.gettingstarted; +import codingblackfemales.action.CancelChildOrder; +import codingblackfemales.action.NoAction; +import codingblackfemales.action.Action; import codingblackfemales.algo.AlgoLogic; +import codingblackfemales.sotw.marketdata.BidLevel; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** @@ -25,21 +30,55 @@ public AlgoLogic createAlgoLogic() { } @Test - public void testMaxOrderLimit() throws Exception { - // create a sample market data tick to test the maxOrder Limit of 10 - send(createTick()); + public void testBidPriceWithinLimit() throws Exception { + //Simulate a tick where the price exceeds the limit + send(createTick()); // does this tick update the market price? + + // Retrieve the updated bid and ask levels + BidLevel bestBid = container.getState().getBidAt(0); // Highest bid price - // Assert that no more than 10 orders were created - assertEquals(container.getState().getChildOrders().size(), 10); + // Check if the bid price now exceeds the price limit + long bidPrice = bestBid.price; + assertTrue(bidPrice >= 100); // Assuming price limit is 99.00 } @Test - public void testDispatchThroughSequencer() throws Exception { + public void testEvaluateNoActionWhenNoConditionsAreMet() throws Exception { + send(createTick()); + Action action = createAlgoLogic().evaluate(container.getState()); + + //simple assert to check if no conditions are met, there's no action returned + assertEquals(NoAction.NoAction, action); + } + + @Test + public void testMaxActiveBuyOrdersLimit() throws Exception { //create a sample market data tick.... send(createTick()); - //simple assert to check we had 3 orders created - assertEquals(container.getState().getChildOrders().size(), 10); + //simple assert to check we only create 10 active orders (maxOrders = 10) + assertEquals(container.getState().getActiveChildOrders().size(), 10); + } + + @Test + public void testCancelBuyOrdersWhenPriceExceedsLimit() throws Exception { + MyAlgoLogic algo = new MyAlgoLogic(); + + // Step 1: Send ticks to simulate placing 10 buy orders below the price limit + send(createTick()); + + // Assert that 10 buy orders were placed + assertEquals(10, container.getState().getActiveChildOrders().size()); + + // Step 2: Simulate a tick where the price exceeds the limit + send(createTick()); + + // Evaluate the algo logic after the price exceeds the limit + Action action = algo.evaluate(container.getState()); + + // Assert that the algo responds by cancelling a buy order + assertEquals(true, action instanceof CancelChildOrder); + } } From 640884cd78891dd5f253ce8e5db4b0a558b15e90 Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Tue, 8 Oct 2024 20:21:29 +0100 Subject: [PATCH 05/13] algo is buying and selling, but not cancel action yet --- .../algo/AddCancelAlgoLogic.java | 1 + .../gettingstarted/MyAlgoLogic.java | 83 ++++++++++++------- .../gettingstarted/MyAlgoBackTest.java | 9 +- .../gettingstarted/MyAlgoTest.java | 44 +++++----- 4 files changed, 82 insertions(+), 55 deletions(-) diff --git a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java index 306fd779..2d95ce52 100644 --- a/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java +++ b/algo-exercise/algo/src/main/java/codingblackfemales/algo/AddCancelAlgoLogic.java @@ -37,6 +37,7 @@ public Action evaluate(SimpleAlgoState state) { final var option = activeOrders.stream().findFirst(); + if (option.isPresent()) { var childOrder = option.get(); logger.info("[ADDCANCELALGO] Cancelling order:" + childOrder); diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index 0a7b926b..eb2982ad 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -21,13 +21,15 @@ public class MyAlgoLogic implements AlgoLogic { private static final Logger logger = LoggerFactory.getLogger(MyAlgoLogic.class); // constraints for the price limit - private static final double priceLimit = 99.00; - private static final double sellLimit = 99.00; // sell when the price reaches £105 + private static final double priceLimit = 98.00; + private static final double sellLimit = 100.00; // sell when the price reaches £105 + private static final int maxOrders = 10; // max allowed orders in the orderbook private static final int quantity = 50; // quantity for each order private static boolean clearActiveOrders = false; // when enabled will clear all active orders - private static boolean shouldBuy = true; // when enabled, place buy orders, else sell orders + private static boolean shouldBuy = false; // when enabled, place buy orders + private static boolean shouldSell = false; // when enabled, sell orders @Override public Action evaluate(SimpleAlgoState state) { @@ -49,54 +51,69 @@ public Action evaluate(SimpleAlgoState state) { // Retrieve the best bid and ask prices BidLevel bestBid = state.getBidAt(0); // the highest bid price - the price buyers are willing to pay AskLevel bestAsk = state.getAskAt(0); // the lowest ask price - the price sellers are asking for + logger.info("[MYALGO] Best Bid prices" + bestBid); + logger.info("[MYALGO] Best Ask prices" + bestAsk); // Actual price long bidPrice = bestBid.price; // the highest bid price long askPrice = bestAsk.price; // the lowest ask price - // cancel all buy orders if the price has exceeded the priceLimit and start selling - if (clearActiveOrders && activeOrdersCount > 0) { - // go over order book and cancel when a cancellable order is found - for (ChildOrder order : activeOrders) { - // cancel order if it's above the price limit - if (bidPrice >= priceLimit) { - logger.info("[MYALGO] Price exceeded limit. Cancelling all buy orders"); - if (order != null) { - logger.info("[ADDCANCELALGO] Cancelling order:" + order); - return new CancelChildOrder(order); - } - } - } - } else if (clearActiveOrders && activeOrdersCount == 0) { - // Stop cancelling orders and now place sell orders - clearActiveOrders = false; - shouldBuy = false; - } - // Place buy orders until you reach 10 - if (shouldBuy && activeOrdersCount < maxOrders) { +// if (clearActiveOrders == true && activeOrdersCount > 0) { + // go over order book and cancel when a cancellable order is found +// for (ChildOrder order : activeOrders) { +// // cancel order if it's above the price limit +// if (bidPrice >= priceLimit) { +// logger.info("[MYALGO] Price exceeded limit. Cancelling all buy orders"); +// if (order != null) { +// logger.info("[ADDCANCELALGO] Cancelling order:" + order); +// return new CancelChildOrder(order); +// } +// } +// } +// } else if (clearActiveOrders == true && activeOrdersCount == 0) { +// // Stop cancelling orders and now place sell orders +// clearActiveOrders = false; +// shouldBuy = false; +// } + + // Buy Logic + if (bidPrice >= sellLimit) { + shouldBuy = true; + logger.info("[MYALGO] The current price is greater than sell limit. Placing buy order"); + } + if (askPrice >= priceLimit) { + shouldSell = true; + logger.info("[MYALGO] The current ask price is greater the price limit. Placing sell order"); + } + if (shouldBuy == true && activeOrdersCount < maxOrders) { logger.info("[MYALGO] The current price is below the price limit. Placing buy order"); if (activeOrdersCount + 1 >= maxOrders) { clearActiveOrders = true; } return new CreateChildOrder(Side.BUY, quantity, bidPrice); } - // sell if the price exceeds the sell limit - else if (shouldBuy == false && bidPrice > sellLimit) { + if (shouldSell == true && activeOrdersCount < maxOrders) { // FOR TESTING - if (activeOrdersCount >= maxOrders) { - logger.info("[MYALGO] Max orders created. End Algo"); - return NoAction.NoAction; + logger.info("[MYALGO] The current price is below the price limit. Placing sell order"); +// logger.info("[MYALGO]. Placing buy order"); + if (activeOrdersCount + 1 >= maxOrders) { + clearActiveOrders = true; } - logger.info("[MYALGO] The price exceeds the sell limit. Placing sell order"); return new CreateChildOrder(Side.SELL, quantity, bidPrice); } - // if no conditions are met - else { +// if (activeOrdersCount >= maxOrders) { +// logger.info("[MYALGO] Max orders created. End Algo"); +// return NoAction.NoAction; +// } +// logger.info("[MYALGO] The price exceeds the sell limit. Placing sell order"); +// return new CreateChildOrder(Side.SELL, quantity, bidPrice); +// } +// else { logger.info("[MYALGO] No conditions met. Take no action"); return NoAction.NoAction; - } +// } } } @@ -105,3 +122,5 @@ else if (shouldBuy == false && bidPrice > sellLimit) { + + diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java index f821393a..424abc2d 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java @@ -2,6 +2,9 @@ import codingblackfemales.algo.AlgoLogic; import org.junit.Test; +import codingblackfemales.sotw.ChildOrder; + +import static org.junit.Assert.assertEquals; /** * This test plugs together all of the infrastructure, including the order book (which you can trade against) @@ -29,7 +32,7 @@ public void testExampleBackTest() throws Exception { send(createTick()); //ADD asserts when you have implemented your algo logic - //assertEquals(container.getState().getChildOrders().size(), 3); + assertEquals(container.getState().getChildOrders().size(), 10); //when: market data moves towards us send(createTick2()); @@ -38,9 +41,9 @@ public void testExampleBackTest() throws Exception { var state = container.getState(); //Check things like filled quantity, cancelled order count etc.... - //long filledQuantity = state.getChildOrders().stream().map(ChildOrder::getFilledQuantity).reduce(Long::sum).get(); + long filledQuantity = state.getChildOrders().stream().map(ChildOrder::getFilledQuantity).reduce(Long::sum).get(); //and: check that our algo state was updated to reflect our fills when the market data - //assertEquals(225, filledQuantity); + assertEquals(150, filledQuantity); } } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index 3a6d9a9a..6f99d71b 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -39,13 +39,15 @@ public void testBidPriceWithinLimit() throws Exception { // Check if the bid price now exceeds the price limit long bidPrice = bestBid.price; - assertTrue(bidPrice >= 100); // Assuming price limit is 99.00 + + assertTrue(bidPrice >= 91); // Assuming price limit is 91.00 } @Test public void testEvaluateNoActionWhenNoConditionsAreMet() throws Exception { send(createTick()); +// Action action = new MyAlgoLogic().evaluate(container.getState()); Action action = createAlgoLogic().evaluate(container.getState()); //simple assert to check if no conditions are met, there's no action returned @@ -61,24 +63,26 @@ public void testMaxActiveBuyOrdersLimit() throws Exception { assertEquals(container.getState().getActiveChildOrders().size(), 10); } - @Test - public void testCancelBuyOrdersWhenPriceExceedsLimit() throws Exception { - MyAlgoLogic algo = new MyAlgoLogic(); - - // Step 1: Send ticks to simulate placing 10 buy orders below the price limit - send(createTick()); - - // Assert that 10 buy orders were placed - assertEquals(10, container.getState().getActiveChildOrders().size()); +// @Test +// public void testCancelBuyOrdersWhenPriceExceedsLimit() throws Exception { +// //Simulate placing 10 buy orders below the price limit +// send(createTick()); +// assertEquals(10, container.getState().getActiveChildOrders().size()); +// +// //Simulate a tick where the price exceeds the limit +// send(createTick()); +// +// // Evaluate the algo logic after the price exceeds the limit +// Action action = createAlgoLogic().evaluate(container.getState()); +// +// // Assert that the algo responds by cancelling a buy order +// assertEquals(true, action instanceof CancelChildOrder); + +// // Simulate the cancellation of a buy order +// send(createTick()); +// +// //Assert that the number of active child orders has decreased by 1 after cancellation +// assertEquals(9, container.getState().getActiveChildOrders().size()); +// } - // Step 2: Simulate a tick where the price exceeds the limit - send(createTick()); - - // Evaluate the algo logic after the price exceeds the limit - Action action = algo.evaluate(container.getState()); - - // Assert that the algo responds by cancelling a buy order - assertEquals(true, action instanceof CancelChildOrder); - - } } From 220616b0394e43bf7e94dc3f159bfe45a0d66c69 Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Fri, 11 Oct 2024 03:47:49 +0100 Subject: [PATCH 06/13] all actions are working, but still need to update the sell transition --- .../gettingstarted/MyAlgoLogic.java | 125 +++++++++++------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index eb2982ad..403f39c6 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -23,13 +23,11 @@ public class MyAlgoLogic implements AlgoLogic { // constraints for the price limit private static final double priceLimit = 98.00; private static final double sellLimit = 100.00; // sell when the price reaches £105 - private static final int maxOrders = 10; // max allowed orders in the orderbook private static final int quantity = 50; // quantity for each order - private static boolean clearActiveOrders = false; // when enabled will clear all active orders - private static boolean shouldBuy = false; // when enabled, place buy orders - private static boolean shouldSell = false; // when enabled, sell orders + private static boolean shouldBuy = true; // when enabled, place buy orders + private static boolean shouldSell = false; // when enabled, place sell orders @Override public Action evaluate(SimpleAlgoState state) { @@ -59,61 +57,91 @@ public Action evaluate(SimpleAlgoState state) { long askPrice = bestAsk.price; // the lowest ask price -// if (clearActiveOrders == true && activeOrdersCount > 0) { - // go over order book and cancel when a cancellable order is found -// for (ChildOrder order : activeOrders) { -// // cancel order if it's above the price limit -// if (bidPrice >= priceLimit) { -// logger.info("[MYALGO] Price exceeded limit. Cancelling all buy orders"); -// if (order != null) { -// logger.info("[ADDCANCELALGO] Cancelling order:" + order); -// return new CancelChildOrder(order); -// } -// } -// } -// } else if (clearActiveOrders == true && activeOrdersCount == 0) { -// // Stop cancelling orders and now place sell orders -// clearActiveOrders = false; -// shouldBuy = false; -// } - - // Buy Logic - if (bidPrice >= sellLimit) { + // ---- Setting shouldBuy Logic ---- // + // Set shouldBuy flag if the ask price is below or equal to the priceLimit + if (askPrice <= priceLimit && !shouldSell && !clearActiveOrders) { shouldBuy = true; - logger.info("[MYALGO] The current price is greater than sell limit. Placing buy order"); + shouldSell = false; // Ensure mutual exclusivity + logger.info("[MYALGO] Setting shouldBuy to true. Ask price is below or equal to the price limit."); + } else { + shouldBuy = false; } - if (askPrice >= priceLimit) { + + // ---- Setting shouldSell Logic ---- // + // Set shouldSell flag if the bid price is greater than or equal to the sellLimit + if (bidPrice >= sellLimit && !shouldBuy && !clearActiveOrders) { shouldSell = true; - logger.info("[MYALGO] The current ask price is greater the price limit. Placing sell order"); + shouldBuy = false; // Ensure mutual exclusivity + logger.info("[MYALGO] Setting shouldSell to true. Bid price is greater than or equal to the sell limit."); + } else { + shouldSell = false; } - if (shouldBuy == true && activeOrdersCount < maxOrders) { - logger.info("[MYALGO] The current price is below the price limit. Placing buy order"); + + // ---- Cancel Logic inside Buy ---- // + if (shouldBuy && activeOrdersCount < maxOrders) { + // Check if any buy orders need to be canceled + for (ChildOrder order : activeOrders) { + if (order.getSide() == Side.BUY && askPrice > priceLimit) { + logger.info("[MYALGO] Ask price exceeded limit. Cancelling buy order: " + order); + return new CancelChildOrder(order); // Cancel one order at a time + } + } + + // If no cancellations, proceed with placing the buy order +// if (askPrice <= priceLimit) { + logger.info("[MYALGO] The current ask price is below or equal to the price limit. Placing buy order."); if (activeOrdersCount + 1 >= maxOrders) { - clearActiveOrders = true; + logger.info("[MYALGO] Max orders reached. Switching to cancellation mode."); + clearActiveOrders = true; // Start clearing orders when max is reached + shouldBuy = false; // Stop buying once max orders reached } - return new CreateChildOrder(Side.BUY, quantity, bidPrice); + return new CreateChildOrder(Side.BUY, quantity, askPrice); // Buy at ask price +// } } - // sell if the price exceeds the sell limit - if (shouldSell == true && activeOrdersCount < maxOrders) { - // FOR TESTING - logger.info("[MYALGO] The current price is below the price limit. Placing sell order"); -// logger.info("[MYALGO]. Placing buy order"); + // ---- Cancel Logic inside Sell ---- // + if (shouldSell && activeOrdersCount < maxOrders) { + // Check if any sell orders need to be canceled + for (ChildOrder order : activeOrders) { + if (order.getSide() == Side.SELL && bidPrice < sellLimit) { + logger.info("[MYALGO] Bid price fell below sell limit. Cancelling sell order: " + order); + return new CancelChildOrder(order); // Cancel one order at a time + } + } + // If no cancellations, proceed with placing the sell order +// if (bidPrice >= sellLimit) { + logger.info("[MYALGO] The current bid price is greater than or equal to the sell limit. Placing sell order."); if (activeOrdersCount + 1 >= maxOrders) { - clearActiveOrders = true; + logger.info("[MYALGO] Max orders reached. Switching to cancellation mode."); + clearActiveOrders = true; // Start clearing orders when max is reached + shouldSell = false; // Stop selling once max orders reached } - return new CreateChildOrder(Side.SELL, quantity, bidPrice); - } -// if (activeOrdersCount >= maxOrders) { -// logger.info("[MYALGO] Max orders created. End Algo"); -// return NoAction.NoAction; + return new CreateChildOrder(Side.SELL, quantity, bidPrice); // Sell at bid price // } -// logger.info("[MYALGO] The price exceeds the sell limit. Placing sell order"); -// return new CreateChildOrder(Side.SELL, quantity, bidPrice); -// } -// else { + } + // ---- Handle Order Cancellations ---- // + if (clearActiveOrders && activeOrdersCount > 0) { + logger.info("[MYALGO] Clearing active orders."); + for (ChildOrder order : activeOrders) { + if (order != null) { + logger.info("[MYALGO] Cancelling order: " + order); + return new CancelChildOrder(order); // Cancel one order at a time + } + } + } else if (clearActiveOrders && activeOrdersCount == 0) { + logger.info("[MYALGO] All orders cancelled. Continuing with the opposite action."); + clearActiveOrders = false; + // Immediately transition to the opposite mode after all orders are cancelled + if (shouldBuy == false) { + shouldSell = true; // Start selling + } else if (shouldSell == false) { + shouldBuy = true; // Start buying + } + } + if (!shouldBuy && !shouldSell) { logger.info("[MYALGO] No conditions met. Take no action"); return NoAction.NoAction; -// } + } + return NoAction.NoAction; } } @@ -124,3 +152,6 @@ public Action evaluate(SimpleAlgoState state) { + + + From a65a2006874bcc3252487fed9028a8cd8f4ae177 Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Mon, 14 Oct 2024 12:14:11 +0100 Subject: [PATCH 07/13] all actions are working, tests are passing --- .../gettingstarted/MyAlgoLogic.java | 82 ++++--------------- .../gettingstarted/MyAlgoBackTest.java | 4 +- .../gettingstarted/MyAlgoTest.java | 23 ------ 3 files changed, 16 insertions(+), 93 deletions(-) diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index 403f39c6..fbd6eb03 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -21,13 +21,12 @@ public class MyAlgoLogic implements AlgoLogic { private static final Logger logger = LoggerFactory.getLogger(MyAlgoLogic.class); // constraints for the price limit - private static final double priceLimit = 98.00; - private static final double sellLimit = 100.00; // sell when the price reaches £105 + private static final long priceLimit = 115; + private static final long sellLimit = 91; // sell when the price reaches £105 private static final int maxOrders = 10; // max allowed orders in the orderbook private static final int quantity = 50; // quantity for each order private static boolean clearActiveOrders = false; // when enabled will clear all active orders private static boolean shouldBuy = true; // when enabled, place buy orders - private static boolean shouldSell = false; // when enabled, place sell orders @Override public Action evaluate(SimpleAlgoState state) { @@ -56,39 +55,8 @@ public Action evaluate(SimpleAlgoState state) { long bidPrice = bestBid.price; // the highest bid price long askPrice = bestAsk.price; // the lowest ask price - - // ---- Setting shouldBuy Logic ---- // - // Set shouldBuy flag if the ask price is below or equal to the priceLimit - if (askPrice <= priceLimit && !shouldSell && !clearActiveOrders) { - shouldBuy = true; - shouldSell = false; // Ensure mutual exclusivity - logger.info("[MYALGO] Setting shouldBuy to true. Ask price is below or equal to the price limit."); - } else { - shouldBuy = false; - } - - // ---- Setting shouldSell Logic ---- // - // Set shouldSell flag if the bid price is greater than or equal to the sellLimit - if (bidPrice >= sellLimit && !shouldBuy && !clearActiveOrders) { - shouldSell = true; - shouldBuy = false; // Ensure mutual exclusivity - logger.info("[MYALGO] Setting shouldSell to true. Bid price is greater than or equal to the sell limit."); - } else { - shouldSell = false; - } - - // ---- Cancel Logic inside Buy ---- // - if (shouldBuy && activeOrdersCount < maxOrders) { - // Check if any buy orders need to be canceled - for (ChildOrder order : activeOrders) { - if (order.getSide() == Side.BUY && askPrice > priceLimit) { - logger.info("[MYALGO] Ask price exceeded limit. Cancelling buy order: " + order); - return new CancelChildOrder(order); // Cancel one order at a time - } - } - - // If no cancellations, proceed with placing the buy order -// if (askPrice <= priceLimit) { + // ---- Buy Logic ---- // + if (shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && askPrice <= priceLimit) { logger.info("[MYALGO] The current ask price is below or equal to the price limit. Placing buy order."); if (activeOrdersCount + 1 >= maxOrders) { logger.info("[MYALGO] Max orders reached. Switching to cancellation mode."); @@ -96,51 +64,30 @@ public Action evaluate(SimpleAlgoState state) { shouldBuy = false; // Stop buying once max orders reached } return new CreateChildOrder(Side.BUY, quantity, askPrice); // Buy at ask price -// } } - // ---- Cancel Logic inside Sell ---- // - if (shouldSell && activeOrdersCount < maxOrders) { - // Check if any sell orders need to be canceled - for (ChildOrder order : activeOrders) { - if (order.getSide() == Side.SELL && bidPrice < sellLimit) { - logger.info("[MYALGO] Bid price fell below sell limit. Cancelling sell order: " + order); - return new CancelChildOrder(order); // Cancel one order at a time - } - } - // If no cancellations, proceed with placing the sell order -// if (bidPrice >= sellLimit) { + // ---- Sell Logic ---- // + if (!shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && bidPrice >= sellLimit) { logger.info("[MYALGO] The current bid price is greater than or equal to the sell limit. Placing sell order."); - if (activeOrdersCount + 1 >= maxOrders) { - logger.info("[MYALGO] Max orders reached. Switching to cancellation mode."); - clearActiveOrders = true; // Start clearing orders when max is reached - shouldSell = false; // Stop selling once max orders reached + if (activeOrdersCount >= maxOrders) { + logger.info("[MYALGO] Max orders reached.End Algo."); + return NoAction.NoAction; } return new CreateChildOrder(Side.SELL, quantity, bidPrice); // Sell at bid price -// } } - // ---- Handle Order Cancellations ---- // + // ---- Cancel logic ---- // if (clearActiveOrders && activeOrdersCount > 0) { logger.info("[MYALGO] Clearing active orders."); for (ChildOrder order : activeOrders) { if (order != null) { + if(activeOrdersCount == 1){ + clearActiveOrders = false; + } logger.info("[MYALGO] Cancelling order: " + order); return new CancelChildOrder(order); // Cancel one order at a time } } - } else if (clearActiveOrders && activeOrdersCount == 0) { - logger.info("[MYALGO] All orders cancelled. Continuing with the opposite action."); - clearActiveOrders = false; - // Immediately transition to the opposite mode after all orders are cancelled - if (shouldBuy == false) { - shouldSell = true; // Start selling - } else if (shouldSell == false) { - shouldBuy = true; // Start buying - } - } - if (!shouldBuy && !shouldSell) { - logger.info("[MYALGO] No conditions met. Take no action"); - return NoAction.NoAction; } + logger.info("[MYALGO] End algo"); return NoAction.NoAction; } } @@ -154,4 +101,3 @@ public Action evaluate(SimpleAlgoState state) { - diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java index 424abc2d..efaeaac4 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java @@ -32,7 +32,7 @@ public void testExampleBackTest() throws Exception { send(createTick()); //ADD asserts when you have implemented your algo logic - assertEquals(container.getState().getChildOrders().size(), 10); + assertEquals(20,container.getState().getChildOrders().size()); //when: market data moves towards us send(createTick2()); @@ -43,7 +43,7 @@ public void testExampleBackTest() throws Exception { //Check things like filled quantity, cancelled order count etc.... long filledQuantity = state.getChildOrders().stream().map(ChildOrder::getFilledQuantity).reduce(Long::sum).get(); //and: check that our algo state was updated to reflect our fills when the market data - assertEquals(150, filledQuantity); + assertEquals(751, filledQuantity); } } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index 6f99d71b..4f63d01e 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -62,27 +62,4 @@ public void testMaxActiveBuyOrdersLimit() throws Exception { //simple assert to check we only create 10 active orders (maxOrders = 10) assertEquals(container.getState().getActiveChildOrders().size(), 10); } - -// @Test -// public void testCancelBuyOrdersWhenPriceExceedsLimit() throws Exception { -// //Simulate placing 10 buy orders below the price limit -// send(createTick()); -// assertEquals(10, container.getState().getActiveChildOrders().size()); -// -// //Simulate a tick where the price exceeds the limit -// send(createTick()); -// -// // Evaluate the algo logic after the price exceeds the limit -// Action action = createAlgoLogic().evaluate(container.getState()); -// -// // Assert that the algo responds by cancelling a buy order -// assertEquals(true, action instanceof CancelChildOrder); - -// // Simulate the cancellation of a buy order -// send(createTick()); -// -// //Assert that the number of active child orders has decreased by 1 after cancellation -// assertEquals(9, container.getState().getActiveChildOrders().size()); -// } - } From baba69b8ab8d0e05e0e6c1f2dcf5f037d145295b Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Wed, 16 Oct 2024 15:35:45 +0100 Subject: [PATCH 08/13] all tests are passing --- .../gettingstarted/MyAlgoBackTest.java | 100 ++++++++++++++++-- .../gettingstarted/MyAlgoTest.java | 56 ++++++---- .../market-depth/MarketDepthFeature.tsx | 39 +++---- .../market-depth/MarketDepthPanel.css | 48 +++++++++ .../market-depth/MarketDepthPanel.tsx | 49 +++++++++ .../src/components/market-depth/PriceCell.tsx | 33 +++--- .../components/market-depth/QuantityCell.tsx | 19 ++-- 7 files changed, 275 insertions(+), 69 deletions(-) create mode 100644 ui-front-end/src/components/market-depth/MarketDepthPanel.css diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java index efaeaac4..2b181caf 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java @@ -1,10 +1,14 @@ package codingblackfemales.gettingstarted; +import codingblackfemales.action.CancelChildOrder; import codingblackfemales.algo.AlgoLogic; +import codingblackfemales.sotw.OrderState; +import messages.order.Side; import org.junit.Test; import codingblackfemales.sotw.ChildOrder; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * This test plugs together all of the infrastructure, including the order book (which you can trade against) @@ -17,33 +21,113 @@ * * If you cancel the order your child order will show the order status as cancelled in the childOrders of the state object. * + * First Tick: This is when the orders are created based on the initial market conditions. + * Second Tick: This is when the market moves towards your orders, potentially filling them or causing other actions to take place. */ public class MyAlgoBackTest extends AbstractAlgoBackTest { @Override public AlgoLogic createAlgoLogic() { - return new MyAlgoLogic(); } +// public void setUp() { +// container.getState().getChildOrders().clear(); // Clear the list of child orders to reset state +// } + @Test - public void testExampleBackTest() throws Exception { - //create a sample market data tick.... + public void testTotalFilledQuantity() throws Exception { + // Step 1: Send initial market tick to create and fill some orders send(createTick()); - //ADD asserts when you have implemented your algo logic + //Assert that the number of orders created is correct assertEquals(20,container.getState().getChildOrders().size()); - //when: market data moves towards us + // Step 2: Send another tick to simulate market data moving and orders being filled send(createTick2()); - //then: get the state + // Step 3: Get the current state of orders var state = container.getState(); - //Check things like filled quantity, cancelled order count etc.... + // Step 4: Calculate the total filled quantity long filledQuantity = state.getChildOrders().stream().map(ChildOrder::getFilledQuantity).reduce(Long::sum).get(); - //and: check that our algo state was updated to reflect our fills when the market data + + // Step 5: Print the total filled quantity + System.out.println("Total filled quantity: " + filledQuantity); + + // Step 6: Assert that the total filled quantity matches the expected value assertEquals(751, filledQuantity); + + // Test 2: Total number of partially filled orders + // Step 7: Count the number of partially filled orders + long partiallyFilledOrdersCount = state.getChildOrders().stream() + .filter(order -> order.getFilledQuantity() > 0 && order.getFilledQuantity() < order.getQuantity()) // Partially filled orders + .count(); + + // Step 8: Find and print the details of each partially filled order + state.getChildOrders().stream() + .filter(order -> order.getFilledQuantity() > 0 && order.getFilledQuantity() < order.getQuantity()) // Partially filled orders + .forEach(order -> { + System.out.println("Partially Filled Order ID: " + order.getOrderId() + + ", Filled Quantity: " + order.getFilledQuantity() + + ", Remaining Quantity: " + (50 - order.getFilledQuantity()) + // Remaining unfilled quantity + ", Total Quantity: 50"); + }); + + // Print the number of partially filled orders + System.out.println("Number of partially filled orders: " + partiallyFilledOrdersCount); + + // Assert the expected number of partially filled orders + assertEquals(1, partiallyFilledOrdersCount); // Adjust expected value + + // Test 3: Total number of fully filled orders + //Count the number of fully filled orders + long fullyFilledOrders = state.getChildOrders().stream() + .filter(order -> order.getFilledQuantity() == order.getQuantity()) // Fully filled + .count(); + + // Find and print the details of each partially filled order + state.getChildOrders().stream() + .filter(order -> order.getFilledQuantity() == order.getQuantity()) // Fully filled orders + .forEach(order -> { + System.out.println("Fully Filled Order ID: " + order.getOrderId() + + ", Filled Quantity: " + order.getFilledQuantity() + + ", Price: " + order.getPrice()); + }); + + //Print the number of fully filled orders + System.out.println("Number of fully filled orders: " + fullyFilledOrders); + + //Assert the number of fully filled orders is as expected + assertEquals(15, fullyFilledOrders); // Adjust based on expected fully filled orders + + // Calculate profit based on buy and sell orders + long totalBuyCost = 0; + long totalSellRevenue = 0; + + // Loop through all child orders in the container's state + for (ChildOrder order : container.getState().getChildOrders()) { + long filledQty = order.getFilledQuantity(); // Get filled quantity (includes full and partial) + long price = order.getPrice(); // Get price for this order + + if (order.getSide() == Side.BUY && filledQty > 0) { + // For buy orders, calculate total buy cost + totalBuyCost += filledQty * price; + } else if (order.getSide() == Side.SELL && filledQty > 0) { + // For sell orders, calculate total sell revenue + totalSellRevenue += filledQty * price; + } + } + // Step 10: Calculate the profit + long profit = totalSellRevenue - totalBuyCost; + + // Step 11: Print the profit + System.out.println("Total Buy Cost: £" + totalBuyCost); + System.out.println("Total Sell Revenue: £" + totalSellRevenue); + System.out.println("Total Profit: £" + profit); + + // Step 12: Assert that the profit is as expected + assertTrue(profit >= 0); // Ensure profit is non-negative as expected } } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index 4f63d01e..f7213a09 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -1,10 +1,13 @@ package codingblackfemales.gettingstarted; import codingblackfemales.action.CancelChildOrder; +import codingblackfemales.action.CreateChildOrder; import codingblackfemales.action.NoAction; import codingblackfemales.action.Action; import codingblackfemales.algo.AlgoLogic; +import codingblackfemales.sotw.marketdata.AskLevel; import codingblackfemales.sotw.marketdata.BidLevel; +import messages.order.Side; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -30,36 +33,47 @@ public AlgoLogic createAlgoLogic() { } @Test - public void testBidPriceWithinLimit() throws Exception { - //Simulate a tick where the price exceeds the limit - send(createTick()); // does this tick update the market price? + public void testDispatchThroughSequencer() throws Exception { + // Step 1: Send a tick to simulate the market (only sent once) + send(createTick()); // Simulate a tick with default bid and ask prices - // Retrieve the updated bid and ask levels + // Step 2: Retrieve the updated bid and ask levels BidLevel bestBid = container.getState().getBidAt(0); // Highest bid price - - // Check if the bid price now exceeds the price limit + AskLevel bestAsk = container.getState().getAskAt(0); // Lowest ask price long bidPrice = bestBid.price; + long askPrice = bestAsk.price; - assertTrue(bidPrice >= 91); // Assuming price limit is 91.00 - } - - @Test - public void testEvaluateNoActionWhenNoConditionsAreMet() throws Exception { - send(createTick()); + // Step 3: Assert that the bid and ask prices are within the expected limits + assertTrue(bidPrice >= 91); // assuming 91 as sell limit + assertTrue(askPrice <= 115); // assuming 115 as price limit -// Action action = new MyAlgoLogic().evaluate(container.getState()); + // Step 4: Test the algorithm's evaluation for buy orders Action action = createAlgoLogic().evaluate(container.getState()); - //simple assert to check if no conditions are met, there's no action returned + // Step 5: Assert that no action is taken (NoAction) when conditions are not met assertEquals(NoAction.NoAction, action); - } - @Test - public void testMaxActiveBuyOrdersLimit() throws Exception { - //create a sample market data tick.... - send(createTick()); + // Step 6: Simulate the conditions for canceling an order +// send(createTick()); + +// // Step 7: Evaluate the algorithm again after the market change +// Action CancelChildOrder = createAlgoLogic().evaluate(container.getState()); +// +// // Step 8: Assert that the algorithm returns a CancelChildOrder action when conditions are met +// assertEquals(CancelChildOrder, action); + + // Step 9: Check if the algorithm has placed or canceled orders correctly + // Check no more than 10 active child orders created at once + assertEquals(container.getState().getActiveChildOrders().size(),10); // maxOrders = 10; - //simple assert to check we only create 10 active orders (maxOrders = 10) - assertEquals(container.getState().getActiveChildOrders().size(), 10); + // Step 10: Verify the total number of child orders (active + canceled) + assertEquals(container.getState().getChildOrders().size(), 20); } } + + + + + + + diff --git a/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx b/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx index 11cce0a0..95e381f0 100644 --- a/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx +++ b/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx @@ -1,34 +1,37 @@ // import { Placeholder } from "../placeholder"; import React from 'react'; -// import { useMarketDepthData } from "./useMarketDepthData"; -// import { schemas } from "../../data/algo-schemas"; +import { useMarketDepthData } from "./useMarketDepthData"; +import { schemas } from "../../data/algo-schemas"; +import { MarketDepthPanel } from './MarketDepthPanel'; -// Example test data -const testData: MarketDepthRow[] = [ -{ symbolLevel:"1230", level: 0, bid: 1000, bidQuantity: 500, offer: 1010, offerQuantity: 700 }, -{ symbolLevel:"1231", level: 1, bid: 990, bidQuantity: 700, offer: 1012, offerQuantity: 400 }, -{ symbolLevel:"1232", level: 2, bid: 985, bidQuantity: 1200, offer: 1013, offerQuantity: 800 }, -{ symbolLevel:"1233", level: 3, bid: 984, bidQuantity: 1300, offer: 1018, offerQuantity: 750 }, -{ symbolLevel:"1234", level: 4, bid: 970, bidQuantity: 800, offer: 1021, offerQuantity: 900 }, -{ symbolLevel:"1235", level: 5, bid: 969, bidQuantity: 700, offer: 1026, offerQuantity: 1500 }, -{ symbolLevel:"1236", level: 6, bid: 950, bidQuantity: 750, offer: 1027, offerQuantity: 1500 }, -{ symbolLevel:"1237", level: 7, bid: 945, bidQuantity: 900, offer: 1029, offerQuantity: 2000 }, -{ symbolLevel:"1238", level: 8, bid: 943, bidQuantity: 500, offer: 1031, offerQuantity: 500 }, -{ symbolLevel:"1239", level: 9, bid: 940, bidQuantity: 200, offer: 1024, offerQuantity: 800 }, - ]; +// // Example test data +// const testData: MarketDepthRow[] = [ +// { symbolLevel:"1230", level: 0, bid: 1000, bidQuantity: 500, offer: 1010, offerQuantity: 700 }, +// { symbolLevel:"1231", level: 1, bid: 990, bidQuantity: 700, offer: 1012, offerQuantity: 400 }, +// { symbolLevel:"1232", level: 2, bid: 985, bidQuantity: 1200, offer: 1013, offerQuantity: 800 }, +// { symbolLevel:"1233", level: 3, bid: 984, bidQuantity: 1300, offer: 1018, offerQuantity: 750 }, +// { symbolLevel:"1234", level: 4, bid: 970, bidQuantity: 800, offer: 1021, offerQuantity: 900 }, +// { symbolLevel:"1235", level: 5, bid: 969, bidQuantity: 700, offer: 1026, offerQuantity: 1500 }, +// { symbolLevel:"1236", level: 6, bid: 950, bidQuantity: 750, offer: 1027, offerQuantity: 1500 }, +// { symbolLevel:"1237", level: 7, bid: 945, bidQuantity: 900, offer: 1029, offerQuantity: 2000 }, +// { symbolLevel:"1238", level: 8, bid: 943, bidQuantity: 500, offer: 1031, offerQuantity: 500 }, +// { symbolLevel:"1239", level: 9, bid: 940, bidQuantity: 200, offer: 1024, offerQuantity: 800 }, +// ]; /** * TODO */ export const MarketDepthFeature = () => { // instead of fetching real data (useMarketDepthData) we will use the testData for now - const data = testData; +// const data = testData; // fetching the real market depth data using the hook -// const data = useMarketDepthData(schemas.prices); + const data = useMarketDepthData(schemas.prices); - return ; +// return ; + // Render the MarketDepthPanel once the data is fetched + return ; }; diff --git a/ui-front-end/src/components/market-depth/MarketDepthPanel.css b/ui-front-end/src/components/market-depth/MarketDepthPanel.css new file mode 100644 index 00000000..926814df --- /dev/null +++ b/ui-front-end/src/components/market-depth/MarketDepthPanel.css @@ -0,0 +1,48 @@ +.MarketDepthPanel { + width: 100%; + border-collapse: collapse; /* Ensure borders are merged between cells */ + margin-top: 20px; + border: 1px solid black; /* Outer border of the table */ +} + +/* Add black borders to table header and data cells */ +.MarketDepthPanel th, .MarketDepthPanel td { + padding: 8px; + text-align: center; + border: 1px solid black; /* Black border for table cells */ +} + +/* Blue background for Bid Quantity only */ +.bid.quantity { + background-color: #007bff; + color: white; + border: 1px solid black; /* Ensure Bid Quantity cells also have black border */ +} + +/* Red background for Ask Quantity only */ +.ask.quantity { + background-color: #dc3545; + color: white; + border: 1px solid black; /* Ensure Ask Quantity cells also have black border */ +} + +/* Styling for Price Cell (no background color) */ +.price { + font-weight: bold; + border: 1px solid black; /* Add black border to price cells */ +} + +/* Arrow color for price movement */ +.arrow { + margin-left: 8px; +} + +.up { + color: green; +} + +.down { + color: red; +} + + diff --git a/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx b/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx index e69de29b..8aafb88c 100644 --- a/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx +++ b/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { PriceCell } from './PriceCell'; +import { QuantityCell } from './QuantityCell'; +import "./MarketDepthPanel.css"; + +interface MarketDepthRow { + symbolLevel: string; + level: number; + bid: number; + bidQuantity: number; + offer: number; + offerQuantity: number; +} + +interface MarketDepthPanelProps { + data: MarketDepthRow[]; +} + +export const MarketDepthPanel = ({ data }: MarketDepthPanelProps) => { + return ( + + + + + + + + + + + + + + + + + {data.map((row, index) => ( + + + + + + + + ))} + +
LevelBidAsk
QuantityPricePriceQuantity
{row.level}
+ ); +}; diff --git a/ui-front-end/src/components/market-depth/PriceCell.tsx b/ui-front-end/src/components/market-depth/PriceCell.tsx index 4d8ff0cc..cb70d8d6 100644 --- a/ui-front-end/src/components/market-depth/PriceCell.tsx +++ b/ui-front-end/src/components/market-depth/PriceCell.tsx @@ -1,21 +1,26 @@ -import React, { useEffect, useState } from 'react'; // importing react library -// UseState hook allows component to remember values between renders (current price or prev price) -// UseEffect hook helps perform actions after a component has rendered (certain values have changed) +import React, { useRef, useEffect } from "react"; interface PriceCellProps { price: number; + type: 'bid' | 'ask'; // We can keep this prop to differentiate, but won't apply color } -// PriceCell component will display the price in the table, with a logic implemented to show if the price -// is going up or down compared to the previous value. +export const PriceCell = ({ price, type }: PriceCellProps) => { + const lastPriceRef = useRef(price); + const priceDifference = price - lastPriceRef.current; -// PriceCell will: -// track the price -// compare prices -// show a direction arrow: -// update the previous price: + useEffect(() => { + lastPriceRef.current = price; + }, [price]); -// STEPS: -// import the needed hooks and libraries -// need to define the structure for my price component props -// setting up a logic \ No newline at end of file + return ( + {/* We will remove the `type` class from here */} + {price} + {priceDifference !== 0 && ( + 0 ? 'up' : 'down'}`}> + {priceDifference > 0 ? '↑' : '↓'} + + )} + + ); +}; diff --git a/ui-front-end/src/components/market-depth/QuantityCell.tsx b/ui-front-end/src/components/market-depth/QuantityCell.tsx index 92b3d875..9ace7f0f 100644 --- a/ui-front-end/src/components/market-depth/QuantityCell.tsx +++ b/ui-front-end/src/components/market-depth/QuantityCell.tsx @@ -1,17 +1,20 @@ -import React from 'react'; +import React from "react"; interface QuantityCellProps { quantity: number; + type: 'bid' | 'ask'; // 'bid' or 'ask' to apply styles } -// QuantityCell recieves the quantity value as a prop and decides what to display or do with that value. +export const QuantityCell = (props: QuantityCellProps) => { + const { quantity, type } = props; + + return ( + + {quantity} + + ); +}; -// logic to implement: -// basic function : show the quantity value in the cell. -// conditional formatting: -// change how the quantity will be showed based on it's value. -// so if the quantity if high, i could highlight in green -// if the quantity is low, i could highlight in red. From d6bf0fd5fb3e52ef2e18ad70a6024c9b76e402dc Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Thu, 17 Oct 2024 15:12:15 +0100 Subject: [PATCH 09/13] completed form - last commit --- .../gettingstarted/MyAlgoLogic.java | 127 +++++++++++------- .../gettingstarted/MyAlgoBackTest.java | 2 - .../gettingstarted/MyAlgoTest.java | 12 +- 3 files changed, 81 insertions(+), 60 deletions(-) diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index fbd6eb03..f97f0280 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -30,65 +30,88 @@ public class MyAlgoLogic implements AlgoLogic { @Override public Action evaluate(SimpleAlgoState state) { - logger.info("[MYALGO] In Algo Logic...."); - - // log the current state of the order book - var orderBookAsString = Util.orderBookToString(state); - logger.info("[MYALGO] The state of the order book is:\n" + orderBookAsString); - - // Retrieve the total Order count - var totalOrderCount = state.getChildOrders().size(); - logger.info("[MYALGO] Total child orders: " + totalOrderCount); - - // Retrieve active orders count - List activeOrders = state.getActiveChildOrders(); - int activeOrdersCount = activeOrders.size(); - logger.info("[MYALGO] Active child orders: " + activeOrdersCount); - - // Retrieve the best bid and ask prices - BidLevel bestBid = state.getBidAt(0); // the highest bid price - the price buyers are willing to pay - AskLevel bestAsk = state.getAskAt(0); // the lowest ask price - the price sellers are asking for - logger.info("[MYALGO] Best Bid prices" + bestBid); - logger.info("[MYALGO] Best Ask prices" + bestAsk); - - // Actual price - long bidPrice = bestBid.price; // the highest bid price - long askPrice = bestAsk.price; // the lowest ask price - - // ---- Buy Logic ---- // - if (shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && askPrice <= priceLimit) { - logger.info("[MYALGO] The current ask price is below or equal to the price limit. Placing buy order."); - if (activeOrdersCount + 1 >= maxOrders) { - logger.info("[MYALGO] Max orders reached. Switching to cancellation mode."); - clearActiveOrders = true; // Start clearing orders when max is reached - shouldBuy = false; // Stop buying once max orders reached + try { + // Check if state is null before proceeding + if (state == null) { + logger.error("[MYALGO] Algo state is null!"); + return NoAction.NoAction; } - return new CreateChildOrder(Side.BUY, quantity, askPrice); // Buy at ask price - } - // ---- Sell Logic ---- // - if (!shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && bidPrice >= sellLimit) { - logger.info("[MYALGO] The current bid price is greater than or equal to the sell limit. Placing sell order."); - if (activeOrdersCount >= maxOrders) { - logger.info("[MYALGO] Max orders reached.End Algo."); + // log the current state of the order book + var orderBookAsString = Util.orderBookToString(state); + logger.info("[MYALGO] The state of the order book is:\n" + orderBookAsString); + + // Get total order count, with a simple null check + if (state.getChildOrders() == null) { + logger.warn("[MYALGO] Child orders are null!"); return NoAction.NoAction; } - return new CreateChildOrder(Side.SELL, quantity, bidPrice); // Sell at bid price - } - // ---- Cancel logic ---- // - if (clearActiveOrders && activeOrdersCount > 0) { - logger.info("[MYALGO] Clearing active orders."); - for (ChildOrder order : activeOrders) { - if (order != null) { - if(activeOrdersCount == 1){ - clearActiveOrders = false; + + // Retrieve the total Order count + var totalOrderCount = state.getChildOrders().size(); + logger.info("[MYALGO] Total child orders: " + totalOrderCount); + + // Get active orders count, simple null check + List activeOrders = state.getActiveChildOrders(); + if (activeOrders == null) { + logger.warn("[MYALGO] Active orders list is null!"); + return NoAction.NoAction; + } + int activeOrdersCount = activeOrders.size(); + logger.info("[MYALGO] Active child orders: " + activeOrdersCount); + + // Get best bid and ask prices, handle potential null values + BidLevel bestBid = state.getBidAt(0); + AskLevel bestAsk = state.getAskAt(0); + if (bestBid == null || bestAsk == null) { + logger.warn("[MYALGO] Best bid or ask price is null!"); + return NoAction.NoAction; + } + + logger.info("[MYALGO] Best Bid prices" + bestBid); + logger.info("[MYALGO] Best Ask prices" + bestAsk); + + long bidPrice = bestBid.price; // the highest bid price + long askPrice = bestAsk.price; // the lowest ask price + + // ---- Buy Logic ---- // + if (shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && askPrice <= priceLimit) { + logger.info("[MYALGO] The current ask price is below or equal to the price limit. Placing buy order."); + if (activeOrdersCount + 1 >= maxOrders) { + logger.info("[MYALGO] Max orders reached. Switching to cancellation mode."); + clearActiveOrders = true; // Start clearing orders when max is reached + shouldBuy = false; // Stop buying once max orders reached + } + return new CreateChildOrder(Side.BUY, quantity, askPrice); // Buy at ask price + } + // ---- Sell Logic ---- // + if (!shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && bidPrice >= sellLimit) { + logger.info("[MYALGO] The current bid price is greater than or equal to the sell limit. Placing sell order."); + if (activeOrdersCount >= maxOrders) { + logger.info("[MYALGO] Max orders reached.End Algo."); + return NoAction.NoAction; + } + return new CreateChildOrder(Side.SELL, quantity, bidPrice); // Sell at bid price + } + // ---- Cancel logic ---- // + if (clearActiveOrders && activeOrdersCount > 0) { + logger.info("[MYALGO] Clearing active orders."); + for (ChildOrder order : activeOrders) { + if (order != null) { + if (activeOrdersCount == 1) { + clearActiveOrders = false; + } + logger.info("[MYALGO] Cancelling order: " + order); + return new CancelChildOrder(order); // Cancel one order at a time } - logger.info("[MYALGO] Cancelling order: " + order); - return new CancelChildOrder(order); // Cancel one order at a time } } + logger.info("[MYALGO] End algo"); + return NoAction.NoAction; + + } catch (Exception e) { + logger.error("[MYALGO] Error during algo evaluation: " + e.getMessage(), e); + return NoAction.NoAction; } - logger.info("[MYALGO] End algo"); - return NoAction.NoAction; } } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java index 2b181caf..afc5cec0 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java @@ -1,8 +1,6 @@ package codingblackfemales.gettingstarted; -import codingblackfemales.action.CancelChildOrder; import codingblackfemales.algo.AlgoLogic; -import codingblackfemales.sotw.OrderState; import messages.order.Side; import org.junit.Test; import codingblackfemales.sotw.ChildOrder; diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index f7213a09..b0cd1e28 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -54,13 +54,13 @@ public void testDispatchThroughSequencer() throws Exception { assertEquals(NoAction.NoAction, action); // Step 6: Simulate the conditions for canceling an order -// send(createTick()); + send(createTick()); -// // Step 7: Evaluate the algorithm again after the market change -// Action CancelChildOrder = createAlgoLogic().evaluate(container.getState()); -// -// // Step 8: Assert that the algorithm returns a CancelChildOrder action when conditions are met -// assertEquals(CancelChildOrder, action); + // Step 7: Evaluate the algorithm again after the market change + Action CancelChildOrder = createAlgoLogic().evaluate(container.getState()); + + // Step 8: Assert that the algorithm returns a CancelChildOrder action when conditions are met + assertEquals(CancelChildOrder, action); // Step 9: Check if the algorithm has placed or canceled orders correctly // Check no more than 10 active child orders created at once From d8fe510ea7d0212b987b5ee6ff63e12deb91c12a Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Tue, 5 Nov 2024 17:49:42 +0000 Subject: [PATCH 10/13] added vwap --- .../gettingstarted/MyAlgoLogic.java | 91 ++++++++++++++++++- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index f97f0280..198b5593 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -28,6 +28,14 @@ public class MyAlgoLogic implements AlgoLogic { private static boolean clearActiveOrders = false; // when enabled will clear all active orders private static boolean shouldBuy = true; // when enabled, place buy orders + private static final double VWAP_BUY_THRESHOLD = 0.995; // Buy if ask is slightly below VWAP + private static final double VWAP_SELL_THRESHOLD = 0.95; // Sell if bid is close to VWAP + + + private double vwap = 0; + private static final double DEFAULT_VWAP = 100.0; // Set a sensible default value + + @Override public Action evaluate(SimpleAlgoState state) { try { @@ -36,6 +44,16 @@ public Action evaluate(SimpleAlgoState state) { logger.error("[MYALGO] Algo state is null!"); return NoAction.NoAction; } + + if (vwap == 0) { + initialiseVWAP(state); + } else { + updateVWAP(state); + } + + double buyThresholdPrice = vwap * VWAP_BUY_THRESHOLD; + double sellThresholdPrice = vwap * VWAP_SELL_THRESHOLD; + // log the current state of the order book var orderBookAsString = Util.orderBookToString(state); logger.info("[MYALGO] The state of the order book is:\n" + orderBookAsString); @@ -74,7 +92,7 @@ public Action evaluate(SimpleAlgoState state) { long askPrice = bestAsk.price; // the lowest ask price // ---- Buy Logic ---- // - if (shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && askPrice <= priceLimit) { + if (shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && askPrice <= priceLimit && askPrice < vwap) { logger.info("[MYALGO] The current ask price is below or equal to the price limit. Placing buy order."); if (activeOrdersCount + 1 >= maxOrders) { logger.info("[MYALGO] Max orders reached. Switching to cancellation mode."); @@ -84,7 +102,7 @@ public Action evaluate(SimpleAlgoState state) { return new CreateChildOrder(Side.BUY, quantity, askPrice); // Buy at ask price } // ---- Sell Logic ---- // - if (!shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && bidPrice >= sellLimit) { + if (!shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && bidPrice >= sellLimit && bidPrice > vwap) { logger.info("[MYALGO] The current bid price is greater than or equal to the sell limit. Placing sell order."); if (activeOrdersCount >= maxOrders) { logger.info("[MYALGO] Max orders reached.End Algo."); @@ -113,6 +131,73 @@ public Action evaluate(SimpleAlgoState state) { return NoAction.NoAction; } } + + // Initialise VWAP based on market data or default value + private void initialiseVWAP(SimpleAlgoState state) { + long totalBidQuantity = 0; + long totalAskQuantity = 0; + long bidPriceQuantitySum = 0; + long askPriceQuantitySum = 0; + + int bidLevels = state.getBidLevels(); + for (int i = 0; i < bidLevels; i++) { + BidLevel bid = state.getBidAt(i); + if (bid != null) { + totalBidQuantity += bid.getQuantity(); + bidPriceQuantitySum += bid.getPrice() * bid.getQuantity(); + } + } + int askLevels = state.getAskLevels(); + for (int i = 0; i < askLevels; i++) { + AskLevel ask = state.getAskAt(i); + if (ask != null) { + totalAskQuantity += ask.getQuantity(); + askPriceQuantitySum += ask.getPrice() * ask.getQuantity(); + } + } + long totalQuantity = totalBidQuantity + totalAskQuantity; + if (totalQuantity > 0) { + vwap = (double) (bidPriceQuantitySum + askPriceQuantitySum) / totalQuantity; + logger.info("[MYALGO] VWAP initialized using order book data: " + vwap); + } else { + vwap = DEFAULT_VWAP; + logger.info("[MYALGO] No market data available. Using default VWAP: " + vwap); + } + } + + // Update VWAP based on current market data + private void updateVWAP(SimpleAlgoState state) { + long totalBidQuantity = 0; + long totalAskQuantity = 0; + long bidPriceQuantitySum = 0; + long askPriceQuantitySum = 0; + + int bidLevels = state.getBidLevels(); + for (int i = 0; i < bidLevels; i++) { + BidLevel bid = state.getBidAt(i); + if (bid != null) { + totalBidQuantity += bid.getQuantity(); + bidPriceQuantitySum += bid.getPrice() * bid.getQuantity(); + } + } + + int askLevels = state.getAskLevels(); + for (int i = 0; i < askLevels; i++) { + AskLevel ask = state.getAskAt(i); + if (ask != null) { + totalAskQuantity += ask.getQuantity(); + askPriceQuantitySum += ask.getPrice() * ask.getQuantity(); + } + } + + long totalQuantity = totalBidQuantity + totalAskQuantity; + if (totalQuantity > 0) { + vwap = (double) (bidPriceQuantitySum + askPriceQuantitySum) / totalQuantity; + logger.info("[MYALGO] VWAP updated based on order book: " + vwap); + } else { + logger.warn("[MYALGO] No sufficient market data for VWAP calculation."); + } + } } @@ -124,3 +209,5 @@ public Action evaluate(SimpleAlgoState state) { + + From a59eca8117ffc1c8227f62fa3c856bdb83f4722e Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Wed, 6 Nov 2024 03:28:05 +0000 Subject: [PATCH 11/13] final form - need to fix UI --- algo-exercise/getting-started/README.md | 54 +++++++ .../gettingstarted/MyAlgoLogic.java | 145 ++++++------------ .../gettingstarted/AbstractAlgoTest.java | 62 ++++++++ .../gettingstarted/MyAlgoBackTest.java | 4 - .../gettingstarted/MyAlgoTest.java | 78 ++++++---- .../market-depth/MarketDepthPanel.css | 48 ------ 6 files changed, 214 insertions(+), 177 deletions(-) create mode 100644 algo-exercise/getting-started/README.md delete mode 100644 ui-front-end/src/components/market-depth/MarketDepthPanel.css diff --git a/algo-exercise/getting-started/README.md b/algo-exercise/getting-started/README.md new file mode 100644 index 00000000..e1934038 --- /dev/null +++ b/algo-exercise/getting-started/README.md @@ -0,0 +1,54 @@ +# MyAlgoLogic Trading Algorithm + +## Overview + +`MyAlgoLogic` is a trading algorithm designed to interact with a simulated market environment, making decisions to place buy and sell orders based on defined constraints. It manages an order book, handles market data changes, and dynamically updates based on predefined price limits and order management rules. + +This repository includes: +- **Algorithm Logic** (`MyAlgoLogic`): Core trading logic with buy, sell, and cancel strategies. +- **Unit Tests** (`MyAlgoTest`): Isolated tests that simulate tick data to verify the algorithm's buy/sell and cancel logic. +- **Back Test** (`MyAlgoBackTest`): End-to-end backtesting that integrates with a mock market environment to validate overall behavior, including order fills and profit calculation. + +## Project Structure + +- **MyAlgoLogic.java**: Core algorithm logic implementing buy, sell, and cancel strategies. +- **MyAlgoTest.java**: Unit tests for isolated testing of the algorithm's behavior under controlled tick data. +- **MyAlgoBackTest.java**: Comprehensive backtest, simulating a live trading environment with order fills and profit evaluation. +- **AbstractAlgoTest.java** and **AbstractAlgoBackTest.java**: Base classes providing test infrastructure, including market simulation, tick data, and sequencing. + +## Algorithm Features + +### Key Logic +- **Buy Logic**: Places buy orders if the current market ask price is within a defined `priceLimit`. +- **Sell Logic**: Places sell orders if the market bid price exceeds a predefined `sellLimit`. +- **Cancel Logic**: Cancels active orders incrementally if the `clearActiveOrders` flag is enabled or `maxOrders` limit is reached. + +### Constraints and Parameters +- **Price Limits**: Defines `priceLimit` for buy orders and `sellLimit` for sell orders. +- **Order Management**: Limits active orders to `maxOrders` and manages incremental cancellations when needed. +- **Quantity Per Order**: Each order is placed with a fixed quantity (`quantity`). + +## Testing Overview + +### Unit Tests (`MyAlgoTest`) +Tests the algorithm’s logic in isolation, ensuring: +- **Buy Logic Test**: Verifies buy orders are only placed when conditions match `priceLimit`. +- **Cancel Logic Test**: Ensures orders are canceled correctly when required. + +### Backtest (`MyAlgoBackTest`) +Simulates the algorithm in a live market environment, validating: +- **Order Fills**: Checks if expected quantities are filled. +- **Order Status**: Confirms correct categorisation of partially and fully filled orders. +- **Profit Calculation**: Evaluates profit and verifies non-negative outcomes. + +## Getting Started + +1. **Run Unit Tests**: + ```bash + mvn test -Dtest=MyAlgoTest +2. **Run Back Tests**: + ```bash + mvn test -Dtest=MyAlgoBackTest + +Happy Testing! + diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index 198b5593..07fc5134 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -17,23 +17,35 @@ import java.util.List; +/** + * MyAlgoLogic class implements the AlgoLogic interface to provide a basic algorithm for + * placing and cancelling buy and sell orders in response to market conditions. + * + * This algorithm operates based on a set of defined constraints such as price limits, + * maximum active orders, and order quantity. + * + * The algorithm follows three main stages: + * - Buy orders are placed when the ask price is below or equal to the defined price limit. + * - Sell orders are placed when the bid price is above or equal to the sell limit. + * - Active orders are cancelled when the maximum number of orders is reached. + * + * Key features: + * - Buy logic is triggered when conditions for placing a buy order (such as price limit and active order count) are met. + * - Sell logic is executed based on sell conditions (such as bid price exceeding the sell limit). + * - Orders are gradually cancelled when active orders exceed the predefined maximum. + * - The algorithm is designed to log all key activities and handle null states or exceptions gracefully. + */ + public class MyAlgoLogic implements AlgoLogic { private static final Logger logger = LoggerFactory.getLogger(MyAlgoLogic.class); - // constraints for the price limit - private static final long priceLimit = 115; - private static final long sellLimit = 91; // sell when the price reaches £105 - private static final int maxOrders = 10; // max allowed orders in the orderbook - private static final int quantity = 50; // quantity for each order - private static boolean clearActiveOrders = false; // when enabled will clear all active orders - private static boolean shouldBuy = true; // when enabled, place buy orders - - private static final double VWAP_BUY_THRESHOLD = 0.995; // Buy if ask is slightly below VWAP - private static final double VWAP_SELL_THRESHOLD = 0.95; // Sell if bid is close to VWAP - - - private double vwap = 0; - private static final double DEFAULT_VWAP = 100.0; // Set a sensible default value + // Define constraints for trading parameters + private static final long priceLimit = 115; // Max price to place a buy order + private static final long sellLimit = 91; // Min price to place a sell order + private static final int maxOrders = 10; // Maximum allowed active orders + private static final int quantity = 50; // Quantity for each order + private static boolean clearActiveOrders = false; // Flag to enable clearing active orders + private static boolean shouldBuy = true; // Flag to enable placing buy orders @Override @@ -45,30 +57,21 @@ public Action evaluate(SimpleAlgoState state) { return NoAction.NoAction; } - if (vwap == 0) { - initialiseVWAP(state); - } else { - updateVWAP(state); - } - - double buyThresholdPrice = vwap * VWAP_BUY_THRESHOLD; - double sellThresholdPrice = vwap * VWAP_SELL_THRESHOLD; - - // log the current state of the order book + // Log the current state of the order book var orderBookAsString = Util.orderBookToString(state); logger.info("[MYALGO] The state of the order book is:\n" + orderBookAsString); - // Get total order count, with a simple null check + // Check for null child orders and log a warning if (state.getChildOrders() == null) { logger.warn("[MYALGO] Child orders are null!"); return NoAction.NoAction; } - // Retrieve the total Order count + // Retrieve the count of child orders var totalOrderCount = state.getChildOrders().size(); logger.info("[MYALGO] Total child orders: " + totalOrderCount); - // Get active orders count, simple null check + // Retrieve active orders and check for potential null values List activeOrders = state.getActiveChildOrders(); if (activeOrders == null) { logger.warn("[MYALGO] Active orders list is null!"); @@ -85,14 +88,15 @@ public Action evaluate(SimpleAlgoState state) { return NoAction.NoAction; } - logger.info("[MYALGO] Best Bid prices" + bestBid); - logger.info("[MYALGO] Best Ask prices" + bestAsk); + // Retrieve bid and ask prices + long bidPrice = bestBid.price; + long askPrice = bestAsk.price; + logger.info("[MYALGO] Best Bid price: " + bidPrice); + logger.info("[MYALGO] Best Ask price: " + askPrice); - long bidPrice = bestBid.price; // the highest bid price - long askPrice = bestAsk.price; // the lowest ask price // ---- Buy Logic ---- // - if (shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && askPrice <= priceLimit && askPrice < vwap) { + if (shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && askPrice <= priceLimit) { logger.info("[MYALGO] The current ask price is below or equal to the price limit. Placing buy order."); if (activeOrdersCount + 1 >= maxOrders) { logger.info("[MYALGO] Max orders reached. Switching to cancellation mode."); @@ -101,8 +105,9 @@ public Action evaluate(SimpleAlgoState state) { } return new CreateChildOrder(Side.BUY, quantity, askPrice); // Buy at ask price } + // ---- Sell Logic ---- // - if (!shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && bidPrice >= sellLimit && bidPrice > vwap) { + if (!shouldBuy && !clearActiveOrders && activeOrdersCount < maxOrders && bidPrice >= sellLimit) { logger.info("[MYALGO] The current bid price is greater than or equal to the sell limit. Placing sell order."); if (activeOrdersCount >= maxOrders) { logger.info("[MYALGO] Max orders reached.End Algo."); @@ -110,13 +115,14 @@ public Action evaluate(SimpleAlgoState state) { } return new CreateChildOrder(Side.SELL, quantity, bidPrice); // Sell at bid price } + // ---- Cancel logic ---- // if (clearActiveOrders && activeOrdersCount > 0) { logger.info("[MYALGO] Clearing active orders."); for (ChildOrder order : activeOrders) { if (order != null) { if (activeOrdersCount == 1) { - clearActiveOrders = false; + clearActiveOrders = false; // Disable clearing when last order is reached } logger.info("[MYALGO] Cancelling order: " + order); return new CancelChildOrder(order); // Cancel one order at a time @@ -132,71 +138,12 @@ public Action evaluate(SimpleAlgoState state) { } } - // Initialise VWAP based on market data or default value - private void initialiseVWAP(SimpleAlgoState state) { - long totalBidQuantity = 0; - long totalAskQuantity = 0; - long bidPriceQuantitySum = 0; - long askPriceQuantitySum = 0; - - int bidLevels = state.getBidLevels(); - for (int i = 0; i < bidLevels; i++) { - BidLevel bid = state.getBidAt(i); - if (bid != null) { - totalBidQuantity += bid.getQuantity(); - bidPriceQuantitySum += bid.getPrice() * bid.getQuantity(); - } - } - int askLevels = state.getAskLevels(); - for (int i = 0; i < askLevels; i++) { - AskLevel ask = state.getAskAt(i); - if (ask != null) { - totalAskQuantity += ask.getQuantity(); - askPriceQuantitySum += ask.getPrice() * ask.getQuantity(); - } - } - long totalQuantity = totalBidQuantity + totalAskQuantity; - if (totalQuantity > 0) { - vwap = (double) (bidPriceQuantitySum + askPriceQuantitySum) / totalQuantity; - logger.info("[MYALGO] VWAP initialized using order book data: " + vwap); - } else { - vwap = DEFAULT_VWAP; - logger.info("[MYALGO] No market data available. Using default VWAP: " + vwap); - } - } - - // Update VWAP based on current market data - private void updateVWAP(SimpleAlgoState state) { - long totalBidQuantity = 0; - long totalAskQuantity = 0; - long bidPriceQuantitySum = 0; - long askPriceQuantitySum = 0; - - int bidLevels = state.getBidLevels(); - for (int i = 0; i < bidLevels; i++) { - BidLevel bid = state.getBidAt(i); - if (bid != null) { - totalBidQuantity += bid.getQuantity(); - bidPriceQuantitySum += bid.getPrice() * bid.getQuantity(); - } - } - - int askLevels = state.getAskLevels(); - for (int i = 0; i < askLevels; i++) { - AskLevel ask = state.getAskAt(i); - if (ask != null) { - totalAskQuantity += ask.getQuantity(); - askPriceQuantitySum += ask.getPrice() * ask.getQuantity(); - } - } - - long totalQuantity = totalBidQuantity + totalAskQuantity; - if (totalQuantity > 0) { - vwap = (double) (bidPriceQuantitySum + askPriceQuantitySum) / totalQuantity; - logger.info("[MYALGO] VWAP updated based on order book: " + vwap); - } else { - logger.warn("[MYALGO] No sufficient market data for VWAP calculation."); - } + /** + * Setter for clearActiveOrders, used primarily for testing purposes. + * @param value true to enable clearing active orders, false to disable. + */ + public void setClearActiveOrders(boolean value) { + clearActiveOrders = value; } } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/AbstractAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/AbstractAlgoTest.java index f57fef15..46a61367 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/AbstractAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/AbstractAlgoTest.java @@ -16,6 +16,11 @@ import java.nio.ByteBuffer; +/** + * Creates a tick with a new set of market data, simulating different bid and ask conditions. + * Use this method to test specific reactions in your algo under varied market conditions. + */ + public abstract class AbstractAlgoTest extends SequencerTestCase { @@ -75,6 +80,63 @@ protected UnsafeBuffer createTick(){ return directBuffer; } + protected UnsafeBuffer createTickHighBid() { + final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder(); + final BookUpdateEncoder encoder = new BookUpdateEncoder(); + + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); + final UnsafeBuffer directBuffer = new UnsafeBuffer(byteBuffer); + + encoder.wrapAndApplyHeader(directBuffer, 0, headerEncoder); + + encoder.venue(Venue.XLON); + encoder.instrumentId(123L); + + encoder.askBookCount(3) + .next().price(120L).size(100L) // Higher ask price + .next().price(125L).size(200L) + .next().price(130L).size(500L); + + encoder.bidBookCount(3) + .next().price(115L).size(100L) // Higher bid price + .next().price(110L).size(200L) + .next().price(105L).size(300L); + + encoder.instrumentStatus(InstrumentStatus.CONTINUOUS); + encoder.source(Source.STREAM); + + return directBuffer; + } + + protected UnsafeBuffer createTickLowAsk() { + final MessageHeaderEncoder headerEncoder = new MessageHeaderEncoder(); + final BookUpdateEncoder encoder = new BookUpdateEncoder(); + + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); + final UnsafeBuffer directBuffer = new UnsafeBuffer(byteBuffer); + + encoder.wrapAndApplyHeader(directBuffer, 0, headerEncoder); + + encoder.venue(Venue.XLON); + encoder.instrumentId(123L); + + encoder.askBookCount(3) + .next().price(90L).size(150L) // Lower ask price to test buy logic + .next().price(85L).size(200L) + .next().price(80L).size(500L); + + encoder.bidBookCount(3) + .next().price(75L).size(100L) + .next().price(70L).size(200L) + .next().price(65L).size(300L); + + encoder.instrumentStatus(InstrumentStatus.CONTINUOUS); + encoder.source(Source.STREAM); + + return directBuffer; + } + + } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java index afc5cec0..7f73b71f 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoBackTest.java @@ -29,10 +29,6 @@ public AlgoLogic createAlgoLogic() { return new MyAlgoLogic(); } -// public void setUp() { -// container.getState().getChildOrders().clear(); // Clear the list of child orders to reset state -// } - @Test public void testTotalFilledQuantity() throws Exception { // Step 1: Send initial market tick to create and fill some orders diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index b0cd1e28..b5fcf04b 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -34,41 +34,67 @@ public AlgoLogic createAlgoLogic() { @Test public void testDispatchThroughSequencer() throws Exception { - // Step 1: Send a tick to simulate the market (only sent once) + // Send a tick to simulate the market (only sent once) send(createTick()); // Simulate a tick with default bid and ask prices - // Step 2: Retrieve the updated bid and ask levels - BidLevel bestBid = container.getState().getBidAt(0); // Highest bid price - AskLevel bestAsk = container.getState().getAskAt(0); // Lowest ask price - long bidPrice = bestBid.price; - long askPrice = bestAsk.price; - - // Step 3: Assert that the bid and ask prices are within the expected limits - assertTrue(bidPrice >= 91); // assuming 91 as sell limit - assertTrue(askPrice <= 115); // assuming 115 as price limit + // Verify no more than 10 active child orders + assertEquals(container.getState().getActiveChildOrders().size(),10); // maxOrders = 10; - // Step 4: Test the algorithm's evaluation for buy orders - Action action = createAlgoLogic().evaluate(container.getState()); + // Verify the total number of child orders (active + canceled) is exactly 20 + assertEquals(container.getState().getChildOrders().size(), 20); + } - // Step 5: Assert that no action is taken (NoAction) when conditions are not met - assertEquals(NoAction.NoAction, action); + @Test + public void testHighBidScenario() throws Exception { + // Send high bid tick to simulate favorable conditions for selling + send(createTickHighBid()); - // Step 6: Simulate the conditions for canceling an order - send(createTick()); + // Check that a sell order was placed due to favorable bid prices + assertTrue(container.getState().getActiveChildOrders().stream() + .anyMatch(order -> order.getSide() == Side.SELL)); + } - // Step 7: Evaluate the algorithm again after the market change - Action CancelChildOrder = createAlgoLogic().evaluate(container.getState()); + @Test + public void testLowAskScenario() throws Exception { + // Send low ask tick to simulate favorable conditions, but below priceLimit + send(createTickLowAsk()); - // Step 8: Assert that the algorithm returns a CancelChildOrder action when conditions are met - assertEquals(CancelChildOrder, action); + // Check that no active buy orders were created due to the ask price being below the price limit + assertEquals(0, container.getState().getActiveChildOrders().size()); + } - // Step 9: Check if the algorithm has placed or canceled orders correctly - // Check no more than 10 active child orders created at once - assertEquals(container.getState().getActiveChildOrders().size(),10); // maxOrders = 10; +// private MyAlgoLogic algo; // Field to store reference to MyAlgoLogic +// +// @Override +// public AlgoLogic createAlgoLogic() { +// // Instantiate MyAlgoLogic and store it in the algo field +// this.algo = new MyAlgoLogic(); +// return algo; // Return it for the container setup +// } +// +// @Test +// public void testCancelLogic() throws Exception { +// // Step 1: Send a tick to create initial active orders +// send(createTick()); // Simulate a tick to populate active orders +// assertEquals(container.getState().getActiveChildOrders().size(), 10); +// +// // Step 2: Use the stored MyAlgoLogic instance to enable clearActiveOrders +// algo.setClearActiveOrders(true); // Trigger cancel logic +// +// // Step 3: Send additional ticks to trigger cancel evaluation and verify cancel logic +// send(createTick()); +//// createTickHighBid();// Trigger evaluation with cancellation +// assertEquals(container.getState().getActiveChildOrders().size(),10); +// +// // Repeat to ensure all orders are canceled +// while (container.getState().getActiveChildOrders().size() > 0) { +// send(createTick()); // Continue cancellation process +// } +// +// // Verify all active orders have been canceled +// assertEquals(0, container.getState().getActiveChildOrders().size()); +// } - // Step 10: Verify the total number of child orders (active + canceled) - assertEquals(container.getState().getChildOrders().size(), 20); - } } diff --git a/ui-front-end/src/components/market-depth/MarketDepthPanel.css b/ui-front-end/src/components/market-depth/MarketDepthPanel.css deleted file mode 100644 index 926814df..00000000 --- a/ui-front-end/src/components/market-depth/MarketDepthPanel.css +++ /dev/null @@ -1,48 +0,0 @@ -.MarketDepthPanel { - width: 100%; - border-collapse: collapse; /* Ensure borders are merged between cells */ - margin-top: 20px; - border: 1px solid black; /* Outer border of the table */ -} - -/* Add black borders to table header and data cells */ -.MarketDepthPanel th, .MarketDepthPanel td { - padding: 8px; - text-align: center; - border: 1px solid black; /* Black border for table cells */ -} - -/* Blue background for Bid Quantity only */ -.bid.quantity { - background-color: #007bff; - color: white; - border: 1px solid black; /* Ensure Bid Quantity cells also have black border */ -} - -/* Red background for Ask Quantity only */ -.ask.quantity { - background-color: #dc3545; - color: white; - border: 1px solid black; /* Ensure Ask Quantity cells also have black border */ -} - -/* Styling for Price Cell (no background color) */ -.price { - font-weight: bold; - border: 1px solid black; /* Add black border to price cells */ -} - -/* Arrow color for price movement */ -.arrow { - margin-left: 8px; -} - -.up { - color: green; -} - -.down { - color: red; -} - - From a7598841f45fc2d5c33ea677cbe48730267f4838 Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Wed, 6 Nov 2024 14:55:11 +0000 Subject: [PATCH 12/13] UI exercise complete --- .../market-depth/MarketDepthFeature.tsx | 43 +++------ .../market-depth/MarketDepthPanel.css | 53 +++++++++++ .../market-depth/MarketDepthPanel.tsx | 87 ++++++++++--------- .../src/components/market-depth/PriceCell.tsx | 17 +++- .../src/components/market-depth/Quantity.css | 43 +++++++++ .../src/components/market-depth/Quantity.tsx | 38 ++++++++ .../components/market-depth/QuantityCell.tsx | 20 ----- 7 files changed, 204 insertions(+), 97 deletions(-) create mode 100644 ui-front-end/src/components/market-depth/MarketDepthPanel.css create mode 100644 ui-front-end/src/components/market-depth/Quantity.css create mode 100644 ui-front-end/src/components/market-depth/Quantity.tsx delete mode 100644 ui-front-end/src/components/market-depth/QuantityCell.tsx diff --git a/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx b/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx index 95e381f0..db4ad024 100644 --- a/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx +++ b/ui-front-end/src/components/market-depth/MarketDepthFeature.tsx @@ -1,37 +1,18 @@ -// import { Placeholder } from "../placeholder"; -import React from 'react'; import { useMarketDepthData } from "./useMarketDepthData"; import { schemas } from "../../data/algo-schemas"; -import { MarketDepthPanel } from './MarketDepthPanel'; +import { MarketDepthPanel } from "./MarketDepthPanel"; +import { PriceCell } from "./PriceCell"; +import { Quantity } from "./Quantity"; - - -// // Example test data -// const testData: MarketDepthRow[] = [ -// { symbolLevel:"1230", level: 0, bid: 1000, bidQuantity: 500, offer: 1010, offerQuantity: 700 }, -// { symbolLevel:"1231", level: 1, bid: 990, bidQuantity: 700, offer: 1012, offerQuantity: 400 }, -// { symbolLevel:"1232", level: 2, bid: 985, bidQuantity: 1200, offer: 1013, offerQuantity: 800 }, -// { symbolLevel:"1233", level: 3, bid: 984, bidQuantity: 1300, offer: 1018, offerQuantity: 750 }, -// { symbolLevel:"1234", level: 4, bid: 970, bidQuantity: 800, offer: 1021, offerQuantity: 900 }, -// { symbolLevel:"1235", level: 5, bid: 969, bidQuantity: 700, offer: 1026, offerQuantity: 1500 }, -// { symbolLevel:"1236", level: 6, bid: 950, bidQuantity: 750, offer: 1027, offerQuantity: 1500 }, -// { symbolLevel:"1237", level: 7, bid: 945, bidQuantity: 900, offer: 1029, offerQuantity: 2000 }, -// { symbolLevel:"1238", level: 8, bid: 943, bidQuantity: 500, offer: 1031, offerQuantity: 500 }, -// { symbolLevel:"1239", level: 9, bid: 940, bidQuantity: 200, offer: 1024, offerQuantity: 800 }, -// ]; - -/** - * TODO - */ export const MarketDepthFeature = () => { - // instead of fetching real data (useMarketDepthData) we will use the testData for now -// const data = testData; - - // fetching the real market depth data using the hook const data = useMarketDepthData(schemas.prices); - -// return ; - // Render the MarketDepthPanel once the data is fetched - return ; + if (!data) { + return
Loading...
; // Display loading message until data is available + } + return ( +
+ +
+ ); + } -}; diff --git a/ui-front-end/src/components/market-depth/MarketDepthPanel.css b/ui-front-end/src/components/market-depth/MarketDepthPanel.css new file mode 100644 index 00000000..64d3d86d --- /dev/null +++ b/ui-front-end/src/components/market-depth/MarketDepthPanel.css @@ -0,0 +1,53 @@ +.market-depth-container { + width: 100%; + overflow-x: auto; + display: flex; + justify-content: center; +} + +.market-depth-table { + width: 70%; /* Adjust this to make the table smaller */ + max-width: 700px; /* Set a maximum width for smaller tables */ + border-collapse: collapse; + margin: 0 auto; +} + +.market-depth-table th, +.market-depth-table td { + border: 1px solid #ddd; + padding: 4px; /* Reduced padding for a more compact look */ + text-align: center; + font-size: 12px; +} + +.market-depth-table th { + background-color: #f4f4f4; + font-weight: bold; +} + +/* Styling for the bid and ask quantity bars */ +.quantity-bar { + position: relative; + height: 20px; + display: flex; + align-items: center; + color: white; + font-weight: bold; +} + +.bid-quantity .quantity-bar { + background-color: #007bff; +} + +.ask-quantity .quantity-bar { + background-color: #dc3545; +} + +.quantity-bar span { + position: absolute; + left: 50%; + transform: translateX(-50%); +} + + + diff --git a/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx b/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx index 8aafb88c..d81e0472 100644 --- a/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx +++ b/ui-front-end/src/components/market-depth/MarketDepthPanel.tsx @@ -1,49 +1,52 @@ import React from "react"; -import { PriceCell } from './PriceCell'; -import { QuantityCell } from './QuantityCell'; import "./MarketDepthPanel.css"; +import { Quantity } from "./Quantity"; +import { PriceCell } from "./PriceCell"; -interface MarketDepthRow { - symbolLevel: string; - level: number; - bid: number; - bidQuantity: number; - offer: number; - offerQuantity: number; -} - -interface MarketDepthPanelProps { - data: MarketDepthRow[]; -} - -export const MarketDepthPanel = ({ data }: MarketDepthPanelProps) => { +export const MarketDepthPanel = ({ data }) => { return ( - - - - - - - - - - - - - - - - - {data.map((row, index) => ( - - - - - - +
+
LevelBidAsk
QuantityPricePriceQuantity
{row.level}
+ + + + + + + + + + + + - ))} - -
BidAsk
LevelQuantityPricePriceQuantity
+ + + {data.map((row, index) => ( + + {row.level} + + + + + + + + + + + + + + ))} + + + ); }; + + + + + + diff --git a/ui-front-end/src/components/market-depth/PriceCell.tsx b/ui-front-end/src/components/market-depth/PriceCell.tsx index cb70d8d6..0817a350 100644 --- a/ui-front-end/src/components/market-depth/PriceCell.tsx +++ b/ui-front-end/src/components/market-depth/PriceCell.tsx @@ -1,26 +1,35 @@ import React, { useRef, useEffect } from "react"; +import "./PriceCell.css"; interface PriceCellProps { price: number; - type: 'bid' | 'ask'; // We can keep this prop to differentiate, but won't apply color + type: 'bid' | 'ask'; // } export const PriceCell = ({ price, type }: PriceCellProps) => { + // Create a reference to store the last price const lastPriceRef = useRef(price); + + // Calculate the price difference by subtracting the last price from the current price const priceDifference = price - lastPriceRef.current; + // useEffect to update the reference with the current price after each render useEffect(() => { - lastPriceRef.current = price; - }, [price]); + lastPriceRef.current = price; // Store the new price in the ref for the next update + }, [price]); // Only run the effect when the price changes return ( - {/* We will remove the `type` class from here */} + + {/* Display the price value */} {price} + {/* If the price has changed, show an up or down arrow */} {priceDifference !== 0 && ( 0 ? 'up' : 'down'}`}> + {/* Show the appropriate arrow based on whether the price increased or decreased */} {priceDifference > 0 ? '↑' : '↓'} )} ); }; + diff --git a/ui-front-end/src/components/market-depth/Quantity.css b/ui-front-end/src/components/market-depth/Quantity.css new file mode 100644 index 00000000..24bdfa91 --- /dev/null +++ b/ui-front-end/src/components/market-depth/Quantity.css @@ -0,0 +1,43 @@ +.quantity-bar { + position: relative; + height: 20px; + color: white; + font-weight: bold; + display: flex; + align-items: center; + transition: transform 0.3s ease; /* Smooth transition for width changes */ + border-radius: 4px; + padding: 0 5px; + overflow: hidden; /* Keeps bar content within bounds */ +} + +/* Quantity text centered or aligned right */ +.quantity-bar .quantity-text { + position: absolute; + transform: translateX(-50%); /* Default centered text */ + pointer-events: none; /* Prevent interaction with text */ +} + +/* Specific alignment for bid quantity text */ +.bid-quantity .quantity-text { + right: 5px; /* Align to the right side */ + transform: translateX(0); /* Override centering */ +} + +.ask-quantity .quantity-text { + left: 50%; + transform: translateX(-50%); /* Center the text */ +} + +/* Blue bar for bid quantity, growing from right to left */ +.bid-quantity { + background-color: #007bff; + transform-origin: right; /* Grow from the right */ +} + +/* Red bar for ask quantity, growing from left to right */ +.ask-quantity { + background-color: #dc3545; + transform-origin: left; /* Default growth from left */ +} + diff --git a/ui-front-end/src/components/market-depth/Quantity.tsx b/ui-front-end/src/components/market-depth/Quantity.tsx new file mode 100644 index 00000000..cb8aa8b6 --- /dev/null +++ b/ui-front-end/src/components/market-depth/Quantity.tsx @@ -0,0 +1,38 @@ +import React, { useEffect, useRef } from "react"; +import "./Quantity.css"; + +interface QuantityProps { + quantity: number; + type: "bid" | "ask"; // Determines color and direction +} + +export const Quantity = ({ quantity, type }: QuantityProps) => { + const lastQuantityRef = useRef(quantity); + const quantityDiff = quantity - lastQuantityRef.current; + + useEffect(() => { + lastQuantityRef.current = quantity; // Update the last quantity after each change + }, [quantity]); + + // Calculate dynamic scaling factor based on quantity + const scaleFactor = Math.min(quantity / 5000, 1); // Adjust divisor as needed for appropriate scaling + + return ( +
+ {quantity} {/* Static quantity text */} +
+ ); +}; + + + + + + + + + + diff --git a/ui-front-end/src/components/market-depth/QuantityCell.tsx b/ui-front-end/src/components/market-depth/QuantityCell.tsx deleted file mode 100644 index 9ace7f0f..00000000 --- a/ui-front-end/src/components/market-depth/QuantityCell.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; - -interface QuantityCellProps { - quantity: number; - type: 'bid' | 'ask'; // 'bid' or 'ask' to apply styles -} - -export const QuantityCell = (props: QuantityCellProps) => { - const { quantity, type } = props; - - return ( - - {quantity} - - ); -}; - - - - From 64189d03802f845f76c0dd342aba2ea75172aacd Mon Sep 17 00:00:00 2001 From: Hani Ali Date: Wed, 6 Nov 2024 15:09:11 +0000 Subject: [PATCH 13/13] Final form - AlgoLogic with no vwap --- algo-exercise/getting-started/README.md | 3 +- .../gettingstarted/MyAlgoLogic.java | 8 ---- .../gettingstarted/AbstractAlgoTest.java | 4 -- .../gettingstarted/MyAlgoTest.java | 38 +------------------ 4 files changed, 2 insertions(+), 51 deletions(-) diff --git a/algo-exercise/getting-started/README.md b/algo-exercise/getting-started/README.md index e1934038..e20c9c93 100644 --- a/algo-exercise/getting-started/README.md +++ b/algo-exercise/getting-started/README.md @@ -33,9 +33,8 @@ This repository includes: ### Unit Tests (`MyAlgoTest`) Tests the algorithm’s logic in isolation, ensuring: - **Buy Logic Test**: Verifies buy orders are only placed when conditions match `priceLimit`. -- **Cancel Logic Test**: Ensures orders are canceled correctly when required. -### Backtest (`MyAlgoBackTest`) +### Backtests (`MyAlgoBackTest`) Simulates the algorithm in a live market environment, validating: - **Order Fills**: Checks if expected quantities are filled. - **Order Status**: Confirms correct categorisation of partially and fully filled orders. diff --git a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java index 07fc5134..89dff3fb 100644 --- a/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java +++ b/algo-exercise/getting-started/src/main/java/codingblackfemales/gettingstarted/MyAlgoLogic.java @@ -137,14 +137,6 @@ public Action evaluate(SimpleAlgoState state) { return NoAction.NoAction; } } - - /** - * Setter for clearActiveOrders, used primarily for testing purposes. - * @param value true to enable clearing active orders, false to disable. - */ - public void setClearActiveOrders(boolean value) { - clearActiveOrders = value; - } } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/AbstractAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/AbstractAlgoTest.java index 46a61367..a71a45ad 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/AbstractAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/AbstractAlgoTest.java @@ -135,8 +135,4 @@ protected UnsafeBuffer createTickLowAsk() { return directBuffer; } - - - - } diff --git a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java index b5fcf04b..f527ed79 100644 --- a/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java +++ b/algo-exercise/getting-started/src/test/java/codingblackfemales/gettingstarted/MyAlgoTest.java @@ -38,10 +38,7 @@ public void testDispatchThroughSequencer() throws Exception { send(createTick()); // Simulate a tick with default bid and ask prices // Verify no more than 10 active child orders - assertEquals(container.getState().getActiveChildOrders().size(),10); // maxOrders = 10; - - // Verify the total number of child orders (active + canceled) is exactly 20 - assertEquals(container.getState().getChildOrders().size(), 20); + assertEquals(10,container.getState().getActiveChildOrders().size()); // maxOrders = 10; } @Test @@ -62,39 +59,6 @@ public void testLowAskScenario() throws Exception { // Check that no active buy orders were created due to the ask price being below the price limit assertEquals(0, container.getState().getActiveChildOrders().size()); } - -// private MyAlgoLogic algo; // Field to store reference to MyAlgoLogic -// -// @Override -// public AlgoLogic createAlgoLogic() { -// // Instantiate MyAlgoLogic and store it in the algo field -// this.algo = new MyAlgoLogic(); -// return algo; // Return it for the container setup -// } -// -// @Test -// public void testCancelLogic() throws Exception { -// // Step 1: Send a tick to create initial active orders -// send(createTick()); // Simulate a tick to populate active orders -// assertEquals(container.getState().getActiveChildOrders().size(), 10); -// -// // Step 2: Use the stored MyAlgoLogic instance to enable clearActiveOrders -// algo.setClearActiveOrders(true); // Trigger cancel logic -// -// // Step 3: Send additional ticks to trigger cancel evaluation and verify cancel logic -// send(createTick()); -//// createTickHighBid();// Trigger evaluation with cancellation -// assertEquals(container.getState().getActiveChildOrders().size(),10); -// -// // Repeat to ensure all orders are canceled -// while (container.getState().getActiveChildOrders().size() > 0) { -// send(createTick()); // Continue cancellation process -// } -// -// // Verify all active orders have been canceled -// assertEquals(0, container.getState().getActiveChildOrders().size()); -// } - }