18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application.
22 |
23 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/Views/Shared/_CookieConsentPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Http.Features
2 |
3 | @{
4 | var consentFeature = Context.Features.Get();
5 | var showBanner = !consentFeature?.CanTrack ?? false;
6 | var cookieString = consentFeature?.CreateConsentCookie();
7 | }
8 |
9 | @if (showBanner)
10 | {
11 |
33 |
41 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using Akka.CQRS.Pricing.Web
2 | @using Akka.CQRS.Pricing.Web.Models
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/app.conf:
--------------------------------------------------------------------------------
1 | akka.cluster.client {
2 | # Interval at which the client retries to establish contact with one of
3 | # ClusterReceptionist on the servers (cluster nodes)
4 | establishing-get-contacts-interval = 3s
5 |
6 | # Interval at which the client will ask the ClusterReceptionist for
7 | # new contact points to be used for next reconnect.
8 | refresh-contacts-interval = 60s
9 |
10 | # How often failure detection heartbeat messages should be sent
11 | heartbeat-interval = 2s
12 |
13 | acceptable-heartbeat-pause = 13s
14 |
15 | buffer-size = 1000
16 |
17 | reconnect-timeout = off
18 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*"
8 | }
9 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification\
2 | for details on configuring this project to bundle and minify static web assets. */
3 | body {
4 | padding-top: 50px;
5 | padding-bottom: 20px;
6 | }
7 |
8 | /* Wrapping element */
9 | /* Set some basic padding to keep content from hitting the edges */
10 | .body-content {
11 | padding-left: 15px;
12 | padding-right: 15px;
13 | }
14 |
15 | /* Carousel */
16 | .carousel-caption p {
17 | font-size: 20px;
18 | line-height: 1.4;
19 | }
20 |
21 | /* Make .svg files in the carousel display properly in older browsers */
22 | .carousel-inner .item img[src$=".svg"] {
23 | width: 100%;
24 | }
25 |
26 | /* QR code generator */
27 | #qrCode {
28 | margin: 15px;
29 | }
30 |
31 | /* Hide/rearrange for smaller screens */
32 | @media screen and (max-width: 767px) {
33 | /* Hide captions */
34 | .carousel-caption {
35 | display: none;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/css/site.min.css:
--------------------------------------------------------------------------------
1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}}
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petabridge/akkadotnet-cluster-workshop/8191b2d985f6a63dbadc12b070430d3804ca5d25/src/Akka.CQRS.Pricing.Web/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your JavaScript code.
5 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/js/site.min.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petabridge/akkadotnet-cluster-workshop/8191b2d985f6a63dbadc12b070430d3804ca5d25/src/Akka.CQRS.Pricing.Web/wwwroot/js/site.min.js
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bootstrap",
3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
4 | "keywords": [
5 | "css",
6 | "js",
7 | "less",
8 | "mobile-first",
9 | "responsive",
10 | "front-end",
11 | "framework",
12 | "web"
13 | ],
14 | "homepage": "http://getbootstrap.com",
15 | "license": "MIT",
16 | "moduleType": "globals",
17 | "main": [
18 | "less/bootstrap.less",
19 | "dist/js/bootstrap.js"
20 | ],
21 | "ignore": [
22 | "/.*",
23 | "_config.yml",
24 | "CNAME",
25 | "composer.json",
26 | "CONTRIBUTING.md",
27 | "docs",
28 | "js/tests",
29 | "test-infra"
30 | ],
31 | "dependencies": {
32 | "jquery": "1.9.1 - 3"
33 | },
34 | "version": "3.3.7",
35 | "_release": "3.3.7",
36 | "_resolution": {
37 | "type": "version",
38 | "tag": "v3.3.7",
39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86"
40 | },
41 | "_source": "https://github.com/twbs/bootstrap.git",
42 | "_target": "v3.3.7",
43 | "_originalSource": "bootstrap",
44 | "_direct": true
45 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2016 Twitter, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petabridge/akkadotnet-cluster-workshop/8191b2d985f6a63dbadc12b070430d3804ca5d25/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petabridge/akkadotnet-cluster-workshop/8191b2d985f6a63dbadc12b070430d3804ca5d25/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petabridge/akkadotnet-cluster-workshop/8191b2d985f6a63dbadc12b070430d3804ca5d25/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/petabridge/akkadotnet-cluster-workshop/8191b2d985f6a63dbadc12b070430d3804ca5d25/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/bootstrap/dist/js/npm.js:
--------------------------------------------------------------------------------
1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
2 | require('../../js/transition.js')
3 | require('../../js/alert.js')
4 | require('../../js/button.js')
5 | require('../../js/carousel.js')
6 | require('../../js/collapse.js')
7 | require('../../js/dropdown.js')
8 | require('../../js/modal.js')
9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/jquery-validation-unobtrusive/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-validation-unobtrusive",
3 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive",
4 | "version": "3.2.9",
5 | "_release": "3.2.9",
6 | "_resolution": {
7 | "type": "version",
8 | "tag": "v3.2.9",
9 | "commit": "a91f5401898e125f10771c5f5f0909d8c4c82396"
10 | },
11 | "_source": "https://github.com/aspnet/jquery-validation-unobtrusive.git",
12 | "_target": "^3.2.9",
13 | "_originalSource": "jquery-validation-unobtrusive",
14 | "_direct": true
15 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .NET Foundation. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/jquery-validation/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-validation",
3 | "homepage": "https://jqueryvalidation.org/",
4 | "repository": {
5 | "type": "git",
6 | "url": "git://github.com/jquery-validation/jquery-validation.git"
7 | },
8 | "authors": [
9 | "Jörn Zaefferer "
10 | ],
11 | "description": "Form validation made easy",
12 | "main": "dist/jquery.validate.js",
13 | "keywords": [
14 | "forms",
15 | "validation",
16 | "validate"
17 | ],
18 | "license": "MIT",
19 | "ignore": [
20 | "**/.*",
21 | "node_modules",
22 | "bower_components",
23 | "test",
24 | "demo",
25 | "lib"
26 | ],
27 | "dependencies": {
28 | "jquery": ">= 1.7.2"
29 | },
30 | "version": "1.17.0",
31 | "_release": "1.17.0",
32 | "_resolution": {
33 | "type": "version",
34 | "tag": "1.17.0",
35 | "commit": "fc9b12d3bfaa2d0c04605855b896edb2934c0772"
36 | },
37 | "_source": "https://github.com/jzaefferer/jquery-validation.git",
38 | "_target": "^1.17.0",
39 | "_originalSource": "jquery-validation",
40 | "_direct": true
41 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/jquery-validation/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright Jörn Zaefferer
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/jquery/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery",
3 | "main": "dist/jquery.js",
4 | "license": "MIT",
5 | "ignore": [
6 | "package.json"
7 | ],
8 | "keywords": [
9 | "jquery",
10 | "javascript",
11 | "browser",
12 | "library"
13 | ],
14 | "homepage": "https://github.com/jquery/jquery-dist",
15 | "version": "3.3.1",
16 | "_release": "3.3.1",
17 | "_resolution": {
18 | "type": "version",
19 | "tag": "3.3.1",
20 | "commit": "9e8ec3d10fad04748176144f108d7355662ae75e"
21 | },
22 | "_source": "https://github.com/jquery/jquery-dist.git",
23 | "_target": "^3.3.1",
24 | "_originalSource": "jquery",
25 | "_direct": true
26 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing.Web/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Akka.CQRS.Pricing.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(NetStandardVersion)
5 | Read-side pricing aggregates and views.
6 | Debug;Release;Phobos
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Commands/FetchPriceAndVolume.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Akka.CQRS.Pricing.Commands
6 | {
7 | ///
8 | /// Fetch the N most recent price and volume updates for a specific ticker symbol.
9 | ///
10 | public sealed class FetchPriceAndVolume : IWithStockId
11 | {
12 | public FetchPriceAndVolume(string stockId)
13 | {
14 | StockId = stockId;
15 | }
16 |
17 | public string StockId { get; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Commands/Ping.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Akka.CQRS.Pricing.Commands
4 | {
5 | ///
6 | /// Used to heartbeat an Akka.Cluster.Sharding entity for a specific ticker symbol.
7 | ///
8 | public sealed class Ping : IWithStockId, IEquatable
9 | {
10 | public Ping(string stockId)
11 | {
12 | StockId = stockId;
13 | }
14 |
15 | public string StockId { get; }
16 |
17 | public bool Equals(Ping other)
18 | {
19 | if (ReferenceEquals(null, other)) return false;
20 | if (ReferenceEquals(this, other)) return true;
21 | return string.Equals(StockId, other.StockId);
22 | }
23 |
24 | public override bool Equals(object obj)
25 | {
26 | if (ReferenceEquals(null, obj)) return false;
27 | if (ReferenceEquals(this, obj)) return true;
28 | return obj is Ping other && Equals(other);
29 | }
30 |
31 | public override int GetHashCode()
32 | {
33 | return StockId.GetHashCode();
34 | }
35 |
36 | public static bool operator ==(Ping left, Ping right)
37 | {
38 | return Equals(left, right);
39 | }
40 |
41 | public static bool operator !=(Ping left, Ping right)
42 | {
43 | return !Equals(left, right);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Commands/PriceAndVolumeSnapshot.cs:
--------------------------------------------------------------------------------
1 | // -----------------------------------------------------------------------
2 | //
3 | // Copyright (C) 2015 - 2019 Petabridge, LLC
4 | //
5 | // -----------------------------------------------------------------------
6 |
7 | using Akka.CQRS.Pricing.Events;
8 |
9 | namespace Akka.CQRS.Pricing.Commands
10 | {
11 | ///
12 | /// The response to a command.
13 | ///
14 | public sealed class PriceAndVolumeSnapshot : IWithStockId
15 | {
16 | public PriceAndVolumeSnapshot(string stockId, IPriceUpdate[] priceUpdates, IVolumeUpdate[] volumeUpdates)
17 | {
18 | StockId = stockId;
19 | PriceUpdates = priceUpdates;
20 | VolumeUpdates = volumeUpdates;
21 | }
22 |
23 | public string StockId { get; }
24 |
25 | public IPriceUpdate[] PriceUpdates { get; }
26 |
27 | public IVolumeUpdate[] VolumeUpdates { get; }
28 |
29 | public static readonly IPriceUpdate[] EmptyPrices = new IPriceUpdate[0];
30 | public static readonly IVolumeUpdate[] EmptyVolumes = new IVolumeUpdate[0];
31 |
32 | public static PriceAndVolumeSnapshot Empty(string stockId)
33 | {
34 | return new PriceAndVolumeSnapshot(stockId, EmptyPrices, EmptyVolumes);
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Events/IPriceUpdate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Akka.CQRS.Pricing.Events
6 | {
7 | ///
8 | /// Used to signal a change in price for a specific stock.
9 | ///
10 | public interface IPriceUpdate : IComparable, IMarketEvent
11 | {
12 | ///
13 | /// The time of this price update.
14 | ///
15 | DateTimeOffset Timestamp { get; }
16 |
17 | ///
18 | /// The current volume-weighted average price.
19 | ///
20 | decimal CurrentAvgPrice { get; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Events/IVolumeUpdate.cs:
--------------------------------------------------------------------------------
1 | // -----------------------------------------------------------------------
2 | //
3 | // Copyright (C) 2015 - 2019 Petabridge, LLC
4 | //
5 | // -----------------------------------------------------------------------
6 |
7 | using System;
8 |
9 | namespace Akka.CQRS.Pricing.Events
10 | {
11 | ///
12 | /// Used to signal a change in volume for a specific stock.
13 | ///
14 | public interface IVolumeUpdate : IComparable, IMarketEvent
15 | {
16 | ///
17 | /// The time of this price update.
18 | ///
19 | DateTimeOffset Timestamp { get; }
20 |
21 | ///
22 | /// The current trade volume.
23 | ///
24 | double CurrentVolume { get; }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Events/PriceChanged.cs:
--------------------------------------------------------------------------------
1 | // -----------------------------------------------------------------------
2 | //
3 | // Copyright (C) 2015 - 2019 Petabridge, LLC
4 | //
5 | // -----------------------------------------------------------------------
6 |
7 | using System;
8 | using System.Collections.Generic;
9 |
10 | namespace Akka.CQRS.Pricing.Events
11 | {
12 | ///
13 | /// Concrete implementation.
14 | ///
15 | public sealed class PriceChanged : IPriceUpdate, IComparable
16 | {
17 | public PriceChanged(string stockId, decimal currentAvgPrice, DateTimeOffset timestamp)
18 | {
19 | StockId = stockId;
20 | CurrentAvgPrice = currentAvgPrice;
21 | Timestamp = timestamp;
22 | }
23 |
24 | public DateTimeOffset Timestamp { get; }
25 |
26 | public decimal CurrentAvgPrice { get; }
27 |
28 | public string StockId { get; }
29 |
30 | public int CompareTo(PriceChanged other)
31 | {
32 | if (ReferenceEquals(this, other)) return 0;
33 | if (ReferenceEquals(null, other)) return 1;
34 | return Timestamp.CompareTo(other.Timestamp);
35 | }
36 |
37 | public int CompareTo(IPriceUpdate other)
38 | {
39 | if (other is PriceChanged c)
40 | {
41 | return CompareTo(c);
42 | }
43 | throw new ArgumentException();
44 | }
45 |
46 | public override string ToString()
47 | {
48 | return $"[{StockId}][{Timestamp}] - $[{CurrentAvgPrice}]";
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Events/VolumeChanged.cs:
--------------------------------------------------------------------------------
1 | // -----------------------------------------------------------------------
2 | //
3 | // Copyright (C) 2015 - 2019 Petabridge, LLC
4 | //
5 | // -----------------------------------------------------------------------
6 |
7 | using System;
8 |
9 | namespace Akka.CQRS.Pricing.Events
10 | {
11 | ///
12 | /// Concrete implementation.
13 | ///
14 | public sealed class VolumeChanged : IVolumeUpdate, IComparable
15 | {
16 | public VolumeChanged(string stockId, double currentVolume, DateTimeOffset timestamp)
17 | {
18 | StockId = stockId;
19 | CurrentVolume = currentVolume;
20 | Timestamp = timestamp;
21 | }
22 |
23 | public string StockId { get; }
24 |
25 | public DateTimeOffset Timestamp { get; }
26 | public double CurrentVolume { get; }
27 |
28 | public int CompareTo(IVolumeUpdate other)
29 | {
30 | if (other is VolumeChanged c)
31 | {
32 | return CompareTo(c);
33 | }
34 | throw new ArgumentException();
35 | }
36 |
37 | public int CompareTo(VolumeChanged other)
38 | {
39 | if (ReferenceEquals(this, other)) return 0;
40 | if (ReferenceEquals(null, other)) return 1;
41 | return Timestamp.CompareTo(other.Timestamp);
42 | }
43 |
44 | public override string ToString()
45 | {
46 | return $"[{StockId}][{Timestamp}] - [{CurrentVolume}] avg shares / trade";
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/IMarketEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Akka.CQRS.Pricing
6 | {
7 | ///
8 | /// Marker interface for "market" events - i.e. changes in the market's view
9 | /// of price, volume, or other "aggregated" events not specific to any individual
10 | /// trade or order.
11 | ///
12 | public interface IMarketEvent : IWithStockId
13 | {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/MatchAggregatorSnapshot.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Akka.CQRS.Pricing.Events;
5 |
6 | namespace Akka.CQRS.Pricing
7 | {
8 | ///
9 | /// Represents the point-in-time state of the match aggregator at any given time.
10 | ///
11 | public sealed class MatchAggregatorSnapshot
12 | {
13 | public MatchAggregatorSnapshot(decimal avgPrice, double avgVolume,
14 | IReadOnlyList recentPriceUpdates, IReadOnlyList recentVolumeUpdates)
15 | {
16 | RecentAvgPrice = avgPrice;
17 | RecentAvgVolume = avgVolume;
18 | RecentPriceUpdates = recentPriceUpdates;
19 | RecentVolumeUpdates = recentVolumeUpdates;
20 | }
21 |
22 | ///
23 | /// The most recently saved average price.
24 | ///
25 | public decimal RecentAvgPrice { get; }
26 |
27 | ///
28 | /// The most recently saved average volume.
29 | ///
30 | public double RecentAvgVolume { get; }
31 |
32 | public IReadOnlyList RecentPriceUpdates { get; }
33 |
34 | public IReadOnlyList RecentVolumeUpdates { get; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Views/EMWA.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Akka.CQRS.Pricing
4 | {
5 | ///
6 | /// Simple data structure for self-contained EMWA mathematics.
7 | ///
8 | public struct EMWA
9 | {
10 | public EMWA(double alpha, double currentAvg)
11 | {
12 | Alpha = alpha;
13 | CurrentAvg = currentAvg;
14 | }
15 |
16 | public double Alpha { get; }
17 |
18 | public double CurrentAvg { get; }
19 |
20 | public EMWA Next(double nextValue)
21 | {
22 | return new EMWA(Alpha, Alpha * nextValue + (1 - Alpha) * CurrentAvg);
23 | }
24 |
25 | public static EMWA Init(int sampleSize, double firstReading)
26 | {
27 | var alpha = 2.0 / (sampleSize + 1);
28 | return new EMWA(alpha, firstReading);
29 | }
30 |
31 | public static double operator %(EMWA e1, EMWA e2)
32 | {
33 | return (e1.CurrentAvg - e2.CurrentAvg) / e1.CurrentAvg;
34 | }
35 |
36 | public static EMWA operator +(EMWA e1, double next)
37 | {
38 | return e1.Next(next);
39 | }
40 | }
41 |
42 | ///
43 | /// Simple data structure for self-contained EMWA mathematics using precision.
44 | ///
45 | public struct EMWAm
46 | {
47 | public EMWAm(decimal alpha, decimal currentAvg)
48 | {
49 | Alpha = alpha;
50 | CurrentAvg = currentAvg;
51 | }
52 |
53 | public decimal Alpha { get; }
54 |
55 | public decimal CurrentAvg { get; }
56 |
57 | public EMWAm Next(decimal nextValue)
58 | {
59 | return new EMWAm(Alpha, Alpha * nextValue + (1 - Alpha) * CurrentAvg);
60 | }
61 |
62 | public static EMWAm Init(int sampleSize, decimal firstReading)
63 | {
64 | var alpha = 2.0m / (sampleSize + 1);
65 | return new EMWAm(alpha, firstReading);
66 | }
67 |
68 | public static decimal operator %(EMWAm e1, EMWAm e2)
69 | {
70 | return (e1.CurrentAvg - e2.CurrentAvg) / e1.CurrentAvg;
71 | }
72 |
73 | public static EMWAm operator +(EMWAm e1, decimal next)
74 | {
75 | return e1.Next(next);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Views/MatchAggregate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Akka.CQRS.Events;
3 | using Akka.CQRS.Pricing.Events;
4 | using Akka.CQRS.Util;
5 |
6 | namespace Akka.CQRS.Pricing.Views
7 | {
8 | ///
9 | /// Aggregates trade events in order to produce price and volume estimates.
10 | ///
11 | public sealed class MatchAggregate
12 | {
13 | ///
14 | /// By default, average all prices and volume over the past 30 matched trades.
15 | ///
16 | public const int DefaultSampleSize = 30;
17 |
18 | public MatchAggregate(string tickerSymbol, decimal initialPrice = 0.0m,
19 | double initialVolume = 0.0d, int sampleSize = DefaultSampleSize)
20 | {
21 | TickerSymbol = tickerSymbol;
22 | AvgPrice = EMWAm.Init(sampleSize, initialPrice);
23 | AvgVolume = EMWA.Init(sampleSize, initialVolume);
24 | }
25 |
26 | public string TickerSymbol { get; }
27 |
28 | public EMWA AvgVolume { get; private set; }
29 |
30 | public EMWAm AvgPrice { get; private set; }
31 |
32 | ///
33 | /// Fetch the current price and volume metrics.
34 | ///
35 | /// We don't do this on every single match since that could become noisy quickly.
36 | /// Instead we do it on a regular clock interval.
37 | ///
38 | /// Optional - the service used for time-stamping the price and volume updates.
39 | /// The current price and volume update events.
40 | public (IPriceUpdate lastestPrice, IVolumeUpdate latestVolume) FetchMetrics(ITimestamper timestampService = null)
41 | {
42 | var currentTime = timestampService?.Now ?? CurrentUtcTimestamper.Instance.Now;
43 | return (new PriceChanged(TickerSymbol, AvgPrice.CurrentAvg, currentTime),
44 | new VolumeChanged(TickerSymbol, AvgVolume.CurrentAvg, currentTime));
45 | }
46 |
47 | ///
48 | /// Feed the most recent match for to update moving price averages.
49 | ///
50 | /// The most recent matched trade for this symbol.
51 | public bool WithMatch(Match latestTrade)
52 | {
53 | if (!latestTrade.StockId.Equals(TickerSymbol))
54 | return false; // Someone fed a match for a stock other than TickerSymbol
55 |
56 | // Update EMWA quantity and volume
57 | AvgVolume += latestTrade.Quantity;
58 | AvgPrice += latestTrade.SettlementPrice;
59 | return true;
60 | }
61 | }
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Pricing/Views/PriceHistory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.Linq;
4 | using Akka.CQRS.Pricing.Events;
5 |
6 | namespace Akka.CQRS.Pricing.Views
7 | {
8 | ///
9 | /// Details the price history for a specific ticker symbol.
10 | ///
11 | /// In-memory, replicated view.
12 | ///
13 | public struct PriceHistory : IWithStockId
14 | {
15 | public PriceHistory(string stockId, ImmutableSortedSet historicalPrices)
16 | {
17 | HistoricalPrices = historicalPrices;
18 | StockId = stockId;
19 | }
20 |
21 | public string StockId { get; }
22 |
23 | public DateTimeOffset From => HistoricalPrices[0].Timestamp;
24 |
25 | public DateTimeOffset Until => CurrentPriceUpdate.Timestamp;
26 |
27 | public decimal CurrentPrice => CurrentPriceUpdate.CurrentAvgPrice;
28 |
29 | public IPriceUpdate CurrentPriceUpdate => HistoricalPrices.Last();
30 |
31 | public TimeSpan Range => Until - From;
32 |
33 | public ImmutableSortedSet HistoricalPrices { get; }
34 |
35 | public PriceHistory WithPrice(IPriceUpdate update)
36 | {
37 | if(!update.StockId.Equals(StockId))
38 | throw new ArgumentOutOfRangeException($"Expected ticker symbol {StockId} but found {update.StockId}", nameof(update));
39 |
40 | return new PriceHistory(StockId, HistoricalPrices.Add(update));
41 | }
42 |
43 | ///
44 | /// Purge older price entries - resetting the window for a new trading day.
45 | ///
46 | /// Delete any entries older than this.
47 | /// An updated .
48 | public PriceHistory Prune(DateTimeOffset earliestStart)
49 | {
50 | return new PriceHistory(StockId, HistoricalPrices.Where(x => x.Timestamp < earliestStart).ToImmutableSortedSet());
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Subscriptions.Tests/Actor/ActorTradeSubscriptionManagerEnd2EndSpecs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Akka.Actor;
7 | using Akka.CQRS.Events;
8 | using Akka.CQRS.Subscriptions.Actor;
9 | using Akka.CQRS.TradeProcessor.Actors;
10 | using Akka.Persistence.Extras;
11 | using FluentAssertions;
12 | using Xunit;
13 | using Xunit.Abstractions;
14 |
15 | namespace Akka.CQRS.Subscriptions.Tests.Actor
16 | {
17 | public class ActorTradeSubscriptionManagerEnd2EndSpecs : TestKit.Xunit2.TestKit
18 | {
19 | public ActorTradeSubscriptionManagerEnd2EndSpecs(ITestOutputHelper output)
20 | : base(output: output)
21 | {
22 | _orderBookMaster = Sys.ActorOf(Props.Create(() => new OrderBookMasterActor()), "orders");
23 | }
24 |
25 | private IActorRef _orderBookMaster;
26 |
27 | [Fact(DisplayName =
28 | "[ActorTradeSubscriptionManager] Should be able to subscribe and publish to trade event topics.")]
29 | public async Task ShouldSubscribeAndPublishToTradeEventTopics()
30 | {
31 | var subManager = new ActorTradeSubscriptionManager(_orderBookMaster);
32 |
33 |
34 | // Subscribe to all topics
35 | var subAck = await subManager.Subscribe("MSFT", TestActor);
36 | subAck.StockId.Should().Be("MSFT");
37 | ExpectMsg(); // message should be sent back to us as well
38 |
39 | // create a matching trade, which should result in a Fill + Match being published.
40 | var time = DateTimeOffset.UtcNow;
41 | var bid = new ConfirmableMessage(new Bid("MSFT", "foo1", 10.0m, 1.0d, time), 100L, "fuber");
42 | var ask = new ConfirmableMessage(new Ask("MSFT", "foo2", 10.0m, 1.0d, time), 101L, "fuber");
43 |
44 | var confirmationProbe = CreateTestProbe();
45 |
46 | _orderBookMaster.Tell(bid, confirmationProbe);
47 | _orderBookMaster.Tell(ask, confirmationProbe);
48 |
49 | confirmationProbe.ReceiveN(2).All(x => x is Confirmation).Should().BeTrue();
50 |
51 | ExpectMsgAllOf(new Fill("foo1", "MSFT", 1.0d, 10.0m, "foo2", time),
52 | new Fill("foo2", "MSFT", 1.0d, 10.0m, "foo1", time),
53 | new Match("MSFT", "foo1", "foo2", 10.0m, 1.0d, time), bid.Message, ask.Message);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Subscriptions.Tests/Akka.CQRS.Subscriptions.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(NetCoreVersion)
5 | Debug;Release;Phobos
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Subscriptions.Tests/DistributedPubSub/DistributedPubSubEnd2EndSpecs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Akka.Actor;
6 | using Akka.Cluster;
7 | using Akka.Configuration;
8 | using Akka.CQRS.Events;
9 | using Akka.CQRS.Subscriptions.DistributedPubSub;
10 | using FluentAssertions;
11 | using Xunit;
12 | using Xunit.Abstractions;
13 |
14 | namespace Akka.CQRS.Subscriptions.Tests.DistributedPubSub
15 | {
16 | public class DistributedPubSubEnd2EndSpecs : TestKit.Xunit2.TestKit
17 | {
18 | private static readonly Config ClusterConfig = @"
19 | akka.actor.provider = cluster
20 | ";
21 |
22 | public DistributedPubSubEnd2EndSpecs(ITestOutputHelper output)
23 | : base(ClusterConfig, output: output) { }
24 |
25 | public Address SelfAddress => Cluster.Cluster.Get(Sys).SelfAddress;
26 |
27 | [Fact(DisplayName = "[DistributedPubSubTradeEventSubscriptionManager] Should be able to subscribe and publish to trade event topics.")]
28 | public async Task ShouldSubscribeAndPublishToTradeEventTopics()
29 | {
30 | // Join the cluster
31 | Within(TimeSpan.FromSeconds(5), () =>
32 | {
33 | Cluster.Cluster.Get(Sys).Join(SelfAddress);
34 | AwaitCondition(
35 | () => Cluster.Cluster.Get(Sys).State.Members.Count(x => x.Status == MemberStatus.Up) == 1);
36 | });
37 |
38 | // Start DistributedPubSub
39 | var subManager = DistributedPubSubTradeEventSubscriptionManager.For(Sys);
40 | var published = DistributedPubSubTradeEventPublisher.For(Sys);
41 |
42 | // Subscribe to all topics
43 | var subAck = await subManager.Subscribe("MSFT", TestActor);
44 | subAck.StockId.Should().Be("MSFT");
45 |
46 | var bid = new Bid("MSFT", "foo", 10.0m, 1.0d, DateTimeOffset.UtcNow);
47 | published.Publish("MSFT", bid);
48 | ExpectMsg();
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Akka.CQRS.Subscriptions.Tests/DistributedPubSub/DistributedPubSubFormatterSpecs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using FluentAssertions;
4 | using static Akka.CQRS.Subscriptions.DistributedPubSub.DistributedPubSubTradeEventTopicFormatter;
5 | using Xunit;
6 |
7 | namespace Akka.CQRS.Subscriptions.Tests.DistributedPubSub
8 | {
9 | public class DistributedPubSubFormatterSpecs
10 | {
11 | public static IEnumerable