It's almost Christmas!

CS-Script - 306 bytes

var c=new string(' ',342).ToCharArray();var r=new Random();int e=18,i,j,w;for(;i<e;i++){c[i*e+e]='\n';w=i<5?i:i<10?i-2:i<16?i-6:2;for(j=1;j++<w*2;)c[i*e+8-w+j]='#';}for(i=0;i<10;){j=37+r.Next(288);if(c[j]=='#'&c[j+1]<42&c[j-1]<42&c[j+e]<42&c[j-e]<42)c[j]=i++<5?'J':'O';}c[8]='^';c[27]='|';Console.Write(c);

Once more with formatting and comments:

// create 'char bitmap' filled with spaces
var c=new string(' ',342).ToCharArray(); 
// Random for placing ornaments
var r=new Random();
int e=18,i,j,w;
// once for each row
for(;i<e;i++)
{
    // add new lines
    c[i*e+e]='\n';
    // determine width of tree for this row
    w=i<5?i:i<10?i-2:i<16?i-6:2;
    for(j=1;j++<w*2;)
        // add leaves
        c[i*e+8-w+j]='#';
}
for(i=0;i<10;)
{
    // select random location
    j=37+r.Next(288); 
    if( // check we have a leaf
        c[j]=='#' &
        // check surrounding to be leaf/space/new-line
        c[j+1]<42 & c[j-1]<42 & c[j+e]<42 & c[j-e]<42)
        // add ornament if location is valid
        c[j]=i++<5?'J':'O';
}
// light candle
c[8]='^';
c[27]='|';
// print
Console.Write(c);

It's basically C#, but using CS-Script allows me to skip all the boiler-plate.

Try it here!

Notes:

This currently outputs another line of white spaces below the tree to make sure the that 'checking for existing ornaments below' does not throw an IndexOutOfBoundsException. Other solutions would be:

  • Checking if it is the last line before checking below (adds a few more character)
  • Not adding ornaments to the 'stem' of the tree (Same byte count, but seems to me to be against rules)

I'll leave it to the OP if this should be changed.

Lastly, this is my first golf, so any feedback is appreciated. ;)


JavaScript (ES6), 148 bytes

Hopefully, this should comply with the 'random enough' condition.

_=>[...'887656543254321077'].map(n=>' '.repeat(n)+'#'.repeat(17-2*n)).join`
`.replace(/#/g,_=>'OJ^|#'[++i<4?i:i>133|++j%13?4:j/13&1],i=1,j=new Date)

Demo

let f =

_=>[...'887656543254321077'].map(n=>' '.repeat(n)+'#'.repeat(17-2*n)).join`
`.replace(/#/g,_=>'OJ^|#'[++i<4?i:i>133|++j%13?4:j/13&1],i=1,j=new Date)

console.log(f())


TSQL, 556 532 494 476 bytes

This script needs to be executed on the master database

Golfed:

DECLARE @ varchar(max)='',@h INT=0,@w INT=0WHILE @h<18SELECT
@+=space(9-@w)+REPLICATE(char(IIF(@h<2,94+30*@h,35)),@w*2+1)+space(9-@w)+CHAR(10),@h+=1,@w+=CHOOSE(@h,0,1,1,1,-1,1,1,1,1,-2,1,1,1,1,1,-8,0)WHILE
@h>7WITH C as(SELECT*,substring(@,number,1)v,number/20r,number%20c
FROM spt_values WHERE type='P'and number<358)SELECT @=stuff(@,number,1,CHAR(74+@h%2*5)),@h-=1FROM
c d WHERE v='#'and not exists(SELECT*FROM c WHERE abs(d.c-c)+abs(d.r-r)<2and'A'<v)ORDER BY newid()PRINT @

Ungolfed:

DECLARE @ varchar(max)='',@h INT=0,@w INT=0

WHILE @h<18
  SELECT @+=
    space(9-@w)+REPLICATE(char(IIF(@h<2,94+30*@h,35)),@w*2+1)
      +space(9-@w)+CHAR(10),
    @h+=1,
    @w+=CHOOSE(@h,0,1,1,1,-1,1,1,1,1,-2,1,1,1,1,1,-8,0)

WHILE @h>7
  WITH C as
  (
    SELECT*,substring(@,number,1)v,number/20r,number%20c
    FROM spt_values
    WHERE type='P'and number<358
  )
  SELECT @=stuff(@,number,1,CHAR(74+@h%2*5)),@h-=1
  FROM c d
  WHERE v='#'and not exists(SELECT*FROM c WHERE abs(d.c-c)+abs(d.r-r)<2and'A'<v)
  ORDER BY newid()

PRINT @

Try it out