Skip to content

Instantly share code, notes, and snippets.

@amast09
Last active December 6, 2022 12:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amast09/032949a2b8f106f7101b52784a1d59ba to your computer and use it in GitHub Desktop.
Save amast09/032949a2b8f106f7101b52784a1d59ba to your computer and use it in GitHub Desktop.
import scala.concurrent.Future
import akka.stream.{ActorMaterializer, KillSwitch, KillSwitches, ThrottleMode}
import akka.stream.scaladsl.{Keep, Sink, Source}
import akka.actor.ActorSystem
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Random
final case class StockQuote(symbol: String, price: Int)
sealed trait Trade {
val stockQuote: StockQuote
val shares: Int
}
final case class BuyTrade(stockQuote: StockQuote, shares: Int) extends Trade
final case class SellTrade(stockQuote: StockQuote, shares: Int) extends Trade
final case class TradeResult(success: Boolean, trade: Trade)
object TradeBot {
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem("TradeBot")
implicit val materializer = ActorMaterializer()
var currentStockPrice = 0
var tradeBotKillSwitch: Option[KillSwitch] = None
def sendEmail(emailAddress: String, emailMessage: String): Future[Boolean] = {
println(emailMessage)
Future.successful(true)
}
def sendTradeEmail(emailAddress: String)(tradeMade: Trade) = {
sendEmail(emailAddress, s"Trade: $tradeMade")
}
def getNextStockQuote(tickerSymbol: String, priceChange: Int) = {
if (priceChange % 2 == 0 && currentStockPrice - priceChange > 0) {
currentStockPrice = currentStockPrice - priceChange
} else {
currentStockPrice = currentStockPrice + priceChange
}
val nextStockQuote = StockQuote(tickerSymbol, currentStockPrice)
println(s"Stock Quote: $nextStockQuote")
nextStockQuote
}
def getQuoteStreamForStock(buyPrice: Int, sellPrice: Int)(tickerSymbol: String) = {
currentStockPrice = (buyPrice + sellPrice) / 2
Source.fromIterator(() => Iterator.continually(getNextStockQuote(tickerSymbol, Random.nextInt(10))))
.throttle(1, 1.second, 1, ThrottleMode.shaping)
.take(100)
}
def makeTrade(tradeToMake: Trade): Future[Boolean] = {
Future.successful(true)
}
def createTrade(buyPrice: Int, sellPrice: Int)(stockQuote: StockQuote): Option[Trade] = {
if (stockQuote.price < buyPrice) {
Some(BuyTrade(stockQuote, 1))
} else if (stockQuote.price > sellPrice) {
Some(SellTrade(stockQuote, 1))
} else {
None
}
}
def startTradeBot(tickerSymbol: String, tradesPerDayLimit: Int, buyPrice: Int, sellPrice: Int, notificationEmailAddress: String): Unit = {
println(s"Starting new trade bot for $tickerSymbol at $tradesPerDayLimit per day, buy at $buyPrice sell at $sellPrice")
tradeBotKillSwitch.foreach(_.shutdown())
val tradeBotTradeCreator = createTrade(buyPrice, sellPrice)(_)
val tradeBotEmailCreator = sendTradeEmail(notificationEmailAddress)(_)
val newTradeBotKillSwitch = getQuoteStreamForStock(buyPrice, sellPrice)(tickerSymbol)
.viaMat(KillSwitches.single)(Keep.right)
.map(tradeBotTradeCreator)
.mapConcat(_.toList)
.throttle(tradesPerDayLimit, 1.day, tradesPerDayLimit, ThrottleMode.shaping)
.mapAsync(tradesPerDayLimit)( trade => makeTrade(trade).map(TradeResult(_, trade)) )
.filter(_.success)
.mapAsync(tradesPerDayLimit) ( tradeResult => tradeBotEmailCreator(tradeResult.trade) )
.toMat(Sink.ignore)(Keep.left)
.run()
tradeBotKillSwitch = Some(newTradeBotKillSwitch)
}
def stopTradeBot(): Unit = {
println("Stopping trade bot")
tradeBotKillSwitch.foreach(_.shutdown())
}
startTradeBot("GOOGL", 10, 900, 950, "foobar@gmail.com")
Thread.sleep(5000)
startTradeBot("TSLA", 10, 330, 340, "foobar@gmail.com")
Thread.sleep(5000)
stopTradeBot()
startTradeBot("AAPL", 1, 163, 165, "foobar@gmail.com")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment